From cbe74e8e6fb4852ad82be3715d32492114a81fd9 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 26 Jul 2023 17:18:27 +0000 Subject: [PATCH 001/274] repo: Dev v1.27.1 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.0.yaml | 575 ++++++++++++++++++++++++++++++++++++++++ changelogs/current.yaml | 568 +-------------------------------------- 3 files changed, 581 insertions(+), 564 deletions(-) create mode 100644 changelogs/1.27.0.yaml diff --git a/VERSION.txt b/VERSION.txt index 5db08bf2dc579..8ea5f6d8e0fc1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.0 +1.27.1-dev diff --git a/changelogs/1.27.0.yaml b/changelogs/1.27.0.yaml new file mode 100644 index 0000000000000..b6e64f270e821 --- /dev/null +++ b/changelogs/1.27.0.yaml @@ -0,0 +1,575 @@ +date: July 26, 2023 + +behavior_changes: +- area: build + change: | + Moved the subset, ring_hash, and maglev LB code into extensions. If you use these load balancers and override + :repo:`bazel/extensions_build_config.bzl` you will need to include them explicitly. +- area: build + change: | + Moved xDS code extensions. If you use the xDS and override :repo:`bazel/extensions_build_config.bzl` you will + need to include the new config_subscriptions explicitly. +- area: http + change: | + When ``append_x_forwarded_host`` is enabled for a given route action it is now only appended iff it is different from the last + value in the list. This resolves issues where a retry caused the same value to be appended multiple times. This + behavioral change can be temporarily reverted by setting runtime guard ``envoy_reloadable_features_append_xfh_idempotent`` to ``false``. +- area: ext_proc + change: | + The proto field :ref:`value ` type is string. + This make it unable to support enconding non-utf8 characters in the ``HeaderValue`` message. + To support sending header value with non-utf8 characters, a new proto field is added in the HeaderValue message: + :ref:`raw_value `. + The header values are now encoded in this ``raw_value`` field when Envoy ext_proc filter sending + and receiving messages from the ext_proc server. This behavioral change can be temporarily + reverted by setting the runtime guard ``envoy_reloadable_features_send_header_raw_value`` to ``false``. +- area: ext_proc + change: | + Apply header mutation rules from the ext_proc config to the ``ImmediateResponse``. This behavior change can be temporarily + reverted by setting the runtime guard ``envoy_reloadable_features_immediate_response_use_filter_mutation_rule`` to false. +- area: active health check + change: | + Preserve the active-health check status of a host after a cluster/assignment update. This is now preserved in cases + where the assignment updates a host's locality. This behavioral change can be temporarily reverted by setting the + runtime flag ``envoy.reloadable_features.keep_endpoint_active_hc_status_on_locality_update`` to ``false``. +- area: quic + change: | + Add a default false runtime flag ``envoy.reloadable_features.quic_reject_all`` to disable QUIC listener if needed. +- area: stats tls + change: | + Fixed metric tag extraction so that TLS parameters are properly extracted from the stats, both for listeners and clusters. + This changes the Prometheus names from + ``envoy_listener_ssl_ciphers_ECDHE_RSA_AES128_GCM_SHA256{envoy_listener_address="0.0.0.0_10000"}`` to + ``envoy_listener_ssl_ciphers{envoy_listener_address="0.0.0.0_10000", envoy_ssl_cipher="ECDHE_RSA_AES128_GCM_SHA256"}``, and + similar for ``envoy_listener_ssl_versions_TLSv1_2``, ``envoy_cluster_ssl_versions_TLSv1_2``, ``envoy_listener_ssl_curves_P_256``, + ``envoy_cluster_ssl_curves_P_256``, ``envoy_listener_ssl_sigalgs_rsa_pss_rsae_sha256``. + +minor_behavior_changes: +- area: connection pool + change: | + Increase granularity mapping connection pool failures to specific stream failure reasons to make it more transparent why + the stream is reset when a connection pool's connection fails. +- area: custom response + change: | + The filter now traverses matchers from most specific to least specific per filter config till a match is found for the response. +- area: http1 + change: | + Allowing mixed case schemes in absolute urls (e.g. HtTp://www.google.com). Mixed case schemes will be normalized to + the lower cased equivalents before being forwarded upstream. This behavior can be reverted by setting runtime flag + ``envoy.reloadable_features.allow_absolute_url_with_mixed_scheme`` to false. +- area: http1 + change: | + The HTTP1 server-side codec no longer considers encoding 1xx headers as + starting the response. This allows the codec to raise protocol errors, + sending detailed local replies instead of just closing the connection. This + behavior can be reverted by setting runtime flag + ``envoy.reloadable_features.http1_allow_codec_error_response_after_1xx_headers`` + to ``false``. +- area: dns + change: | + Changing the DNS cache to use ``host:port`` as the cache key rather than ``host``. This allows a + downstream DFP filter to serve both secure and insecure clusters. This behavioral change + can be reverted by setting runtime flag ``envoy.reloadable_features.dfp_mixed_scheme`` to ``false``. +- area: uhv + change: | + Preserve case of %-encoded triplets in the default header validator. This behavior can be reverted by setting runtime flag + ``envoy.reloadable_features.uhv_preserve_url_encoded_case`` to ``false``, in which case %-encoded triplets are normalized + to uppercase characters. This setting is only applicable when the Unversal Header Validator is enabled and has no effect otherwise. +- area: uhv + change: | + Allow malformed URL encoded triplets in the default header validator. This behavior can be reverted by setting runtime flag + ``envoy.reloadable_features.uhv_allow_malformed_url_encoding`` to ``false``, in which case requests with malformed URL encoded triplets + in path are rejected. This setting is only applicable when the Unversal Header Validator is enabled and has no effect otherwise. +- area: ext_proc + change: | + When :ref:`clear_route_cache ` is set, ext_proc will check + for header mutations beforce clearing the route cache. Failures due to this check will be counted under the + ``clear_route_cache_ignored`` stat. +- area: aws + change: | + Added support for fetching credentials from the AWS credentials file, which only happens if credentials cannot be fetched + from environment variables. This behavioral change can be reverted by setting runtime guard + ``envoy.reloadable_features.enable_aws_credentials_file`` to ``false``. +- area: http cookies + change: | + Changed internal format of http cookie to protobuf and added expiry timestamp. Processing expired cookie + results in selection of a new upstream host and sending a new cookie to the client. Previous format of + the cookie is still accepted, but is planned to be obsoleted in the future. + This behavior change can be reverted by setting + ``envoy.reloadable_features.stateful_session_encode_ttl_in_cookie`` to ``false``. +- area: overload manager + change: | + Changed behavior of the overload manager to error on unknown overload + manager actions. Prior it would silently fail. This change can be reverted + temporarily by setting the runtime guard + ``envoy.reloadable_features.overload_manager_error_unknown_action`` to + false. +- area: router + change: | + Added check for existing metadata before setting metadata due to ``auto_sni``, ``auto_san_validation``, or + ``override_auto_sni_header`` to prevent triggering ``ENVOY_BUG`` when an earlier filter has set the metadata. +- area: resource_monitors + change: | + Changed behavior of the fixed heap monitor to count unused mapped pages as + free memory. This change can be reverted temporarily by setting the runtime guard + ``envoy.reloadable_features.count_unused_mapped_pages_as_free`` to ``false``. +- area: ext_proc + change: | + Filter metadata containing ext proc stats has been moved from ``ext-proc-logging-info`` to a namespace corresponding + to the name of the ext_proc filter. +- area: stats + change: | + Added new type of gauge with type hidden. These stats are hidden from admin/stats-sinks but can shown with a + query-parameter of ``/stats?hidden=include`` or ``/stats?hidden=showonly``. +- area: ext_authz + change: | + Forward :ref:`typed_filter_metadata ` selected by + ``typed_metadata_context_namespaces`` and :ref:`filter_metadata ` + selected by + :ref:`metadata_context_namespaces ` + from connection metadata to external auth service. This is addition to the current behavior of forwarding request metadata. + In the event of both connection and request metadata containing the requested metadata the request value will be provided. +- area: eds + change: | + Added the ability to specify mulitple addresses for a host in an EDS cluster. Connections to the host with more than one + address will be established using the Happy Eyeballs algorithm. +- area: upstream + change: | + Changed behavior of the unpausing connect with 2xx status codes. This change can be reverted temporarily by + setting the runtime guard ``envoy.reloadable_features.upstream_allow_connect_with_2xx`` to ``false``. +- area: http + change: | + Round trip time will not be refreshed for every request by default. And if this is necessary, it can be + enabled by setting runtime guard ``envoy.reloadable_features.refresh_rtt_after_request`` to ``true``. +- area: http + change: | + Envoy will now lower case scheme values by default. This behaviorial change can be temporarily reverted + by setting runtime guard ``envoy.reloadable_features.lowercase_scheme`` to ``false``. + +bug_fixes: +- area: oauth2 + change: | + The Max-Age attribute of Set-Cookie HTTP response header was being assigned a value representing Seconds Since + the Epoch, causing cookies to expire in ~53 years. This was fixed an now it is being assigned a value representing + the number of seconds until the cookie expires. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.oauth_use_standard_max_age_value`` to ``false``. +- area: tls + change: | + Fix build FIPS compliance when using both FIPS mode and Wasm extensions (``--define boringssl=fips`` and ``--define wasm=v8``). +- area: http + change: | + Switched Envoy internal scheme checks from case sensitive to case insensitive. This behaviorial change can be temporarily + reverted by setting runtime guard ``envoy.reloadable_features.handle_uppercase_scheme`` to ``false``. + + Fix `CVE-2023-35944 `_. + +- area: ext_authz + change: | + Fix a bug where the ext_authz filter will ignore the request body when the + :ref:`pack_as_bytes ` is set to ``true`` and + HTTP authorization service is configured. +- area: ext_authz + change: | + Fix a bug where the ext_authz filter will remove non UTF-8 characters from the body of a request when configured + to use :ref:`http_service `, if configured + to send the body. +- area: router + change: | + Fixed the bug that updating :ref:`scope_key_builder + ` + of SRDS config doesn't work and multiple HCM share the same ``scope_key_builder``. +- area: http + change: | + The :ref:`is_optional + ` + field of HTTP filter can only be used for configuration loading of + :ref:`HTTP filter ` + and will be ignored for loading of route or virtual host level filter config. This behavioral change + can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.ignore_optional_option_from_hcm_for_route_config`` to ``false``. + You can also use + :ref:`route/virtual host optional flag ` + as a replacement of the feature. +- area: logging + change: | + Do not display GRPC_STATUS_NUMBER for non gRPC requests. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.validate_grpc_header_before_log_grpc_status`` to ``false``. +- area: boringssl + change: | + Fixed the crash that occurs when contrib is compiled with ``boringssl=fips`` defined. +- area: oauth2 + change: | + The ``httpOnly`` attribute for ``Set-Cookie`` for tokens in HTTP response header was missing, + causing tokens to be accessible from the JavaScript making the apps vulnerable. + This was fixed now by marking the cookie as ``httpOnly``. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.oauth_make_token_cookie_httponly`` to ``false``. + + Fix `CVE-2023-35941 `_. + +- area: opentelemetry/grpc/access log + change: | + Fixed a bug in the open telemetry access logger. This logger now uses the + server scope for stats instead of the listener's global scope. This fixes a + use-after-free that can occur if the listener is drained but the cached + gRPC access logger uses the listener's global scope for stats. + + Fix `CVE-2023-35942 `_. + +- area: dependency + change: | + Update Wasmtime and related deps -> 9.0.3 to resolve + `CVE-2023-30624 `_. +- area: dependency + change: | + Update C-ares -> 1.91.1 to resolve: + + - `CVE-2023-31130 `_. + - `CVE-2023-31147 `_. + - `CVE-2023-31124 `_. + - `CVE-2023-32067 `_. +- area: tcp_proxy + change: | + Fixed assert crash when multiple ``readDisable`` are called for TCP tunneling + scenarios, by allowing multiple calls. This will also cause stats that indicate + disable or enable of downstream read to be flushed only once per actual disabling + or enabling. +- area: redis_proxy + change: | + Fixes a bug where route properties such as ``key_formatter``, + ``prefix`` and ``remove_prefix`` do not take effect when configured for :ref:`catch_all_route + `. +- area: upstream + change: | + Fixes a bug where the ``healthStatus()`` method of host return incorrect health status + when the host status is updated by the EDS. +- area: upstream + change: | + Fixes a bug where the ``healthStatus()`` method of host return unmatched health status + with the ``coarseHealth()`` method. +- area: original_dst + change: | + Fixes an issue with the ``ORIGINAL_DST`` cluster cleanup timer lifetime, which + can occur if the cluster is removed while the timer is armed. +- area: maglev loadbalancer + change: | + Fixes maglev stability problem. Previously, maglev returns slightly different backend assignment from the same backends and keys. +- area: redis + change: | + Fixes a bug where redis transactions do not work properly when redis traffic is mirrored. +- area: http2 + change: | + Fix memory leak in nghttp2 when scheduled requests are cancelled due to the ``GOAWAY`` frame being received from the + upstream service. +- area: cors + change: | + Fix a use-after-free bug that occurs in the CORS filter if the ``origin`` header is removed between + request header decoding and response header encoding. + + Fix `CVE-2023-35943 `_. + +- area: oauth2 + change: | + Fixed a cookie validator bug that meant the HMAC calculation could be the same for different payloads. + + This prevents malicious clients from constructing credentials with permanent validity in some specific scenarios. +- area: postgres + change: | + Enable parsing when using upstream SSL. + +removed_config_or_runtime: +- area: http + change: | + Removed runtime key ``envoy.reloadable_features.closer_shadow_behavior`` and legacy code paths. +- area: http + change: | + Removed runtime key ``envoy.reloadable_features.allow_upstream_filters`` and legacy code paths. +- area: quic + change: | + Removed runtime key ``envoy.reloadable_features.quic_defer_send_in_response_to_packet`` and legacy code paths. +- area: upstream + change: | + Removed runtime key ``envoy.reloadable_features.fix_hash_key`` and legacy code paths. +- area: logging + change: | + Removed runtime key ``envoy.reloadable_features.correct_remote_address`` and legacy code paths. +- area: http + change: | + Removed runtime key ``envoy.reloadable_features.http_response_half_close`` and legacy code paths. +- area: udp + change: | + Removed runtime key ``envoy.reloadable_features.udp_proxy_connect`` and legacy code paths. +- area: header_formatters + change: | + Removed runtime key ``envoy.reloadable_features.unified_header_formatter`` and legacy code paths. +- area: tls + change: | + Remove runtime key ``envoy.reloadable_features.tls_async_cert_validation`` and legacy code paths. +- area: config + change: | + Removed runtime key ``envoy.reloadable_features.delta_xds_subscription_state_tracking_fix`` and legacy code paths. +- area: http + change: | + Removed runtime key ``envoy.reloadable_features.http_strip_fragment_from_path_unsafe_if_disabled`` and legacy code paths. +- area: grpc_stats + change: | + Removed runtime key ``envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`` and legacy code paths. + +new_features: +- area: golang + change: | + Added new :ref:`l4 golang network filter `. +- area: access_log + change: | + Added ``%ACCESS_LOG_TYPE%`` substitution string, to help distinguishing between access log records and when they are being + recorded. Please refer to the access log configuration documentation for more information. +- area: access_log + change: | + Added :ref:`CEL ` access log formatter to print CEL expression. +- area: access_log + change: | + (QUIC only) Added support for %BYTES_RETRANSMITTED% and %PACKETS_RETRANSMITTED%. +- area: access_log + change: | + Added :ref:`DisableBuiltinLables + ` + to disable envoy builtin resource labels. +- area: dynamic_forward_proxy + change: | + Added :ref:`sub_clusters_config + ` to enable + independent sub cluster for each host:port, with STRICT_DNS cluster type. +- area: http + change: | + Added runtime feature ``envoy.reloadable_features.max_request_headers_size_kb`` to override the default value of + :ref:`max request headers size + `. +- area: http + change: | + Added support for CONNECT-UDP (RFC 9298). Can be disabled by setting runtime feature + ``envoy.reloadable_features.enable_connect_udp_support`` to false. +- area: listeners + change: | + Added :ref:`max_connections_to_accept_per_socket_event + ` + that sets the maximum number of new connections to be accepted per socket + event on a listener. If there are more connections to be accepted beyond + the maximum, the remaining connections would be processed in later + dispatcher loop iterations. Added listener histogram + ``connections_accepted_per_socket_event`` to allow users to empirically + determine an appropriate configuration for their deployment. +- area: load shed point + change: | + Added load shed point ``envoy.load_shed_points.http_connection_manager_decode_headers`` that rejects new http streams + by sending a local reply. +- area: load shed point + change: | + Added load shed point ``envoy.load_shed_points.http1_server_abort_dispatch`` that rejects HTTP1 server processing of requests. +- area: load shed point + change: | + Added load shed point ``envoy.load_shed_points.http2_server_go_away_on_dispatch`` that sends + ``GOAWAY`` for HTTP2 server processing of requests. When a ``GOAWAY`` frame is submitted by + this the counter ``http2.goaway_sent`` will be incremented. +- area: matchers + change: | + Added :ref:`RuntimeFraction ` input + matcher. It allows matching hash of the input on a runtime key. +- area: stat_sinks + change: | + Added ``envoy.stat_sinks.open_telemetry`` stats_sink, that supports flushing metrics by the OTLP protocol, + for supported Open Telemetry collectors. +- area: redis_proxy + change: | + Added new configuration field :ref:`key_formatter + ` to format redis key. + The field supports using %KEY% as a formatter command for substituting the redis key as part of the substitution formatter expression. +- area: stats + change: | + Added config :ref:`enable_deferred_creation_stats + `. + When set to ``true``, enables deferred instantiation on supported stats structures. +- area: ratelimit + change: | + Added new configuration field :ref:`domain + ` to allow for setting rate limit domains on a + per-route basis. +- area: tls_inspector + change: | + Added histogram ``bytes_processed`` which records the number of bytes of + the tls_inspector processed while analyzing for tls usage. In cases where + the connection uses tls this records the tls client hello size. In cases + where the connection doesn't use tls this records the amount of bytes the + tls_inspector processed until it realized the connection was not using tls. +- area: tls_inspector + change: | + Added new configuration field :ref:`initial_read_buffer_size + ` + to allow users to tune the buffer size requested by the filter. If + configured, and the filter needs additional bytes, the filter will double + the number of bytes requested up to the default 64KiB maximum. +- area: access_log + change: | + Added access log filter :ref:`log_type_filter ` + to filter access log records based on the type of the record. +- area: ext_proc + change: | + Added new configuration field + :ref:`disable_clear_route_cache ` + to force the ext_proc filter from clearing the route cache. Failures to clear from setting this field will be counted under the + ``clear_route_cache_disabled`` stat. +- area: ext_proc + change: | + Added new configuration field + :ref:`allow_mode_override ` + If set to true, the filter config + :ref:`processing_mode ` + can be overridden by the + :ref:`mode_override ` + in the response message from the external processing server. + If not set, the ``mode_override`` API in the response message will be ignored. +- area: ext_proc + change: | + :ref:`forward_rules ` + to only allow headers matching the rules to be forwarded to the external processing server. +- area: redis_proxy + change: | + Added new field :ref:`connection_rate_limit + ` + to limit reconnection rate to redis server to avoid reconnection storm. +- area: match_delegate + change: | + Added :ref:`per route configuration + ` to the + :ref:`ExtensionWithMatcher + ` filter. + Which allows the associated matcher to be defined on a per route basis. +- area: match_delegate + change: | + If no matcher is set the :ref:`ExtensionWithMatcher + ` filter is now set to skip rather than erroring out. +- area: access_log + change: | + Added additional HCM access log option :ref:`flush_log_on_tunnel_successfully_established + `. + Enabling this option will write a log to all access loggers when HTTP tunnels (e.g. Websocket and ``CONNECT``) + are successfully established. +- area: admin + change: | + Adds a new admin stats html bucket-mode ``detailed`` to generate all recorded buckets and summary percentiles. +- area: http + change: | + Add support to the route/virtual host level + :ref:`is_optional ` field. + A route/virtual host level per filter config can be marked as optional, which means that if + the filter fails to load, the configuration will no be rejected. +- area: upstream + change: | + Added :ref:`cluster provided extension + ` + to suppport the :ref:`load balancer policy `. +- area: fault + change: | + Added new field ``envoy.extensions.filters.http.fault.v3.HTTPFault.filter_metadata`` to aid in logging. + Metadata will be stored in StreamInfo dynamic metadata under a namespace corresponding to the name of the fault filter. +- area: load_balancing + change: | + Added new option + :ref:`weighted_priority_health ` + to compute the health of a :ref:`priority level ` by using + :ref:`load balancing weight ` + instead of the count of healthy hosts. +- area: application_logs + change: | + Added bootstrap option + :ref:`application_log_format ` + to enable setting application log format as JSON structure. +- area: application_logs + change: | + Added bootstrap option + :ref:`application_log_format ` + to enable setting application log text format from config. +- area: ext_proc + change: | + Added new field ``filter_metadata ` + and :ref:`CEL input matcher `. +- area: tls + change: | + Added support for hot-reloading CRL file when the file changes on disk. + This works with dynamic secrets when + :ref:`CertificateValidationContext ` + is delivered via SDS. +- area: http + change: | + Added support for configuring additional :ref:`cookie attributes `. +- area: http + change: | + Added support for the route/virtual host level + :ref:`disabled ` field. + A route/virtual host level per filter config can be marked as disabled, which means that + the filter will be disabled in a specific route/virtual host. +- area: health_check + change: | + Added host related information :ref:`metadata ` and + :ref:`locality ` to + the :ref:`health check event ` definition. +- area: zookeeper + change: | + Added the ``addWatch`` opcode support to the ZooKeeper proxy filter. +- area: config + change: | + added a statistic :ref:`warming_state ` to indicate the current warming state of a cluster. +- area: access_log + change: | + Added bytes snapshotting for upstream and downstream logging that will be reset after every periodic log. Downstream + periodic loggers should read ``BytesMeter::bytesAtLastDownstreamPeriodicLog()``, and upstream periodic loggers should read + ``BytesMeter::bytesAtLastUpstreamPeriodicLog()``. +- area: lds + change: | + Pause SRDS when LDS is updated. +- area: http + change: | + Added :ref:`outbound_control_frames_active ` and :ref:`outbound_frames_active ` + statistic. +- area: original_dst + change: | + Filter state is pulled from request context first (if available), then falls back to connection context. Added ability to pick host + from dynamic metadata using :ref:`metadata_key `. + Same behavior - looks in request context first (if available), falls back to connection context. +- area: tls + change: | + Added support to configure the new config option + :ref:`enforce_rsa_key_usage `. + This can be used to override its configuration in BoringSSL. It is currently default to false but expected to be changed + to true by default in a future release. ``ssl.was_key_usage_invalid`` is added to :ref:`listener metrics ` + and will be incremented for certificate configurations that would fail if this option were set to true. +- area: http + change: | + Added ``OVERWRITE_IF_EXISTS`` header manipulation keyword to overwrite a header only when it exists before manipulation. +- area: tls + change: | + Added FIPS compliant build for arm64. + +deprecated: +- area: access_log + change: | + Deprecated (1.25.0) :ref:`intermediate_log_entry ` + in favour of :ref:`access_log_type `. +- area: health_check + change: | + deprecated the :ref:`HealthCheck event_log_path ` in favor of + :ref:`HealthCheck event_logger extension `. +- area: stats + change: | + Added :ref:`enable_deferred_creation_stats + `. + support for ``ClusterTrafficStats``. +- area: access_log + change: | + Added ``%DOWNSTREAM_LOCAL_DNS_SAN%``, ``%DOWNSTREAM_PEER_DNS_SAN%``, ``%DOWNSTREAM_LOCAL_IP_SAN%`` + and ``%DOWNSTREAM_PEER_IP_SAN%`` substitution formatters. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b6e64f270e821..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,575 +1,17 @@ -date: July 26, 2023 +date: Pending behavior_changes: -- area: build - change: | - Moved the subset, ring_hash, and maglev LB code into extensions. If you use these load balancers and override - :repo:`bazel/extensions_build_config.bzl` you will need to include them explicitly. -- area: build - change: | - Moved xDS code extensions. If you use the xDS and override :repo:`bazel/extensions_build_config.bzl` you will - need to include the new config_subscriptions explicitly. -- area: http - change: | - When ``append_x_forwarded_host`` is enabled for a given route action it is now only appended iff it is different from the last - value in the list. This resolves issues where a retry caused the same value to be appended multiple times. This - behavioral change can be temporarily reverted by setting runtime guard ``envoy_reloadable_features_append_xfh_idempotent`` to ``false``. -- area: ext_proc - change: | - The proto field :ref:`value ` type is string. - This make it unable to support enconding non-utf8 characters in the ``HeaderValue`` message. - To support sending header value with non-utf8 characters, a new proto field is added in the HeaderValue message: - :ref:`raw_value `. - The header values are now encoded in this ``raw_value`` field when Envoy ext_proc filter sending - and receiving messages from the ext_proc server. This behavioral change can be temporarily - reverted by setting the runtime guard ``envoy_reloadable_features_send_header_raw_value`` to ``false``. -- area: ext_proc - change: | - Apply header mutation rules from the ext_proc config to the ``ImmediateResponse``. This behavior change can be temporarily - reverted by setting the runtime guard ``envoy_reloadable_features_immediate_response_use_filter_mutation_rule`` to false. -- area: active health check - change: | - Preserve the active-health check status of a host after a cluster/assignment update. This is now preserved in cases - where the assignment updates a host's locality. This behavioral change can be temporarily reverted by setting the - runtime flag ``envoy.reloadable_features.keep_endpoint_active_hc_status_on_locality_update`` to ``false``. -- area: quic - change: | - Add a default false runtime flag ``envoy.reloadable_features.quic_reject_all`` to disable QUIC listener if needed. -- area: stats tls - change: | - Fixed metric tag extraction so that TLS parameters are properly extracted from the stats, both for listeners and clusters. - This changes the Prometheus names from - ``envoy_listener_ssl_ciphers_ECDHE_RSA_AES128_GCM_SHA256{envoy_listener_address="0.0.0.0_10000"}`` to - ``envoy_listener_ssl_ciphers{envoy_listener_address="0.0.0.0_10000", envoy_ssl_cipher="ECDHE_RSA_AES128_GCM_SHA256"}``, and - similar for ``envoy_listener_ssl_versions_TLSv1_2``, ``envoy_cluster_ssl_versions_TLSv1_2``, ``envoy_listener_ssl_curves_P_256``, - ``envoy_cluster_ssl_curves_P_256``, ``envoy_listener_ssl_sigalgs_rsa_pss_rsae_sha256``. +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* minor_behavior_changes: -- area: connection pool - change: | - Increase granularity mapping connection pool failures to specific stream failure reasons to make it more transparent why - the stream is reset when a connection pool's connection fails. -- area: custom response - change: | - The filter now traverses matchers from most specific to least specific per filter config till a match is found for the response. -- area: http1 - change: | - Allowing mixed case schemes in absolute urls (e.g. HtTp://www.google.com). Mixed case schemes will be normalized to - the lower cased equivalents before being forwarded upstream. This behavior can be reverted by setting runtime flag - ``envoy.reloadable_features.allow_absolute_url_with_mixed_scheme`` to false. -- area: http1 - change: | - The HTTP1 server-side codec no longer considers encoding 1xx headers as - starting the response. This allows the codec to raise protocol errors, - sending detailed local replies instead of just closing the connection. This - behavior can be reverted by setting runtime flag - ``envoy.reloadable_features.http1_allow_codec_error_response_after_1xx_headers`` - to ``false``. -- area: dns - change: | - Changing the DNS cache to use ``host:port`` as the cache key rather than ``host``. This allows a - downstream DFP filter to serve both secure and insecure clusters. This behavioral change - can be reverted by setting runtime flag ``envoy.reloadable_features.dfp_mixed_scheme`` to ``false``. -- area: uhv - change: | - Preserve case of %-encoded triplets in the default header validator. This behavior can be reverted by setting runtime flag - ``envoy.reloadable_features.uhv_preserve_url_encoded_case`` to ``false``, in which case %-encoded triplets are normalized - to uppercase characters. This setting is only applicable when the Unversal Header Validator is enabled and has no effect otherwise. -- area: uhv - change: | - Allow malformed URL encoded triplets in the default header validator. This behavior can be reverted by setting runtime flag - ``envoy.reloadable_features.uhv_allow_malformed_url_encoding`` to ``false``, in which case requests with malformed URL encoded triplets - in path are rejected. This setting is only applicable when the Unversal Header Validator is enabled and has no effect otherwise. -- area: ext_proc - change: | - When :ref:`clear_route_cache ` is set, ext_proc will check - for header mutations beforce clearing the route cache. Failures due to this check will be counted under the - ``clear_route_cache_ignored`` stat. -- area: aws - change: | - Added support for fetching credentials from the AWS credentials file, which only happens if credentials cannot be fetched - from environment variables. This behavioral change can be reverted by setting runtime guard - ``envoy.reloadable_features.enable_aws_credentials_file`` to ``false``. -- area: http cookies - change: | - Changed internal format of http cookie to protobuf and added expiry timestamp. Processing expired cookie - results in selection of a new upstream host and sending a new cookie to the client. Previous format of - the cookie is still accepted, but is planned to be obsoleted in the future. - This behavior change can be reverted by setting - ``envoy.reloadable_features.stateful_session_encode_ttl_in_cookie`` to ``false``. -- area: overload manager - change: | - Changed behavior of the overload manager to error on unknown overload - manager actions. Prior it would silently fail. This change can be reverted - temporarily by setting the runtime guard - ``envoy.reloadable_features.overload_manager_error_unknown_action`` to - false. -- area: router - change: | - Added check for existing metadata before setting metadata due to ``auto_sni``, ``auto_san_validation``, or - ``override_auto_sni_header`` to prevent triggering ``ENVOY_BUG`` when an earlier filter has set the metadata. -- area: resource_monitors - change: | - Changed behavior of the fixed heap monitor to count unused mapped pages as - free memory. This change can be reverted temporarily by setting the runtime guard - ``envoy.reloadable_features.count_unused_mapped_pages_as_free`` to ``false``. -- area: ext_proc - change: | - Filter metadata containing ext proc stats has been moved from ``ext-proc-logging-info`` to a namespace corresponding - to the name of the ext_proc filter. -- area: stats - change: | - Added new type of gauge with type hidden. These stats are hidden from admin/stats-sinks but can shown with a - query-parameter of ``/stats?hidden=include`` or ``/stats?hidden=showonly``. -- area: ext_authz - change: | - Forward :ref:`typed_filter_metadata ` selected by - ``typed_metadata_context_namespaces`` and :ref:`filter_metadata ` - selected by - :ref:`metadata_context_namespaces ` - from connection metadata to external auth service. This is addition to the current behavior of forwarding request metadata. - In the event of both connection and request metadata containing the requested metadata the request value will be provided. -- area: eds - change: | - Added the ability to specify mulitple addresses for a host in an EDS cluster. Connections to the host with more than one - address will be established using the Happy Eyeballs algorithm. -- area: upstream - change: | - Changed behavior of the unpausing connect with 2xx status codes. This change can be reverted temporarily by - setting the runtime guard ``envoy.reloadable_features.upstream_allow_connect_with_2xx`` to ``false``. -- area: http - change: | - Round trip time will not be refreshed for every request by default. And if this is necessary, it can be - enabled by setting runtime guard ``envoy.reloadable_features.refresh_rtt_after_request`` to ``true``. -- area: http - change: | - Envoy will now lower case scheme values by default. This behaviorial change can be temporarily reverted - by setting runtime guard ``envoy.reloadable_features.lowercase_scheme`` to ``false``. +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: oauth2 - change: | - The Max-Age attribute of Set-Cookie HTTP response header was being assigned a value representing Seconds Since - the Epoch, causing cookies to expire in ~53 years. This was fixed an now it is being assigned a value representing - the number of seconds until the cookie expires. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.oauth_use_standard_max_age_value`` to ``false``. -- area: tls - change: | - Fix build FIPS compliance when using both FIPS mode and Wasm extensions (``--define boringssl=fips`` and ``--define wasm=v8``). -- area: http - change: | - Switched Envoy internal scheme checks from case sensitive to case insensitive. This behaviorial change can be temporarily - reverted by setting runtime guard ``envoy.reloadable_features.handle_uppercase_scheme`` to ``false``. - - Fix `CVE-2023-35944 `_. - -- area: ext_authz - change: | - Fix a bug where the ext_authz filter will ignore the request body when the - :ref:`pack_as_bytes ` is set to ``true`` and - HTTP authorization service is configured. -- area: ext_authz - change: | - Fix a bug where the ext_authz filter will remove non UTF-8 characters from the body of a request when configured - to use :ref:`http_service `, if configured - to send the body. -- area: router - change: | - Fixed the bug that updating :ref:`scope_key_builder - ` - of SRDS config doesn't work and multiple HCM share the same ``scope_key_builder``. -- area: http - change: | - The :ref:`is_optional - ` - field of HTTP filter can only be used for configuration loading of - :ref:`HTTP filter ` - and will be ignored for loading of route or virtual host level filter config. This behavioral change - can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.ignore_optional_option_from_hcm_for_route_config`` to ``false``. - You can also use - :ref:`route/virtual host optional flag ` - as a replacement of the feature. -- area: logging - change: | - Do not display GRPC_STATUS_NUMBER for non gRPC requests. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.validate_grpc_header_before_log_grpc_status`` to ``false``. -- area: boringssl - change: | - Fixed the crash that occurs when contrib is compiled with ``boringssl=fips`` defined. -- area: oauth2 - change: | - The ``httpOnly`` attribute for ``Set-Cookie`` for tokens in HTTP response header was missing, - causing tokens to be accessible from the JavaScript making the apps vulnerable. - This was fixed now by marking the cookie as ``httpOnly``. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.oauth_make_token_cookie_httponly`` to ``false``. - - Fix `CVE-2023-35941 `_. - -- area: opentelemetry/grpc/access log - change: | - Fixed a bug in the open telemetry access logger. This logger now uses the - server scope for stats instead of the listener's global scope. This fixes a - use-after-free that can occur if the listener is drained but the cached - gRPC access logger uses the listener's global scope for stats. - - Fix `CVE-2023-35942 `_. - -- area: dependency - change: | - Update Wasmtime and related deps -> 9.0.3 to resolve - `CVE-2023-30624 `_. -- area: dependency - change: | - Update C-ares -> 1.91.1 to resolve: - - - `CVE-2023-31130 `_. - - `CVE-2023-31147 `_. - - `CVE-2023-31124 `_. - - `CVE-2023-32067 `_. -- area: tcp_proxy - change: | - Fixed assert crash when multiple ``readDisable`` are called for TCP tunneling - scenarios, by allowing multiple calls. This will also cause stats that indicate - disable or enable of downstream read to be flushed only once per actual disabling - or enabling. -- area: redis_proxy - change: | - Fixes a bug where route properties such as ``key_formatter``, - ``prefix`` and ``remove_prefix`` do not take effect when configured for :ref:`catch_all_route - `. -- area: upstream - change: | - Fixes a bug where the ``healthStatus()`` method of host return incorrect health status - when the host status is updated by the EDS. -- area: upstream - change: | - Fixes a bug where the ``healthStatus()`` method of host return unmatched health status - with the ``coarseHealth()`` method. -- area: original_dst - change: | - Fixes an issue with the ``ORIGINAL_DST`` cluster cleanup timer lifetime, which - can occur if the cluster is removed while the timer is armed. -- area: maglev loadbalancer - change: | - Fixes maglev stability problem. Previously, maglev returns slightly different backend assignment from the same backends and keys. -- area: redis - change: | - Fixes a bug where redis transactions do not work properly when redis traffic is mirrored. -- area: http2 - change: | - Fix memory leak in nghttp2 when scheduled requests are cancelled due to the ``GOAWAY`` frame being received from the - upstream service. -- area: cors - change: | - Fix a use-after-free bug that occurs in the CORS filter if the ``origin`` header is removed between - request header decoding and response header encoding. - - Fix `CVE-2023-35943 `_. - -- area: oauth2 - change: | - Fixed a cookie validator bug that meant the HMAC calculation could be the same for different payloads. - - This prevents malicious clients from constructing credentials with permanent validity in some specific scenarios. -- area: postgres - change: | - Enable parsing when using upstream SSL. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* removed_config_or_runtime: -- area: http - change: | - Removed runtime key ``envoy.reloadable_features.closer_shadow_behavior`` and legacy code paths. -- area: http - change: | - Removed runtime key ``envoy.reloadable_features.allow_upstream_filters`` and legacy code paths. -- area: quic - change: | - Removed runtime key ``envoy.reloadable_features.quic_defer_send_in_response_to_packet`` and legacy code paths. -- area: upstream - change: | - Removed runtime key ``envoy.reloadable_features.fix_hash_key`` and legacy code paths. -- area: logging - change: | - Removed runtime key ``envoy.reloadable_features.correct_remote_address`` and legacy code paths. -- area: http - change: | - Removed runtime key ``envoy.reloadable_features.http_response_half_close`` and legacy code paths. -- area: udp - change: | - Removed runtime key ``envoy.reloadable_features.udp_proxy_connect`` and legacy code paths. -- area: header_formatters - change: | - Removed runtime key ``envoy.reloadable_features.unified_header_formatter`` and legacy code paths. -- area: tls - change: | - Remove runtime key ``envoy.reloadable_features.tls_async_cert_validation`` and legacy code paths. -- area: config - change: | - Removed runtime key ``envoy.reloadable_features.delta_xds_subscription_state_tracking_fix`` and legacy code paths. -- area: http - change: | - Removed runtime key ``envoy.reloadable_features.http_strip_fragment_from_path_unsafe_if_disabled`` and legacy code paths. -- area: grpc_stats - change: | - Removed runtime key ``envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`` and legacy code paths. +# *Normally occurs at the end of the* :ref:`deprecation period ` new_features: -- area: golang - change: | - Added new :ref:`l4 golang network filter `. -- area: access_log - change: | - Added ``%ACCESS_LOG_TYPE%`` substitution string, to help distinguishing between access log records and when they are being - recorded. Please refer to the access log configuration documentation for more information. -- area: access_log - change: | - Added :ref:`CEL ` access log formatter to print CEL expression. -- area: access_log - change: | - (QUIC only) Added support for %BYTES_RETRANSMITTED% and %PACKETS_RETRANSMITTED%. -- area: access_log - change: | - Added :ref:`DisableBuiltinLables - ` - to disable envoy builtin resource labels. -- area: dynamic_forward_proxy - change: | - Added :ref:`sub_clusters_config - ` to enable - independent sub cluster for each host:port, with STRICT_DNS cluster type. -- area: http - change: | - Added runtime feature ``envoy.reloadable_features.max_request_headers_size_kb`` to override the default value of - :ref:`max request headers size - `. -- area: http - change: | - Added support for CONNECT-UDP (RFC 9298). Can be disabled by setting runtime feature - ``envoy.reloadable_features.enable_connect_udp_support`` to false. -- area: listeners - change: | - Added :ref:`max_connections_to_accept_per_socket_event - ` - that sets the maximum number of new connections to be accepted per socket - event on a listener. If there are more connections to be accepted beyond - the maximum, the remaining connections would be processed in later - dispatcher loop iterations. Added listener histogram - ``connections_accepted_per_socket_event`` to allow users to empirically - determine an appropriate configuration for their deployment. -- area: load shed point - change: | - Added load shed point ``envoy.load_shed_points.http_connection_manager_decode_headers`` that rejects new http streams - by sending a local reply. -- area: load shed point - change: | - Added load shed point ``envoy.load_shed_points.http1_server_abort_dispatch`` that rejects HTTP1 server processing of requests. -- area: load shed point - change: | - Added load shed point ``envoy.load_shed_points.http2_server_go_away_on_dispatch`` that sends - ``GOAWAY`` for HTTP2 server processing of requests. When a ``GOAWAY`` frame is submitted by - this the counter ``http2.goaway_sent`` will be incremented. -- area: matchers - change: | - Added :ref:`RuntimeFraction ` input - matcher. It allows matching hash of the input on a runtime key. -- area: stat_sinks - change: | - Added ``envoy.stat_sinks.open_telemetry`` stats_sink, that supports flushing metrics by the OTLP protocol, - for supported Open Telemetry collectors. -- area: redis_proxy - change: | - Added new configuration field :ref:`key_formatter - ` to format redis key. - The field supports using %KEY% as a formatter command for substituting the redis key as part of the substitution formatter expression. -- area: stats - change: | - Added config :ref:`enable_deferred_creation_stats - `. - When set to ``true``, enables deferred instantiation on supported stats structures. -- area: ratelimit - change: | - Added new configuration field :ref:`domain - ` to allow for setting rate limit domains on a - per-route basis. -- area: tls_inspector - change: | - Added histogram ``bytes_processed`` which records the number of bytes of - the tls_inspector processed while analyzing for tls usage. In cases where - the connection uses tls this records the tls client hello size. In cases - where the connection doesn't use tls this records the amount of bytes the - tls_inspector processed until it realized the connection was not using tls. -- area: tls_inspector - change: | - Added new configuration field :ref:`initial_read_buffer_size - ` - to allow users to tune the buffer size requested by the filter. If - configured, and the filter needs additional bytes, the filter will double - the number of bytes requested up to the default 64KiB maximum. -- area: access_log - change: | - Added access log filter :ref:`log_type_filter ` - to filter access log records based on the type of the record. -- area: ext_proc - change: | - Added new configuration field - :ref:`disable_clear_route_cache ` - to force the ext_proc filter from clearing the route cache. Failures to clear from setting this field will be counted under the - ``clear_route_cache_disabled`` stat. -- area: ext_proc - change: | - Added new configuration field - :ref:`allow_mode_override ` - If set to true, the filter config - :ref:`processing_mode ` - can be overridden by the - :ref:`mode_override ` - in the response message from the external processing server. - If not set, the ``mode_override`` API in the response message will be ignored. -- area: ext_proc - change: | - :ref:`forward_rules ` - to only allow headers matching the rules to be forwarded to the external processing server. -- area: redis_proxy - change: | - Added new field :ref:`connection_rate_limit - ` - to limit reconnection rate to redis server to avoid reconnection storm. -- area: match_delegate - change: | - Added :ref:`per route configuration - ` to the - :ref:`ExtensionWithMatcher - ` filter. - Which allows the associated matcher to be defined on a per route basis. -- area: match_delegate - change: | - If no matcher is set the :ref:`ExtensionWithMatcher - ` filter is now set to skip rather than erroring out. -- area: access_log - change: | - Added additional HCM access log option :ref:`flush_log_on_tunnel_successfully_established - `. - Enabling this option will write a log to all access loggers when HTTP tunnels (e.g. Websocket and ``CONNECT``) - are successfully established. -- area: admin - change: | - Adds a new admin stats html bucket-mode ``detailed`` to generate all recorded buckets and summary percentiles. -- area: http - change: | - Add support to the route/virtual host level - :ref:`is_optional ` field. - A route/virtual host level per filter config can be marked as optional, which means that if - the filter fails to load, the configuration will no be rejected. -- area: upstream - change: | - Added :ref:`cluster provided extension - ` - to suppport the :ref:`load balancer policy `. -- area: fault - change: | - Added new field ``envoy.extensions.filters.http.fault.v3.HTTPFault.filter_metadata`` to aid in logging. - Metadata will be stored in StreamInfo dynamic metadata under a namespace corresponding to the name of the fault filter. -- area: load_balancing - change: | - Added new option - :ref:`weighted_priority_health ` - to compute the health of a :ref:`priority level ` by using - :ref:`load balancing weight ` - instead of the count of healthy hosts. -- area: application_logs - change: | - Added bootstrap option - :ref:`application_log_format ` - to enable setting application log format as JSON structure. -- area: application_logs - change: | - Added bootstrap option - :ref:`application_log_format ` - to enable setting application log text format from config. -- area: ext_proc - change: | - Added new field ``filter_metadata ` - and :ref:`CEL input matcher `. -- area: tls - change: | - Added support for hot-reloading CRL file when the file changes on disk. - This works with dynamic secrets when - :ref:`CertificateValidationContext ` - is delivered via SDS. -- area: http - change: | - Added support for configuring additional :ref:`cookie attributes `. -- area: http - change: | - Added support for the route/virtual host level - :ref:`disabled ` field. - A route/virtual host level per filter config can be marked as disabled, which means that - the filter will be disabled in a specific route/virtual host. -- area: health_check - change: | - Added host related information :ref:`metadata ` and - :ref:`locality ` to - the :ref:`health check event ` definition. -- area: zookeeper - change: | - Added the ``addWatch`` opcode support to the ZooKeeper proxy filter. -- area: config - change: | - added a statistic :ref:`warming_state ` to indicate the current warming state of a cluster. -- area: access_log - change: | - Added bytes snapshotting for upstream and downstream logging that will be reset after every periodic log. Downstream - periodic loggers should read ``BytesMeter::bytesAtLastDownstreamPeriodicLog()``, and upstream periodic loggers should read - ``BytesMeter::bytesAtLastUpstreamPeriodicLog()``. -- area: lds - change: | - Pause SRDS when LDS is updated. -- area: http - change: | - Added :ref:`outbound_control_frames_active ` and :ref:`outbound_frames_active ` - statistic. -- area: original_dst - change: | - Filter state is pulled from request context first (if available), then falls back to connection context. Added ability to pick host - from dynamic metadata using :ref:`metadata_key `. - Same behavior - looks in request context first (if available), falls back to connection context. -- area: tls - change: | - Added support to configure the new config option - :ref:`enforce_rsa_key_usage `. - This can be used to override its configuration in BoringSSL. It is currently default to false but expected to be changed - to true by default in a future release. ``ssl.was_key_usage_invalid`` is added to :ref:`listener metrics ` - and will be incremented for certificate configurations that would fail if this option were set to true. -- area: http - change: | - Added ``OVERWRITE_IF_EXISTS`` header manipulation keyword to overwrite a header only when it exists before manipulation. -- area: tls - change: | - Added FIPS compliant build for arm64. deprecated: -- area: access_log - change: | - Deprecated (1.25.0) :ref:`intermediate_log_entry ` - in favour of :ref:`access_log_type `. -- area: health_check - change: | - deprecated the :ref:`HealthCheck event_log_path ` in favor of - :ref:`HealthCheck event_logger extension `. -- area: stats - change: | - Added :ref:`enable_deferred_creation_stats - `. - support for ``ClusterTrafficStats``. -- area: access_log - change: | - Added ``%DOWNSTREAM_LOCAL_DNS_SAN%``, ``%DOWNSTREAM_PEER_DNS_SAN%``, ``%DOWNSTREAM_LOCAL_IP_SAN%`` - and ``%DOWNSTREAM_PEER_IP_SAN%`` substitution formatters. From ddd12f75f6aa708bf0104443bfe35fb67823b41e Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Mon, 31 Jul 2023 21:23:03 +0800 Subject: [PATCH 002/274] bugfix: add the real api.h file into the include path (#28722) bugfix: add the real api.h file into the include path. Since go module will ignore symlink: https://github.com/golang/go/issues/39600#issuecomment-644487979 Signed-off-by: doujiang24 --- contrib/golang/filters/http/source/go/pkg/http/capi_impl.go | 2 +- contrib/golang/filters/http/source/go/pkg/http/filter.go | 2 +- contrib/golang/filters/http/source/go/pkg/http/shim.go | 2 +- contrib/golang/filters/network/source/go/pkg/network/shim.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go index e208c30819c8e..6aa9dc401dc28 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go +++ b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go @@ -20,7 +20,7 @@ package http /* // ref https://github.com/golang/go/issues/25832 -#cgo CFLAGS: -I../api +#cgo CFLAGS: -I../../../../../../common/go/api -I../api #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup diff --git a/contrib/golang/filters/http/source/go/pkg/http/filter.go b/contrib/golang/filters/http/source/go/pkg/http/filter.go index 25ac0ced7ff4a..ecaccab8bf578 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/filter.go +++ b/contrib/golang/filters/http/source/go/pkg/http/filter.go @@ -20,7 +20,7 @@ package http /* // ref https://github.com/golang/go/issues/25832 -#cgo CFLAGS: -I../api +#cgo CFLAGS: -I../../../../../../common/go/api -I../api #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup diff --git a/contrib/golang/filters/http/source/go/pkg/http/shim.go b/contrib/golang/filters/http/source/go/pkg/http/shim.go index aa91732e47bcf..1493430bc86fe 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/shim.go +++ b/contrib/golang/filters/http/source/go/pkg/http/shim.go @@ -20,7 +20,7 @@ package http /* // ref https://github.com/golang/go/issues/25832 -#cgo CFLAGS: -I../api +#cgo CFLAGS: -I../../../../../../common/go/api -I../api #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup diff --git a/contrib/golang/filters/network/source/go/pkg/network/shim.go b/contrib/golang/filters/network/source/go/pkg/network/shim.go index 65d26c28ed056..0e5e993d74e8b 100644 --- a/contrib/golang/filters/network/source/go/pkg/network/shim.go +++ b/contrib/golang/filters/network/source/go/pkg/network/shim.go @@ -20,7 +20,7 @@ package network /* // ref https://github.com/golang/go/issues/25832 -#cgo CFLAGS: -I../api +#cgo CFLAGS: -I../../../../../../common/go/api -I../api #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup From 8bb72ed47fd49cdddbbcbf3dfe073c52a2250edc Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 1 Aug 2023 00:53:31 +0100 Subject: [PATCH 003/274] ext_proc/test: Increase shards to prevent timeouts (#28734) Signed-off-by: Ryan Northey --- test/extensions/filters/http/ext_proc/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 4f6af31bc0f78..2b0d771603b4d 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -118,7 +118,7 @@ envoy_extension_cc_test( size = "large", # This test can take a while under tsan. srcs = ["ext_proc_integration_test.cc"], extension_names = ["envoy.filters.http.ext_proc"], - shard_count = 2, + shard_count = 4, tags = [ "cpu:3", ], From 60f9840d19051d6dc93852bbf1c728a5a95bf732 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 2 Aug 2023 14:32:44 +0100 Subject: [PATCH 004/274] ci/bazel: Optimize test sizings (#28609) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/checks.yml | 3 +- .bazelrc | 1 + bazel/envoy_test.bzl | 2 -- test/common/http/BUILD | 1 - test/common/http/http2/BUILD | 1 + test/extensions/clusters/redis/BUILD | 3 -- test/extensions/filters/http/buffer/BUILD | 2 +- test/extensions/filters/http/cache/BUILD | 1 - test/extensions/filters/http/csrf/BUILD | 3 -- .../filters/http/custom_response/BUILD | 1 - test/extensions/filters/http/ext_proc/BUILD | 1 - test/extensions/filters/http/fault/BUILD | 1 - .../filters/http/file_system_buffer/BUILD | 1 - .../filters/http/grpc_field_extraction/BUILD | 1 - .../filters/http/health_check/BUILD | 1 - test/extensions/filters/http/jwt_authn/BUILD | 2 +- .../filters/http/kill_request/BUILD | 2 +- test/extensions/filters/http/rbac/BUILD | 1 - test/extensions/filters/http/tap/BUILD | 1 - test/extensions/filters/http/wasm/BUILD | 3 +- .../filters/network/redis_proxy/BUILD | 3 -- .../filters/network/thrift_proxy/BUILD | 2 -- .../listener_managers/listener_manager/BUILD | 1 - test/extensions/transport_sockets/tls/BUILD | 1 - test/integration/BUILD | 34 ++++++------------- 25 files changed, 19 insertions(+), 54 deletions(-) diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index 54cb0c899d33b..05f8e8afa763c 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -99,7 +99,8 @@ jobs: condition: | and(not(canceled()), eq(${{ parameters.runChecks }}, 'true')) - timeoutInMinutes: 300 + # TODO(phlax): Make this configurable and ratchet down! + timeoutInMinutes: 240 pool: "envoy-x64-large" strategy: maxParallel: 2 diff --git a/.bazelrc b/.bazelrc index 17a7fa0b9b4e4..55e4457ae3b65 100644 --- a/.bazelrc +++ b/.bazelrc @@ -143,6 +143,7 @@ build:clang-tsan --copt -DEVENT__DISABLE_DEBUG_MODE # https://github.com/abseil/abseil-cpp/issues/760 # https://github.com/google/sanitizers/issues/953 build:clang-tsan --test_env="TSAN_OPTIONS=report_atomic_races=0" +build:clang-tsan --test_timeout=120,600,1500,4800 # Clang MSAN - this is the base config for remote-msan and docker-msan. To run this config without # our build image, follow https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo diff --git a/bazel/envoy_test.bzl b/bazel/envoy_test.bzl index c331735abe53c..ad05121a99223 100644 --- a/bazel/envoy_test.bzl +++ b/bazel/envoy_test.bzl @@ -81,7 +81,6 @@ def envoy_cc_fuzz_test( dictionaries = [], repository = "", size = "medium", - shard_count = None, deps = [], tags = [], **kwargs): @@ -121,7 +120,6 @@ def envoy_cc_fuzz_test( "//conditions:default": ["$(locations %s)" % corpus_name], }), data = [corpus_name], - shard_count = shard_count, # No fuzzing on macOS or Windows deps = select({ "@envoy//bazel:apple": [repository + "//test:dummy_main"], diff --git a/test/common/http/BUILD b/test/common/http/BUILD index f4f7aaeb001a6..b6a1bd4600ef5 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -264,7 +264,6 @@ envoy_cc_test( "conn_manager_impl_test.cc", "conn_manager_impl_test_2.cc", ], - shard_count = 3, deps = [ ":conn_manager_impl_test_base_lib", ":custom_header_extension_lib", diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index 2f07020e559a9..35f902f8a1943 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -13,6 +13,7 @@ envoy_package() envoy_cc_test( name = "codec_impl_test", + size = "large", srcs = ["codec_impl_test.cc"], external_deps = [ "quiche_http2_adapter", diff --git a/test/extensions/clusters/redis/BUILD b/test/extensions/clusters/redis/BUILD index 9a630a0860ad2..e989050baa9a0 100644 --- a/test/extensions/clusters/redis/BUILD +++ b/test/extensions/clusters/redis/BUILD @@ -94,9 +94,6 @@ envoy_extension_cc_test( size = "large", srcs = ["redis_cluster_integration_test.cc"], extension_names = ["envoy.clusters.redis"], - # This test takes a while to run specially under tsan. - # Shard it to avoid test timeout. - shard_count = 2, deps = [ "//source/extensions/clusters/redis:redis_cluster", "//source/extensions/clusters/redis:redis_cluster_lb", diff --git a/test/extensions/filters/http/buffer/BUILD b/test/extensions/filters/http/buffer/BUILD index ade6084d5580c..31250dd10aeb2 100644 --- a/test/extensions/filters/http/buffer/BUILD +++ b/test/extensions/filters/http/buffer/BUILD @@ -37,7 +37,7 @@ envoy_extension_cc_test( size = "large", srcs = ["buffer_filter_integration_test.cc"], extension_names = ["envoy.filters.http.buffer"], - shard_count = 16, + shard_count = 4, deps = [ "//source/extensions/filters/http/buffer:config", "//test/config:utility_lib", diff --git a/test/extensions/filters/http/cache/BUILD b/test/extensions/filters/http/cache/BUILD index 78aa59e762db4..e516b6b916f8b 100644 --- a/test/extensions/filters/http/cache/BUILD +++ b/test/extensions/filters/http/cache/BUILD @@ -125,7 +125,6 @@ envoy_extension_cc_test( "cache_filter_integration_test.cc", ], extension_names = ["envoy.filters.http.cache"], - shard_count = 2, deps = [ "//source/extensions/filters/http/cache:config", "//source/extensions/filters/http/cache:http_cache_lib", diff --git a/test/extensions/filters/http/csrf/BUILD b/test/extensions/filters/http/csrf/BUILD index 6e618a50641c3..913a61c7c447c 100644 --- a/test/extensions/filters/http/csrf/BUILD +++ b/test/extensions/filters/http/csrf/BUILD @@ -32,9 +32,6 @@ envoy_extension_cc_test( size = "large", srcs = ["csrf_filter_integration_test.cc"], extension_names = ["envoy.filters.http.csrf"], - # TODO(kbaichoo): remove when deferred processing is enabled by default and the - # test is no longer parameterized by it. - shard_count = 4, deps = [ "//source/extensions/filters/http/csrf:config", "//test/config:utility_lib", diff --git a/test/extensions/filters/http/custom_response/BUILD b/test/extensions/filters/http/custom_response/BUILD index 01c7daba1d5c5..b5fc9a4784039 100644 --- a/test/extensions/filters/http/custom_response/BUILD +++ b/test/extensions/filters/http/custom_response/BUILD @@ -81,7 +81,6 @@ envoy_extension_cc_test( "custom_response_integration_test.cc", ], extension_names = ["envoy.filters.http.custom_response"], - shard_count = 4, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 2b0d771603b4d..c5b0a68e499e5 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -118,7 +118,6 @@ envoy_extension_cc_test( size = "large", # This test can take a while under tsan. srcs = ["ext_proc_integration_test.cc"], extension_names = ["envoy.filters.http.ext_proc"], - shard_count = 4, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/fault/BUILD b/test/extensions/filters/http/fault/BUILD index fd7a607726a56..a7413da5feaff 100644 --- a/test/extensions/filters/http/fault/BUILD +++ b/test/extensions/filters/http/fault/BUILD @@ -55,7 +55,6 @@ envoy_extension_cc_test( size = "large", srcs = ["fault_filter_integration_test.cc"], extension_names = ["envoy.filters.http.fault"], - shard_count = 2, deps = [ "//source/extensions/filters/http/fault:config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/file_system_buffer/BUILD b/test/extensions/filters/http/file_system_buffer/BUILD index 5c46acb03b8df..fb28ba6f0b588 100644 --- a/test/extensions/filters/http/file_system_buffer/BUILD +++ b/test/extensions/filters/http/file_system_buffer/BUILD @@ -47,7 +47,6 @@ envoy_extension_cc_test( "filter_integration_test.cc", ], extension_names = ["envoy.filters.http.file_system_buffer"], - shard_count = 2, tags = [ "cpu:3", "skip_on_windows", diff --git a/test/extensions/filters/http/grpc_field_extraction/BUILD b/test/extensions/filters/http/grpc_field_extraction/BUILD index b3add12547f25..5c606a2ccf861 100644 --- a/test/extensions/filters/http/grpc_field_extraction/BUILD +++ b/test/extensions/filters/http/grpc_field_extraction/BUILD @@ -55,7 +55,6 @@ envoy_extension_cc_test( ], data = ["//test/proto:apikeys_proto_descriptor"], extension_names = ["envoy.filters.http.grpc_field_extraction"], - shard_count = 2, deps = [ "//source/extensions/filters/http/grpc_field_extraction:config", "//test/extensions/filters/http/grpc_field_extraction/message_converter:message_converter_test_lib", diff --git a/test/extensions/filters/http/health_check/BUILD b/test/extensions/filters/http/health_check/BUILD index cb1aee58974ee..8d7a622ebf1df 100644 --- a/test/extensions/filters/http/health_check/BUILD +++ b/test/extensions/filters/http/health_check/BUILD @@ -45,7 +45,6 @@ envoy_extension_cc_test( "health_check_integration_test.cc", ], extension_names = ["envoy.filters.http.health_check"], - shard_count = 4, deps = [ "//source/extensions/filters/http/buffer:config", "//source/extensions/filters/http/health_check:config", diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 2e6c1ec110cc6..805df65cce86a 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -146,7 +146,7 @@ envoy_extension_cc_test( size = "large", srcs = ["filter_integration_test.cc"], extension_names = ["envoy.filters.http.jwt_authn"], - shard_count = 6, + shard_count = 4, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/kill_request/BUILD b/test/extensions/filters/http/kill_request/BUILD index c9d7d33d0d3f0..e4d78880db0e8 100644 --- a/test/extensions/filters/http/kill_request/BUILD +++ b/test/extensions/filters/http/kill_request/BUILD @@ -56,7 +56,7 @@ envoy_cc_test( name = "crash_integration_test", size = "large", srcs = ["crash_integration_test.cc"], - shard_count = 16, # This is really slow on coverage. + shard_count = 8, deps = [ "//source/extensions/filters/http/kill_request:kill_request_config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 18317bdb44674..20c1222ed9715 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -51,7 +51,6 @@ envoy_extension_cc_test( size = "large", srcs = ["rbac_filter_integration_test.cc"], extension_names = ["envoy.filters.http.rbac"], - shard_count = 16, deps = [ "//source/extensions/clusters/dynamic_forward_proxy:cluster", "//source/extensions/filters/http/dynamic_forward_proxy:config", diff --git a/test/extensions/filters/http/tap/BUILD b/test/extensions/filters/http/tap/BUILD index 3c084c91c5faf..86c40954c6a1b 100644 --- a/test/extensions/filters/http/tap/BUILD +++ b/test/extensions/filters/http/tap/BUILD @@ -54,7 +54,6 @@ envoy_extension_cc_test( size = "large", srcs = envoy_select_admin_functionality(["tap_filter_integration_test.cc"]), extension_names = ["envoy.filters.http.tap"], - shard_count = 4, deps = [ "//source/extensions/filters/http/tap:config", "//test/extensions/common/tap:common", diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index 59e7d75b33522..dfaa4a054170a 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -37,6 +37,7 @@ envoy_extension_cc_test( ]), extension_names = ["envoy.filters.http.wasm"], shard_count = 50, + tags = ["cpu:4"], deps = [ "//source/common/http:message_lib", "//source/extensions/filters/http/wasm:wasm_filter_lib", @@ -57,7 +58,7 @@ envoy_extension_cc_test( "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", ]), extension_names = ["envoy.filters.http.wasm"], - shard_count = 50, + shard_count = 16, deps = [ "//source/common/common:base64_lib", "//source/common/common:hex_lib", diff --git a/test/extensions/filters/network/redis_proxy/BUILD b/test/extensions/filters/network/redis_proxy/BUILD index 3ff6acbfc0e92..d884fbbe00b14 100644 --- a/test/extensions/filters/network/redis_proxy/BUILD +++ b/test/extensions/filters/network/redis_proxy/BUILD @@ -18,9 +18,6 @@ envoy_extension_cc_test( name = "command_splitter_impl_test", srcs = ["command_splitter_impl_test.cc"], extension_names = ["envoy.filters.network.redis_proxy"], - # This test takes a while to run specially under tsan. - # Shard it to avoid test timeout. - shard_count = 2, deps = [ ":redis_mocks", "//source/common/stats:isolated_store_lib", diff --git a/test/extensions/filters/network/thrift_proxy/BUILD b/test/extensions/filters/network/thrift_proxy/BUILD index 75d745bb8442c..1d0ce333508ea 100644 --- a/test/extensions/filters/network/thrift_proxy/BUILD +++ b/test/extensions/filters/network/thrift_proxy/BUILD @@ -345,7 +345,6 @@ envoy_extension_cc_test( "//test/extensions/filters/network/thrift_proxy/driver:generate_fixture", ], extension_names = ["envoy.filters.network.thrift_proxy"], - shard_count = 12, tags = ["skip_on_windows"], deps = [ ":integration_lib", @@ -364,7 +363,6 @@ envoy_extension_cc_test( "//test/extensions/filters/network/thrift_proxy/driver:generate_fixture", ], extension_names = ["envoy.filters.network.thrift_proxy"], - shard_count = 4, deps = [ ":integration_lib", ":utility_lib", diff --git a/test/extensions/listener_managers/listener_manager/BUILD b/test/extensions/listener_managers/listener_manager/BUILD index a0dda90322761..11bf849371e1f 100644 --- a/test/extensions/listener_managers/listener_manager/BUILD +++ b/test/extensions/listener_managers/listener_manager/BUILD @@ -50,7 +50,6 @@ envoy_cc_test_library( envoy_cc_test( name = "listener_manager_impl_test", srcs = ["listener_manager_impl_test.cc"], - shard_count = 4, deps = [ ":listener_manager_impl_test_lib", "//source/common/api:os_sys_calls_lib", diff --git a/test/extensions/transport_sockets/tls/BUILD b/test/extensions/transport_sockets/tls/BUILD index ccc4a1cdb7fa8..2324f740dba95 100644 --- a/test/extensions/transport_sockets/tls/BUILD +++ b/test/extensions/transport_sockets/tls/BUILD @@ -26,7 +26,6 @@ envoy_cc_test( "//test/extensions/transport_sockets/tls/test_data:certs", ], external_deps = ["ssl"], - shard_count = 4, deps = [ ":test_private_key_method_provider_test_lib", "//envoy/network:transport_socket_interface", diff --git a/test/integration/BUILD b/test/integration/BUILD index 257fde93a7fc8..57444f307d1b5 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -53,7 +53,7 @@ envoy_cc_test_library( envoy_cc_test( name = "ads_integration_test", - size = "enormous", + size = "large", srcs = envoy_select_admin_functionality( ["ads_integration_test.cc"], ), @@ -327,7 +327,6 @@ envoy_cc_test( srcs = envoy_select_admin_functionality([ "drain_close_integration_test.cc", ]), - shard_count = 2, tags = [ "cpu:3", ], @@ -356,7 +355,7 @@ envoy_cc_test_binary( envoy_sh_test( name = "hotrestart_test", - size = "enormous", + size = "large", srcs = select({ "//bazel:disable_hot_restart_or_admin": [], "//conditions:default": ["hotrestart_test.sh"], @@ -463,7 +462,7 @@ envoy_cc_test( srcs = [ "http2_flood_integration_test.cc", ], - shard_count = 8, + shard_count = 4, tags = [ "cpu:3", ], @@ -491,7 +490,7 @@ envoy_cc_test( srcs = [ "multiplexed_integration_test.cc", ], - shard_count = 16, + shard_count = 8, tags = [ "cpu:3", ], @@ -569,7 +568,6 @@ envoy_cc_test( srcs = [ "buffer_accounting_integration_test.cc", ], - shard_count = 4, tags = [ "cpu:3", ], @@ -698,7 +696,7 @@ envoy_cc_test( srcs = envoy_select_admin_functionality([ "filter_integration_test.cc", ]), - shard_count = 16, + shard_count = 8, tags = [ "cpu:3", ], @@ -775,7 +773,7 @@ envoy_cc_test( ], # As this test has many H1/H2/v4/v6 tests it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. - shard_count = 48, + shard_count = 8, tags = [ "cpu:3", ], @@ -801,7 +799,6 @@ envoy_cc_test( srcs = [ "multiplexed_upstream_integration_test.cc", ], - shard_count = 4, tags = [ "cpu:3", ], @@ -924,7 +921,7 @@ envoy_cc_test( srcs = ["idle_timeout_integration_test.cc"], # As this test has many pauses for idle timeouts, it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. - shard_count = 8, + shard_count = 4, tags = [ "cpu:3", ], @@ -1253,7 +1250,6 @@ envoy_cc_test( "integration_test.cc", "integration_test.h", ], - shard_count = 2, tags = [ "cpu:3", ], @@ -1287,7 +1283,6 @@ envoy_cc_test( srcs = [ "redirect_integration_test.cc", ], - shard_count = 8, tags = [ "cpu:3", "nofips", @@ -1321,7 +1316,6 @@ envoy_cc_test( "websocket_integration_test.cc", "websocket_integration_test.h", ], - shard_count = 4, tags = [ "cpu:3", ], @@ -1355,8 +1349,6 @@ envoy_cc_test( "//test/config/integration/certs", ], # The symbol table cluster memory tests take a while to run specially under tsan. - # Shard it to avoid test timeout. - shard_count = 2, deps = [ ":integration_lib", "//source/common/memory:stats_lib", @@ -1397,7 +1389,6 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 2, tags = [ "cpu:3", ], @@ -1470,7 +1461,7 @@ envoy_cc_test( name = "overload_integration_test", size = "large", srcs = ["overload_integration_test.cc"], - shard_count = 8, + shard_count = 4, tags = [ "cpu:3", ], @@ -1654,7 +1645,6 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 2, tags = [ "cpu:3", ], @@ -1691,7 +1681,6 @@ envoy_cc_test( srcs = [ "tcp_proxy_many_connections_test.cc", ], - shard_count = 1, tags = [ "cpu:3", ], @@ -1724,7 +1713,7 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 16, + shard_count = 8, tags = [ "cpu:3", ], @@ -1750,7 +1739,6 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 16, deps = [ ":http_integration_lib", ":http_protocol_integration_lib", @@ -2172,7 +2160,6 @@ envoy_cc_test( srcs = [ "local_reply_integration_test.cc", ], - shard_count = 2, tags = [ "cpu:2", ], @@ -2247,7 +2234,7 @@ envoy_cc_test( "//conditions:default": ["quic_protocol_integration_test.cc"], }), data = ["//test/config/integration/certs"], - shard_count = 24, + shard_count = 16, tags = [ "cpu:4", "nofips", @@ -2278,7 +2265,6 @@ envoy_cc_test( "//conditions:default": ["quic_http_integration_test.cc"], }), data = ["//test/config/integration/certs"], - shard_count = 6, # TODO(envoyproxy/windows-dev): Diagnose failure shown only on clang-cl build, see: # https://gist.github.com/wrowe/a152cb1d12c2f751916122aed39d8517 # TODO(envoyproxy/windows-dev): Diagnose timeout, why opt build test under Windows GCP RBE From 07aa59a6360e02dd7bbefdfbc5d8476837bb216c Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 31 Jul 2023 15:47:41 +0100 Subject: [PATCH 005/274] coverage: Disable hotrestart test (#28703) Signed-off-by: Ryan Northey --- test/integration/BUILD | 1 + test/per_file_coverage.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/integration/BUILD b/test/integration/BUILD index 57444f307d1b5..a35b00d0b144d 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -361,6 +361,7 @@ envoy_sh_test( "//conditions:default": ["hotrestart_test.sh"], }), cc_binary = [":hotrestart_main"], + coverage = False, data = [ "test_utility.sh", "//test/config/integration:server_config_files", diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 48772bbd04aac..35e0e48d006ef 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -23,7 +23,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/tcp:94.1" "source/common/thread:0.0" # Death tests don't report LCOV "source/common/watchdog:58.6" # Death tests don't report LCOV -"source/exe:94.9" +"source/exe:90.7" "source/extensions/access_loggers/grpc:95.8" "source/extensions/access_loggers/wasm:93.5" "source/extensions/clusters/common:91.5" # This can be increased again once `#24903` lands @@ -68,7 +68,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/watchdog:83.3" # Death tests within extensions "source/extensions/listener_managers/validation_listener_manager:70.0" "source/extensions/watchdog/profile_action:83.3" -"source/server:93.8" # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 +"source/server:90.8" # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 "source/server/config_validation:88.4" "source/extensions/health_checkers:96.0" "source/extensions/health_checkers/http:93.9" From 8233a409aa0a51d4e0c1b5d6dd19c7349bf4191d Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 3 Aug 2023 13:30:09 +0100 Subject: [PATCH 006/274] ci/test: Add back some sharding (#28789) Signed-off-by: Ryan Northey --- test/extensions/filters/http/custom_response/BUILD | 1 + test/extensions/filters/http/ext_proc/BUILD | 1 + test/extensions/filters/http/rbac/BUILD | 1 + test/integration/BUILD | 1 + 4 files changed, 4 insertions(+) diff --git a/test/extensions/filters/http/custom_response/BUILD b/test/extensions/filters/http/custom_response/BUILD index b5fc9a4784039..704c20d197f4c 100644 --- a/test/extensions/filters/http/custom_response/BUILD +++ b/test/extensions/filters/http/custom_response/BUILD @@ -81,6 +81,7 @@ envoy_extension_cc_test( "custom_response_integration_test.cc", ], extension_names = ["envoy.filters.http.custom_response"], + shard_count = 2, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index c5b0a68e499e5..4f6af31bc0f78 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -118,6 +118,7 @@ envoy_extension_cc_test( size = "large", # This test can take a while under tsan. srcs = ["ext_proc_integration_test.cc"], extension_names = ["envoy.filters.http.ext_proc"], + shard_count = 2, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 20c1222ed9715..92f3b3eb00672 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -51,6 +51,7 @@ envoy_extension_cc_test( size = "large", srcs = ["rbac_filter_integration_test.cc"], extension_names = ["envoy.filters.http.rbac"], + shard_count = 2, deps = [ "//source/extensions/clusters/dynamic_forward_proxy:cluster", "//source/extensions/filters/http/dynamic_forward_proxy:config", diff --git a/test/integration/BUILD b/test/integration/BUILD index a35b00d0b144d..5c7359f365b9f 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1284,6 +1284,7 @@ envoy_cc_test( srcs = [ "redirect_integration_test.cc", ], + shard_count = 2, tags = [ "cpu:3", "nofips", From c59ff1f32bf7671e28f64ab826707835bbc8a21e Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Wed, 2 Aug 2023 20:31:03 -0400 Subject: [PATCH 007/274] Fix UAF bug in connection limit filter. (#28785) * Fix UAF bug in connection limit filter. Signed-off-by: Kevin Baichoo Co-authored-by: Haoruo Lei Signed-off-by: Ryan Northey --- changelogs/current.yaml | 3 +++ .../filters/network/connection_limit/connection_limit.cc | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..3ce065d85d446 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,9 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: connection limit + change: | + fixed a use-after-free bug in the connection limit filter. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/filters/network/connection_limit/connection_limit.cc b/source/extensions/filters/network/connection_limit/connection_limit.cc index 64298a89cea59..8220c82086e04 100644 --- a/source/extensions/filters/network/connection_limit/connection_limit.cc +++ b/source/extensions/filters/network/connection_limit/connection_limit.cc @@ -80,7 +80,6 @@ Network::FilterStatus Filter::onNewConnection() { absl::optional duration = config_->delay(); if (duration.has_value() && duration.value() > std::chrono::milliseconds(0)) { delay_timer_ = read_callbacks_->connection().dispatcher().createTimer([this]() -> void { - resetTimerState(); read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); }); delay_timer_->enableTimer(duration.value()); From f6a7bc7044db87b49139adebef43b6243e448020 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 4 Aug 2023 15:08:14 +0100 Subject: [PATCH 008/274] deps: Bump `com_github_curl` -> 8.2.1 (#28830) Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index e4454e3d454bd..13d39c4c53f6b 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1065,8 +1065,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "curl", project_desc = "Library for transferring data with URLs", project_url = "https://curl.haxx.se", - version = "8.0.1", - sha256 = "5fd29000a4089934f121eff456101f0a5d09e2a3e89da1d714adf06c4be887cb", + version = "8.2.1", + sha256 = "f98bdb06c0f52bdd19e63c4a77b5eb19b243bcbbd0f5b002b9f3cba7295a3a42", strip_prefix = "curl-{version}", urls = ["https://github.com/curl/curl/releases/download/curl-{underscore_version}/curl-{version}.tar.gz"], use_category = ["dataplane_ext", "observability_ext"], @@ -1076,7 +1076,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.grpc_credentials.aws_iam", "envoy.tracers.opencensus", ], - release_date = "2023-03-20", + release_date = "2023-07-26", cpe = "cpe:2.3:a:haxx:libcurl:*", license = "curl", license_url = "https://github.com/curl/curl/blob/curl-{underscore_version}/COPYING", From 9a3bc7ed4a64ab865debc9885232cde6e702443f Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 2 Aug 2023 01:45:47 +0100 Subject: [PATCH 009/274] deps: Bump `rules_rust` -> 0.26.0 (#28744) Fix #28705 Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 13d39c4c53f6b..ffb93651a0ebb 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1384,12 +1384,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel rust rules", project_desc = "Bazel rust rules (used by Wasm)", project_url = "https://github.com/bazelbuild/rules_rust", - version = "0.25.1", - sha256 = "4a9cb4fda6ccd5b5ec393b2e944822a62e050c7c06f1ea41607f14c4fdec57a2", + version = "0.26.0", + sha256 = "9d04e658878d23f4b00163a72da3db03ddb451273eb347df7d7c50838d698f49", urls = ["https://github.com/bazelbuild/rules_rust/releases/download/{version}/rules_rust-v{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.wasm.runtime.wasmtime"], - release_date = "2023-07-05", + release_date = "2023-07-28", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_rust/blob/{version}/LICENSE.txt", From a117e508a5ef04d7146aecd328badcfe235b3be6 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Sun, 6 Aug 2023 10:25:48 +0100 Subject: [PATCH 010/274] ci: Remove mobile config Signed-off-by: Ryan Northey --- .github/workflows/mobile-android_build.yml | 229 ---------------- .github/workflows/mobile-android_tests.yml | 104 ------- .github/workflows/mobile-asan.yml | 41 --- .github/workflows/mobile-cc_tests.yml | 41 --- .../workflows/mobile-compile_time_options.yml | 126 --------- .github/workflows/mobile-core.yml | 44 --- .github/workflows/mobile-coverage.yml | 49 ---- .github/workflows/mobile-docs.yml | 44 --- .github/workflows/mobile-format.yml | 100 ------- .github/workflows/mobile-ios_build.yml | 258 ------------------ .github/workflows/mobile-ios_tests.yml | 62 ----- .github/workflows/mobile-perf.yml | 98 ------- .../workflows/mobile-release_validation.yml | 42 --- .github/workflows/mobile-traffic_director.yml | 46 ---- .github/workflows/mobile-tsan.yml | 42 --- .github/workflows/mobile_release.yml | 115 -------- 16 files changed, 1441 deletions(-) delete mode 100644 .github/workflows/mobile-android_build.yml delete mode 100644 .github/workflows/mobile-android_tests.yml delete mode 100644 .github/workflows/mobile-asan.yml delete mode 100644 .github/workflows/mobile-cc_tests.yml delete mode 100644 .github/workflows/mobile-compile_time_options.yml delete mode 100644 .github/workflows/mobile-core.yml delete mode 100644 .github/workflows/mobile-coverage.yml delete mode 100644 .github/workflows/mobile-docs.yml delete mode 100644 .github/workflows/mobile-format.yml delete mode 100644 .github/workflows/mobile-ios_build.yml delete mode 100644 .github/workflows/mobile-ios_tests.yml delete mode 100644 .github/workflows/mobile-perf.yml delete mode 100644 .github/workflows/mobile-release_validation.yml delete mode 100644 .github/workflows/mobile-traffic_director.yml delete mode 100644 .github/workflows/mobile-tsan.yml delete mode 100644 .github/workflows/mobile_release.yml diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml deleted file mode 100644 index 4ce6c329c7d1c..0000000000000 --- a/.github/workflows/mobile-android_build.yml +++ /dev/null @@ -1,229 +0,0 @@ -name: android_build - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - androidbuild: - if: ${{ needs.env.outputs.mobile_android_build == 'true' }} - needs: env - name: android_build - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 90 - container: - image: ${{ needs.env.outputs.build_image_ubuntu_mobile }} - env: - CC: /opt/llvm/bin/clang - CXX: /opt/llvm/bin/clang++ - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Build envoy.aar distributable' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-clang") \ - --fat_apk_cpu=x86_64 \ - --linkopt=-fuse-ld=lld \ - //:android_dist - - javahelloworld: - if: ${{ needs.env.outputs.mobile_android_build_all == 'true' }} - needs: - - env - - androidbuild - name: java_helloworld - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - run: cd mobile && ./ci/mac_ci_setup.sh --android - name: 'Install dependencies' - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start emulator' - with: - timeout_minutes: 10 - max_attempts: 3 - command: ./mobile/ci/start_android_emulator.sh - # Return to using: - # cd mobile && ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/java/hello_world:hello_envoy - # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start java app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --fat_apk_cpu=x86_64 \ - //examples/java/hello_world:hello_envoy - adb install -r --no-incremental bazel-bin/examples/java/hello_world/hello_envoy.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoy/.MainActivity - - name: 'Check connectivity' - run: | - timeout 30 adb logcat -e "received headers with status 301" -m 1 || { - echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { - echo "Failed dumping adb logcat" >&2 - } - exit 1 - } - kotlinhelloworld: - if: ${{ needs.env.outputs.mobile_android_build == 'true' }} - needs: - - env - - androidbuild - name: kotlin_helloworld - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start emulator' - with: - timeout_minutes: 10 - max_attempts: 3 - command: ./mobile/ci/start_android_emulator.sh - # Return to using: - # ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/kotlin/hello_world:hello_envoy_kt - # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start kotlin app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --fat_apk_cpu=x86_64 \ - //examples/kotlin/hello_world:hello_envoy_kt - adb install -r --no-incremental bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity - - name: 'Check connectivity' - run: | - timeout 30 adb logcat -e "received headers with status 200" -m 1 || { - echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { - echo "Failed dumping adb logcat" >&2 - } - exit 1 - } - - kotlinbaselineapp: - if: ${{ needs.env.outputs.mobile_android_build_all == 'true' }} - needs: - - env - - androidbuild - name: kotlin_baseline_app - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start emulator' - with: - timeout_minutes: 10 - max_attempts: 3 - command: ./mobile/ci/start_android_emulator.sh - # Return to using: - # ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/kotlin/hello_world:hello_envoy_kt - # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start kotlin app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --fat_apk_cpu=x86_64 \ - //test/kotlin/apps/baseline:hello_envoy_kt - adb install -r --no-incremental bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity - - name: 'Check connectivity' - run: | - timeout 30 adb logcat -e "received headers with status 301" -m 1 || { - echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { - echo "Failed dumping adb logcat" >&2 - } - exit 1 - } - kotlinexperimentalapp: - if: ${{ needs.env.outputs.mobile_android_build_all == 'true' }} - needs: - - env - - androidbuild - name: kotlin_experimental_app - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start emulator' - with: - timeout_minutes: 10 - max_attempts: 3 - command: ./mobile/ci/start_android_emulator.sh - # Return to using: - # ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/kotlin/hello_world:hello_envoy_kt - # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start kotlin app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --fat_apk_cpu=x86_64 \ - --define envoy_mobile_listener=enabled \ - //test/kotlin/apps/experimental:hello_envoy_kt - adb install -r --no-incremental bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity - - name: 'Check connectivity' - run: | - timeout 30 adb logcat -e "received headers with status 200" -m 1 || { - echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { - echo "Failed dumping adb logcat" >&2 - } - exit 1 - } diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml deleted file mode 100644 index 917c7f8871e5d..0000000000000 --- a/.github/workflows/mobile-android_tests.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: android_tests - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - uses: ./.github/workflows/_env.yml - secrets: inherit - - kotlintestsmac: - if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} - needs: env - # revert to //test/kotlin/... once fixed - # https://github.com/envoyproxy/envoy-mobile/issues/1932 - name: kotlin_tests_mac - runs-on: macos-12 - timeout-minutes: 90 - steps: - - uses: actions/checkout@v3 - - name: 'Java setup' - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh - - name: 'Run Kotlin library tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw test \ - --test_output=all \ - --build_tests_only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --define=signal_trace=disabled \ - //test/kotlin/io/... - javatestsmac: - if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} - needs: env - name: java_tests_mac - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v3 - - name: 'Java setup' - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh - - name: 'Run Java library tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw test \ - --test_output=all \ - --build_tests_only \ - --config test-android \ - --define envoy_mobile_listener=enabled \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --define=signal_trace=disabled \ - --define=system-helper=android \ - //test/java/... - kotlintestslinux: - if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} - needs: env - # Only kotlin tests are executed since with linux: - # https://github.com/envoyproxy/envoy-mobile/issues/1418. - name: kotlin_tests_linux - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 90 - container: - image: ${{ needs.env.outputs.build_image_ubuntu_mobile }} - env: - CC: /opt/llvm/bin/clang - CXX: /opt/llvm/bin/clang++ - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Run Kotlin library integration tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw test \ - --test_output=all \ - --build_tests_only \ - --config test-android \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-clang") \ - --define=signal_trace=disabled \ - //test/kotlin/... diff --git a/.github/workflows/mobile-asan.yml b/.github/workflows/mobile-asan.yml deleted file mode 100644 index c54a9a028eb45..0000000000000 --- a/.github/workflows/mobile-asan.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: mobile_asan - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - asan: - if: ${{ needs.env.outputs.mobile_asan == 'true' }} - needs: env - name: asan - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 180 - container: - image: ${{ needs.env.outputs.build_image_ubuntu_mobile }} - env: - CC: /opt/llvm/bin/clang - CXX: /opt/llvm/bin/clang++ - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Run tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw test --test_output=all \ - --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-asan") \ - //test/common/... diff --git a/.github/workflows/mobile-cc_tests.yml b/.github/workflows/mobile-cc_tests.yml deleted file mode 100644 index b9fb3b5cfad1b..0000000000000 --- a/.github/workflows/mobile-cc_tests.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: mobile_cc_tests - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - cctests: - if: ${{ needs.env.outputs.mobile_cc_tests == 'true' }} - needs: env - name: cc_tests - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 120 - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: 'Run tests' - # Regression test using the new API listener. TODO(#2711) clean up. - run: | - cd mobile && ./bazelw test \ - --action_env=LD_LIBRARY_PATH \ - --test_output=all \ - --copt=-DUSE_API_LISTENER \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ - //test/cc/... diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml deleted file mode 100644 index f724428c794c9..0000000000000 --- a/.github/workflows/mobile-compile_time_options.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: mobile_compile_time_options - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - cc_test_no_yaml: - needs: env - name: cc_test_no_yaml - runs-on: ubuntu-20.04 - timeout-minutes: 120 - container: - image: envoyproxy/envoy-build-ubuntu:41c5a05d708972d703661b702a63ef5060125c33 - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Running C++ test with YAML disabled' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Envoy Mobile build which verifies that the build configuration where YAML is disabled. - run: | - cd mobile - ./bazelw test \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ - --config=ci \ - --define=envoy_yaml=disabled \ - --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - //test/common/integration:client_integration_test --test_output=all - cc_test: - needs: env - name: cc_test - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 120 - container: - image: envoyproxy/envoy-build-ubuntu:41c5a05d708972d703661b702a63ef5060125c33 - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Running C++ tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile - ./bazelw test \ - --test_output=all \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ - --config=ci \ - --define=signal_trace=disabled \ - --define=envoy_mobile_request_compression=disabled \ - --define=envoy_enable_http_datagrams=disabled \ - --define=google_grpc=disabled \ - --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ - $(bazel query //test/cc/... + //test/common/... except //test/common/integration:client_integration_test) - swift_build: - if: ${{ needs.env.outputs.mobile_compile_time_options == 'true' }} - needs: env - name: swift_build - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build Swift library' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile - ./bazelw shutdown - ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --define=signal_trace=disabled \ - --define=envoy_mobile_request_compression=disabled \ - --define=envoy_mobile_stats_reporting=disabled \ - --define=envoy_mobile_swift_cxx_interop=disabled \ - --define=envoy_enable_http_datagrams=disabled \ - --define=google_grpc=disabled \ - --@envoy//bazel:http3=False \ - --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ - //library/swift:ios_framework - kotlin_build: - if: ${{ needs.env.outputs.mobile_compile_time_options == 'true' }} - needs: env - name: kotlin_build - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android - - name: 'Build Kotlin library' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile - ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --fat_apk_cpu=x86_64 \ - --define=signal_trace=disabled \ - --define=envoy_mobile_request_compression=disabled \ - --define=envoy_enable_http_datagrams=disabled \ - --define=google_grpc=disabled \ - --define=envoy_yaml=disabled \ - --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ - //:android_dist diff --git a/.github/workflows/mobile-core.yml b/.github/workflows/mobile-core.yml deleted file mode 100644 index a35a77397178d..0000000000000 --- a/.github/workflows/mobile-core.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: mobile_core - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - unittests: - if: ${{ github.repository == 'envoyproxy/envoy' }} - needs: env - name: unit_tests - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 120 - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - steps: - - uses: actions/checkout@v3 - - name: Ensure no listener leaks - run: rm source/extensions/listener_managers/listener_manager/listener_manager_impl.h - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Run tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw test \ - --build_tests_only \ - --action_env=LD_LIBRARY_PATH \ - --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - --test_output=all \ - --define envoy_mobile_listener=disabled \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ - //test/common/... diff --git a/.github/workflows/mobile-coverage.yml b/.github/workflows/mobile-coverage.yml deleted file mode 100644 index afd6a89430883..0000000000000 --- a/.github/workflows/mobile-coverage.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: mobile_coverage - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - coverage: - if: ${{ needs.env.outputs.mobile_coverage == 'true' }} - needs: env - name: coverage - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 120 - defaults: - run: - shell: bash - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Run coverage' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && BAZEL_BUILD_OPTION_LIST="--config=remote-ci-linux-coverage" \ - PATH=/opt/llvm/bin:${PATH} \ - COVERAGE_THRESHOLD=76 \ - ../test/run_envoy_bazel_coverage.sh //test/common/... //test/cc/... - - name: 'Package coverage' - run: | - cd mobile && tar -czf coverage.tar.gz generated/coverage - - name: 'Upload report' - uses: actions/upload-artifact@v3 - with: - name: coverage.tar.gz - path: mobile/coverage.tar.gz diff --git a/.github/workflows/mobile-docs.yml b/.github/workflows/mobile-docs.yml deleted file mode 100644 index b0180a972aa5f..0000000000000 --- a/.github/workflows/mobile-docs.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: mobile_docs - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - docs: - if: ${{ github.repository == 'envoyproxy/envoy' }} - needs: env - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 20 - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Generate docs - run: mobile/docs/build.sh - - name: Set up deploy key - if: github.ref == 'refs/heads/main' - uses: shimataro/ssh-key-action@v2.5.1 - with: - key: ${{ secrets.ENVOY_MOBILE_WEBSITE_DEPLOY_KEY }} - known_hosts: unnecessary - - name: Publish docs - if: github.ref == 'refs/heads/main' - run: mobile/docs/publish.sh - - uses: actions/upload-artifact@v3 - with: - name: docs - path: generated/docs diff --git a/.github/workflows/mobile-format.yml b/.github/workflows/mobile-format.yml deleted file mode 100644 index 68a871d39d2d6..0000000000000 --- a/.github/workflows/mobile-format.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: mobile_format - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - formatall: - if: ${{ needs.env.outputs.mobile_formatting == 'true' }} - needs: env - name: format_all - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 45 - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - env: - CLANG_FORMAT: /opt/llvm/bin/clang-format - BUILDIFIER_BIN: /usr/local/bin/buildifier - BUILDOZER_BIN: /usr/local/bin/buildozer - ENVOY_BAZEL_PREFIX: "@envoy" - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Run formatters' - run: cd mobile && ./tools/check_format.sh - precommit: - if: ${{ needs.env.outputs.mobile_formatting == 'true' }} - needs: env - name: precommit - runs-on: macos-12 - timeout-minutes: 45 - steps: - - uses: actions/checkout@v3 - - name: 'Install precommit' - run: brew install pre-commit - - name: 'Run precommit' - run: cd mobile && find mobile/* | pre-commit run --files - swiftlint: - if: ${{ needs.env.outputs.mobile_formatting == 'true' }} - needs: env - name: swift_lint - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 5 - container: - image: ghcr.io/realm/swiftlint:0.50.3 - steps: - - uses: actions/checkout@v3 - - name: 'Run Swift Lint (SwiftLint)' - run: swiftlint lint --strict - working-directory: mobile - drstring: - if: ${{ needs.env.outputs.mobile_formatting == 'true' }} - needs: env - name: drstring - runs-on: macos-12 - timeout-minutes: 10 - steps: - - uses: actions/checkout@v3 - - name: 'Run DrString' - env: - DEVELOPER_DIR: /Applications/Xcode_14.1.app - run: cd mobile && ./bazelw run @DrString//:drstring check - kotlinlint: - if: ${{ needs.env.outputs.mobile_formatting == 'true' }} - needs: env - name: kotlin_lint - runs-on: macos-12 - timeout-minutes: 45 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Run Kotlin Lint (Detekt)' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //library/kotlin/io/envoyproxy/envoymobile:envoy_lib_lint \ - //examples/kotlin/hello_world:hello_envoy_kt_lint - - name: 'Run Kotlin Formatter (ktlint)' - run: cd mobile && ./bazelw build kotlin_format diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml deleted file mode 100644 index 33ef5fbca5b2f..0000000000000 --- a/.github/workflows/mobile-ios_build.yml +++ /dev/null @@ -1,258 +0,0 @@ -name: ios_build - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - iosbuild: - if: ${{ needs.env.outputs.mobile_ios_build == 'true' }} - needs: env - name: ios_build - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build Envoy.framework distributable' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw shutdown - ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //library/swift:ios_framework - swifthelloworld: - if: ${{ needs.env.outputs.mobile_ios_build == 'true' }} - name: swift_helloworld - needs: - - env - - iosbuild - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //examples/swift/hello_world:app - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start simulator' - with: - timeout_minutes: 5 - max_attempts: 3 - command: ./mobile/ci/start_ios_simulator.sh - # Run the app in the background and redirect logs. - - name: 'Run app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw run \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //examples/swift/hello_world:app &> /tmp/envoy.log & - - run: sed '/received headers with status 200/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) - name: 'Check connectivity' - - run: cat /tmp/envoy.log - if: ${{ failure() || cancelled() }} - name: 'Log app run' - swiftbaselineapp: - if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} - needs: - - env - - iosbuild - name: swift_baseline_app - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //test/swift/apps/baseline:app - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start simulator' - with: - timeout_minutes: 5 - max_attempts: 3 - command: ./mobile/ci/start_ios_simulator.sh - # Run the app in the background and redirect logs. - - name: 'Run app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw run \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //test/swift/apps/baseline:app &> /tmp/envoy.log & - - run: sed '/received headers with status 301/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) - name: 'Check connectivity' - - run: cat /tmp/envoy.log - if: ${{ failure() || cancelled() }} - name: 'Log app run' - swiftexperimentalapp: - if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} - needs: - - env - - iosbuild - name: swift_experimental_app - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --define=admin_functionality=enabled \ - --define envoy_mobile_listener=enabled \ - //test/swift/apps/experimental:app - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start simulator' - with: - timeout_minutes: 5 - max_attempts: 3 - command: ./mobile/ci/start_ios_simulator.sh - # Run the app in the background and redirect logs. - - name: 'Run app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw run \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - --define=admin_functionality=enabled \ - --define envoy_mobile_listener=enabled \ - //test/swift/apps/experimental:app &> /tmp/envoy.log & - - run: sed '/received headers with status 200/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) - name: 'Check connectivity' - - run: cat /tmp/envoy.log - if: ${{ failure() || cancelled() }} - name: 'Log app run' - swiftasyncawait: - if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} - needs: - - env - - iosbuild - name: swift_async_await - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //examples/swift/async_await:app - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start simulator' - with: - timeout_minutes: 5 - max_attempts: 3 - command: ./mobile/ci/start_ios_simulator.sh - # Run the app in the background and redirect logs. - - name: 'Run app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw run \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //examples/swift/async_await:app &> /tmp/envoy.log & - - run: | - checklogs () { - sed '/\[2\] Uploaded 7 MB of data/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) - } - export -f checklogs - # TODO(phlax): figure if this needs this long - timeout 5m bash -c checklogs || { - retcode=$? - if [[ "$retcode" != 124 ]]; then - echo "Command failed" >&2 - elif grep -q "Upload failed" /tmp/envoy.log; then - echo "Upload failed" >&2 - else - echo "Upload timed out" >&2 - fi - exit 1 - } - if: steps.should_run.outputs.run_ci_job == 'true' - name: 'Check upload succeeded' - - run: cat /tmp/envoy.log - if: ${{ failure() || cancelled() }} - name: 'Log app run' - objchelloworld: - if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} - needs: - - env - - iosbuild - name: objc_helloworld - runs-on: macos-12 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //examples/objective-c/hello_world:app - - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - name: 'Start simulator' - with: - timeout_minutes: 5 - max_attempts: 3 - command: ./mobile/ci/start_ios_simulator.sh - # Run the app in the background and redirect logs. - - name: 'Run app' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw run \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //examples/objective-c/hello_world:app &> /tmp/envoy.log & - - run: sed '/received headers with status 301/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) - name: 'Check connectivity' - - run: cat /tmp/envoy.log - if: ${{ failure() || cancelled() }} - name: 'Log app run' diff --git a/.github/workflows/mobile-ios_tests.yml b/.github/workflows/mobile-ios_tests.yml deleted file mode 100644 index 02df1e8d2f6b7..0000000000000 --- a/.github/workflows/mobile-ios_tests.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: ios_tests - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - swifttests: - if: ${{ needs.env.outputs.mobile_ios_tests == 'true' }} - needs: env - name: swift_tests - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v3 - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh - - name: 'Run swift library tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # runs with the listener enabled due to IdleTimeoutTest not setting up a test backend. - run: | - cd mobile && ./bazelw test \ - --experimental_ui_max_stdouterr_bytes=10485760 \ - --test_output=all \ - --config=ios \ - --define envoy_mobile_listener=enabled \ - --build_tests_only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //test/swift/... - objctests: - if: ${{ needs.env.outputs.mobile_ios_tests == 'true' }} - needs: env - name: c_and_objc_tests - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v3 - - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh - - name: 'Run Objective-C library tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw test \ - --test_output=all \ - --config=ios \ - --build_tests_only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //test/objective-c/... \ - //test/cc/unit:envoy_config_test diff --git a/.github/workflows/mobile-perf.yml b/.github/workflows/mobile-perf.yml deleted file mode 100644 index 754097c2b0aaf..0000000000000 --- a/.github/workflows/mobile-perf.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: mobile_perf - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - sizecurrent: - if: ${{ github.repository == 'envoyproxy/envoy' }} - name: size_current - runs-on: ubuntu-22.04 - timeout-minutes: 120 - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - env: - CC: /opt/llvm/bin/clang - CXX: /opt/llvm/bin/clang++ - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Build test binary' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - --config=sizeopt \ - --config=release-common \ - --config=remote-ci-linux-clang \ - //test/performance:test_binary_size - - uses: actions/upload-artifact@v3 - with: - name: sizecurrent - path: mobile/bazel-bin/test/performance/test_binary_size - sizemain: - if: ${{ github.repository == 'envoyproxy/envoy' }} - name: size_main - runs-on: ubuntu-22.04 - timeout-minutes: 90 - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - env: - CC: /opt/llvm/bin/clang - CXX: /opt/llvm/bin/clang++ - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: | - git config --global --add safe.directory /__w/envoy/envoy - - name: 'Build test binary' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git checkout main && git pull origin main - cd mobile && ./bazelw build \ - --config=sizeopt \ - --config=release-common \ - --config=remote-ci-linux-clang \ - //test/performance:test_binary_size - - uses: actions/upload-artifact@v3 - with: - name: sizemain - path: mobile/bazel-bin/test/performance/test_binary_size - sizecompare: - if: ${{ github.repository == 'envoyproxy/envoy' }} - needs: - - sizecurrent - - sizemain - name: size_compare - runs-on: ubuntu-22.04 - timeout-minutes: 30 - container: - image: ${{ needs.env.outputs.build_image_ubuntu }} - steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 - with: - name: sizecurrent - path: dist/sizecurrent - - uses: actions/download-artifact@v3 - with: - name: sizemain - path: dist/sizemain - - name: 'Strip and Zip binary' - run: | - ls -lh dist/ - strip -s -o dist/main.stripped dist/sizemain/test_binary_size - strip -s -o dist/current.stripped dist/sizecurrent/test_binary_size - zip -9 dist/main.zip dist/main.stripped - zip -9 dist/current.zip dist/current.stripped - - name: 'Test size regression' - run: cd mobile && ./ci/test_size_regression.sh ../dist/main.zip ../dist/current.zip diff --git a/.github/workflows/mobile-release_validation.yml b/.github/workflows/mobile-release_validation.yml deleted file mode 100644 index 88286e8a3e81a..0000000000000 --- a/.github/workflows/mobile-release_validation.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: mobile_release_validation - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - validate_swiftpm_example: - if: ${{ needs.env.outputs.mobile_release_validation == 'true' }} - needs: env - name: validate_swiftpm_example - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh - name: 'Install dependencies' - - name: 'Build xcframework' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw build \ - --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ - //:ios_xcframework - # Ignore errors: Bad CRC when unzipping large files: https://bbs.archlinux.org/viewtopic.php?id=153011 - - run: unzip mobile/bazel-bin/library/swift/Envoy.xcframework.zip -d mobile/examples/swift/swiftpm/Packages || true - name: 'Unzip xcframework' - - run: xcodebuild -project mobile/examples/swift/swiftpm/EnvoySwiftPMExample.xcodeproj -scheme EnvoySwiftPMExample -destination platform="iOS Simulator,name=iPhone 14 Pro Max,OS=16.1" - name: 'Build app' - # TODO(jpsim): Run app and inspect logs to validate diff --git a/.github/workflows/mobile-traffic_director.yml b/.github/workflows/mobile-traffic_director.yml deleted file mode 100644 index 85a9bdf0b8925..0000000000000 --- a/.github/workflows/mobile-traffic_director.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: mobile_traffic_director - -on: - schedule: - # Once a day at midnight. - - cron: '0 0 * * *' - # Allows manual triggering in the UI. Makes it easier to test. - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.head_ref || github.run_id }}-github.workflow - cancel-in-progress: true - -jobs: - cc_test: - if: | - ${{ - github.repository == 'envoyproxy/envoy' - && (github.event.schedule - || !contains(github.actor, '[bot]')) - }} - name: cc_test - permissions: - packages: read - runs-on: ubuntu-20.04 - timeout-minutes: 120 - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Run GcpTrafficDirectorIntegrationTest' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GCP_JWT_PRIVATE_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_JWT_TOKEN }} - ENVOY_IP_TEST_VERSIONS: v4only - run: | - cd mobile - ./bazelw run \ - --config=remote-ci-linux \ - --config=ci \ - --test_output=all \ - //test/non_hermetic:gcp_traffic_director_integration_test diff --git a/.github/workflows/mobile-tsan.yml b/.github/workflows/mobile-tsan.yml deleted file mode 100644 index f72a907666c8d..0000000000000 --- a/.github/workflows/mobile-tsan.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: mobile_tsan - -on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: ${{ github.repository == 'envoyproxy/envoy' }} - uses: ./.github/workflows/_env.yml - secrets: inherit - - tsan: - if: ${{ needs.env.outputs.mobile_tsan == 'true' }} - needs: env - name: tsan - runs-on: ${{ needs.env.outputs.agent_ubuntu }} - timeout-minutes: 90 - container: - image: ${{ needs.env.outputs.build_image_ubuntu_mobile }} - env: - CC: /opt/llvm/bin/clang - CXX: /opt/llvm/bin/clang++ - steps: - - uses: actions/checkout@v3 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Run tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile && ./bazelw test \ - --test_output=all \ - --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-tsan") \ - //test/common/... diff --git a/.github/workflows/mobile_release.yml b/.github/workflows/mobile_release.yml deleted file mode 100644 index aaecefb138b6d..0000000000000 --- a/.github/workflows/mobile_release.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: mobile_release - -on: - workflow_dispatch: - schedule: - # Mondays at 1pm UTC (8am EST) - - cron: "0 13 * * 1" - -jobs: - android_release_artifacts: - if: | - ${{ - github.repository == 'envoyproxy/envoy' - && (github.event.schedule - || !contains(github.actor, '[bot]')) - }} - name: android_release_artifacts - runs-on: ubuntu-22.04 - timeout-minutes: 120 - container: - image: ${{ needs.env.outputs.build_image_ubuntu_mobile }} - env: - CC: /opt/llvm/bin/clang - CXX: /opt/llvm/bin/clang++ - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - name: 'Build envoy.aar distributable' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - working-directory: mobile - run: | - version="0.5.0.$(date '+%Y%m%d')" - ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-clang") \ - --remote_header="Authorization=Bearer $GITHUB_TOKEN" \ - --fat_apk_cpu=x86,x86_64,armeabi-v7a,arm64-v8a \ - --define=pom_version="$version" \ - --config=release-android \ - --linkopt=-fuse-ld=lld \ - //:android_dist - - name: 'Tar artifacts' - run: | - tar -czvf envoy_android_aar_sources.tar.gz \ - bazel-bin/library/kotlin/io/envoyproxy/envoymobile/envoy.aar \ - bazel-bin/library/kotlin/io/envoyproxy/envoymobile/envoy-pom.xml \ - bazel-bin/library/kotlin/io/envoyproxy/envoymobile/envoy-sources.jar \ - bazel-bin/library/kotlin/io/envoyproxy/envoymobile/envoy-javadoc.jar - working-directory: mobile - - uses: actions/upload-artifact@v3 - with: - name: envoy_android_aar_sources - path: mobile/envoy_android_aar_sources.tar.gz - android_release_deploy: - name: android_release_deploy - needs: android_release_artifacts - runs-on: ubuntu-22.04 - timeout-minutes: 20 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - uses: actions/download-artifact@v3 - with: - name: envoy_android_aar_sources - path: . - - name: Expand archive - run: | - tar -xvf envoy_android_aar_sources.tar.gz - mv bazel-bin/library/kotlin/io/envoyproxy/envoymobile/* . - - name: 'Configure gpg signing' - env: - GPG_KEY: ${{ secrets.EM_GPG_KEY }} - GPG_KEY_NAME: ${{ secrets.EM_GPG_KEY_NAME }} - GPG_PASSPHRASE: ${{ secrets.EM_GPG_PASSPHRASE }} - run: | - # https://github.com/keybase/keybase-issues/issues/2798 - export GPG_TTY=$(tty) - # Import gpg keys and warm the passphrase to avoid the gpg - # passphrase prompt when initating a deploy - # `--pinentry-mode=loopback` could be needed to ensure we - # suppress the gpg prompt - echo $GPG_KEY | base64 --decode > signing-key - gpg --passphrase $GPG_PASSPHRASE --batch --import signing-key - shred signing-key - - gpg --pinentry-mode=loopback --passphrase $GPG_PASSPHRASE -ab envoy.aar - gpg --pinentry-mode=loopback --passphrase $GPG_PASSPHRASE -ab envoy-pom.xml - gpg --pinentry-mode=loopback --passphrase $GPG_PASSPHRASE -ab envoy-javadoc.jar - gpg --pinentry-mode=loopback --passphrase $GPG_PASSPHRASE -ab envoy-sources.jar - - name: 'Release to sonatype repository' - env: - READWRITE_USER: ${{ secrets.EM_SONATYPE_USER }} - READWRITE_API_KEY: ${{ secrets.EM_SONATYPE_PASSWORD }} - SONATYPE_PROFILE_ID: ${{ secrets.EM_SONATYPE_PROFILE_ID }} - run: | - version="0.5.0.$(date '+%Y%m%d')" - python mobile/ci/sonatype_nexus_upload.py \ - --profile_id=$SONATYPE_PROFILE_ID \ - --version=$version \ - --files \ - envoy.aar \ - envoy-pom.xml \ - envoy-sources.jar \ - envoy-javadoc.jar \ - --signed_files \ - envoy.aar.asc \ - envoy-pom.xml.asc \ - envoy-sources.jar.asc \ - envoy-javadoc.jar.asc From 74bde65d638a0bd83746a2a027f1ab1c5c756c56 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 7 Aug 2023 18:32:14 +0100 Subject: [PATCH 011/274] bazel/ci: Improve flags/config (#28856) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .azure-pipelines/bazel.yml | 4 +- .azure-pipelines/stage/prechecks.yml | 8 +- .azure-pipelines/stage/publish.yml | 8 +- .azure-pipelines/stage/verify.yml | 6 +- .azure-pipelines/stage/windows.yml | 4 +- .bazelrc | 87 +++++- .github/actions/diskspace/action.yml | 27 ++ .github/workflows/POLICY.md | 2 +- .github/workflows/_ci.yml | 15 +- .github/workflows/_env.yml | 2 + .github/workflows/check-deps.yml | 9 +- .github/workflows/envoy-publish.yml | 6 +- .github/workflows/pr_notifier.yml | 2 +- .github/workflows/stale.yml | 17 +- ci/build_setup.sh | 1 - ci/do_ci.sh | 48 ++-- ci/filter_example_setup.sh | 4 +- ci/setup_cache.sh | 12 +- mobile/.bazelrc | 257 ++++++++---------- .../run_configuration_example_debug_arm64.xml | 2 +- .../run_configuration_example_debug_x86.xml | 2 +- .../run_configuration_example_debug_arm64.xml | 2 +- .../run_configuration_example_debug_x86.xml | 2 +- .../run_configuration_example_debug_arm64.xml | 2 +- .../run_configuration_example_debug_x86.xml | 2 +- .../run_configuration_example_debug_arm64.xml | 2 +- .../run_configuration_example_debug_x86.xml | 2 +- test/run_envoy_bazel_coverage.sh | 6 +- 28 files changed, 284 insertions(+), 257 deletions(-) create mode 100644 .github/actions/diskspace/action.yml diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index 8b170a05982ce..3385b9deef285 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -195,9 +195,7 @@ steps: ${{ if parameters.rbe }}: GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs) ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs) ${{ parameters.bazelBuildExtraOptions }}" ${{ if eq(parameters.rbe, false) }}: BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" BAZEL_REMOTE_CACHE: $(LocalBuildCache) diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 6e7c82e577d7e..8928038419256 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -114,9 +114,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') @@ -146,9 +144,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index d80c1f5057277..39ba7e5c0bc5b 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -252,9 +252,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} @@ -270,9 +268,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs-publish-latest' diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index 67898770c463b..c638cb8aa0726 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -29,8 +29,7 @@ jobs: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_DOCKER_IN_DOCKER: 1 ENVOY_RBE: 1 - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} displayName: "Verify packages" @@ -54,8 +53,7 @@ jobs: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_DOCKER_IN_DOCKER: 1 ENVOY_RBE: 1 - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} displayName: "Verify packages" diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index a59e01d024d32..83ab4774765eb 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -32,9 +32,7 @@ jobs: CI_TARGET: "windows" ENVOY_DOCKER_BUILD_DIR: "$(Build.StagingDirectory)" ENVOY_RBE: "true" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=remote-msvc-cl --jobs=$(RbeJobs) --flaky_test_attempts=2" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --config=remote-msvc-cl --jobs=$(RbeJobs) --flaky_test_attempts=2" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} - task: PublishTestResults@2 diff --git a/.bazelrc b/.bazelrc index 55e4457ae3b65..e49155fa1a65b 100644 --- a/.bazelrc +++ b/.bazelrc @@ -13,6 +13,7 @@ startup --host_jvm_args=-Xmx3g run --color=yes build --color=yes +build --jobs=HOST_CPUS-1 build --workspace_status_command="bash bazel/get_workspace_status" build --incompatible_strict_action_env build --java_runtime_version=remotejdk_11 @@ -69,8 +70,6 @@ build --@com_googlesource_googleurl//build_config:system_icu=0 # Common flags for sanitizers build:sanitizer --define tcmalloc=disabled build:sanitizer --linkopt -ldl -build:sanitizer --build_tag_filters=-no_san -build:sanitizer --test_tag_filters=-no_san # Common flags for Clang build:clang --action_env=BAZEL_COMPILER=clang @@ -90,6 +89,8 @@ build:asan --config=sanitizer # ASAN install its signal handler, disable ours so the stacktrace will be printed by ASAN build:asan --define signal_trace=disabled build:asan --define ENVOY_CONFIG_ASAN=1 +build:asan --build_tag_filters=-no_san +build:asan --test_tag_filters=-no_san build:asan --copt -fsanitize=address,undefined build:asan --linkopt -fsanitize=address,undefined # vptr and function sanitizer are enabled in clang-asan if it is set up via bazel/setup_clang.sh. @@ -150,6 +151,8 @@ build:clang-tsan --test_timeout=120,600,1500,4800 # with libc++ instruction and provide corresponding `--copt` and `--linkopt` as well. build:clang-msan --action_env=ENVOY_MSAN=1 build:clang-msan --config=sanitizer +build:clang-msan --build_tag_filters=-no_san +build:clang-msan --test_tag_filters=-no_san build:clang-msan --define ENVOY_CONFIG_MSAN=1 build:clang-msan --copt -fsanitize=memory build:clang-msan --linkopt -fsanitize=memory @@ -199,12 +202,14 @@ build:coverage --strategy=TestRunner=sandboxed,local build:coverage --strategy=CoverageReport=sandboxed,local build:coverage --experimental_use_llvm_covmap build:coverage --collect_code_coverage -build:coverage --test_tag_filters=-nocoverage build:coverage --instrumentation_filter="//source(?!/common/quic/platform)[/:],//envoy[/:],//contrib(?!/.*/test)[/:]" + build:test-coverage --test_arg="-l trace" build:test-coverage --test_arg="--log-path /dev/null" +build:test-coverage --test_tag_filters=-nocoverage,-fuzz_target build:fuzz-coverage --config=plain-fuzzer build:fuzz-coverage --run_under=@envoy//bazel/coverage:fuzz_coverage_wrapper.sh +build:fuzz-coverage --test_tag_filters=-nocoverage # Remote execution: https://docs.bazel.build/versions/master/remote-execution.html build:rbe-toolchain --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 @@ -264,10 +269,6 @@ build:remote --spawn_strategy=remote,sandboxed,local build:remote --strategy=Javac=remote,sandboxed,local build:remote --strategy=Closure=remote,sandboxed,local build:remote --strategy=Genrule=remote,sandboxed,local -build:remote --remote_timeout=7200 -build:remote --google_default_credentials=true -build:remote --remote_download_toplevel -build:remote --nobuild_runfile_links # Windows bazel does not allow sandboxed as a spawn strategy build:remote-windows --spawn_strategy=remote,local @@ -307,6 +308,25 @@ build:remote-clang-cl --config=remote-windows build:remote-clang-cl --config=clang-cl build:remote-clang-cl --config=rbe-toolchain-clang-cl +## Compile-time-options testing +# Right now, none of the available compile-time options conflict with each other. If this +# changes, this build type may need to be broken up. +build:compile-time-options --define=admin_html=disabled +build:compile-time-options --define=signal_trace=disabled +build:compile-time-options --define=hot_restart=disabled +build:compile-time-options --define=google_grpc=disabled +build:compile-time-options --define=boringssl=fips +build:compile-time-options --define=log_debug_assert_in_release=enabled +build:compile-time-options --define=path_normalization_by_default=true +build:compile-time-options --define=deprecated_features=disabled +build:compile-time-options --define=tcmalloc=gperftools +build:compile-time-options --define=zlib=ng +build:compile-time-options --define=uhv=enabled +build:compile-time-options --config=libc++20 +build:compile-time-options --test_env=ENVOY_HAS_EXTRA_EXTENSIONS=true +build:compile-time-options --@envoy//bazel:http3=False +build:compile-time-options --@envoy//source/extensions/filters/http/kill_request:enabled + # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:41c5a05d708972d703661b702a63ef5060125c33 @@ -340,16 +360,13 @@ build:docker-tsan --config=rbe-toolchain-clang-libc++ build:docker-tsan --config=rbe-toolchain-tsan # CI configurations -build:remote-ci --remote_cache=grpcs://remotebuildexecution.googleapis.com -build:remote-ci --remote_executor=grpcs://remotebuildexecution.googleapis.com build:remote-ci --config=ci +build:remote-ci --remote_download_minimal + # Note this config is used by mobile CI also. build:ci --noshow_progress build:ci --noshow_loading_progress - -# Build Event Service -build:google-bes --bes_backend=grpcs://buildeventservice.googleapis.com -build:google-bes --bes_results_url=https://source.cloud.google.com/results/invocations/ +build:ci --test_output=errors # Fuzz builds @@ -440,6 +457,50 @@ build:windows --features=fully_static_link build:windows --features=static_link_msvcrt build:windows --dynamic_mode=off +# RBE (Google) +build:rbe-google --google_default_credentials=true +build:rbe-google --remote_cache=grpcs://remotebuildexecution.googleapis.com +build:rbe-google --remote_executor=grpcs://remotebuildexecution.googleapis.com +build:rbe-google --remote_timeout=7200 +build:rbe-google --remote_instance_name=projects/envoy-ci/instances/default_instance + +build:rbe-google-bes --bes_backend=grpcs://buildeventservice.googleapis.com +build:rbe-google-bes --bes_results_url=https://source.cloud.google.com/results/invocations/ + +# RBE (Engflow mobile) +build:rbe-engflow --google_default_credentials=false +build:rbe-engflow --remote_cache=grpcs://envoy.cluster.engflow.com +build:rbe-engflow --remote_executor=grpcs://envoy.cluster.engflow.com +build:rbe-engflow --bes_backend=grpcs://envoy.cluster.engflow.com/ +build:rbe-engflow --bes_results_url=https://envoy.cluster.engflow.com/invocation/ +build:rbe-engflow --experimental_credential_helper=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:rbe-engflow --grpc_keepalive_time=30s +build:rbe-engflow --remote_timeout=3600s +build:rbe-engflow --bes_timeout=3600s +build:rbe-engflow --bes_upload_mode=fully_async + +############################################################################# +# debug: Various Bazel debugging flags +############################################################################# +# debug/bazel +common:debug-bazel --announce_rc +common:debug-bazel -s +# debug/sandbox +common:debug-sandbox --verbose_failures +common:debug-sandbox --sandbox_debug +# debug/coverage +common:debug-coverage --action_env=VERBOSE_COVERAGE=true +common:debug-coverage --test_env=VERBOSE_COVERAGE=true +common:debug-coverage --test_env=DISPLAY_LCOV_CMD=true +common:debug-coverage --config=debug-tests +# debug/tests +common:debug-tests --test_output=all +# debug/everything +common:debug --config=debug-bazel +common:debug --config=debug-sandbox +common:debug --config=debug-coverage +common:debug --config=debug-tests + try-import %workspace%/clang.bazelrc try-import %workspace%/user.bazelrc try-import %workspace%/local_tsan.bazelrc diff --git a/.github/actions/diskspace/action.yml b/.github/actions/diskspace/action.yml new file mode 100644 index 0000000000000..8df3a6f89957b --- /dev/null +++ b/.github/actions/diskspace/action.yml @@ -0,0 +1,27 @@ +inputs: + to_remove: + type: string + default: | + /opt/hostedtoolcache + /usr/local/lib/android + /usr/local/.ghcup + +runs: + using: composite + steps: + - id: remove_cruft + name: Cruft removal + run: | + echo "Disk space before cruft removal" + df -h + + TO_REMOVE=(${{ inputs.to_remove }}) + + for removal in "${TO_REMOVE[@]}"; do + echo "Removing: ${removal} ..." + sudo rm -rf "$removal" + done + + echo "Disk after before cruft removal" + df -h + shell: bash diff --git a/.github/workflows/POLICY.md b/.github/workflows/POLICY.md index 86d775493dc9d..c52488cd22efe 100644 --- a/.github/workflows/POLICY.md +++ b/.github/workflows/POLICY.md @@ -40,7 +40,7 @@ Do not allow any bots or app users to do so, unless this is specifically require For example, you could add a `job` condition to prevent any bots from triggering the workflow: ```yaml - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index c036a726ef335..11a61854caf74 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -115,20 +115,7 @@ jobs: run: git config --global --add safe.directory /__w/envoy/envoy - if: ${{ inputs.diskspace_hack }} - name: Cruft removal - run: | - echo "Disk space before cruft removal" - df -h - - TO_REMOVE=( - /opt/hostedtoolcache - /usr/local/lib/android - /usr/local/.ghcup) - - for removal in "${TO_REMOVE[@]}"; do - echo "Removing: ${removal} ..." - sudo rm -rf "$removal" - done + uses: ./.github/actions/diskspace - run: | echo "disk space at beginning of build:" df -h diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index c31814c893ca4..d7263d96d99ea 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -42,6 +42,8 @@ on: default: outputs: + debug: + value: false agent_ubuntu: value: ubuntu-22.04 build_image_ubuntu: diff --git a/.github/workflows/check-deps.yml b/.github/workflows/check-deps.yml index 984a52a57b0ff..9bb8055cb624a 100644 --- a/.github/workflows/check-deps.yml +++ b/.github/workflows/check-deps.yml @@ -1,16 +1,17 @@ name: Check dependencies +permissions: + contents: read + on: schedule: - - cron: '0 8 * * *' + - cron: '0 8 * * *' workflow_dispatch: -permissions: read-all - jobs: build: runs-on: ubuntu-22.04 - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 2ec5bd5969bd0..4eec30a211494 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -1,5 +1,8 @@ name: Publish & verify +permissions: + contents: read + on: # This runs untrusted code, do not expose secrets in the verify job workflow_dispatch: @@ -19,9 +22,6 @@ concurrency: }}-${{ github.workflow }} cancel-in-progress: true -permissions: - contents: read - jobs: env: if: | diff --git a/.github/workflows/pr_notifier.yml b/.github/workflows/pr_notifier.yml index f7303a1678d68..df31d16768d9d 100644 --- a/.github/workflows/pr_notifier.yml +++ b/.github/workflows/pr_notifier.yml @@ -14,7 +14,7 @@ jobs: pull-requests: read # for pr_notifier.py name: PR Notifier runs-on: ubuntu-22.04 - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index aa6d198d07449..d8f8986bae8a2 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,3 +1,8 @@ +name: Prune stale + +permissions: + contents: read + on: workflow_dispatch: schedule: @@ -5,17 +10,17 @@ on: jobs: prune_stale: - permissions: - issues: write # for actions/stale to close stale issues - pull-requests: write # for actions/stale to close stale PRs - name: Prune Stale - runs-on: ubuntu-22.04 - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule || !contains(github.actor, '[bot]')) }} + permissions: + issues: write # for actions/stale to close stale issues + pull-requests: write # for actions/stale to close stale PRs + name: Prune stale + runs-on: ubuntu-22.04 steps: - name: Prune Stale diff --git a/ci/build_setup.sh b/ci/build_setup.sh index f4a94398f1bf6..811371612d4e9 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -134,7 +134,6 @@ BAZEL_BUILD_OPTIONS=( "${BAZEL_GLOBAL_OPTIONS[@]}" "--verbose_failures" "--experimental_generate_json_trace_profile" - "--test_output=errors" "--action_env=CLANG_FORMAT" "${BAZEL_BUILD_EXTRA_OPTIONS[@]}" "${BAZEL_EXTRA_TEST_OPTIONS[@]}") diff --git a/ci/do_ci.sh b/ci/do_ci.sh index b0a48d6b8aaa4..52e00564f6c4b 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -326,25 +326,7 @@ case $CI_TARGET in ;; compile_time_options) - # Right now, none of the available compile-time options conflict with each other. If this - # changes, this build type may need to be broken up. - COMPILE_TIME_OPTIONS=( - "--define" "admin_html=disabled" - "--define" "signal_trace=disabled" - "--define" "hot_restart=disabled" - "--define" "google_grpc=disabled" - "--define" "boringssl=fips" - "--define" "log_debug_assert_in_release=enabled" - "--define" "path_normalization_by_default=true" - "--define" "deprecated_features=disabled" - "--define" "tcmalloc=gperftools" - "--define" "zlib=ng" - "--define" "uhv=enabled" - "--@envoy//bazel:http3=False" - "--@envoy//source/extensions/filters/http/kill_request:enabled" - "--test_env=ENVOY_HAS_EXTRA_EXTENSIONS=true" - "--remote_download_minimal" - "--config=libc++20") + # See `compile-time-options` in `.bazelrc` setup_clang_toolchain # This doesn't go into CI but is available for developer convenience. echo "bazel with different compiletime options build with tests..." @@ -354,8 +336,8 @@ case $CI_TARGET in echo "Building and testing with wasm=wamr: ${TEST_TARGETS[*]}" bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wamr \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c fastbuild \ "${TEST_TARGETS[@]}" \ --test_tag_filters=-nofips \ @@ -363,10 +345,9 @@ case $CI_TARGET in echo "Building and testing with wasm=wasmtime: and admin_functionality and admin_html disabled ${TEST_TARGETS[*]}" bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wasmtime \ - --define admin_html=disabled \ --define admin_functionality=disabled \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c fastbuild \ "${TEST_TARGETS[@]}" \ --test_tag_filters=-nofips \ @@ -374,8 +355,8 @@ case $CI_TARGET in echo "Building and testing with wasm=wavm: ${TEST_TARGETS[*]}" bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wavm \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c fastbuild \ "${TEST_TARGETS[@]}" \ --test_tag_filters=-nofips \ @@ -384,28 +365,28 @@ case $CI_TARGET in # these tests under "-c opt" to save time in CI. bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wavm \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c opt \ @envoy//test/common/common:assert_test \ @envoy//test/server:server_test # "--define log_fast_debug_assert_in_release=enabled" must be tested with a release build, so run only these tests under "-c opt" to save time in CI. This option will test only ASSERT()s without SLOW_ASSERT()s, so additionally disable "--define log_debug_assert_in_release" which compiles in both. bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wavm \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c opt \ @envoy//test/common/common:assert_test \ --define log_fast_debug_assert_in_release=enabled \ --define log_debug_assert_in_release=disabled echo "Building binary with wasm=wavm... and logging disabled" bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ - --define wasm=wavm \ - --define enable_logging=disabled \ - "${COMPILE_TIME_OPTIONS[@]}" \ - -c fastbuild \ - @envoy//source/exe:envoy-static \ - --build_tag_filters=-nofips + --config=compile-time-options \ + --define wasm=wavm \ + --define enable_logging=disabled \ + -c fastbuild \ + @envoy//source/exe:envoy-static \ + --build_tag_filters=-nofips collect_build_profile build ;; @@ -560,6 +541,7 @@ case $CI_TARGET in dockerhub-readme) setup_clang_toolchain bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ + --remote_download_toplevel \ //distribution/dockerhub:readme cat bazel-bin/distribution/dockerhub/readme.md ;; @@ -662,7 +644,9 @@ case $CI_TARGET in "${BAZEL_RELEASE_OPTIONS[@]}" \ "${TEST_TARGETS[@]}" # Build release binaries - bazel build "${BAZEL_BUILD_OPTIONS[@]}" "${BAZEL_RELEASE_OPTIONS[@]}" \ + bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ + "${BAZEL_RELEASE_OPTIONS[@]}" \ + --remote_download_outputs=toplevel \ //distribution/binary:release # Copy release binaries to binary export directory cp -a \ diff --git a/ci/filter_example_setup.sh b/ci/filter_example_setup.sh index f0605cadb2369..94511ac5babeb 100644 --- a/ci/filter_example_setup.sh +++ b/ci/filter_example_setup.sh @@ -16,10 +16,10 @@ ENVOY_FILTER_EXAMPLE_TESTS=( if [[ ! -d "${ENVOY_FILTER_EXAMPLE_SRCDIR}/.git" ]]; then rm -rf "${ENVOY_FILTER_EXAMPLE_SRCDIR}" - git clone https://github.com/envoyproxy/envoy-filter-example.git "${ENVOY_FILTER_EXAMPLE_SRCDIR}" + git clone -q https://github.com/envoyproxy/envoy-filter-example.git "${ENVOY_FILTER_EXAMPLE_SRCDIR}" fi -(cd "${ENVOY_FILTER_EXAMPLE_SRCDIR}" && git fetch origin && git checkout -f "${ENVOY_FILTER_EXAMPLE_GITSHA}") +(cd "${ENVOY_FILTER_EXAMPLE_SRCDIR}" && git fetch -q origin && git checkout -q -f "${ENVOY_FILTER_EXAMPLE_GITSHA}") sed -e "s|{ENVOY_SRCDIR}|${ENVOY_SRCDIR}|" "${ENVOY_SRCDIR}"/ci/WORKSPACE.filter.example > "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/WORKSPACE mkdir -p "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/bazel diff --git a/ci/setup_cache.sh b/ci/setup_cache.sh index 6c770323eb355..da0b189dd4a88 100755 --- a/ci/setup_cache.sh +++ b/ci/setup_cache.sh @@ -19,7 +19,7 @@ if [[ -n "${GCP_SERVICE_ACCOUNT_KEY:0:1}" ]]; then export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_FILE}" if [[ -n "${GOOGLE_BES_PROJECT_ID}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --config=google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" + export BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" fi fi @@ -29,10 +29,14 @@ if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then echo "Set up bazel remote read/write cache at ${BAZEL_REMOTE_CACHE}." if [[ -z "${ENVOY_RBE}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --jobs=HOST_CPUS*.99 --remote_timeout=600" + export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_timeout=600" echo "using local build cache." # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` - BRANCH_NAME="$(echo "${CI_TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" + TARGET_BRANCH="${CI_TARGET_BRANCH}" + if [[ "$TARGET_BRANCH" =~ ^origin/ ]]; then + TARGET_BRANCH=$(echo "$TARGET_BRANCH" | cut -d/ -f2-) + fi + BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" if [[ "$BRANCH_NAME" == "merge" ]]; then # Manually run PR commit - there is no easy way of telling which branch # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` @@ -45,6 +49,4 @@ if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_instance_name=${BAZEL_REMOTE_INSTANCE}" echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." fi -else - echo "No remote cache is set, skipping setup remote cache." fi diff --git a/mobile/.bazelrc b/mobile/.bazelrc index f520875f31871..ad6d2aaa65fc4 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -1,3 +1,6 @@ +## Any new configs - ie that are not defined in Envoy's bazelrc ... +# **should be prefixed with mobile-** + # Envoy Mobile Bazel build/test options. try-import ../.bazelrc @@ -46,9 +49,9 @@ build:rules_xcodeproj --features=-swift.use_global_index_store # Override PGV validation with NOP functions build --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor=nop -build:dbg-common --compilation_mode=dbg +build:mobile-dbg-common --compilation_mode=dbg # Enable source map for debugging in IDEs -build:dbg-common --copt="-fdebug-compilation-dir" --copt="/proc/self/cwd" +build:mobile-dbg-common --copt="-fdebug-compilation-dir" --copt="/proc/self/cwd" # Default flags for builds targeting iOS # Manual stamping is necessary in order to get versioning information in the iOS @@ -62,12 +65,12 @@ build:ios --test_timeout=390,750,1500,5700 build:android --define=logger=android # Default flags for Android debug builds -build:dbg-android --config=dbg-common -build:dbg-android --config=android +build:mobile-dbg-android --config=mobile-dbg-common +build:mobile-dbg-android --config=android # Default flags for iOS debug builds -build:dbg-ios --config=dbg-common -build:dbg-ios --config=ios +build:mobile-dbg-ios --config=mobile-dbg-common +build:mobile-dbg-ios --config=ios # Default flags for Android tests # TODO(jpsim): Explicitly register test extensions for Android tests @@ -77,193 +80,165 @@ build:test-android --define=static_extension_registration=enabled # Locally-runnable ASAN config for MacOS & Linux with standard dev environment # See also: # https://github.com/bazelbuild/bazel/issues/6932 -build:asan-dev --strip=never -build:asan-dev --copt -Wno-macro-redefined # ASAN sets _FORTIFY_SOURCE=0 -build:asan-dev --copt -DADDRESS_SANITIZER -build:asan-dev --copt -D_LIBCPP_HAS_NO_ASAN -build:asan-dev --copt -g -build:asan-dev --copt -fno-omit-frame-pointer -build:asan-dev --copt -fno-optimize-sibling-calls -build:asan-dev --copt -fsanitize=address -build:asan-dev --linkopt -fsanitize=address -build:asan-dev --platform_suffix=-asan +build:mobile-asan-dev --strip=never +build:mobile-asan-dev --copt -Wno-macro-redefined # ASAN sets _FORTIFY_SOURCE=0 +build:mobile-asan-dev --copt -DADDRESS_SANITIZER +build:mobile-asan-dev --copt -D_LIBCPP_HAS_NO_ASAN +build:mobile-asan-dev --copt -g +build:mobile-asan-dev --copt -fno-omit-frame-pointer +build:mobile-asan-dev --copt -fno-optimize-sibling-calls +build:mobile-asan-dev --copt -fsanitize=address +build:mobile-asan-dev --linkopt -fsanitize=address +build:mobile-asan-dev --platform_suffix=-asan build:clang-asan --linkopt --rtlib=compiler-rt build:clang-asan --linkopt --unwindlib=libgcc # Locally-runnable TSAN config -build:tsan-dev --features=tsan -build:tsan-dev --copt -fsanitize=thread -build:tsan-dev --linkopt -fsanitize=thread -build:tsan-dev --test_env=ENVOY_IP_TEST_VERSIONS=v4only -build:tsan-dev --platform_suffix=-tsan +build:mobile-tsan-dev --features=tsan +build:mobile-tsan-dev --copt -fsanitize=thread +build:mobile-tsan-dev --linkopt -fsanitize=thread +build:mobile-tsan-dev --test_env=ENVOY_IP_TEST_VERSIONS=v4only +build:mobile-tsan-dev --platform_suffix=-tsan # Needed due to https://github.com/libevent/libevent/issues/777 -build:tsan-dev --copt -DEVENT__DISABLE_DEBUG_MODE +build:mobile-tsan-dev --copt -DEVENT__DISABLE_DEBUG_MODE # https://github.com/abseil/abseil-cpp/issues/760 # https://github.com/google/sanitizers/issues/953 -build:tsan-dev --test_env="TSAN_OPTIONS=report_atomic_races=0" +build:mobile-tsan-dev --test_env="TSAN_OPTIONS=report_atomic_races=0" # Exclude debug info from the release binary since it makes it too large to fit # into a zip file. This shouldn't affect crash reports. -build:release-common --define=no_debug_info=1 +build:mobile-release-common --define=no_debug_info=1 + +# order matters here to ensure downloads +build:mobile-remote-release-clang --config=mobile-remote-ci-linux-clang +build:mobile-remote-release-clang --config=mobile-release-common +build:mobile-remote-release-clang --remote_download_toplevel +build:mobile-remote-release-clang --config=ci +build:mobile-remote-release-clang --config=remote # Compile releases optimizing for size (eg -Os, etc). -build:release-common --config=sizeopt +build:mobile-release-common --config=sizeopt # Set default symbols visibility to hidden to reduce .dynstr and the symbol table size -build:release-common --copt=-fvisibility=hidden +build:mobile-release-common --copt=-fvisibility=hidden # Disable google_grpc in production by default -build:release-common --define=google_grpc=disabled +build:mobile-release-common --define=google_grpc=disabled # Enable automatic extension factory registration for release builds -build:release-common --define=static_extension_registration=enabled +build:mobile-release-common --define=static_extension_registration=enabled # Flags for release builds targeting iOS -build:release-ios --config=ios -build:release-ios --config=release-common -build:release-ios --compilation_mode=opt +build:mobile-release-ios --config=ios +build:mobile-release-ios --config=mobile-release-common +build:mobile-release-ios --compilation_mode=opt # Flags for release builds targeting Android or the JVM # Release does not use the option --define=logger=android -build:release-android --config=release-common -build:release-android --compilation_mode=opt +build:mobile-release-android --config=mobile-release-common +build:mobile-release-android --compilation_mode=opt # Instrument Envoy Mobile's C++ code for coverage -build:coverage --instrumentation_filter="//library/common[/:]" +coverage --instrumentation_filter="//library/common[/:]" ############################################################################# # Experimental EngFlow Remote Execution Configs ############################################################################# -# remote-ci-common: These options are valid for any platform, use the configs below +# mobile-remote-ci-common: These options are valid for any platform, use the configs below # to add platform-specific options. Avoid using this config directly and # instead use a platform-specific config ############################################################################# -build:remote-ci-common --config=ci -build:remote-ci-common --config=remote -build:remote-ci-common --google_default_credentials=false -build:remote-ci-common --remote_cache=grpcs://envoy.cluster.engflow.com -build:remote-ci-common --remote_executor=grpcs://envoy.cluster.engflow.com -build:remote-ci-common --bes_backend=grpcs://envoy.cluster.engflow.com/ -build:remote-ci-common --bes_results_url=https://envoy.cluster.engflow.com/invocation/ -build:remote-ci-common --experimental_credential_helper=%workspace%/bazel/engflow-bazel-credential-helper.sh -build:remote-ci-common --jobs=40 -build:remote-ci-common --verbose_failures -build:remote-ci-common --spawn_strategy=remote,sandboxed,local -build:remote-ci-common --grpc_keepalive_time=30s -build:remote-ci-common --remote_timeout=3600s -build:remote-ci-common --bes_timeout=3600s -build:remote-ci-common --bes_upload_mode=fully_async +build:mobile-remote-ci-common --config=rbe-engflow +build:mobile-remote-ci-common --experimental_credential_helper=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:mobile-remote-ci-common --jobs=40 +build:mobile-remote-ci-common --verbose_failures +build:mobile-remote-ci-common --spawn_strategy=remote,sandboxed,local + ############################################################################# -# remote-ci-linux: These options are linux-only using GCC by default +# mobile-remote-ci-linux: These options are linux-only using GCC by default ############################################################################# # Common Engflow flags -build:remote-ci-linux --define=EXECUTOR=remote -build:remote-ci-linux --disk_cache= -build:remote-ci-linux --incompatible_strict_action_env=true +build:mobile-remote-ci-linux --define=EXECUTOR=remote +build:mobile-remote-ci-linux --disk_cache= +build:mobile-remote-ci-linux --incompatible_strict_action_env=true # GCC toolchain options -build:remote-ci-linux --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux --platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux --config=remote-ci-common +build:mobile-remote-ci-linux --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +build:mobile-remote-ci-linux --crosstool_top=//third_party/rbe_configs/cc:toolchain +build:mobile-remote-ci-linux --extra_execution_platforms=//third_party/rbe_configs/config:platform +build:mobile-remote-ci-linux --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain +build:mobile-remote-ci-linux --host_platform=//third_party/rbe_configs/config:platform +build:mobile-remote-ci-linux --platforms=//third_party/rbe_configs/config:platform +build:mobile-remote-ci-linux --config=mobile-remote-ci-common + ############################################################################# -# remote-ci-linux-clang: These options are linux-only using Clang by default +# mobile-remote-ci-linux-clang: These options are linux-only using Clang by default ############################################################################# -build:remote-ci-linux-clang --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-clang --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-clang --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-clang --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-clang --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-clang --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-clang --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux-clang --platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-clang --config=remote-ci-common +build:mobile-remote-ci-linux-clang --action_env=CC=/opt/llvm/bin/clang +build:mobile-remote-ci-linux-clang --action_env=CXX=/opt/llvm/bin/clang++ +build:mobile-remote-ci-linux-clang --config=mobile-remote-ci-linux + ############################################################################# -# remote-ci-linux-asan: These options are Linux-only using Clang and AddressSanitizer +# mobile-remote-ci-linux-asan: These options are Linux-only using Clang and AddressSanitizer ############################################################################# -build:remote-ci-linux-asan --config=clang-asan -build:remote-ci-linux-asan --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-asan --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-asan --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-asan --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-asan --extra_execution_platforms=//third_party/rbe_configs/config:platform-asan -build:remote-ci-linux-asan --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-asan --host_platform=//third_party/rbe_configs/config:platform-asan -build:remote-ci-linux-asan --platforms=//third_party/rbe_configs/config:platform-asan -build:remote-ci-linux-asan --config=remote-ci-common +build:mobile-remote-ci-linux-asan --config=clang-asan +build:mobile-remote-ci-linux-asan --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci-linux-asan --config=remote-ci + ############################################################################# -# remote-ci-linux-tsan: These options are Linux-only using Clang and ThreadSanitizer +# mobile-remote-ci-linux-tsan: These options are Linux-only using Clang and ThreadSanitizer ############################################################################# -build:remote-ci-linux-tsan --config=clang-tsan -build:remote-ci-linux-tsan --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-tsan --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-tsan --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-tsan --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-tsan --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-tsan --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-tsan --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux-tsan --platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-tsan --config=remote-ci-common +build:mobile-remote-ci-linux-tsan --config=clang-tsan +build:mobile-remote-ci-linux-tsan --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci-linux-tsan --config=remote-ci + ############################################################################# -# remote-ci-linux-coverage: These options are Linux-only using Clang and LLVM coverage +# ci-linux-coverage: These options are Linux-only using Clang and LLVM coverage +############################################################################# +# Clang environment variables (keep in sync with //third_party/rbe_configs) +# Coverage environment variables (keep in sync with //third_party/rbe_configs) +build:mobile-ci-linux-coverage --action_env=GCOV=/opt/llvm/bin/llvm-profdata +build:mobile-ci-linux-coverage --test_env=GCOV=/opt/llvm/bin/llvm-profdata +build:mobile-ci-linux-coverage --action_env=BAZEL_LLVM_COV=/opt/llvm/bin/llvm-cov +build:mobile-ci-linux-coverage --test_env=BAZEL_LLVM_COV=/opt/llm/bin/llvm-cov +build:mobile-ci-linux-coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 +build:mobile-ci-linux-coverage --test_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 + +############################################################################# +# mobile-remote-ci-linux-coverage: These options are Linux-only using Clang and LLVM coverage ############################################################################# # Clang environment variables (keep in sync with //third_party/rbe_configs) -build:remote-ci-linux-coverage --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-coverage --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-coverage --test_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-coverage --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-coverage --test_env=CXX=/opt/llvm/bin/clang++ # Coverage environment variables (keep in sync with //third_party/rbe_configs) -build:remote-ci-linux-coverage --action_env=GCOV=/opt/llvm/bin/llvm-profdata -build:remote-ci-linux-coverage --test_env=GCOV=/opt/llvm/bin/llvm-profdata -build:remote-ci-linux-coverage --action_env=BAZEL_LLVM_COV=/opt/llvm/bin/llvm-cov -build:remote-ci-linux-coverage --test_env=BAZEL_LLVM_COV=/opt/llvm/bin/llvm-cov -build:remote-ci-linux-coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 -build:remote-ci-linux-coverage --test_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 -# Toolchain flags (Java is required for C++ coverage due to Bazel's LCOV merger) -build:remote-ci-linux-coverage --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-coverage --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-coverage --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-coverage --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux-coverage --platforms=//third_party/rbe_configs/config:platform # Flags to run tests locally which are necessary since Bazel C++ LLVM coverage isn't fully supported for remote builds # TODO(lfpino): Reference upstream Bazel issue here on the incompatibility of remote test execution and LLVM coverage. -build:remote-ci-linux-coverage --remote_download_outputs=all -build:remote-ci-linux-coverage --strategy=TestRunner=local,remote -build:remote-ci-linux-coverage --strategy=CoverageReport=local,remote +build:mobile-remote-ci-linux-coverage --config=mobile-ci-linux-coverage +build:mobile-remote-ci-linux-coverage --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci-linux-coverage --strategy=TestRunner=local,remote +build:mobile-remote-ci-linux-coverage --strategy=CoverageReport=local,remote # Bazel remote caching is incompatible with C++ LLVM coverage so we need to deactivate it for coverage builds # TODO(lfpino): Reference upstream Bazel issue here on the incompatibility of remote caching and LLVM coverage. -build:remote-ci-linux-coverage --noremote_accept_cached -build:remote-ci-linux-coverage --config=remote-ci-common -build:remote-ci-linux-coverage --build_runfile_links -build:remote-ci-linux-coverage --legacy_important_outputs=false -build:remote-ci-linux-coverage --test_env=CC_CODE_COVERAGE_SCRIPT=external/envoy/bazel/coverage/collect_cc_coverage.sh -build:remote-ci-linux-coverage --nocache_test_results +build:mobile-remote-ci-linux-coverage --noremote_accept_cached +build:mobile-remote-ci-linux-coverage --remote_download_toplevel +build:mobile-remote-ci-linux-coverage --build_runfile_links +build:mobile-remote-ci-linux-coverage --legacy_important_outputs=false +build:mobile-remote-ci-linux-coverage --test_env=CC_CODE_COVERAGE_SCRIPT=external/envoy/bazel/coverage/collect_cc_coverage.sh +build:mobile-remote-ci-linux-coverage --nocache_test_results +build:mobile-remote-ci-linux-coverage --config=ci +build:mobile-remote-ci-linux-coverage --config=remote # IPv6 tests fail on CI -build:remote-ci-linux-coverage --test_env=ENVOY_IP_TEST_VERSIONS=v4only -############################################################################# -# remote-ci-macos: These options are macOS-only +build:mobile-remote-ci-linux-coverage --test_env=ENVOY_IP_TEST_VERSIONS=v4only ############################################################################# -build:remote-ci-macos --config=remote-ci-common -build:remote-ci-macos --host_platform=//ci/platform:macos -build:remote-ci-macos --platforms=//ci/platform:macos -build:remote-ci-macos --extra_execution_platforms=//ci/platform:macos -build:remote-ci-macos --xcode_version_config=//ci:xcode_config -############################################################################# -# remote-ci-debug: Various Bazel debugging flags -############################################################################# -common:remote-ci-debug --announce_rc -common:remote-ci-debug -s -common:remote-ci-debug -c dbg -common:remote-ci-debug --verbose_failures -common:remote-ci-debug --sandbox_debug -common:remote-ci-debug --action_env=VERBOSE_COVERAGE=true -common:remote-ci-debug --test_env=VERBOSE_COVERAGE=true -common:remote-ci-debug --test_env=DISPLAY_LCOV_CMD=true -############################################################################# -# Experimental EngFlow Remote Execution Configs. +# mobile-remote-ci-macos: These options are macOS-only ############################################################################# +build:mobile-remote-ci-macos --config=mobile-remote-ci-common +build:mobile-remote-ci-macos --host_platform=//ci/platform:macos +build:mobile-remote-ci-macos --platforms=//ci/platform:macos +build:mobile-remote-ci-macos --extra_execution_platforms=//ci/platform:macos +build:mobile-remote-ci-macos --xcode_version_config=//ci:xcode_config +build:mobile-remote-ci-macos --remote_download_toplevel +build:mobile-remote-ci-macos --config=ci +build:mobile-remote-ci-macos --config=remote + +build:mobile-remote-ci --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci --config=remote-ci diff --git a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index 86eaf56db987b..b0f9709d5f484 100644 --- a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //examples/java/hello_world:hello_envoy diff --git a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index e67d4e7976ea7..3de57d07b455c 100644 --- a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //examples/java/hello_world:hello_envoy diff --git a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index 95206accf6e26..062867dacbdfe 100644 --- a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //examples/kotlin/hello_world:hello_envoy_kt diff --git a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index 7e8657807886e..240b0c5bd3eb5 100644 --- a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //examples/kotlin/hello_world:hello_envoy_kt diff --git a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index 062ef17d8bc84..0fe4d3fa8aebf 100644 --- a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //test/kotlin/apps/baseline:hello_envoy_kt diff --git a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index 568962403d866..3510ee45dc774 100644 --- a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //test/kotlin/apps/baseline:hello_envoy_kt diff --git a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index a6e5c1d53f0f0..f47ede5a35097 100644 --- a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //test/kotlin/apps/experimental:hello_envoy_kt diff --git a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index 08d2d5e7ee749..277fae9260073 100644 --- a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //test/kotlin/apps/experimental:hello_envoy_kt diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 9a5a751fb5271..a2c53dc1a2cb0 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -59,12 +59,10 @@ if [[ "${FUZZ_COVERAGE}" == "true" ]]; then while read -r line; do COVERAGE_TARGETS+=("$line"); done \ <<< "$_targets" BAZEL_COVERAGE_OPTIONS+=( - "--config=fuzz-coverage" - "--test_tag_filters=-nocoverage") + "--config=fuzz-coverage") else BAZEL_COVERAGE_OPTIONS+=( - "--config=test-coverage" - "--test_tag_filters=-nocoverage,-fuzz_target") + "--config=test-coverage") fi # Output unusually long logs due to trace logging. From 064efa3c13d333a06a0d3c1962130a93dc5920d9 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 8 Aug 2023 19:00:56 +0100 Subject: [PATCH 012/274] ci/verify: Dont use RBE for arm verify job (#28897) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/verify.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index c638cb8aa0726..0e7c32be01759 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -52,9 +52,13 @@ jobs: AZP_BRANCH: $(Build.SourceBranch) ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_DOCKER_IN_DOCKER: 1 - ENVOY_RBE: 1 - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" + BAZEL_REMOTE_CACHE: $(LocalBuildCache) + BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} + ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" + ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" displayName: "Verify packages" - job: verified From cf9af5c0628ccccbd194c1ef361ace49ca072c8c Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 9 Aug 2023 10:07:40 +0100 Subject: [PATCH 013/274] ci: Add clang toolchain for python dep compilation (#28922) Signed-off-by: Ryan Northey --- ci/do_ci.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 52e00564f6c4b..38999ebed7914 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -721,6 +721,8 @@ case $CI_TARGET in ;; verify_distro) + # this can be required if any python deps require compilation + setup_clang_toolchain if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then PACKAGE_BUILD=/build/bazel.distribution/x64/packages.x64.tar.gz else From 573ebfd9eb847655ad6eae3a78c55f41fbe114d8 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 9 Aug 2023 23:41:50 +0100 Subject: [PATCH 014/274] ci/caching: Unify (AZP) bazel build caches (#28876) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 77 ++++++++----------- .azure-pipelines/cached.yml | 80 ++++++++++++++------ .azure-pipelines/docker/clean_docker.sh | 11 +++ .azure-pipelines/docker/create_cache.sh | 22 ++++++ .azure-pipelines/docker/load_cache.sh | 59 --------------- .azure-pipelines/docker/load_caches.sh | 95 ++++++++++++++++++++++++ .azure-pipelines/docker/prepare_cache.sh | 7 +- .azure-pipelines/docker/prime_cache.sh | 88 ++++++++++++++++------ .azure-pipelines/docker/save_cache.sh | 2 + .azure-pipelines/env.yml | 5 +- .azure-pipelines/pipelines.yml | 23 ++++-- .azure-pipelines/stage/publish.yml | 18 ++--- .azure-pipelines/stages.yml | 1 + .bazelrc | 1 + ci/do_ci.sh | 55 ++++++++++++++ 15 files changed, 373 insertions(+), 171 deletions(-) create mode 100755 .azure-pipelines/docker/clean_docker.sh create mode 100755 .azure-pipelines/docker/create_cache.sh delete mode 100755 .azure-pipelines/docker/load_cache.sh create mode 100755 .azure-pipelines/docker/load_caches.sh diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index 3385b9deef285..a123d7d8b8e15 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -9,30 +9,33 @@ parameters: default: "" # caching +- name: cacheKeyBazelisk + type: string + default: .bazelversion - name: cacheKeyDocker type: string default: ".devcontainer/Dockerfile" -- name: cacheKeyDockerVersion - type: string - default: $(cacheKeyBuildImage) -- name: cacheKeyDockerName - type: string - default: envoy_build_image -- name: cacheKeyDockerPath +- name: cacheKeyVersion type: string - default: /mnt/docker -- name: cacheKeyDockerTmpDir + default: $(cacheKeyVersion) +- name: pathCacheTemp type: string - default: /mnt/docker_cache -- name: cacheKeyDockerNoTmpfs + default: $(pathCacheTemp) + +- name: tmpfsCacheDisabled type: string default: '' -- name: cacheKey + +- name: cacheKeyBazel type: string - default: $(cacheKeyBazelFiles) + default: $(cacheKeyBazel) - name: cacheVersion type: string - default: $(cacheKeyBazel) + default: $(cacheKeyVersion) + +- name: pathDockerBind + type: string + default: $(pathDockerBind) - name: rbe displayName: "Enable RBE" @@ -72,6 +75,10 @@ parameters: type: string default: true +- name: diskspaceHack + type: boolean + default: false + - name: stepsPre type: stepList default: [] @@ -88,6 +95,10 @@ steps: fetchDepth: ${{ parameters.repoFetchDepth }} fetchTags: ${{ parameters.repoFetchTags }} +- bash: ./.azure-pipelines/cleanup.sh + displayName: "Free disk space" + condition: and(succeeded(), eq('${{ parameters.diskspaceHack }}', true)) + # Set up tmpfs directories for self-hosted agents which have a surplus of mem. # # NB: Do not add any directory that grow larger than spare memory capacity! @@ -135,27 +146,15 @@ steps: condition: and(succeeded(), eq('${{ parameters.managedAgent }}', true)) # Caching -- task: Cache@2 - inputs: - key: '"${{ parameters.ciTarget }}" | "${{ parameters.cacheVersion }}" | "${{ parameters.artifactSuffix }}" | ${{ parameters.cacheKey }}' - path: $(Build.StagingDirectory)/bazel - cacheHitVar: BAZEL_CACHE_RESTORED - continueOnError: true -- script: | - set -e - sudo tar xf $(Build.StagingDirectory)/bazel/cache.tar.zst -C $(Build.StagingDirectory) --warning=no-timestamp - sudo rm -rf $(Build.StagingDirectory)/bazel/* - displayName: "Cache/restore (${{ parameters.ciTarget }})" - condition: and(not(canceled()), eq(variables.BAZEL_CACHE_RESTORED, 'true')) - template: cached.yml parameters: - key: "${{ parameters.cacheKeyDocker }}" - version: "${{ parameters.cacheKeyDockerVersion }}" - name: "${{ parameters.cacheKeyDockerName }}" - path: "${{ parameters.cacheKeyDockerPath }}" - tmpDirectory: "${{ parameters.cacheKeyDockerTmpDir }}" - tmpNoTmpfs: "${{ parameters.cacheKeyDockerNoTmpfs }}" + keyBazel: "${{ parameters.cacheKeyBazel }}" + keyBazelisk: "${{ parameters.cacheKeyBazelisk }}" + keyDocker: "${{ parameters.cacheKeyDocker }}" + pathDockerBind: "${{ parameters.pathDockerBind }}" arch: "${{ parameters.artifactSuffix }}" + pathTemp: "${{ parameters.pathCacheTemp }}" + tmpfsDisabled: "${{ parameters.tmpfsCacheDisabled }}" - ${{ each step in parameters.stepsPre }}: - ${{ each pair in step }}: @@ -219,20 +218,6 @@ steps: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} -- script: | - set -e - CACHE_DIRS=( - ".cache" - "bazel_root/install" - "repository_cache/" - "bazel_root/base/external") - mkdir -p $(Build.StagingDirectory)/bazel/ - sudo tar cf - -C $(Build.StagingDirectory) "${CACHE_DIRS[@]}" \ - | zstd - -T0 -o $(Build.StagingDirectory)/bazel/cache.tar.zst - echo "Created tarball ($(Build.StagingDirectory)/bazel/cache.tar.zst): ${CACHE_DIRS[@]}" - displayName: "Cache/save (${{ parameters.ciTarget }})" - condition: and(not(canceled()), ne(variables.BAZEL_CACHE_RESTORED, 'true')) - - task: PublishTestResults@2 inputs: testResultsFiles: "**/bazel-out/**/testlogs/**/test.xml" diff --git a/.azure-pipelines/cached.yml b/.azure-pipelines/cached.yml index d75ef8b5771e3..22b7fcfdf3553 100644 --- a/.azure-pipelines/cached.yml +++ b/.azure-pipelines/cached.yml @@ -2,25 +2,36 @@ parameters: - name: name type: string - default: envoy_build_image -- name: version - type: string - default: "" + default: $(cacheKeyName) - name: arch type: string default: "" -- name: key +- name: version + type: string + default: $(cacheKeyVersion) + +- name: keyDocker type: string - default: ".devcontainer/Dockerfile" -- name: tmpDirectory + default: $(cacheKeyDocker) +- name: keyBazel type: string - default: /mnt/docker_cache -- name: tmpNoTmpfs + default: $(cacheKeyBazel) +- name: keyBazelisk + type: string + default: $(cacheKeyBazelisk) + +- name: pathTemp + type: string + default: $(pathCacheTemp) + +- name: tmpfsDisabled type: string default: -- name: path + +- name: pathDockerBind type: string - default: /mnt/docker + default: $(pathDockerBind) + - name: cacheTimeoutWorkaround type: number default: 5 @@ -30,24 +41,47 @@ parameters: steps: -- script: sudo .azure-pipelines/docker/prepare_cache.sh "${{ parameters.tmpDirectory }}" "${{ parameters.tmpNoTmpfs }}" - displayName: "Cache/prepare (${{ parameters.name }})" +- script: sudo .azure-pipelines/docker/prepare_cache.sh "${{ parameters.pathTemp }}" "${{ parameters.tmpfsDisabled }}" + displayName: "Cache/prepare" + +- task: Cache@2 + env: + VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" + displayName: "Cache (Docker)" + inputs: + key: '${{ parameters.name }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }}' + path: "${{ parameters.pathTemp }}/docker" + cacheHitVar: DOCKER_CACHE_RESTORED + - task: Cache@2 env: VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" - displayName: "Cache/fetch (${{ parameters.name }})" + displayName: "Cache (Bazelisk)" inputs: - key: '${{ parameters.name }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.key }}' - path: "${{ parameters.tmpDirectory }}" - cacheHitVar: CACHE_RESTORED + key: '${{ parameters.name }} | bazelisk | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazelisk }}' + path: "${{ parameters.pathTemp }}/bazelisk" + cacheHitVar: BAZELISK_CACHE_RESTORED -# Prime the cache for all jobs -- script: sudo .azure-pipelines/docker/prime_cache.sh "${{ parameters.tmpDirectory }}" "${{ parameters.arch }}" +- task: Cache@2 + env: + VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" + displayName: "Cache (Bazel)" + inputs: + key: '${{ parameters.name }} | bazel | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazel }}' + path: "${{ parameters.pathTemp }}/bazel" + cacheHitVar: BAZEL_CACHE_RESTORED + +# Prime the caches for all jobs +- script: .azure-pipelines/docker/prime_cache.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.arch }}" + env: + DOCKER_RESTORED: $(DOCKER_CACHE_RESTORED) + BAZELISK_RESTORED: $(BAZELISK_CACHE_RESTORED) + BAZEL_RESTORED: $(BAZEL_CACHE_RESTORED) displayName: "Cache/prime (${{ parameters.name }})" # TODO(phlax): figure if there is a way to test cache without downloading it - condition: and(not(canceled()), eq(${{ parameters.prime }}, true), ne(variables.CACHE_RESTORED, 'true')) + condition: and(not(canceled()), eq(${{ parameters.prime }}, true), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZELISK_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) -# Load the cache for a job -- script: sudo .azure-pipelines/docker/load_cache.sh "${{ parameters.tmpDirectory }}" "${{ parameters.path }}" - displayName: "Cache/restore (${{ parameters.name }})" +# Load the caches for a job +- script: sudo .azure-pipelines/docker/load_caches.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.pathDockerBind }}" + displayName: "Cache/restore" condition: and(not(canceled()), eq(${{ parameters.prime }}, false)) diff --git a/.azure-pipelines/docker/clean_docker.sh b/.azure-pipelines/docker/clean_docker.sh new file mode 100755 index 0000000000000..cbad33a4ad579 --- /dev/null +++ b/.azure-pipelines/docker/clean_docker.sh @@ -0,0 +1,11 @@ +#!/bin/bash -e + +set -o pipefail + +echo "Stopping Docker ..." +systemctl stop docker + +echo "Restarting Docker with empty /var/lib/docker ..." +mv /var/lib/docker/ /var/lib/docker.old +mkdir /var/lib/docker +systemctl start docker diff --git a/.azure-pipelines/docker/create_cache.sh b/.azure-pipelines/docker/create_cache.sh new file mode 100755 index 0000000000000..4079df028f4c2 --- /dev/null +++ b/.azure-pipelines/docker/create_cache.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e + +set -o pipefail + +CACHE_TARBALL="${1}" +shift + +echo "Exporting ${*} -> ${CACHE_TARBALL}" + +CACHE_PATH="$(dirname "$CACHE_TARBALL")" +mkdir -p "$CACHE_PATH" + +CACHE_ARGS=() +for path in "$@"; do + total="$(du -sh "$path" | cut -f1)" + echo "Adding cache dir (${path}): ${total}" + CACHE_ARGS+=(-C "$path" .) +done + +tar cf - "${CACHE_ARGS[@]}" | zstd - -q -T0 -o "$CACHE_TARBALL" +echo "Cache tarball created: ${CACHE_TARBALL}" +ls -lh "$CACHE_TARBALL" diff --git a/.azure-pipelines/docker/load_cache.sh b/.azure-pipelines/docker/load_cache.sh deleted file mode 100755 index 78c6cd8e5d99b..0000000000000 --- a/.azure-pipelines/docker/load_cache.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash -e - -DOCKER_CACHE_PATH="$1" -DOCKER_BIND_PATH="$2" - -if [[ -z "$DOCKER_CACHE_PATH" ]]; then - echo "load_docker_cache called without path arg" >&2 - exit 1 -fi - - -DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" - -echo "Stopping Docker daemon ..." -systemctl stop docker docker.socket - -mv /var/lib/docker/ /var/lib/docker.old -mkdir -p /var/lib/docker - -if id -u vsts &> /dev/null && [[ -n "$DOCKER_BIND_PATH" ]]; then - # use separate disk on windows hosted - echo "Binding docker directory ${DOCKER_BIND_PATH} -> /var/lib/docker ..." - mkdir -p "$DOCKER_BIND_PATH" - mount -o bind "$DOCKER_BIND_PATH" /var/lib/docker -elif ! id -u vsts &> /dev/null; then - echo "Mounting tmpfs directory -> /var/lib/docker ..." - # Use a ramdisk to load docker (avoids Docker slow start on big disk) - mount -t tmpfs none /var/lib/docker -else - # If we are on a managed host but the bind path is not set then we need to remove - # the old /var/lib/docker to free some space (maybe) - DOCKER_REMOVE_EXISTING=1 -fi - -if [[ -e "${DOCKER_CACHE_TARBALL}" ]]; then - echo "Extracting docker cache ${DOCKER_CACHE_TARBALL} -> /var/lib/docker ..." - zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar -xf - -C /var/lib/docker - touch /tmp/DOCKER_CACHE_RESTORED -else - echo "No cache to restore, starting Docker with no data" -fi - -echo "Starting Docker daemon ..." -systemctl start docker - -if mountpoint -q "${DOCKER_CACHE_PATH}"; then - echo "Unmount cache tmp ${DOCKER_CACHE_PATH} ..." - umount "${DOCKER_CACHE_PATH}" -else - echo "Remove cache tmp ${DOCKER_CACHE_PATH} ..." - rm -rf "${DOCKER_CACHE_PATH}" -fi -docker images -df -h - -# this takes time but may be desirable in some situations -if [[ -n "$DOCKER_REMOVE_EXISTING" ]]; then - rm -rf /var/lib/docker.old -fi diff --git a/.azure-pipelines/docker/load_caches.sh b/.azure-pipelines/docker/load_caches.sh new file mode 100755 index 0000000000000..ffaf3979a8a76 --- /dev/null +++ b/.azure-pipelines/docker/load_caches.sh @@ -0,0 +1,95 @@ +#!/bin/bash -e + +ENVOY_DOCKER_BUILD_DIR="$1" +CACHE_PATH="$2" +DOCKER_BIND_PATH="$3" +DOCKER_NO_TMPFS="$4" + + +if [[ -z "$CACHE_PATH" ]]; then + echo "load_caches called without path arg" >&2 + exit 1 +fi + +DOCKER_CACHE_PATH="${CACHE_PATH}/docker" +DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" + +BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" +BAZEL_CACHE_TARBALL="${BAZEL_CACHE_PATH}/bazel.tar.zst" + +BAZELISK_CACHE_PATH="${CACHE_PATH}/bazelisk" +BAZELISK_CACHE_TARBALL="${BAZELISK_CACHE_PATH}/bazelisk.tar.zst" + +echo "Stopping Docker daemon ..." +systemctl stop docker docker.socket + +mv /var/lib/docker/ /var/lib/docker.old +mkdir -p /var/lib/docker + +if id -u vsts &> /dev/null && [[ -n "$DOCKER_BIND_PATH" ]]; then + # use separate disk on windows hosted + echo "Binding docker directory ${DOCKER_BIND_PATH} -> /var/lib/docker ..." + mkdir -p "$DOCKER_BIND_PATH" + mount -o bind "$DOCKER_BIND_PATH" /var/lib/docker +elif ! id -u vsts &> /dev/null && [[ -z "$DOCKER_NO_TMPFS" ]]; then + echo "Mounting tmpfs directory -> /var/lib/docker ..." + # Use a ramdisk to load docker (avoids Docker slow start on big disk) + mount -t tmpfs none /var/lib/docker +else + # If we are on a managed/resource-constrained host but the bind path is not set then we need to remove + # the old /var/lib/docker to free some space (maybe) + DOCKER_REMOVE_EXISTING=1 +fi + +if [[ -e "${DOCKER_CACHE_TARBALL}" ]]; then + echo "Extracting docker cache ${DOCKER_CACHE_TARBALL} -> /var/lib/docker ..." + zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C /var/lib/docker + touch /tmp/DOCKER_CACHE_RESTORED +else + echo "No Docker cache to restore, starting Docker with no data" +fi + +echo "Starting Docker daemon ..." +systemctl start docker + +if mountpoint -q "${DOCKER_CACHE_PATH}"; then + echo "Unmount cache tmp ${DOCKER_CACHE_PATH} ..." + umount "${DOCKER_CACHE_PATH}" +else + echo "Remove cache tmp ${DOCKER_CACHE_PATH} ..." + rm -rf "${DOCKER_CACHE_PATH}" +fi +docker images + +mkdir -p "${ENVOY_DOCKER_BUILD_DIR}" + +if [[ -e "${BAZEL_CACHE_TARBALL}" ]]; then + echo "Extracting bazel cache ${BAZEL_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR} ..." + zstd --stdout -d "$BAZEL_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}" +else + echo "No bazel cache to restore, starting bazel with no data" +fi + +mkdir -p "${ENVOY_DOCKER_BUILD_DIR}/.cache/bazelisk" + +if [[ -e "${BAZELISK_CACHE_TARBALL}" ]]; then + echo "Extracting bazelisk cache ${BAZELISK_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR}/.cache/bazelisk ..." + zstd --stdout -d "$BAZELISK_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}/.cache/bazelisk" +else + echo "No bazelisk cache to restore, starting bazelisk with no data" +fi + +if mountpoint -q "${CACHE_PATH}"; then + echo "Unmount cache tmp ${CACHE_PATH} ..." + umount "${CACHE_PATH}" +else + echo "Remove cache tmp ${CACHE_PATH} ..." + rm -rf "${CACHE_PATH}" +fi + +# this takes time but may be desirable in some situations +if [[ -n "$DOCKER_REMOVE_EXISTING" ]]; then + rm -rf /var/lib/docker.old +fi + +df -h diff --git a/.azure-pipelines/docker/prepare_cache.sh b/.azure-pipelines/docker/prepare_cache.sh index fe417d5f5e419..66cf2b4b7260d 100755 --- a/.azure-pipelines/docker/prepare_cache.sh +++ b/.azure-pipelines/docker/prepare_cache.sh @@ -3,7 +3,7 @@ DOCKER_CACHE_PATH="$1" NO_MOUNT_TMPFS="${2:-}" DOCKER_CACHE_OWNERSHIP="vsts:vsts" - +TMPFS_SIZE=5G if [[ -z "$DOCKER_CACHE_PATH" ]]; then echo "prepare_docker_cache called without path arg" >&2 @@ -18,6 +18,9 @@ echo "Creating cache directory (${DOCKER_CACHE_PATH}) ..." mkdir -p "${DOCKER_CACHE_PATH}" if [[ -z "$NO_MOUNT_TMPFS" ]]; then echo "Mount tmpfs directory: ${DOCKER_CACHE_PATH}" - mount -t tmpfs none "$DOCKER_CACHE_PATH" + mount -o size="$TMPFS_SIZE" -t tmpfs none "$DOCKER_CACHE_PATH" fi +mkdir -p "${DOCKER_CACHE_PATH}/docker" +mkdir -p "${DOCKER_CACHE_PATH}/bazel" +mkdir -p "${DOCKER_CACHE_PATH}/bazelisk" chown -R "$DOCKER_CACHE_OWNERSHIP" "${DOCKER_CACHE_PATH}" diff --git a/.azure-pipelines/docker/prime_cache.sh b/.azure-pipelines/docker/prime_cache.sh index d5bef3388a44c..151f2d3a3d00d 100755 --- a/.azure-pipelines/docker/prime_cache.sh +++ b/.azure-pipelines/docker/prime_cache.sh @@ -1,40 +1,82 @@ #!/bin/bash -e -DOCKER_CACHE_PATH="$1" -DOCKER_CACHE_ARCH="$2" +ENVOY_DOCKER_BUILD_DIR="$1" +CACHE_PATH="$2" +CACHE_ARCH="$3" -if [[ -z "$DOCKER_CACHE_PATH" ]]; then +echo "Docker restored: $DOCKER_RESTORED" +echo "Bazelisk restored: $BAZELISK_RESTORED" +echo "Bazel restored: $BAZEL_RESTORED" + +if [[ -z "$CACHE_PATH" ]]; then echo "prime_docker_cache called without path arg" >&2 exit 1 fi -if [[ "$DOCKER_CACHE_ARCH" == ".arm64" ]]; then - DOCKER_CACHE_ARCH=linux/arm64 +if [[ "$CACHE_ARCH" == ".arm64" ]]; then + CACHE_ARCH=linux/arm64 else - DOCKER_CACHE_ARCH=linux/amd64 + CACHE_ARCH=linux/amd64 fi -DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" +DOCKER_CACHE_TARBALL="${CACHE_PATH}/docker/docker.tar.zst" +BAZEL_CACHE_TARBALL="${CACHE_PATH}/bazel/bazel.tar.zst" +BAZEL_PATH=/tmp/envoy-docker-build +BAZELISK_CACHE_TARBALL="${CACHE_PATH}/bazelisk/bazelisk.tar.zst" +BAZELISK_PATHS=( + "${BAZEL_PATH}/.cache/bazelisk" + "${BAZEL_PATH}/bazel_root/install") -echo "Stopping Docker ..." -systemctl stop docker +echo +echo "================ Load caches ===================" +if [[ "$DOCKER_RESTORED" == "true" ]] || [[ "$BAZELISK_RESTORED" == "true" ]] || [[ "$BAZEL_RESTORED" == "true" ]]; then + sudo ./.azure-pipelines/docker/load_caches.sh "$ENVOY_DOCKER_BUILD_DIR" "$CACHE_PATH" "" true +else + sudo ./.azure-pipelines/docker/clean_docker.sh + echo "No caches to restore" +fi +echo "===================================================" +echo -echo "Restarting Docker with empty /var/lib/docker ..." -mv /var/lib/docker/ /var/lib/docker.old -mkdir /var/lib/docker -systemctl start docker +echo +echo "================ Bazel info =======================" +# Get bazelisk and print bazel info +./ci/run_envoy_docker.sh './ci/do_ci.sh info' +echo "===================================================" +echo -BUILD_IMAGE=$(head -n1 .devcontainer/Dockerfile | cut -d: -f2) +echo +echo "================ Bazel fetch ======================" +# Fetch bazel dependencies +if [[ "$BAZEL_RESTORED" != "true" ]]; then + echo "Fetching bazel" + ./ci/run_envoy_docker.sh './ci/do_ci.sh fetch' +else + echo "Not fetching bazel as it was restored" +fi +echo "===================================================" +echo -echo "Pulling build image for ${DOCKER_CACHE_ARCH} (${BUILD_IMAGE}) ..." -docker pull -q --platform "${DOCKER_CACHE_ARCH}" "envoyproxy/envoy-build-ubuntu:${BUILD_IMAGE}" +docker images +df -h -echo "Stopping docker" -systemctl stop docker +echo +echo "================ Save caches ======================" +# Save the caches -> tarballs +if [[ "$DOCKER_RESTORED" != "true" ]]; then + echo "Stopping docker" + sudo systemctl stop docker docker.socket + sudo ./.azure-pipelines/docker/create_cache.sh "${DOCKER_CACHE_TARBALL}" /var/lib/docker +fi -echo "Exporting /var/lib/docker -> ${DOCKER_CACHE_PATH}" -mkdir -p "$DOCKER_CACHE_PATH" -tar cf - -C /var/lib/docker . | zstd - -T0 -o "$DOCKER_CACHE_TARBALL" +if [[ "$BAZELISK_RESTORED" != "true" ]]; then + sudo ./.azure-pipelines/docker/create_cache.sh "${BAZELISK_CACHE_TARBALL}" "${BAZELISK_PATHS[@]}" +fi -echo "Docker cache tarball created: ${DOCKER_CACHE_TARBALL}" -ls -lh "$DOCKER_CACHE_TARBALL" +if [[ "$BAZEL_RESTORED" != "true" ]]; then + rm -rf "{BAZELISK_PATHS[@]}" + sudo ./.azure-pipelines/docker/create_cache.sh "${BAZEL_CACHE_TARBALL}" "${BAZEL_PATH}" +fi +sudo chmod o+r -R "${CACHE_PATH}" +echo "===================================================" +echo diff --git a/.azure-pipelines/docker/save_cache.sh b/.azure-pipelines/docker/save_cache.sh index 85f912cbad2d6..462177b3f03f5 100755 --- a/.azure-pipelines/docker/save_cache.sh +++ b/.azure-pipelines/docker/save_cache.sh @@ -1,5 +1,7 @@ #!/bin/bash -e +set -o pipefail + DOCKER_CACHE_PATH="$1" NO_MOUNT_TMPFS="${2:-}" diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index ed70d498c8ccb..ca6e5ecb97e47 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -42,19 +42,16 @@ jobs: steps: - template: cached.yml parameters: - version: "$(cacheKeyBuildImage)" prime: true - job: cache_arm dependsOn: [] displayName: Cache (arm64) - pool: - vmImage: $(agentUbuntu) + pool: envoy-arm-small steps: - template: cached.yml parameters: prime: true arch: .arm64 - version: "$(cacheKeyBuildImage)" - job: repo dependsOn: [] diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index fdb87e4631f16..c0e321a3bdc80 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -44,14 +44,27 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) -- name: cacheKeyBuildImage - value: v0 -- name: cacheKeyDockerBuild +- name: cacheKeyName + value: envoy +- name: cacheKeyVersion value: v0 - name: cacheKeyBazel - value: v0 -- name: cacheKeyBazelFiles value: './WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' +- name: cacheKeyBazelisk + value: ".bazelversion" +- name: cacheKeyDocker + value: ".devcontainer/Dockerfile" +# Docker build uses separate docker cache +- name: cacheKeyDockerBuild + # VERSION.txt is included to refresh Docker images for release + value: '"publish_docker" | ci/Dockerfile-envoy | VERSION.txt' +- name: cacheKeyDockerBuildVersion + value: v0 + +- name: pathCacheTemp + value: /mnt/cache +- name: pathDockerBind + value: /mnt/docker - name: authGithubSSHKeyPublic value: "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=" diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 39ba7e5c0bc5b..0fbcb19ade84e 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -107,16 +107,14 @@ jobs: - template: ../bazel.yml parameters: ciTarget: docker-upload - # cacheVersion: $(cacheKeyBazel) publishEnvoy: false publishTestResults: false - # VERSION.txt is included to refresh Docker images for release - cacheKeyDocker: "ci/Dockerfile-envoy | VERSION.txt| $(cacheKeyBazelFiles)" - cacheKeyDockerName: publish_docker - cacheKeyDockerTmpDir: /var/azpcache - cacheKeyDockerNoTmpfs: true - cacheKeyDockerPath: "" - cacheKeyDockerVersion: "$(cacheKeyDockerBuild)" + pathDockerBind: "" + cacheKeyDocker: "$(cacheKeyDockerBuild)" + cacheKeyVersion: "$(cacheKeyDockerBuildVersion)" + pathCacheTemp: /var/azpcache + tmpfsCacheDisabled: true + diskspaceHack: true env: GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} stepsPre: @@ -152,9 +150,11 @@ jobs: DOCKER_BUILD_TIMEOUT: ${{ parameters.timeoutDockerBuild }} stepsPost: - script: | - sudo .azure-pipelines/docker/save_cache.sh /var/azpcache true + set -e + sudo .azure-pipelines/docker/save_cache.sh /var/azpcache/docker true sudo rm -rf /var/lib/docker displayName: "Cache/save (publish_docker)" + condition: and(succeeded(), ne(variables.DOCKER_CACHE_RESTORED, 'true')) - job: package_x64 displayName: Linux debs (x64) diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 39ca4fc3a8f32..f5ae006eb6825 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -57,6 +57,7 @@ stages: jobs: - template: env.yml + - stage: prechecks displayName: Prechecks dependsOn: ["env"] diff --git a/.bazelrc b/.bazelrc index e49155fa1a65b..6f41ddbdc78d0 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,6 +10,7 @@ # Startup options cannot be selected via config. startup --host_jvm_args=-Xmx3g +fetch --color=yes run --color=yes build --color=yes diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 38999ebed7914..9567c3e5cab8a 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -19,6 +19,18 @@ echo "building for ${ENVOY_BUILD_ARCH}" cd "${SRCDIR}" +FETCH_TARGETS=( + //contrib/... + //distribution/... + //docs/... + //source/... + //test/... + //tools/... + @nodejs//... + @envoy_api//... + @envoy_build_tools//...) + + if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then BUILD_ARCH_DIR="/linux/amd64" elif [[ "${ENVOY_BUILD_ARCH}" == "aarch64" ]]; then @@ -325,6 +337,14 @@ case $CI_TARGET in } ;; + clean|expunge) + setup_clang_toolchain + if [[ "$CI_TARGET" == "expunge" ]]; then + CLEAN_ARGS+=(--expunge) + fi + bazel clean "${BAZEL_GLOBAL_OPTIONS[@]}" "${CLEAN_ARGS[@]}" + ;; + compile_time_options) # See `compile-time-options` in `.bazelrc` setup_clang_toolchain @@ -546,6 +566,27 @@ case $CI_TARGET in cat bazel-bin/distribution/dockerhub/readme.md ;; + fetch) + setup_clang_toolchain + echo "Fetching ${FETCH_TARGETS[*]} ..." + FETCH_ARGS=( + --noshow_progress + --noshow_loading_progress) + # TODO(phlax): separate out retry logic + n=0 + until [ "$n" -ge 10 ]; do + bazel fetch "${BAZEL_GLOBAL_OPTIONS[@]}" \ + "${FETCH_ARGS[@]}" \ + "${FETCH_TARGETS[@]}" \ + && break + n=$((n+1)) + if [[ "$n" -ne 10 ]]; then + sleep 15 + echo "Retrying fetch ..." + fi + done + ;; + fix_proto_format) # proto_format.sh needs to build protobuf. setup_clang_toolchain @@ -582,6 +623,20 @@ case $CI_TARGET in bazel_envoy_binary_build fastbuild ;; + info) + setup_clang_toolchain + n=0 + until [ "$n" -ge 10 ]; do + bazel info "${BAZEL_GLOBAL_OPTIONS[@]}" \ + && break + n=$((n+1)) + if [[ "$n" -ne 10 ]]; then + sleep 15 + echo "Retrying fetch ..." + fi + done + ;; + msan) ENVOY_STDLIB=libc++ setup_clang_toolchain From 074092dd0f0f97776fd714f0220cc72e9e2a0cae Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 10 Aug 2023 18:24:37 +0100 Subject: [PATCH 015/274] arm64/ci: Use small VMs for publishing/verify jobs (#28880) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 8 ++++++-- .azure-pipelines/cached.yml | 5 ++++- .azure-pipelines/stage/publish.yml | 4 +++- .azure-pipelines/stage/verify.yml | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index a123d7d8b8e15..b0495321395e6 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -25,6 +25,9 @@ parameters: - name: tmpfsCacheDisabled type: string default: '' +- name: tmpfsDockerDisabled + type: string + default: '' - name: cacheKeyBazel type: string @@ -130,7 +133,7 @@ steps: done sudo chown -R azure-pipelines:azure-pipelines $(Build.StagingDirectory)/bazel_root/ displayName: "Mount/tmpfs bazel directories" - condition: and(succeeded(), eq('${{ parameters.managedAgent }}', false)) + condition: and(succeeded(), eq('${{ parameters.managedAgent }}', false), ne('${{ parameters.tmpfsDockerDisabled }}', true)) - bash: | set -e @@ -143,7 +146,7 @@ steps: sudo chown -R vsts:vsts "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ echo "Created bazel cache directories: "${CACHE_DIRS[*]}"" displayName: "Create bazel directories" - condition: and(succeeded(), eq('${{ parameters.managedAgent }}', true)) + condition: and(succeeded(), eq('${{ parameters.managedAgent }}', true), eq('${{ parameters.tmpfsDockerDisabled }}', true)) # Caching - template: cached.yml @@ -155,6 +158,7 @@ steps: arch: "${{ parameters.artifactSuffix }}" pathTemp: "${{ parameters.pathCacheTemp }}" tmpfsDisabled: "${{ parameters.tmpfsCacheDisabled }}" + tmpfsDockerDisabled: "${{ parameters.tmpfsDockerDisabled }}" - ${{ each step in parameters.stepsPre }}: - ${{ each pair in step }}: diff --git a/.azure-pipelines/cached.yml b/.azure-pipelines/cached.yml index 22b7fcfdf3553..4c5a20e9a12a7 100644 --- a/.azure-pipelines/cached.yml +++ b/.azure-pipelines/cached.yml @@ -27,6 +27,9 @@ parameters: - name: tmpfsDisabled type: string default: +- name: tmpfsDockerDisabled + type: string + default: - name: pathDockerBind type: string @@ -82,6 +85,6 @@ steps: condition: and(not(canceled()), eq(${{ parameters.prime }}, true), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZELISK_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) # Load the caches for a job -- script: sudo .azure-pipelines/docker/load_caches.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.pathDockerBind }}" +- script: sudo .azure-pipelines/docker/load_caches.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.pathDockerBind }}" "${{ parameters.tmpfsDockerDisabled }}" displayName: "Cache/restore" condition: and(not(canceled()), eq(${{ parameters.prime }}, false)) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 0fbcb19ade84e..7827eae79ff2a 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -175,6 +175,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: bazel.distribution + tmpfsCacheDisabled: true stepsPre: - template: ../gpg.yml parameters: @@ -195,7 +196,7 @@ jobs: and(not(canceled()), eq(${{ parameters.runPackaging }}, 'true')) timeoutInMinutes: 120 - pool: "envoy-arm-large" + pool: "envoy-arm-small" steps: - task: DownloadBuildArtifacts@0 inputs: @@ -211,6 +212,7 @@ jobs: rbe: false artifactSuffix: ".arm64" bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" + tmpfsDockerDisabled: true stepsPre: - template: ../gpg.yml parameters: diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index 0e7c32be01759..eeffccda92eb3 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -37,7 +37,7 @@ jobs: displayName: Debs (arm64) condition: and(not(canceled()), succeeded(), ne(stageDependencies.env.repo.outputs['changed.mobileOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.docsOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.examplesOnly'], 'true')) timeoutInMinutes: 120 - pool: "envoy-arm-large" + pool: "envoy-arm-small" steps: - task: DownloadBuildArtifacts@0 inputs: From 9636ded93bc6cfa4a540c15ae8a7c71a0a776882 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 10 Aug 2023 18:49:54 +0100 Subject: [PATCH 016/274] ci/cache: Expire bazel cache on version change (#28954) Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index c0e321a3bdc80..5571440917d22 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -49,7 +49,7 @@ variables: - name: cacheKeyVersion value: v0 - name: cacheKeyBazel - value: './WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' + value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyBazelisk value: ".bazelversion" - name: cacheKeyDocker From d3c7aad9a654a1e572ece83b5b7e6fdc1e4c9b24 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 11 Aug 2023 16:08:20 +0100 Subject: [PATCH 017/274] ci/caching: Simplify bazel cache (#28975) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 4 ---- .azure-pipelines/cached.yml | 15 +-------------- .azure-pipelines/docker/load_caches.sh | 12 ------------ .azure-pipelines/docker/prepare_cache.sh | 1 - .azure-pipelines/docker/prime_cache.sh | 19 +------------------ .azure-pipelines/pipelines.yml | 2 -- ci/do_ci.sh | 11 +---------- 7 files changed, 3 insertions(+), 61 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index b0495321395e6..e431e3ebb057b 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -9,9 +9,6 @@ parameters: default: "" # caching -- name: cacheKeyBazelisk - type: string - default: .bazelversion - name: cacheKeyDocker type: string default: ".devcontainer/Dockerfile" @@ -152,7 +149,6 @@ steps: - template: cached.yml parameters: keyBazel: "${{ parameters.cacheKeyBazel }}" - keyBazelisk: "${{ parameters.cacheKeyBazelisk }}" keyDocker: "${{ parameters.cacheKeyDocker }}" pathDockerBind: "${{ parameters.pathDockerBind }}" arch: "${{ parameters.artifactSuffix }}" diff --git a/.azure-pipelines/cached.yml b/.azure-pipelines/cached.yml index 4c5a20e9a12a7..acbf9e4864914 100644 --- a/.azure-pipelines/cached.yml +++ b/.azure-pipelines/cached.yml @@ -16,9 +16,6 @@ parameters: - name: keyBazel type: string default: $(cacheKeyBazel) -- name: keyBazelisk - type: string - default: $(cacheKeyBazelisk) - name: pathTemp type: string @@ -56,15 +53,6 @@ steps: path: "${{ parameters.pathTemp }}/docker" cacheHitVar: DOCKER_CACHE_RESTORED -- task: Cache@2 - env: - VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" - displayName: "Cache (Bazelisk)" - inputs: - key: '${{ parameters.name }} | bazelisk | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazelisk }}' - path: "${{ parameters.pathTemp }}/bazelisk" - cacheHitVar: BAZELISK_CACHE_RESTORED - - task: Cache@2 env: VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" @@ -78,11 +66,10 @@ steps: - script: .azure-pipelines/docker/prime_cache.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.arch }}" env: DOCKER_RESTORED: $(DOCKER_CACHE_RESTORED) - BAZELISK_RESTORED: $(BAZELISK_CACHE_RESTORED) BAZEL_RESTORED: $(BAZEL_CACHE_RESTORED) displayName: "Cache/prime (${{ parameters.name }})" # TODO(phlax): figure if there is a way to test cache without downloading it - condition: and(not(canceled()), eq(${{ parameters.prime }}, true), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZELISK_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) + condition: and(not(canceled()), eq(${{ parameters.prime }}, true), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) # Load the caches for a job - script: sudo .azure-pipelines/docker/load_caches.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.pathDockerBind }}" "${{ parameters.tmpfsDockerDisabled }}" diff --git a/.azure-pipelines/docker/load_caches.sh b/.azure-pipelines/docker/load_caches.sh index ffaf3979a8a76..5170249f2f6d4 100755 --- a/.azure-pipelines/docker/load_caches.sh +++ b/.azure-pipelines/docker/load_caches.sh @@ -17,9 +17,6 @@ DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" BAZEL_CACHE_TARBALL="${BAZEL_CACHE_PATH}/bazel.tar.zst" -BAZELISK_CACHE_PATH="${CACHE_PATH}/bazelisk" -BAZELISK_CACHE_TARBALL="${BAZELISK_CACHE_PATH}/bazelisk.tar.zst" - echo "Stopping Docker daemon ..." systemctl stop docker docker.socket @@ -70,15 +67,6 @@ else echo "No bazel cache to restore, starting bazel with no data" fi -mkdir -p "${ENVOY_DOCKER_BUILD_DIR}/.cache/bazelisk" - -if [[ -e "${BAZELISK_CACHE_TARBALL}" ]]; then - echo "Extracting bazelisk cache ${BAZELISK_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR}/.cache/bazelisk ..." - zstd --stdout -d "$BAZELISK_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}/.cache/bazelisk" -else - echo "No bazelisk cache to restore, starting bazelisk with no data" -fi - if mountpoint -q "${CACHE_PATH}"; then echo "Unmount cache tmp ${CACHE_PATH} ..." umount "${CACHE_PATH}" diff --git a/.azure-pipelines/docker/prepare_cache.sh b/.azure-pipelines/docker/prepare_cache.sh index 66cf2b4b7260d..792b9f8f56843 100755 --- a/.azure-pipelines/docker/prepare_cache.sh +++ b/.azure-pipelines/docker/prepare_cache.sh @@ -22,5 +22,4 @@ if [[ -z "$NO_MOUNT_TMPFS" ]]; then fi mkdir -p "${DOCKER_CACHE_PATH}/docker" mkdir -p "${DOCKER_CACHE_PATH}/bazel" -mkdir -p "${DOCKER_CACHE_PATH}/bazelisk" chown -R "$DOCKER_CACHE_OWNERSHIP" "${DOCKER_CACHE_PATH}" diff --git a/.azure-pipelines/docker/prime_cache.sh b/.azure-pipelines/docker/prime_cache.sh index 151f2d3a3d00d..8f8a0af6190c6 100755 --- a/.azure-pipelines/docker/prime_cache.sh +++ b/.azure-pipelines/docker/prime_cache.sh @@ -5,7 +5,6 @@ CACHE_PATH="$2" CACHE_ARCH="$3" echo "Docker restored: $DOCKER_RESTORED" -echo "Bazelisk restored: $BAZELISK_RESTORED" echo "Bazel restored: $BAZEL_RESTORED" if [[ -z "$CACHE_PATH" ]]; then @@ -22,14 +21,10 @@ fi DOCKER_CACHE_TARBALL="${CACHE_PATH}/docker/docker.tar.zst" BAZEL_CACHE_TARBALL="${CACHE_PATH}/bazel/bazel.tar.zst" BAZEL_PATH=/tmp/envoy-docker-build -BAZELISK_CACHE_TARBALL="${CACHE_PATH}/bazelisk/bazelisk.tar.zst" -BAZELISK_PATHS=( - "${BAZEL_PATH}/.cache/bazelisk" - "${BAZEL_PATH}/bazel_root/install") echo echo "================ Load caches ===================" -if [[ "$DOCKER_RESTORED" == "true" ]] || [[ "$BAZELISK_RESTORED" == "true" ]] || [[ "$BAZEL_RESTORED" == "true" ]]; then +if [[ "$DOCKER_RESTORED" == "true" ]] || [[ "$BAZEL_RESTORED" == "true" ]]; then sudo ./.azure-pipelines/docker/load_caches.sh "$ENVOY_DOCKER_BUILD_DIR" "$CACHE_PATH" "" true else sudo ./.azure-pipelines/docker/clean_docker.sh @@ -38,13 +33,6 @@ fi echo "===================================================" echo -echo -echo "================ Bazel info =======================" -# Get bazelisk and print bazel info -./ci/run_envoy_docker.sh './ci/do_ci.sh info' -echo "===================================================" -echo - echo echo "================ Bazel fetch ======================" # Fetch bazel dependencies @@ -69,12 +57,7 @@ if [[ "$DOCKER_RESTORED" != "true" ]]; then sudo ./.azure-pipelines/docker/create_cache.sh "${DOCKER_CACHE_TARBALL}" /var/lib/docker fi -if [[ "$BAZELISK_RESTORED" != "true" ]]; then - sudo ./.azure-pipelines/docker/create_cache.sh "${BAZELISK_CACHE_TARBALL}" "${BAZELISK_PATHS[@]}" -fi - if [[ "$BAZEL_RESTORED" != "true" ]]; then - rm -rf "{BAZELISK_PATHS[@]}" sudo ./.azure-pipelines/docker/create_cache.sh "${BAZEL_CACHE_TARBALL}" "${BAZEL_PATH}" fi sudo chmod o+r -R "${CACHE_PATH}" diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 5571440917d22..14406ecf7e96e 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -50,8 +50,6 @@ variables: value: v0 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' -- name: cacheKeyBazelisk - value: ".bazelversion" - name: cacheKeyDocker value: ".devcontainer/Dockerfile" # Docker build uses separate docker cache diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 9567c3e5cab8a..4ad6f70f47ce1 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -625,16 +625,7 @@ case $CI_TARGET in info) setup_clang_toolchain - n=0 - until [ "$n" -ge 10 ]; do - bazel info "${BAZEL_GLOBAL_OPTIONS[@]}" \ - && break - n=$((n+1)) - if [[ "$n" -ne 10 ]]; then - sleep 15 - echo "Retrying fetch ..." - fi - done + bazel info "${BAZEL_GLOBAL_OPTIONS[@]}" ;; msan) From 104731e3c81d10b02033b33543137dcdc7e08a28 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 11 Aug 2023 19:40:49 +0100 Subject: [PATCH 018/274] bazel/rbe: Remove redundant toolchain (#28980) Signed-off-by: Ryan Northey --- bazel/toolchains/BUILD | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 bazel/toolchains/BUILD diff --git a/bazel/toolchains/BUILD b/bazel/toolchains/BUILD deleted file mode 100644 index e6a6833650289..0000000000000 --- a/bazel/toolchains/BUILD +++ /dev/null @@ -1,17 +0,0 @@ -licenses(["notice"]) # Apache 2 - -platform( - name = "rbe_ubuntu_clang_platform", - parents = ["@rbe_ubuntu_clang//config:platform"], - remote_execution_properties = """ - {PARENT_REMOTE_EXECUTION_PROPERTIES} - properties: { - name: "dockerAddCapabilities" - value: "SYS_PTRACE,NET_RAW,NET_ADMIN" - } - properties: { - name: "dockerNetwork" - value: "standard" - } - """, -) From f627e123018e87838559ed2598446aeb2dd11046 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 11 Aug 2023 22:16:54 +0100 Subject: [PATCH 019/274] deps/mobile: Bump `bazel` -> 6.3.1, `rules_java` -> 6.4.0, `io_bazel_rules_kotlin` -> 1.8 (#28854) Signed-off-by: Ryan Northey Co-authored-by: Ryan Hamilton --- .bazelrc | 2 +- .bazelversion | 2 +- bazel/rules_java.patch | 293 +++++++++++++++++++++ mobile/bazel/envoy_mobile_repositories.bzl | 13 +- mobile/tools/what_to_run.sh | 2 +- 5 files changed, 303 insertions(+), 9 deletions(-) create mode 100644 bazel/rules_java.patch diff --git a/.bazelrc b/.bazelrc index 6f41ddbdc78d0..934fd11cb0ce0 100644 --- a/.bazelrc +++ b/.bazelrc @@ -474,7 +474,7 @@ build:rbe-engflow --remote_cache=grpcs://envoy.cluster.engflow.com build:rbe-engflow --remote_executor=grpcs://envoy.cluster.engflow.com build:rbe-engflow --bes_backend=grpcs://envoy.cluster.engflow.com/ build:rbe-engflow --bes_results_url=https://envoy.cluster.engflow.com/invocation/ -build:rbe-engflow --experimental_credential_helper=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:rbe-engflow --experimental_credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh build:rbe-engflow --grpc_keepalive_time=30s build:rbe-engflow --remote_timeout=3600s build:rbe-engflow --bes_timeout=3600s diff --git a/.bazelversion b/.bazelversion index dfda3e0b4f011..dc0208aba8e45 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.1.0 +6.3.1 diff --git a/bazel/rules_java.patch b/bazel/rules_java.patch new file mode 100644 index 0000000000000..91bd69eb69fa7 --- /dev/null +++ b/bazel/rules_java.patch @@ -0,0 +1,293 @@ +diff --git a/java/repositories.bzl b/java/repositories.bzl +index 7e5b939..e8d10b3 100644 +--- a/java/repositories.bzl ++++ b/java/repositories.bzl +@@ -88,7 +88,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -103,7 +103,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_linux_s390x", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], +@@ -117,7 +117,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -132,7 +132,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -146,7 +146,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -161,7 +161,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_windows", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +@@ -189,7 +189,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -205,7 +205,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -221,7 +221,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux_ppc64le", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:ppc", + ], +@@ -237,7 +237,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux_s390x", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], +@@ -253,7 +253,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -269,7 +269,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -285,7 +285,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_win", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +@@ -301,7 +301,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_win_arm64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:arm64", + ], +@@ -318,7 +318,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -334,7 +334,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -350,7 +350,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux_s390x", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], +@@ -366,7 +366,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux_ppc64le", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:ppc", + ], +@@ -382,7 +382,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -398,7 +398,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -413,7 +413,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_win", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +@@ -428,7 +428,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_win_arm64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:arm64", + ], +@@ -446,7 +446,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -462,7 +462,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -478,7 +478,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -494,7 +494,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -509,7 +509,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_win", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +diff --git a/toolchains/remote_java_repository.bzl b/toolchains/remote_java_repository.bzl +index 86916ec..5521fcf 100644 +--- a/toolchains/remote_java_repository.bzl ++++ b/toolchains/remote_java_repository.bzl +@@ -32,20 +32,20 @@ _toolchain_config = repository_rule( + }, + ) + +-def remote_java_repository(name, version, target_compatible_with = None, prefix = "remotejdk", **kwargs): ++def remote_java_repository(name, version, exec_compatible_with = None, prefix = "remotejdk", **kwargs): + """Imports a JDK from a http archive and creates runtime toolchain definitions for it. + + Register the toolchains defined by this macro via `register_toolchains("@//:all")`, where + `` is the value of the `name` parameter. + +- Toolchain resolution is determined with target_compatible_with ++ Toolchain resolution is determined with exec_compatible_with + parameter and constrained with --java_runtime_version flag either having value + of "version" or "{prefix}_{version}" parameters. + + Args: + name: A unique name for this rule. + version: Version of the JDK imported. +- target_compatible_with: Target platform constraints (CPU and OS) for this JDK. ++ exec_compatible_with: Target platform constraints (CPU and OS) for this JDK. + prefix: Optional alternative prefix for configuration flag value used to determine this JDK. + **kwargs: Refer to http_archive documentation + """ +@@ -77,7 +77,7 @@ alias( + ) + toolchain( + name = "toolchain", +- target_compatible_with = {target_compatible_with}, ++ exec_compatible_with = {exec_compatible_with}, + target_settings = [":version_or_prefix_version_setting"], + toolchain_type = "@bazel_tools//tools/jdk:runtime_toolchain_type", + toolchain = "{toolchain}", +@@ -85,7 +85,7 @@ toolchain( + """.format( + prefix = prefix, + version = version, +- target_compatible_with = target_compatible_with, ++ exec_compatible_with = exec_compatible_with, + toolchain = "@{repo}//:jdk".format(repo = name), + ), + ) diff --git a/mobile/bazel/envoy_mobile_repositories.bzl b/mobile/bazel/envoy_mobile_repositories.bzl index 923e060bcf1ac..86e64ddb9bbd5 100644 --- a/mobile/bazel/envoy_mobile_repositories.bzl +++ b/mobile/bazel/envoy_mobile_repositories.bzl @@ -65,10 +65,11 @@ def swift_repos(): def kotlin_repos(): http_archive( name = "rules_java", - sha256 = "19462d64b1586c0d4ea0e87f9325be2514f0eb84e56dbf3245450451b3701581", - strip_prefix = "rules_java-43243982abc76390ef64be62379a1353f9011771", - # TODO(jpsim): Switch back to bazelbuild repo when https://github.com/bazelbuild/rules_java/issues/64 is fixed - url = "https://github.com/jpsim/rules_java/archive/43243982abc76390ef64be62379a1353f9011771.tar.gz", + sha256 = "241822bf5fad614e3e1c42431002abd9af757136fa590a6a7870c6e0640a82e3", + strip_prefix = "rules_java-6.4.0", + url = "https://github.com/bazelbuild/rules_java/archive/6.4.0.tar.gz", + patch_args = ["-p1"], + patches = ["@envoy//bazel:rules_java.patch"], ) http_archive( @@ -80,8 +81,8 @@ def kotlin_repos(): http_archive( name = "io_bazel_rules_kotlin", - sha256 = "f033fa36f51073eae224f18428d9493966e67c27387728b6be2ebbdae43f140e", - urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.7.0-RC-3/rules_kotlin_release.tgz"], + sha256 = "01293740a16e474669aba5b5a1fe3d368de5832442f164e4fbfc566815a8bc3a", + urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.8/rules_kotlin_release.tgz"], ) http_archive( diff --git a/mobile/tools/what_to_run.sh b/mobile/tools/what_to_run.sh index 2ca3941818b42..939d23173b8e1 100755 --- a/mobile/tools/what_to_run.sh +++ b/mobile/tools/what_to_run.sh @@ -5,7 +5,7 @@ set -euo pipefail BRANCH_NAME="$GITHUB_REF_NAME" BASE_COMMIT="$(git merge-base origin/main HEAD)" CHANGED_FILES="$(git diff "${BASE_COMMIT}" --name-only)" -CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml' +CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml' # The logic in this file is roughly: # From 056058b7b5f382c05b8bf4e4f87dbd9200f463d8 Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 13 Aug 2023 08:37:07 +0100 Subject: [PATCH 020/274] coverage: Use upstream coverage collect script (#28802) Signed-off-by: Ryan Northey --- .bazelrc | 7 +- bazel/coverage/BUILD | 6 - bazel/coverage/collect_cc_coverage.sh | 175 ----------------------- mobile/.bazelrc | 3 - test/extensions/tracers/dynamic_ot/BUILD | 2 + test/integration/BUILD | 2 +- test/run_envoy_bazel_coverage.sh | 8 ++ test/server/config_validation/BUILD | 1 + 8 files changed, 16 insertions(+), 188 deletions(-) delete mode 100755 bazel/coverage/collect_cc_coverage.sh diff --git a/.bazelrc b/.bazelrc index 934fd11cb0ce0..9749d5adf05bf 100644 --- a/.bazelrc +++ b/.bazelrc @@ -187,6 +187,7 @@ build --test_env=HEAPCHECK=normal --test_env=PPROF_PATH # Coverage options coverage --config=coverage coverage --build_tests_only + build:coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 build:coverage --action_env=GCOV=llvm-profdata build:coverage --copt=-DNDEBUG @@ -195,15 +196,15 @@ build:coverage --test_timeout=390,750,1500,5700 build:coverage --define=dynamic_link_tests=true build:coverage --define=ENVOY_CONFIG_COVERAGE=1 build:coverage --cxxopt="-DENVOY_CONFIG_COVERAGE=1" -build:coverage --coverage_support=@envoy//bazel/coverage:coverage_support -build:coverage --test_env=CC_CODE_COVERAGE_SCRIPT=bazel/coverage/collect_cc_coverage.sh build:coverage --test_env=HEAPCHECK= build:coverage --combined_report=lcov build:coverage --strategy=TestRunner=sandboxed,local build:coverage --strategy=CoverageReport=sandboxed,local build:coverage --experimental_use_llvm_covmap +build:coverage --experimental_generate_llvm_lcov build:coverage --collect_code_coverage -build:coverage --instrumentation_filter="//source(?!/common/quic/platform)[/:],//envoy[/:],//contrib(?!/.*/test)[/:]" +build:coverage --instrumentation_filter="^//source(?!/common/quic/platform)[/:],^//envoy[/:],^//contrib(?!/.*/test)[/:]" +build:coverage --remote_download_toplevel build:test-coverage --test_arg="-l trace" build:test-coverage --test_arg="--log-path /dev/null" diff --git a/bazel/coverage/BUILD b/bazel/coverage/BUILD index 9aa87d0869687..56f73dc2ad1d4 100644 --- a/bazel/coverage/BUILD +++ b/bazel/coverage/BUILD @@ -1,9 +1,3 @@ licenses(["notice"]) # Apache 2 -# TODO(lizan): Add test for this and upstream to upstream Bazel. -filegroup( - name = "coverage_support", - srcs = ["collect_cc_coverage.sh"], -) - exports_files(["fuzz_coverage_wrapper.sh"]) diff --git a/bazel/coverage/collect_cc_coverage.sh b/bazel/coverage/collect_cc_coverage.sh deleted file mode 100755 index 3f9fd700a8edf..0000000000000 --- a/bazel/coverage/collect_cc_coverage.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/bash -e -# -# This is a fork of https://github.com/bazelbuild/bazel/blob/3.1.0/tools/test/collect_cc_coverage.sh -# to cover most of use cases in Envoy. -# TODO(lizan): Move this to upstream Bazel -# -# Copyright 2016 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script collects code coverage data for C++ sources, after the tests -# were executed. -# -# Bazel C++ code coverage collection support is poor and limited. There is -# an ongoing effort to improve this (tracking issue #1118). -# -# Bazel uses the lcov tool for gathering coverage data. There is also -# an experimental support for clang llvm coverage, which uses the .profraw -# data files to compute the coverage report. -# -# This script assumes the following environment variables are set: -# - COVERAGE_DIR Directory containing metadata files needed for -# coverage collection (e.g. gcda files, profraw). -# - COVERAGE_MANIFEST Location of the instrumented file manifest. -# - COVERAGE_GCOV_PATH Location of gcov. This is set by the TestRunner. -# - COVERAGE_GCOV_OPTIONS Additional options to pass to gcov. -# - ROOT Location from where the code coverage collection -# was invoked. -# -# The script looks in $COVERAGE_DIR for the C++ metadata coverage files (either -# gcda or profraw) and uses either lcov or gcov to get the coverage data. -# The coverage data is placed in $COVERAGE_OUTPUT_FILE. - -read -ra COVERAGE_GCOV_OPTIONS <<< "${COVERAGE_GCOV_OPTIONS:-}" - -# Checks if clang llvm coverage should be used instead of lcov. -function uses_llvm() { - if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Returns 0 if gcov must be used, 1 otherwise. -function uses_gcov() { - [[ "$GCOV_COVERAGE" -eq "1" ]] && return 0 - return 1 -} - -function init_gcov() { - # Symlink the gcov tool such with a link called gcov. Clang comes with a tool - # called llvm-cov, which behaves like gcov if symlinked in this way (otherwise - # we would need to invoke it with "llvm-cov gcov"). - # For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html. - GCOV="${COVERAGE_DIR}/gcov" - ln -s "${COVERAGE_GCOV_PATH}" "${GCOV}" -} - -# Computes code coverage data using the clang generated metadata found under -# $COVERAGE_DIR. -# Writes the collected coverage into the given output file. -function llvm_coverage() { - local output_file="${1}" object_file object_files object_param=() - shift - export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" - "${COVERAGE_GCOV_PATH}" merge -output "${output_file}.data" \ - "${COVERAGE_DIR}"/*.profraw - - - object_files="$(find -L "${RUNFILES_DIR}" -type f -exec file -L {} \; \ - | grep ELF | grep -v "LSB core" | sed 's,:.*,,')" - - for object_file in ${object_files}; do - object_param+=(-object "${object_file}") - done - - llvm-cov export -instr-profile "${output_file}.data" -format=lcov \ - -ignore-filename-regex='.*external/.+' \ - -ignore-filename-regex='/tmp/.+' \ - "${object_param[@]}" | sed 's#/proc/self/cwd/##' > "${output_file}" -} - -# Generates a code coverage report in gcov intermediate text format by invoking -# gcov and using the profile data (.gcda) and notes (.gcno) files. -# -# The profile data files are expected to be found under $COVERAGE_DIR. -# The notes file are expected to be found under $ROOT. -# -# - output_file The location of the file where the generated code coverage -# report is written. -function gcov_coverage() { - local gcda gcno_path line output_file="${1}" - shift - - # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR - # because gcov expects them to be in the same directory. - while read -r line; do - if [[ ${line: -4} == "gcno" ]]; then - gcno_path=${line} - gcda="${COVERAGE_DIR}/$(dirname "${gcno_path}")/$(basename "${gcno_path}" .gcno).gcda" - # If the gcda file was not found we skip generating coverage from the gcno - # file. - if [[ -f "$gcda" ]]; then - # gcov expects both gcno and gcda files to be in the same directory. - # We overcome this by copying the gcno to $COVERAGE_DIR where the gcda - # files are expected to be. - if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then - mkdir -p "${COVERAGE_DIR}/$(dirname "${gcno_path}")" - cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}" - fi - # Invoke gcov to generate a code coverage report with the flags: - # -i Output gcov file in an intermediate text format. - # The output is a single .gcov file per .gcda file. - # No source code is required. - # -o directory The directory containing the .gcno and - # .gcda data files. - # "${gcda"} The input file name. gcov is looking for data files - # named after the input filename without its extension. - # gcov produces files called .gcov in the current - # directory. These contain the coverage information of the source file - # they correspond to. One .gcov file is produced for each source - # (or header) file containing code which was compiled to produce the - # .gcda files. - # Don't generate branch coverage (-b) because of a gcov issue that - # segfaults when both -i and -b are used (see - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879). - "${GCOV}" -i "${COVERAGE_GCOV_OPTIONS[@]}" -o "$(dirname "${gcda}")" "${gcda}" - - # Append all .gcov files in the current directory to the output file. - cat ./*.gcov >> "$output_file" - # Delete the .gcov files. - rm ./*.gcov - fi - fi - done < "${COVERAGE_MANIFEST}" -} - -function main() { - init_gcov - - # If llvm code coverage is used, we output the raw code coverage report in - # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other - # format by LcovMerger. - # TODO(#5881): Convert profdata reports to lcov. - if uses_llvm; then - BAZEL_CC_COVERAGE_TOOL="PROFDATA" - fi - - # When using either gcov or lcov, have an output file specific to the test - # and format used. For lcov we generate a ".dat" output file and for gcov - # a ".gcov" output file. It is important that these files are generated under - # COVERAGE_DIR. - # When this script is invoked by tools/test/collect_coverage.sh either of - # these two coverage reports will be picked up by LcovMerger and their - # content will be converted and/or merged with other reports to an lcov - # format, generating the final code coverage report. - case "$BAZEL_CC_COVERAGE_TOOL" in - ("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;; - ("PROFDATA") llvm_coverage "$COVERAGE_DIR/_cc_coverage.dat" ;; - (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \ - && exit 1 - esac -} - -main diff --git a/mobile/.bazelrc b/mobile/.bazelrc index ad6d2aaa65fc4..a5c07f3c00a0a 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -218,10 +218,7 @@ build:mobile-remote-ci-linux-coverage --strategy=CoverageReport=local,remote # Bazel remote caching is incompatible with C++ LLVM coverage so we need to deactivate it for coverage builds # TODO(lfpino): Reference upstream Bazel issue here on the incompatibility of remote caching and LLVM coverage. build:mobile-remote-ci-linux-coverage --noremote_accept_cached -build:mobile-remote-ci-linux-coverage --remote_download_toplevel -build:mobile-remote-ci-linux-coverage --build_runfile_links build:mobile-remote-ci-linux-coverage --legacy_important_outputs=false -build:mobile-remote-ci-linux-coverage --test_env=CC_CODE_COVERAGE_SCRIPT=external/envoy/bazel/coverage/collect_cc_coverage.sh build:mobile-remote-ci-linux-coverage --nocache_test_results build:mobile-remote-ci-linux-coverage --config=ci build:mobile-remote-ci-linux-coverage --config=remote diff --git a/test/extensions/tracers/dynamic_ot/BUILD b/test/extensions/tracers/dynamic_ot/BUILD index b982d22efa6a2..793c9884499bb 100644 --- a/test/extensions/tracers/dynamic_ot/BUILD +++ b/test/extensions/tracers/dynamic_ot/BUILD @@ -13,6 +13,7 @@ envoy_package() envoy_extension_cc_test( name = "dynamic_opentracing_driver_impl_test", + size = "large", srcs = [ "dynamic_opentracing_driver_impl_test.cc", ], @@ -35,6 +36,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "config_test", + size = "large", srcs = ["config_test.cc"], data = [ "@io_opentracing_cpp//mocktracer:libmocktracer_plugin.so", diff --git a/test/integration/BUILD b/test/integration/BUILD index 5c7359f365b9f..301ae0cd7c3b3 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -463,7 +463,7 @@ envoy_cc_test( srcs = [ "http2_flood_integration_test.cc", ], - shard_count = 4, + shard_count = 6, tags = [ "cpu:3", ], diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index a2c53dc1a2cb0..da2c8b76313ce 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -72,6 +72,7 @@ BAZEL_OUTPUT_BASE="$(bazel "${BAZEL_STARTUP_OPTIONS[@]}" info "${BAZEL_BUILD_OPT echo "Running bazel coverage with:" echo " Options: ${BAZEL_BUILD_OPTIONS[*]} ${BAZEL_COVERAGE_OPTIONS[*]}" echo " Targets: ${COVERAGE_TARGETS[*]}" + bazel "${BAZEL_STARTUP_OPTIONS[@]}" coverage "${BAZEL_BUILD_OPTIONS[@]}" "${BAZEL_COVERAGE_OPTIONS[@]}" "${COVERAGE_TARGETS[@]}" echo "Collecting profile and testlogs" @@ -80,6 +81,9 @@ if [[ -n "${ENVOY_BUILD_PROFILE}" ]]; then fi if [[ -n "${ENVOY_BUILD_DIR}" ]]; then + if [[ -e "${ENVOY_BUILD_DIR}/testlogs.tar.zst" ]]; then + rm -f "${ENVOY_BUILD_DIR}/testlogs.tar.zst" + fi find bazel-testlogs/ -name test.log \ | tar cf - -T - \ | bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ @@ -117,6 +121,10 @@ if [[ "${FUZZ_COVERAGE}" == "true" ]]; then - -T0 -o "${ENVOY_FUZZ_COVERAGE_ARTIFACT}" fi elif [[ -n "${ENVOY_COVERAGE_ARTIFACT}" ]]; then + if [[ -e "${ENVOY_COVERAGE_ARTIFACT}" ]]; then + rm "${ENVOY_COVERAGE_ARTIFACT}" + fi + tar cf - -C "${COVERAGE_DIR}" --transform 's/^\./coverage/' . \ | bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ - -T0 -o "${ENVOY_COVERAGE_ARTIFACT}" diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index d10324140f321..816f1f97a3241 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -157,6 +157,7 @@ envoy_cc_test_library( envoy_cc_fuzz_test( name = "xds_fuzz_test", + size = "large", srcs = ["xds_fuzz_test.cc"], corpus = "xds_corpus", deps = [ From 90a3d105d19efec4f9e5b1b0095b53ffd0e0d895 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 16 Aug 2023 10:54:20 +0100 Subject: [PATCH 021/274] bazel/coverage: Increase buffer memory (#29053) Signed-off-by: Ryan Northey --- .bazelrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.bazelrc b/.bazelrc index 9749d5adf05bf..d0b9df7ac3b07 100644 --- a/.bazelrc +++ b/.bazelrc @@ -9,6 +9,7 @@ # The number 3G is chosen heuristically to both support large VM and small VM with RBE. # Startup options cannot be selected via config. startup --host_jvm_args=-Xmx3g +startup --host_jvm_args=-XX:MaxDirectMemorySize=512m" fetch --color=yes run --color=yes From 8bd8ce7db56768eebc8d8c0b80cbcd30a0c62e13 Mon Sep 17 00:00:00 2001 From: Adam Kotwasinski Date: Wed, 16 Aug 2023 16:53:27 -0700 Subject: [PATCH 022/274] kafka: upgrade dependencies to 3.5.1 (#28932) Signed-off-by: Adam Kotwasinski Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 12 ++++++------ .../filters/network/source/protocol/generator.py | 8 +++++--- .../network_filters/kafka_broker_filter.rst | 4 ++-- .../listeners/network_filters/kafka_mesh_filter.rst | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index ffb93651a0ebb..f5ed9e394379b 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1250,13 +1250,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Kafka (source)", project_desc = "Open-source distributed event streaming platform", project_url = "https://kafka.apache.org", - version = "3.4.0", - sha256 = "9eeaf83ffddb85d253a2441a29ba6be0a563cd3d6eb9ddf0eeb8d6e2f49c0ef7", + version = "3.5.1", + sha256 = "9715589a02148fb21bc80d79f29763dbd371457bedcbbeab3db4f5c7fdd2d29c", strip_prefix = "kafka-{version}/clients/src/main/resources/common/message", urls = ["https://github.com/apache/kafka/archive/{version}.zip"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.network.kafka_broker", "envoy.filters.network.kafka_mesh"], - release_date = "2023-01-31", + release_date = "2023-07-14", cpe = "cpe:2.3:a:apache:kafka:*", license = "Apache-2.0", license_url = "https://github.com/apache/kafka/blob/{version}/LICENSE", @@ -1280,11 +1280,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Kafka (server binary)", project_desc = "Open-source distributed event streaming platform", project_url = "https://kafka.apache.org", - version = "3.4.0", - sha256 = "67025feb03eb963a8852d4adc5b2810744f493a672c5992728955e38bed43da8", + version = "3.5.1", + sha256 = "f7b74d544023f2c0ec52a179de59975cb64e34ea03650d829328b407b560e4da", strip_prefix = "kafka_2.13-{version}", urls = ["https://archive.apache.org/dist/kafka/{version}/kafka_2.13-{version}.tgz"], - release_date = "2023-01-31", + release_date = "2023-07-21", use_category = ["test_only"], ), kafka_python_client = dict( diff --git a/contrib/kafka/filters/network/source/protocol/generator.py b/contrib/kafka/filters/network/source/protocol/generator.py index 8aede752f2a9e..d1417ecfc6b78 100755 --- a/contrib/kafka/filters/network/source/protocol/generator.py +++ b/contrib/kafka/filters/network/source/protocol/generator.py @@ -128,8 +128,10 @@ def parse_messages(self, input_files): amended = re.sub(r'-2147483648', 'INT32_MIN', without_empty_newlines) message_spec = json.loads(amended) api_key = message_spec['apiKey'] - message = self.parse_top_level_element(message_spec) - messages.append(message) + # (adam.kotwasinski) ConsumerGroupHeartbeat needs some more changes to parse. + if api_key not in [68]: + message = self.parse_top_level_element(message_spec) + messages.append(message) except Exception as e: print('could not process %s' % input_file) raise @@ -165,7 +167,7 @@ def parse_top_level_element(self, spec): # So let's parse them and store them in state. common_structs = spec.get('commonStructs') if common_structs is not None: - for common_struct in common_structs: + for common_struct in reversed(common_structs): common_struct_name = common_struct['name'] common_struct_versions = Statics.parse_version_string( common_struct['versions'], versions[-1]) diff --git a/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst b/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst index 78dcb5bceb3b6..8d9f96258b8f1 100644 --- a/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst +++ b/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst @@ -5,8 +5,8 @@ Kafka Broker filter The Apache Kafka broker filter decodes the client protocol for `Apache Kafka `_, both the requests and responses in the payload. -The message versions in `Kafka 3.4.0 `_ -are supported. +The message versions in `Kafka 3.5.1 `_ +are supported (apart from ConsumerGroupHeartbeat). The filter attempts not to influence the communication between client and brokers, so the messages that could not be decoded (due to Kafka client or broker running a newer version than supported by this filter) are forwarded as-is. diff --git a/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst b/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst index 6a318826e70ef..4729473489711 100644 --- a/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst +++ b/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst @@ -12,7 +12,7 @@ clients. The requests received by this filter instance can be forwarded to one of multiple clusters, depending on the configured forwarding rules. -Corresponding message versions from Kafka 3.4.0 are supported. +Corresponding message versions from Kafka 3.5.1 are supported. * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.network.kafka_mesh.v3alpha.KafkaMesh``. * :ref:`v3 API reference ` From ccdb251cbbc3b77b38e570f26a7969e0e7d0af71 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 16 Aug 2023 15:10:30 +0100 Subject: [PATCH 023/274] bazel: Minor updates/cleanups (#29057) Signed-off-by: Ryan Northey --- .bazelrc | 5 +++-- .bazelversion | 2 +- ci/do_ci.sh | 4 +--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.bazelrc b/.bazelrc index d0b9df7ac3b07..d649bc8435188 100644 --- a/.bazelrc +++ b/.bazelrc @@ -9,7 +9,7 @@ # The number 3G is chosen heuristically to both support large VM and small VM with RBE. # Startup options cannot be selected via config. startup --host_jvm_args=-Xmx3g -startup --host_jvm_args=-XX:MaxDirectMemorySize=512m" +startup --host_jvm_args=-XX:MaxDirectMemorySize=512m fetch --color=yes run --color=yes @@ -206,6 +206,7 @@ build:coverage --experimental_generate_llvm_lcov build:coverage --collect_code_coverage build:coverage --instrumentation_filter="^//source(?!/common/quic/platform)[/:],^//envoy[/:],^//contrib(?!/.*/test)[/:]" build:coverage --remote_download_toplevel +build:coverage --define=tcmalloc=gperftools build:test-coverage --test_arg="-l trace" build:test-coverage --test_arg="--log-path /dev/null" @@ -476,7 +477,7 @@ build:rbe-engflow --remote_cache=grpcs://envoy.cluster.engflow.com build:rbe-engflow --remote_executor=grpcs://envoy.cluster.engflow.com build:rbe-engflow --bes_backend=grpcs://envoy.cluster.engflow.com/ build:rbe-engflow --bes_results_url=https://envoy.cluster.engflow.com/invocation/ -build:rbe-engflow --experimental_credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:rbe-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh build:rbe-engflow --grpc_keepalive_time=30s build:rbe-engflow --remote_timeout=3600s build:rbe-engflow --bes_timeout=3600s diff --git a/.bazelversion b/.bazelversion index dc0208aba8e45..91e4a9f262244 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.3.1 +6.3.2 diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 4ad6f70f47ce1..992eac4c6a367 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -416,9 +416,7 @@ case $CI_TARGET in if [[ "$CI_TARGET" == "fuzz_coverage" ]]; then export FUZZ_COVERAGE=true fi - # We use custom BAZEL_BUILD_OPTIONS here to cover profiler's code. - BAZEL_BUILD_OPTION_LIST="${BAZEL_BUILD_OPTIONS[*]} --define tcmalloc=gperftools" \ - "${ENVOY_SRCDIR}/test/run_envoy_bazel_coverage.sh" \ + "${ENVOY_SRCDIR}/test/run_envoy_bazel_coverage.sh" \ "${COVERAGE_TEST_TARGETS[@]}" collect_build_profile coverage ;; From fc821243dd26dd4d99f63ed2cc73b65296acd128 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 18 Aug 2023 16:53:05 +0100 Subject: [PATCH 024/274] ci: Use RBE for coverage (#26049) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 33 +++++++++++++++++-- .azure-pipelines/stage/checks.yml | 11 +++++-- .bazelrc | 1 - ci/build_setup.sh | 8 +++-- ci/do_ci.sh | 15 +++++---- ci/run_envoy_docker.sh | 1 + test/common/router/BUILD | 1 + test/exe/BUILD | 2 ++ .../filters/http/kill_request/BUILD | 1 + test/integration/BUILD | 1 + test/run_envoy_bazel_coverage.sh | 19 ++++++----- test/tools/router_check/test/BUILD | 1 + 12 files changed, 72 insertions(+), 22 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index e431e3ebb057b..2b9ec67affc48 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -26,6 +26,9 @@ parameters: type: string default: '' +- name: bazelConfigRBE + type: string + default: --config=remote-ci --config=rbe-google --jobs=$(RbeJobs) - name: cacheKeyBazel type: string default: $(cacheKeyBazel) @@ -47,6 +50,13 @@ parameters: - name: bazelBuildExtraOptions type: string default: "" +- name: bazelStartupExtraOptions + type: string + default: "" +- name: bazelUseBES + displayName: "Upload bazel run data to BES" + type: boolean + default: true - name: envoyBuildFilterExample type: string default: "" @@ -175,12 +185,17 @@ steps: displayName: "Enable IPv6" condition: ${{ parameters.managedAgent }} -- script: ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' +- script: | + if [[ "${{ parameters.bazelUseBES }}" != 'true' ]]; then + unset GOOGLE_BES_PROJECT_ID + fi + ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' workingDirectory: $(Build.SourcesDirectory) env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_BUILD_FILTER_EXAMPLE: ${{ parameters.envoyBuildFilterExample }} GITHUB_TOKEN: "${{ parameters.authGithub }}" + BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" ${{ if ne(parameters['cacheTestResults'], true) }}: BAZEL_NO_CACHE_TEST_RESULTS: 1 ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: @@ -194,7 +209,7 @@ steps: ${{ if parameters.rbe }}: GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs) ${{ parameters.bazelBuildExtraOptions }}" + BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" ${{ if eq(parameters.rbe, false) }}: BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" BAZEL_REMOTE_CACHE: $(LocalBuildCache) @@ -206,6 +221,20 @@ steps: echo "disk space at end of build:" df -h + for hprof in $(find "$(Build.StagingDirectory)" -name "*heapdump.hprof"); do + echo + mkdir -p $(Build.StagingDirectory)/envoy/hprof + echo "Copying ${hprof}" + cp -a $hprof $(Build.StagingDirectory)/envoy/hprof + done + + cp -a "$(Build.StagingDirectory)/bazel_root/base/server/jvm.out" $(Build.StagingDirectory)/envoy + + if [[ "${{ parameters.artifactSuffix }}" == ".arm64" ]]; then + # Dump bazel-remote logging (only required for arm/self-hosted). + sudo systemctl status --no-pager bazel-remote > $(Build.StagingDirectory)/envoy/br.status + sudo journalctl --no-pager -xu bazel-remote > $(Build.StagingDirectory)/envoy/br.journal + fi echo du -ch "$(Build.StagingDirectory)" | grep -E "[0-9]{2,}M|[0-9]G" diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index 05f8e8afa763c..bdc847ab5ea72 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -102,11 +102,14 @@ jobs: # TODO(phlax): Make this configurable and ratchet down! timeoutInMinutes: 240 pool: "envoy-x64-large" + variables: + BAZEL_STARTUP_EXTRA: '' strategy: maxParallel: 2 matrix: coverage: CI_TARGET: "coverage" + BAZEL_STARTUP_EXTRA: --host_jvm_args=-Xmx20g --host_jvm_args=-XX:MaxDirectMemorySize=4G fuzz_coverage: CI_TARGET: "fuzz_coverage" steps: @@ -114,11 +117,13 @@ jobs: parameters: managedAgent: false ciTarget: bazel.$(CI_TARGET) - rbe: false + rbe: true # /tmp/sandbox_base is a tmpfs in CI environment to optimize large I/O for coverage traces - bazelBuildExtraOptions: "--define=no_debug_info=1 --linkopt=-Wl,-s --test_env=ENVOY_IP_TEST_VERSIONS=v4only --sandbox_base=/tmp/sandbox_base" + bazelConfigRBE: --config=ci --config=rbe-google --jobs=$(RbeJobs) + bazelBuildExtraOptions: --sandbox_base=/tmp/sandbox_base --define=no_debug_info=1 --linkopt=-Wl,-s --test_env=ENVOY_IP_TEST_VERSIONS=v4only + bazelStartupExtraOptions: $(BAZEL_STARTUP_EXTRA) + bazelUseBES: false cacheTestResults: ${{ parameters.cacheTestResults }} - - script: ci/run_envoy_docker.sh 'ci/do_ci.sh $(CI_TARGET)-upload' displayName: "Upload $(CI_TARGET) Report to GCS" env: diff --git a/.bazelrc b/.bazelrc index d649bc8435188..9d76cc8d398e4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -9,7 +9,6 @@ # The number 3G is chosen heuristically to both support large VM and small VM with RBE. # Startup options cannot be selected via config. startup --host_jvm_args=-Xmx3g -startup --host_jvm_args=-XX:MaxDirectMemorySize=512m fetch --color=yes run --color=yes diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 811371612d4e9..9dc4c1b0deb04 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -28,6 +28,7 @@ export ENVOY_BUILD_FILTER_EXAMPLE="${ENVOY_BUILD_FILTER_EXAMPLE:-0}" read -ra BAZEL_BUILD_EXTRA_OPTIONS <<< "${BAZEL_BUILD_EXTRA_OPTIONS:-}" read -ra BAZEL_EXTRA_TEST_OPTIONS <<< "${BAZEL_EXTRA_TEST_OPTIONS:-}" +read -ra BAZEL_STARTUP_EXTRA_OPTIONS <<< "${BAZEL_STARTUP_EXTRA_OPTIONS:-}" read -ra BAZEL_OPTIONS <<< "${BAZEL_OPTIONS:-}" echo "ENVOY_SRCDIR=${ENVOY_SRCDIR}" @@ -105,12 +106,15 @@ trap cleanup EXIT _bazel="$(which bazel)" BAZEL_STARTUP_OPTIONS=( + "${BAZEL_STARTUP_EXTRA_OPTIONS[@]}" "--output_user_root=${BUILD_DIR}/bazel_root" "--output_base=${BUILD_DIR}/bazel_root/base") bazel () { - # echo "RUNNING BAZEL (${PWD}): ${BAZEL_STARTUP_OPTIONS[*]} <> ${*}" >&2 - "$_bazel" "${BAZEL_STARTUP_OPTIONS[@]}" "$@" + local startup_options + read -ra startup_options <<< "${BAZEL_STARTUP_OPTION_LIST:-}" + # echo "RUNNING BAZEL (${PWD}): ${startup_options[*]} <> ${*}" >&2 + "$_bazel" "${startup_options[@]}" "$@" } export _bazel diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 992eac4c6a367..cce2bff00e7a7 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -41,12 +41,14 @@ else fi function collect_build_profile() { - declare -g build_profile_count=${build_profile_count:-1} - mv -f \ - "$(bazel info "${BAZEL_BUILD_OPTIONS[@]}" output_base)/command.profile.gz" \ - "${ENVOY_BUILD_PROFILE}/${build_profile_count}-$1.profile.gz" \ - || : - ((build_profile_count++)) + local output_base + declare -g build_profile_count=${build_profile_count:-1} + output_base="$(bazel info "${BAZEL_BUILD_OPTIONS[@]}" output_base)" + mv -f \ + "${output_base}/command.profile.gz" \ + "${ENVOY_BUILD_PROFILE}/${build_profile_count}-$1.profile.gz" \ + || : + ((build_profile_count++)) } function bazel_with_collection() { @@ -416,6 +418,7 @@ case $CI_TARGET in if [[ "$CI_TARGET" == "fuzz_coverage" ]]; then export FUZZ_COVERAGE=true fi + export BAZEL_GRPC_LOG="${ENVOY_BUILD_DIR}/grpc.log" "${ENVOY_SRCDIR}/test/run_envoy_bazel_coverage.sh" \ "${COVERAGE_TEST_TARGETS[@]}" collect_build_profile coverage diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index a158d851b5878..0fe264980d9c4 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -113,6 +113,7 @@ docker run --rm \ -e BAZEL_EXTRA_TEST_OPTIONS \ -e BAZEL_FAKE_SCM_REVISION \ -e BAZEL_REMOTE_CACHE \ + -e BAZEL_STARTUP_EXTRA_OPTIONS \ -e CI_TARGET_BRANCH \ -e DOCKERHUB_USERNAME \ -e DOCKERHUB_PASSWORD \ diff --git a/test/common/router/BUILD b/test/common/router/BUILD index f7a93e02fd263..982a9c35cb873 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -273,6 +273,7 @@ filegroup( envoy_cc_fuzz_test( name = "route_fuzz_test", + size = "large", srcs = ["route_fuzz_test.cc"], corpus = ":route_corpus", deps = [ diff --git a/test/exe/BUILD b/test/exe/BUILD index f90ff7cd1d887..e2dc8214d9778 100644 --- a/test/exe/BUILD +++ b/test/exe/BUILD @@ -79,6 +79,7 @@ envoy_cc_test( envoy_cc_test( name = "extra_extensions_test", + size = "large", srcs = ["extra_extensions_test.cc"], deps = [ "//test/test_common:environment_lib", @@ -102,6 +103,7 @@ envoy_cc_test( envoy_cc_test( name = "check_extensions_against_registry_test", + size = "large", srcs = ["check_extensions_against_registry_test.cc"], data = [ "//source/extensions:extensions_metadata.yaml", diff --git a/test/extensions/filters/http/kill_request/BUILD b/test/extensions/filters/http/kill_request/BUILD index e4d78880db0e8..da485bc0394d5 100644 --- a/test/extensions/filters/http/kill_request/BUILD +++ b/test/extensions/filters/http/kill_request/BUILD @@ -56,6 +56,7 @@ envoy_cc_test( name = "crash_integration_test", size = "large", srcs = ["crash_integration_test.cc"], + coverage = False, shard_count = 8, deps = [ "//source/extensions/filters/http/kill_request:kill_request_config", diff --git a/test/integration/BUILD b/test/integration/BUILD index 301ae0cd7c3b3..260c51d042c7e 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -373,6 +373,7 @@ envoy_sh_test( envoy_sh_test( name = "run_envoy_test", + size = "large", srcs = ["run_envoy_test.sh"], cc_binary = [":hotrestart_main"], data = [ diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index da2c8b76313ce..3261eeded5ab2 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -32,7 +32,6 @@ fi COVERAGE_TARGET="${COVERAGE_TARGET:-}" read -ra BAZEL_BUILD_OPTIONS <<< "${BAZEL_BUILD_OPTION_LIST:-}" read -ra BAZEL_GLOBAL_OPTIONS <<< "${BAZEL_GLOBAL_OPTION_LIST:-}" -read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTION_LIST:-}" echo "Starting run_envoy_bazel_coverage.sh..." echo " PWD=$(pwd)" @@ -50,11 +49,15 @@ else COVERAGE_TARGETS=(//test/...) fi -BAZEL_COVERAGE_OPTIONS=() +BAZEL_COVERAGE_OPTIONS=(--heap_dump_on_oom) + +if [[ -n "${BAZEL_GRPC_LOG}" ]]; then + BAZEL_COVERAGE_OPTIONS+=(--remote_grpc_log="${BAZEL_GRPC_LOG}") +fi if [[ "${FUZZ_COVERAGE}" == "true" ]]; then # Filter targets to just fuzz tests. - _targets=$(bazel "${BAZEL_STARTUP_OPTIONS[@]}" query "${BAZEL_GLOBAL_OPTIONS[@]}" "attr('tags', 'fuzz_target', ${COVERAGE_TARGETS[*]})") + _targets=$(bazel query "${BAZEL_GLOBAL_OPTIONS[@]}" --noshow_loading_progress --noshow_progress "attr('tags', 'fuzz_target', ${COVERAGE_TARGETS[*]})") COVERAGE_TARGETS=() while read -r line; do COVERAGE_TARGETS+=("$line"); done \ <<< "$_targets" @@ -67,13 +70,13 @@ fi # Output unusually long logs due to trace logging. BAZEL_COVERAGE_OPTIONS+=("--experimental_ui_max_stdouterr_bytes=80000000") -BAZEL_OUTPUT_BASE="$(bazel "${BAZEL_STARTUP_OPTIONS[@]}" info "${BAZEL_BUILD_OPTIONS[@]}" output_base)" +BAZEL_OUTPUT_BASE="$(bazel info "${BAZEL_BUILD_OPTIONS[@]}" output_base)" echo "Running bazel coverage with:" echo " Options: ${BAZEL_BUILD_OPTIONS[*]} ${BAZEL_COVERAGE_OPTIONS[*]}" echo " Targets: ${COVERAGE_TARGETS[*]}" -bazel "${BAZEL_STARTUP_OPTIONS[@]}" coverage "${BAZEL_BUILD_OPTIONS[@]}" "${BAZEL_COVERAGE_OPTIONS[@]}" "${COVERAGE_TARGETS[@]}" +bazel coverage "${BAZEL_BUILD_OPTIONS[@]}" "${BAZEL_COVERAGE_OPTIONS[@]}" "${COVERAGE_TARGETS[@]}" echo "Collecting profile and testlogs" if [[ -n "${ENVOY_BUILD_PROFILE}" ]]; then @@ -86,7 +89,7 @@ if [[ -n "${ENVOY_BUILD_DIR}" ]]; then fi find bazel-testlogs/ -name test.log \ | tar cf - -T - \ - | bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ + | bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ - -T0 -o "${ENVOY_BUILD_DIR}/testlogs.tar.zst" echo "Profile/testlogs collected: ${ENVOY_BUILD_DIR}/testlogs.tar.zst" fi @@ -117,7 +120,7 @@ echo "Compressing coveraged data" if [[ "${FUZZ_COVERAGE}" == "true" ]]; then if [[ -n "${ENVOY_FUZZ_COVERAGE_ARTIFACT}" ]]; then tar cf - -C "${COVERAGE_DIR}" --transform 's/^\./fuzz_coverage/' . \ - | bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ + | bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ - -T0 -o "${ENVOY_FUZZ_COVERAGE_ARTIFACT}" fi elif [[ -n "${ENVOY_COVERAGE_ARTIFACT}" ]]; then @@ -126,7 +129,7 @@ elif [[ -n "${ENVOY_COVERAGE_ARTIFACT}" ]]; then fi tar cf - -C "${COVERAGE_DIR}" --transform 's/^\./coverage/' . \ - | bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ + | bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ - -T0 -o "${ENVOY_COVERAGE_ARTIFACT}" fi diff --git a/test/tools/router_check/test/BUILD b/test/tools/router_check/test/BUILD index 80c55f09aaf30..9f2158dd66f03 100644 --- a/test/tools/router_check/test/BUILD +++ b/test/tools/router_check/test/BUILD @@ -11,6 +11,7 @@ envoy_package() envoy_sh_test( name = "router_tool_test", + size = "large", srcs = ["route_tests.sh"], cc_binary = ["//test/tools/router_check:router_check_tool"], data = [ From 8d038c0d51550292df1a04cdb11780d932f19374 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Fri, 18 Aug 2023 09:08:27 -0700 Subject: [PATCH 025/274] bazel: update rules_rust (#29068) Fixes: https://github.com/envoyproxy/envoy/issues/28940 Signed-off-by: Keith Smiley Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index f5ed9e394379b..c2538dd939f0d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1384,12 +1384,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel rust rules", project_desc = "Bazel rust rules (used by Wasm)", project_url = "https://github.com/bazelbuild/rules_rust", - version = "0.26.0", - sha256 = "9d04e658878d23f4b00163a72da3db03ddb451273eb347df7d7c50838d698f49", - urls = ["https://github.com/bazelbuild/rules_rust/releases/download/{version}/rules_rust-v{version}.tar.gz"], + version = "262b6a5ea17ec91b3c07e37e82af0eb15dd6ceef", + strip_prefix = "rules_rust-{version}", + sha256 = "e968b3d01c305282bd237bcdafe5e5141192e93eaefb22712e8bc4299e672b16", + urls = ["https://github.com/bazelbuild/rules_rust/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.wasm.runtime.wasmtime"], - release_date = "2023-07-28", + release_date = "2023-08-11", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_rust/blob/{version}/LICENSE.txt", From 5c9368cf2fc6a4579ed750ff6b81b25680dbef88 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 21 Aug 2023 12:57:55 +0100 Subject: [PATCH 026/274] ci/cache: Ensure failure on cache non-fetch (#29164) Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 2 +- ci/do_ci.sh | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 14406ecf7e96e..d757dbb239782 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -47,7 +47,7 @@ variables: - name: cacheKeyName value: envoy - name: cacheKeyVersion - value: v0 + value: v1 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker diff --git a/ci/do_ci.sh b/ci/do_ci.sh index cce2bff00e7a7..0e5cec26719d0 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -581,9 +581,12 @@ case $CI_TARGET in "${FETCH_TARGETS[@]}" \ && break n=$((n+1)) - if [[ "$n" -ne 10 ]]; then + if [[ "$n" -lt 10 ]]; then sleep 15 echo "Retrying fetch ..." + else + echo "Fetch failed" + exit 1 fi done ;; From 68f0b580f7a1941f51c8b5737ba6daffea41a94d Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 21 Aug 2023 15:27:12 +0100 Subject: [PATCH 027/274] bazel/coverage: Test remotely (#29145) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/checks.yml | 67 +++++++++---------------------- .azure-pipelines/stages.yml | 2 +- .bazelrc | 9 ++++- mobile/.bazelrc | 6 --- 4 files changed, 27 insertions(+), 57 deletions(-) diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index bdc847ab5ea72..c50d5409a1b13 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -42,6 +42,10 @@ jobs: maxParallel: ${{ parameters.concurrencyChecks }} matrix: # These are ordered by most time-consuming first. + coverage: + CI_TARGET: "bazel.coverage" + fuzz_coverage: + CI_TARGET: "bazel.fuzz_coverage" compile_time_options: CI_TARGET: "bazel.compile_time_options" ENVOY_FILTER_EXAMPLE: true @@ -84,7 +88,6 @@ jobs: publishTestResults: variables.PUBLISH_TEST_RESULTS publishEnvoy: variables.PUBLISH_ENVOY stepsPost: - # TODO(phlax): consolidate "fix" paths/jobs - task: PublishBuildArtifacts@1 inputs: @@ -93,54 +96,23 @@ jobs: timeoutInMinutes: 10 condition: and(failed(), eq(variables['CI_TARGET'], 'bazel.clang_tidy')) -- job: coverage - displayName: "Linux x64" - dependsOn: [] - condition: | - and(not(canceled()), - eq(${{ parameters.runChecks }}, 'true')) - # TODO(phlax): Make this configurable and ratchet down! - timeoutInMinutes: 240 - pool: "envoy-x64-large" - variables: - BAZEL_STARTUP_EXTRA: '' - strategy: - maxParallel: 2 - matrix: - coverage: - CI_TARGET: "coverage" - BAZEL_STARTUP_EXTRA: --host_jvm_args=-Xmx20g --host_jvm_args=-XX:MaxDirectMemorySize=4G - fuzz_coverage: - CI_TARGET: "fuzz_coverage" - steps: - - template: ../bazel.yml - parameters: - managedAgent: false - ciTarget: bazel.$(CI_TARGET) - rbe: true - # /tmp/sandbox_base is a tmpfs in CI environment to optimize large I/O for coverage traces - bazelConfigRBE: --config=ci --config=rbe-google --jobs=$(RbeJobs) - bazelBuildExtraOptions: --sandbox_base=/tmp/sandbox_base --define=no_debug_info=1 --linkopt=-Wl,-s --test_env=ENVOY_IP_TEST_VERSIONS=v4only - bazelStartupExtraOptions: $(BAZEL_STARTUP_EXTRA) - bazelUseBES: false - cacheTestResults: ${{ parameters.cacheTestResults }} - - script: ci/run_envoy_docker.sh 'ci/do_ci.sh $(CI_TARGET)-upload' - displayName: "Upload $(CI_TARGET) Report to GCS" - env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} - GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci" - BAZEL_REMOTE_CACHE: $(LocalBuildCache) - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(Build.SourceBranchName)" - condition: not(canceled()) + - script: ci/run_envoy_docker.sh 'ci/do_ci.sh $(CI_TARGET)-upload' + displayName: "Upload $(CI_TARGET) Report to GCS" + condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'bazel.coverage'), eq(variables['CI_TARGET'], 'bazel.fuzz_coverage'))) + env: + ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) + ENVOY_RBE: "1" + BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci --config=rbe-google --jobs=$(RbeJobs)" + GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} + GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} + ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + BAZEL_REMOTE_INSTANCE_BRANCH: "$(System.PullRequest.TargetBranch)" + ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + BAZEL_REMOTE_INSTANCE_BRANCH: "$(Build.SourceBranchName)" - job: complete displayName: "Checks complete" - dependsOn: ["bazel", "coverage"] + dependsOn: ["bazel"] pool: x64-nano # This condition ensures that this (required) check passes if all of # the preceding checks either pass or are skipped @@ -149,8 +121,7 @@ jobs: condition: | and( eq(variables['Build.Reason'], 'PullRequest'), - in(dependencies.bazel.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), - in(dependencies.coverage.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')) + in(dependencies.bazel.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')) steps: - checkout: none - bash: | diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index f5ae006eb6825..108bf18952eee 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -29,7 +29,7 @@ parameters: - name: concurrencyChecks displayName: "Check concurrency" type: number - default: 3 + default: 5 - name: concurrencyPrechecks displayName: "Prechecks concurrency" type: number diff --git a/.bazelrc b/.bazelrc index 9d76cc8d398e4..efeb63ccb4539 100644 --- a/.bazelrc +++ b/.bazelrc @@ -198,14 +198,19 @@ build:coverage --define=ENVOY_CONFIG_COVERAGE=1 build:coverage --cxxopt="-DENVOY_CONFIG_COVERAGE=1" build:coverage --test_env=HEAPCHECK= build:coverage --combined_report=lcov -build:coverage --strategy=TestRunner=sandboxed,local +build:coverage --strategy=TestRunner=remote,sandboxed,local build:coverage --strategy=CoverageReport=sandboxed,local build:coverage --experimental_use_llvm_covmap build:coverage --experimental_generate_llvm_lcov +build:coverage --experimental_split_coverage_postprocessing +build:coverage --experimental_fetch_all_coverage_outputs build:coverage --collect_code_coverage build:coverage --instrumentation_filter="^//source(?!/common/quic/platform)[/:],^//envoy[/:],^//contrib(?!/.*/test)[/:]" -build:coverage --remote_download_toplevel +build:coverage --remote_download_minimal build:coverage --define=tcmalloc=gperftools +build:coverage --define=no_debug_info=1 +build:coverage --linkopt=-Wl,-s +build:coverage --test_env=ENVOY_IP_TEST_VERSIONS=v4only build:test-coverage --test_arg="-l trace" build:test-coverage --test_arg="--log-path /dev/null" diff --git a/mobile/.bazelrc b/mobile/.bazelrc index a5c07f3c00a0a..553615af22634 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -213,13 +213,7 @@ build:mobile-ci-linux-coverage --test_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 # TODO(lfpino): Reference upstream Bazel issue here on the incompatibility of remote test execution and LLVM coverage. build:mobile-remote-ci-linux-coverage --config=mobile-ci-linux-coverage build:mobile-remote-ci-linux-coverage --config=mobile-remote-ci-linux-clang -build:mobile-remote-ci-linux-coverage --strategy=TestRunner=local,remote -build:mobile-remote-ci-linux-coverage --strategy=CoverageReport=local,remote -# Bazel remote caching is incompatible with C++ LLVM coverage so we need to deactivate it for coverage builds -# TODO(lfpino): Reference upstream Bazel issue here on the incompatibility of remote caching and LLVM coverage. -build:mobile-remote-ci-linux-coverage --noremote_accept_cached build:mobile-remote-ci-linux-coverage --legacy_important_outputs=false -build:mobile-remote-ci-linux-coverage --nocache_test_results build:mobile-remote-ci-linux-coverage --config=ci build:mobile-remote-ci-linux-coverage --config=remote From a06a222340e4753a9ecff0027fde6a419c55b143 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 21 Aug 2023 16:57:03 +0100 Subject: [PATCH 028/274] coverage/ci: Enable/fix BEP/BES (#29134) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 2 +- .azure-pipelines/stage/checks.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index 2b9ec67affc48..c204ec5076fcf 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -186,7 +186,7 @@ steps: condition: ${{ parameters.managedAgent }} - script: | - if [[ "${{ parameters.bazelUseBES }}" != 'true' ]]; then + if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then unset GOOGLE_BES_PROJECT_ID fi ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index c50d5409a1b13..7e8d4ec8d3002 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -95,7 +95,6 @@ jobs: artifactName: "$(CI_TARGET).fixes" timeoutInMinutes: 10 condition: and(failed(), eq(variables['CI_TARGET'], 'bazel.clang_tidy')) - - script: ci/run_envoy_docker.sh 'ci/do_ci.sh $(CI_TARGET)-upload' displayName: "Upload $(CI_TARGET) Report to GCS" condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'bazel.coverage'), eq(variables['CI_TARGET'], 'bazel.fuzz_coverage'))) From 8a56d95e043e0d3fc787d2742baed4475923ce88 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 22 Aug 2023 16:04:05 +0100 Subject: [PATCH 029/274] ci/checks: Use self-hosted x64 small VMs (#29177) Signed-off-by: Ryan Northey --- .azure-pipelines/docker/load_caches.sh | 127 +++++++++++++------------ .azure-pipelines/stage/checks.yml | 5 +- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/.azure-pipelines/docker/load_caches.sh b/.azure-pipelines/docker/load_caches.sh index 5170249f2f6d4..56e4089fb9448 100755 --- a/.azure-pipelines/docker/load_caches.sh +++ b/.azure-pipelines/docker/load_caches.sh @@ -17,67 +17,76 @@ DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" BAZEL_CACHE_TARBALL="${BAZEL_CACHE_PATH}/bazel.tar.zst" -echo "Stopping Docker daemon ..." -systemctl stop docker docker.socket -mv /var/lib/docker/ /var/lib/docker.old -mkdir -p /var/lib/docker +remount_docker () { + echo "Stopping Docker daemon ..." + systemctl stop docker docker.socket + mv /var/lib/docker/ /var/lib/docker.old + mkdir -p /var/lib/docker + if id -u vsts &> /dev/null && [[ -n "$DOCKER_BIND_PATH" ]]; then + # use separate disk on windows hosted + echo "Binding docker directory ${DOCKER_BIND_PATH} -> /var/lib/docker ..." + mkdir -p "$DOCKER_BIND_PATH" + mount -o bind "$DOCKER_BIND_PATH" /var/lib/docker + elif ! id -u vsts &> /dev/null && [[ -z "$DOCKER_NO_TMPFS" ]]; then + echo "Mounting tmpfs directory -> /var/lib/docker ..." + # Use a ramdisk to load docker (avoids Docker slow start on big disk) + mount -t tmpfs none /var/lib/docker + else + # If we are on a managed/resource-constrained host but the bind path is not set then we need to remove + # the old /var/lib/docker to free some space (maybe) + DOCKER_REMOVE_EXISTING=1 + fi +} + +extract_docker () { + if [[ -e "${DOCKER_CACHE_TARBALL}" ]]; then + echo "Extracting docker cache ${DOCKER_CACHE_TARBALL} -> /var/lib/docker ..." + ls -alh "$DOCKER_CACHE_TARBALL" + zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C /var/lib/docker + touch /tmp/DOCKER_CACHE_RESTORED + else + echo "No Docker cache to restore, starting Docker with no data" + fi +} + +extract_bazel () { + if [[ -e "${BAZEL_CACHE_TARBALL}" ]]; then + echo "Extracting bazel cache ${BAZEL_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR} ..." + zstd --stdout -d "$BAZEL_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}" + else + echo "No bazel cache to restore, starting bazel with no data" + fi +} + +cleanup_cache () { + if mountpoint -q "${CACHE_PATH}"; then + echo "Unmount cache tmp ${CACHE_PATH} ..." + umount "${CACHE_PATH}" + else + echo "Remove cache tmp ${CACHE_PATH} ..." + rm -rf "${CACHE_PATH}" + fi + + # this takes time but may be desirable in some situations + if [[ -n "$DOCKER_REMOVE_EXISTING" ]]; then + rm -rf /var/lib/docker.old + fi +} + +restart_docker () { + echo "Starting Docker daemon ..." + systemctl start docker + docker images + mkdir -p "${ENVOY_DOCKER_BUILD_DIR}" +} -if id -u vsts &> /dev/null && [[ -n "$DOCKER_BIND_PATH" ]]; then - # use separate disk on windows hosted - echo "Binding docker directory ${DOCKER_BIND_PATH} -> /var/lib/docker ..." - mkdir -p "$DOCKER_BIND_PATH" - mount -o bind "$DOCKER_BIND_PATH" /var/lib/docker -elif ! id -u vsts &> /dev/null && [[ -z "$DOCKER_NO_TMPFS" ]]; then - echo "Mounting tmpfs directory -> /var/lib/docker ..." - # Use a ramdisk to load docker (avoids Docker slow start on big disk) - mount -t tmpfs none /var/lib/docker -else - # If we are on a managed/resource-constrained host but the bind path is not set then we need to remove - # the old /var/lib/docker to free some space (maybe) - DOCKER_REMOVE_EXISTING=1 -fi - -if [[ -e "${DOCKER_CACHE_TARBALL}" ]]; then - echo "Extracting docker cache ${DOCKER_CACHE_TARBALL} -> /var/lib/docker ..." - zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C /var/lib/docker - touch /tmp/DOCKER_CACHE_RESTORED -else - echo "No Docker cache to restore, starting Docker with no data" -fi - -echo "Starting Docker daemon ..." -systemctl start docker - -if mountpoint -q "${DOCKER_CACHE_PATH}"; then - echo "Unmount cache tmp ${DOCKER_CACHE_PATH} ..." - umount "${DOCKER_CACHE_PATH}" -else - echo "Remove cache tmp ${DOCKER_CACHE_PATH} ..." - rm -rf "${DOCKER_CACHE_PATH}" -fi -docker images - -mkdir -p "${ENVOY_DOCKER_BUILD_DIR}" - -if [[ -e "${BAZEL_CACHE_TARBALL}" ]]; then - echo "Extracting bazel cache ${BAZEL_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR} ..." - zstd --stdout -d "$BAZEL_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}" -else - echo "No bazel cache to restore, starting bazel with no data" -fi - -if mountpoint -q "${CACHE_PATH}"; then - echo "Unmount cache tmp ${CACHE_PATH} ..." - umount "${CACHE_PATH}" -else - echo "Remove cache tmp ${CACHE_PATH} ..." - rm -rf "${CACHE_PATH}" -fi +df -h -# this takes time but may be desirable in some situations -if [[ -n "$DOCKER_REMOVE_EXISTING" ]]; then - rm -rf /var/lib/docker.old -fi +remount_docker +extract_bazel +extract_docker +restart_docker +cleanup_cache df -h diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index 7e8d4ec8d3002..dbc4714739d85 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -75,18 +75,19 @@ jobs: api: CI_TARGET: "bazel.api" timeoutInMinutes: 180 - pool: - vmImage: $(agentUbuntu) + pool: envoy-x64-small steps: - template: ../bazel.yml parameters: ciTarget: $(CI_TARGET) envoyBuildFilterExample: $(ENVOY_FILTER_EXAMPLE) cacheTestResults: ${{ parameters.cacheTestResults }} + managedAgent: false repoFetchDepth: $(REPO_FETCH_DEPTH) repoFetchTags: $(REPO_FETCH_TAGS) publishTestResults: variables.PUBLISH_TEST_RESULTS publishEnvoy: variables.PUBLISH_ENVOY + tmpfsDockerDisabled: true stepsPost: # TODO(phlax): consolidate "fix" paths/jobs - task: PublishBuildArtifacts@1 From c228b2225bc757fe27e6cee6f2c225148dba78dc Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 22 Aug 2023 17:58:31 +0100 Subject: [PATCH 030/274] ci/verify: Use bazel.yml (#29189) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/verify.yml | 38 ++++++++++++------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index eeffccda92eb3..83cc680a60506 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -22,16 +22,11 @@ jobs: itemPattern: "bazel.distribution/x64/packages.x64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - - script: ci/run_envoy_docker.sh 'ci/do_ci.sh verify_distro' - workingDirectory: $(Build.SourcesDirectory) - env: - AZP_BRANCH: $(Build.SourceBranch) - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_DOCKER_IN_DOCKER: 1 - ENVOY_RBE: 1 - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} - displayName: "Verify packages" + - template: ../bazel.yml + parameters: + ciTarget: verify_distro + env: + ENVOY_DOCKER_IN_DOCKER: 1 - job: packages_arm64 displayName: Debs (arm64) @@ -46,20 +41,15 @@ jobs: itemPattern: "bazel.distribution/arm64/packages.arm64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - - script: ci/run_envoy_docker.sh 'ci/do_ci.sh verify_distro' - workingDirectory: $(Build.SourcesDirectory) - env: - AZP_BRANCH: $(Build.SourceBranch) - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_DOCKER_IN_DOCKER: 1 - BAZEL_REMOTE_CACHE: $(LocalBuildCache) - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" - displayName: "Verify packages" + - template: ../bazel.yml + parameters: + managedAgent: false + ciTarget: verify_distro + rbe: false + artifactSuffix: ".arm64" + tmpfsDockerDisabled: true + env: + ENVOY_DOCKER_IN_DOCKER: 1 - job: verified displayName: Verification complete From 51976f5208843d5138a9d6f7ca0a9f9454eb1fd7 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 22 Aug 2023 18:44:41 +0100 Subject: [PATCH 031/274] ci/publish: Fix for cache load (#29192) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 7827eae79ff2a..702094bc95b6a 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -175,7 +175,6 @@ jobs: - template: ../bazel.yml parameters: ciTarget: bazel.distribution - tmpfsCacheDisabled: true stepsPre: - template: ../gpg.yml parameters: From de00b1d5a1171af21020a9c9201f6257a9200206 Mon Sep 17 00:00:00 2001 From: Xie Zhihao Date: Tue, 22 Aug 2023 04:03:47 +0800 Subject: [PATCH 032/274] tls: fix handshake failure when both private key provider and cert validation are set (#29002) Signed-off-by: Xie Zhihao zhihao.xie@intel.com Commit Message: tls: fix handshake failure when both private key provider and cert validation are set Additional Description: After #24297, when both private key provider and validation are set, e.g., using Envoy as sidecars, handshake may end with SSL internal error and HTTP 503 will return. When both private key provider and validation are set, when the private key provider completed its calculation and resumed the handshake (ssl_do_handshake), an async cert validation will be triggered. If the custom validation is not provided, then the async cert validation will complete in the sync way and resume the handshake again (ssl_do_handshake), which cause the error. Risk Level: Low Testing: Unit tests Docs Changes: N/A Release Notes: Added Platform Specific Features: N/A Fixes commit 55a4cc7 Signed-off-by: Xie Zhihao --- changelogs/current.yaml | 3 ++ envoy/ssl/ssl_socket_extended_info.h | 4 +- .../transport_sockets/tls/context_impl.cc | 2 +- .../transport_sockets/tls/ssl_handshaker.cc | 9 +++-- .../transport_sockets/tls/ssl_handshaker.h | 2 +- .../tls/cert_validator/test_common.h | 2 +- .../transport_sockets/tls/ssl_socket_test.cc | 40 +++++++++++++++++++ 7 files changed, 53 insertions(+), 9 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 3ce065d85d446..32fc3bc047fd2 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -11,6 +11,9 @@ bug_fixes: - area: connection limit change: | fixed a use-after-free bug in the connection limit filter. +- area: tls + change: | + fixed a bug where handshake may fail when both private key provider and cert validation are set. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/envoy/ssl/ssl_socket_extended_info.h b/envoy/ssl/ssl_socket_extended_info.h index 478fd07791f93..b26bc96ce851d 100644 --- a/envoy/ssl/ssl_socket_extended_info.h +++ b/envoy/ssl/ssl_socket_extended_info.h @@ -60,7 +60,6 @@ class SslExtendedSocketInfo { virtual ClientValidationStatus certificateValidationStatus() const PURE; /** - * Only called when doing asynchronous cert validation. * @return ValidateResultCallbackPtr a callback used to return the validation result. */ virtual ValidateResultCallbackPtr createValidateResultCallback() PURE; @@ -68,8 +67,9 @@ class SslExtendedSocketInfo { /** * Called after the cert validation completes either synchronously or asynchronously. * @param succeeded true if the validation succeeded. + * @param async true if the validation is completed asynchronously. */ - virtual void onCertificateValidationCompleted(bool succeeded) PURE; + virtual void onCertificateValidationCompleted(bool succeeded, bool async) PURE; /** * @return ValidateStatus the validation status. diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 14e93fc73985e..f3132d2ffb9bd 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -520,7 +520,7 @@ ValidationResults ContextImpl::customVerifyCertChain( if (result.status != ValidationResults::ValidationStatus::Pending) { extended_socket_info->setCertificateValidationStatus(result.detailed_status); extended_socket_info->onCertificateValidationCompleted( - result.status == ValidationResults::ValidationStatus::Successful); + result.status == ValidationResults::ValidationStatus::Successful, false); } return result; } diff --git a/source/extensions/transport_sockets/tls/ssl_handshaker.cc b/source/extensions/transport_sockets/tls/ssl_handshaker.cc index c594ced08aab0..40d889de23b48 100644 --- a/source/extensions/transport_sockets/tls/ssl_handshaker.cc +++ b/source/extensions/transport_sockets/tls/ssl_handshaker.cc @@ -26,7 +26,7 @@ void ValidateResultCallbackImpl::onCertValidationResult(bool succeeded, } extended_socket_info_->setCertificateValidationStatus(detailed_status); extended_socket_info_->setCertificateValidationAlert(tls_alert); - extended_socket_info_->onCertificateValidationCompleted(succeeded); + extended_socket_info_->onCertificateValidationCompleted(succeeded, true); } SslExtendedSocketInfoImpl::~SslExtendedSocketInfoImpl() { @@ -44,14 +44,15 @@ Envoy::Ssl::ClientValidationStatus SslExtendedSocketInfoImpl::certificateValidat return certificate_validation_status_; } -void SslExtendedSocketInfoImpl::onCertificateValidationCompleted(bool succeeded) { +void SslExtendedSocketInfoImpl::onCertificateValidationCompleted(bool succeeded, bool async) { cert_validation_result_ = succeeded ? Ssl::ValidateStatus::Successful : Ssl::ValidateStatus::Failed; if (cert_validate_result_callback_.has_value()) { - // This is an async cert validation. cert_validate_result_callback_.reset(); // Resume handshake. - ssl_handshaker_.handshakeCallbacks()->onAsynchronousCertValidationComplete(); + if (async) { + ssl_handshaker_.handshakeCallbacks()->onAsynchronousCertValidationComplete(); + } } } diff --git a/source/extensions/transport_sockets/tls/ssl_handshaker.h b/source/extensions/transport_sockets/tls/ssl_handshaker.h index 56b05ed94d1ef..2499dd186880c 100644 --- a/source/extensions/transport_sockets/tls/ssl_handshaker.h +++ b/source/extensions/transport_sockets/tls/ssl_handshaker.h @@ -59,7 +59,7 @@ class SslExtendedSocketInfoImpl : public Envoy::Ssl::SslExtendedSocketInfo { void setCertificateValidationStatus(Envoy::Ssl::ClientValidationStatus validated) override; Envoy::Ssl::ClientValidationStatus certificateValidationStatus() const override; Ssl::ValidateResultCallbackPtr createValidateResultCallback() override; - void onCertificateValidationCompleted(bool succeeded) override; + void onCertificateValidationCompleted(bool succeeded, bool async) override; Ssl::ValidateStatus certificateValidationResult() const override { return cert_validation_result_; } diff --git a/test/extensions/transport_sockets/tls/cert_validator/test_common.h b/test/extensions/transport_sockets/tls/cert_validator/test_common.h index 230e3e913e2a7..b08c94b65237b 100644 --- a/test/extensions/transport_sockets/tls/cert_validator/test_common.h +++ b/test/extensions/transport_sockets/tls/cert_validator/test_common.h @@ -26,7 +26,7 @@ class TestSslExtendedSocketInfo : public Envoy::Ssl::SslExtendedSocketInfo { Ssl::ValidateResultCallbackPtr createValidateResultCallback() override { return nullptr; }; - void onCertificateValidationCompleted(bool succeeded) override { + void onCertificateValidationCompleted(bool succeeded, bool) override { validate_result_ = succeeded ? Ssl::ValidateStatus::Successful : Ssl::ValidateStatus::Failed; } Ssl::ValidateStatus certificateValidationResult() const override { return validate_result_; } diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 825bdb203d158..0755b4f0f2ed5 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -6733,6 +6733,46 @@ TEST_P(SslSocketTest, RsaAndEcdsaPrivateKeyProviderMultiCertFail) { .setExpectedServerStats("ssl.connection_error")); } +// Test private key provider and cert validation can work together. +TEST_P(SslSocketTest, PrivateKeyProviderWithCertValidation) { + const std::string client_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" +)EOF"; + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" + private_key_provider: + provider_name: test + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_key.pem" + expected_operation: sign + sync_mode: false + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" +)EOF"; + + TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, version_); + testUtil(test_options.setPrivateKeyMethodExpected(true) + .setExpectedSha256Digest(TEST_NO_SAN_CERT_256_HASH) + .setExpectedSha1Digest(TEST_NO_SAN_CERT_1_HASH) + .setExpectedSerialNumber(TEST_NO_SAN_CERT_SERIAL)); +} + TEST_P(SslSocketTest, TestStaplesOcspResponseSuccess) { const std::string server_ctx_yaml = R"EOF( common_tls_context: From 47297e26f07520d39272e5925ac1fee05f50ced3 Mon Sep 17 00:00:00 2001 From: Tianyu <72890320+tyxia@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:29:34 -0400 Subject: [PATCH 033/274] tls: Remove additional tracking on SSL_ERROR_SYSCALL Backport of #29263 Fixes #28415 This reverts part of #24923 Signed-off-by: tyxia Signed-off-by: Greg Greenway --- envoy/ssl/handshaker.h | 2 +- .../transport_sockets/tls/ssl_handshaker.cc | 6 ------ .../transport_sockets/tls/ssl_socket.cc | 16 ++------------ .../transport_sockets/tls/ssl_socket.h | 4 ++-- .../transport_sockets/tls/handshaker_test.cc | 2 +- .../transport_sockets/tls/ssl_socket_test.cc | 21 ++++++++++++------- 6 files changed, 20 insertions(+), 31 deletions(-) diff --git a/envoy/ssl/handshaker.h b/envoy/ssl/handshaker.h index ec897d9420357..534ca4e2a1f94 100644 --- a/envoy/ssl/handshaker.h +++ b/envoy/ssl/handshaker.h @@ -31,7 +31,7 @@ class HandshakeCallbacks { /** * A callback which will be executed at most once upon handshake failure. */ - virtual void onFailure(bool syscall_error_occurred = false) PURE; + virtual void onFailure() PURE; /** * Returns a pointer to the transportSocketCallbacks struct, or nullptr if diff --git a/source/extensions/transport_sockets/tls/ssl_handshaker.cc b/source/extensions/transport_sockets/tls/ssl_handshaker.cc index 40d889de23b48..f94c152c2be21 100644 --- a/source/extensions/transport_sockets/tls/ssl_handshaker.cc +++ b/source/extensions/transport_sockets/tls/ssl_handshaker.cc @@ -99,12 +99,6 @@ Network::PostIoAction SslHandshakerImpl::doHandshake() { case SSL_ERROR_WANT_CERTIFICATE_VERIFY: state_ = Ssl::SocketState::HandshakeInProgress; return PostIoAction::KeepOpen; - case SSL_ERROR_SYSCALL: - // By default, when SSL_ERROR_SYSCALL occurred, the underlying transport does not participate - // in the error queue. Therefore, setting `syscall_error_occurred` to true to report the error - // in `drainErrorQueue`. - handshake_callbacks_->onFailure(/*syscall_error_occurred=*/true); - return PostIoAction::Close; default: handshake_callbacks_->onFailure(); return PostIoAction::Close; diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index 5bda4debdef5c..3a982d5a23a75 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -197,11 +197,11 @@ void SslSocket::onSuccess(SSL* ssl) { callbacks_->raiseEvent(Network::ConnectionEvent::Connected); } -void SslSocket::onFailure(bool syscall_error_occurred) { drainErrorQueue(syscall_error_occurred); } +void SslSocket::onFailure() { drainErrorQueue(); } PostIoAction SslSocket::doHandshake() { return info_->doHandshake(); } -void SslSocket::drainErrorQueue(bool syscall_error_occurred) { +void SslSocket::drainErrorQueue() { bool saw_error = false; bool saw_counted_error = false; while (uint64_t err = ERR_get_error()) { @@ -229,18 +229,6 @@ void SslSocket::drainErrorQueue(bool syscall_error_occurred) { absl::NullSafeStringView(ERR_reason_error_string(err)))); } - if (syscall_error_occurred) { - if (failure_reason_.empty()) { - failure_reason_ = "TLS error:"; - } - failure_reason_.append( - "SSL_ERROR_SYSCALL error has occured, which indicates the operation failed externally to " - "the library. This is typically |errno| but may be something custom if using a custom " - "|BIO|. It may also be signaled if the transport returned EOF, in which case the " - "operation's return value will be zero."); - saw_error = true; - } - if (!failure_reason_.empty()) { ENVOY_CONN_LOG(debug, "remote address:{},{}", callbacks_->connection(), callbacks_->connection().connectionInfoProvider().remoteAddress()->asString(), diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 1433218bbf7bd..ea9213ffe6d17 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -69,7 +69,7 @@ class SslSocket : public Network::TransportSocket, // Ssl::HandshakeCallbacks Network::Connection& connection() const override; void onSuccess(SSL* ssl) override; - void onFailure(bool syscall_error_occurred = false) override; + void onFailure() override; Network::TransportSocketCallbacks* transportSocketCallbacks() override { return callbacks_; } void onAsynchronousCertValidationComplete() override; @@ -86,7 +86,7 @@ class SslSocket : public Network::TransportSocket, ReadResult sslReadIntoSlice(Buffer::RawSlice& slice); Network::PostIoAction doHandshake(); - void drainErrorQueue(bool syscall_error_occurred = false); + void drainErrorQueue(); void shutdownSsl(); void shutdownBasic(); void resumeHandshake(); diff --git a/test/extensions/transport_sockets/tls/handshaker_test.cc b/test/extensions/transport_sockets/tls/handshaker_test.cc index 883da3892ff64..e29304bce10a6 100644 --- a/test/extensions/transport_sockets/tls/handshaker_test.cc +++ b/test/extensions/transport_sockets/tls/handshaker_test.cc @@ -43,7 +43,7 @@ class MockHandshakeCallbacks : public Ssl::HandshakeCallbacks { ~MockHandshakeCallbacks() override = default; MOCK_METHOD(Network::Connection&, connection, (), (const, override)); MOCK_METHOD(void, onSuccess, (SSL*), (override)); - MOCK_METHOD(void, onFailure, (bool syscall_error_occurred), (override)); + MOCK_METHOD(void, onFailure, (), (override)); MOCK_METHOD(Network::TransportSocketCallbacks*, transportSocketCallbacks, (), (override)); MOCK_METHOD(void, onAsynchronousCertValidationComplete, (), (override)); }; diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 0755b4f0f2ed5..8f59823d3c5f1 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -573,8 +573,10 @@ class TestUtilOptionsV2 : public TestUtilOptionsBase { TestUtilOptionsV2( const envoy::config::listener::v3::Listener& listener, const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext& client_ctx_proto, - bool expect_success, Network::Address::IpVersion version) - : TestUtilOptionsBase(expect_success, version), listener_(listener), + bool expect_success, Network::Address::IpVersion version, + bool skip_server_failure_reason_check = false) + : TestUtilOptionsBase(expect_success, version), + skip_server_failure_reason_check_(skip_server_failure_reason_check), listener_(listener), client_ctx_proto_(client_ctx_proto), transport_socket_options_(nullptr) { if (expect_success) { setExpectedServerStats("ssl.handshake").setExpectedClientStats("ssl.handshake"); @@ -584,6 +586,7 @@ class TestUtilOptionsV2 : public TestUtilOptionsBase { } } + bool skipServerFailureReasonCheck() const { return skip_server_failure_reason_check_; } const envoy::config::listener::v3::Listener& listener() const { return listener_; } const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext& clientCtxProto() const { return client_ctx_proto_; @@ -675,6 +678,7 @@ class TestUtilOptionsV2 : public TestUtilOptionsBase { } private: + bool skip_server_failure_reason_check_; const envoy::config::listener::v3::Listener& listener_; const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext& client_ctx_proto_; std::string expected_client_stats_; @@ -879,7 +883,9 @@ void testUtilV2(const TestUtilOptionsV2& options) { } else { EXPECT_THAT(std::string(client_connection->transportFailureReason()), ContainsRegex(options.expectedTransportFailureReasonContains())); - EXPECT_NE("", server_connection->transportFailureReason()); + if (!options.skipServerFailureReasonCheck()) { + EXPECT_NE("", server_connection->transportFailureReason()); + } } } @@ -7187,11 +7193,12 @@ TEST_P(SslSocketTest, RsaKeyUsageVerificationEnforcementOn) { // Enable the rsa_key_usage enforcement. client_tls_context.mutable_enforce_rsa_key_usage()->set_value(true); - TestUtilOptionsV2 test_options(listener, client_tls_context, /*expect_success=*/false, version_); - // Client connection is failed with key_usage_mismatch, which is expected. + TestUtilOptionsV2 test_options(listener, client_tls_context, /*expect_success=*/false, version_, + /*skip_server_failure_reason_check=*/true); + // Client connection is failed with key_usage_mismatch. test_options.setExpectedTransportFailureReasonContains("KEY_USAGE_BIT_INCORRECT"); - // Server connection failed with connection error. - test_options.setExpectedServerStats("ssl.connection_error"); + // Server connection error was not populated in this case. + test_options.setExpectedServerStats(""); testUtilV2(test_options); } From e835178180fb13893d0253ceb722ba1c9c547508 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 24 Aug 2023 16:40:05 +0100 Subject: [PATCH 034/274] ci/x64: Use small VM for x64 build (#29190) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/linux.yml | 18 +++++++++++++++--- .azure-pipelines/stages.yml | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index 80bfe1a0f549e..d59b709de82f4 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -6,7 +6,7 @@ parameters: - name: pool displayName: "Agent pool" type: string - default: envoy-x64-large + default: envoy-x64-small - name: artifactSuffix displayName: "Artifact suffix" type: string @@ -19,6 +19,17 @@ parameters: displayName: "Build timeout" type: number default: 120 +- name: bazelBuildExtraOptions + type: string + default: "" + +- name: managedAgent + type: boolean + default: false +- name: tmpfsDockerDisabled + type: string + default: '' + - name: runBuild displayName: "Run build" @@ -36,13 +47,14 @@ jobs: steps: - template: ../bazel.yml parameters: - managedAgent: false + managedAgent: ${{ parameters.managedAgent }} ciTarget: bazel.release - bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" + bazelBuildExtraOptions: ${{ parameters.bazelBuildExtraOptions }} cacheTestResults: ${{ parameters.cacheTestResults }} cacheVersion: $(cacheKeyBazel) artifactSuffix: ${{ parameters.artifactSuffix }} rbe: ${{ parameters.rbe }} + tmpfsDockerDisabled: ${{ parameters.tmpfsDockerDisabled }} - job: released displayName: Complete diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 108bf18952eee..03f3458da412a 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -82,6 +82,7 @@ stages: parameters: cacheTestResults: ${{ parameters.cacheTestResults }} runBuild: variables['RUN_BUILD'] + tmpfsDockerDisabled: true - stage: linux_arm64 displayName: Linux arm64 @@ -97,6 +98,7 @@ stages: timeoutBuild: 180 pool: envoy-arm-large runBuild: variables['RUN_BUILD'] + bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" - stage: check displayName: Checks (Linux x64) From 9bca18793a049db677e23f65987faef11854b47a Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 28 Aug 2023 22:50:12 +0100 Subject: [PATCH 035/274] mac/ci: Dont reinstall bazelisk (#29307) Signed-off-by: Ryan Northey --- ci/mac_ci_setup.sh | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ci/mac_ci_setup.sh b/ci/mac_ci_setup.sh index 2d9ad14caa702..ee2e3f029522d 100755 --- a/ci/mac_ci_setup.sh +++ b/ci/mac_ci_setup.sh @@ -53,13 +53,4 @@ do is_installed "${DEP}" || install "${DEP}" done -# Required as bazel and a foreign bazelisk are installed in the latest macos vm image, we have -# to unlink/overwrite them to install bazelisk -echo "Installing bazelisk" -brew reinstall --force bazelisk -if ! brew link --overwrite bazelisk; then - echo "Failed to install and link bazelisk" - exit 1 -fi - bazel version From 82f2119ee7a71e236de17532e503aea70becce94 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 29 Aug 2023 02:29:29 +0100 Subject: [PATCH 036/274] azp/ci: Use ms-hosted agents for confirmation (#29248) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/checks.yml | 3 ++- .azure-pipelines/stage/linux.yml | 3 ++- .azure-pipelines/stage/macos.yml | 3 ++- .azure-pipelines/stage/prechecks.yml | 3 ++- .azure-pipelines/stage/publish.yml | 3 ++- .azure-pipelines/stage/verify.yml | 3 ++- .azure-pipelines/stage/windows.yml | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index dbc4714739d85..09bec400b6ee3 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -113,7 +113,8 @@ jobs: - job: complete displayName: "Checks complete" dependsOn: ["bazel"] - pool: x64-nano + pool: + vmImage: $(agentUbuntu) # This condition ensures that this (required) check passes if all of # the preceding checks either pass or are skipped # adapted from: diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index d59b709de82f4..d35f67094b5f1 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -59,7 +59,8 @@ jobs: - job: released displayName: Complete dependsOn: ["release"] - pool: x64-nano + pool: + vmImage: $(agentUbuntu) # This condition ensures that this (required) job passes if all of # the preceeding jobs either pass or are skipped # adapted from: diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml index d049e140eacd3..6089bd89ee896 100644 --- a/.azure-pipelines/stage/macos.yml +++ b/.azure-pipelines/stage/macos.yml @@ -43,7 +43,8 @@ jobs: - job: tested displayName: Complete dependsOn: ["test"] - pool: x64-nano + pool: + vmImage: $(agentUbuntu) # This condition ensures that this (required) job passes if all of # the preceeding jobs either pass or are skipped # adapted from: diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 8928038419256..6df51ab8d569c 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -152,7 +152,8 @@ jobs: - job: prechecked displayName: Prechecked dependsOn: ["prechecks"] - pool: x64-nano + pool: + vmImage: $(agentUbuntu) # This condition ensures that this (required) job passes if all of # the preceeding jobs either pass or are skipped # adapted from: diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 702094bc95b6a..cc02f5a0e43a0 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -338,7 +338,8 @@ jobs: - job: success dependsOn: ["docker", "docs", "signed_release"] displayName: Success (linux artefacts) - pool: x64-nano + pool: + vmImage: $(agentUbuntu) # This condition ensures that this (required) check passes if all of # the preceding checks either pass or are skipped # adapted from: diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index 83cc680a60506..3aa57a8b55620 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -54,7 +54,8 @@ jobs: - job: verified displayName: Verification complete dependsOn: ["packages_x64", "packages_arm64"] - pool: x64-nano + pool: + vmImage: $(agentUbuntu) # This condition ensures that this (required) check passes if all of # the preceding checks either pass or are skipped # adapted from: diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index 83ab4774765eb..d2884938ac9c2 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -101,7 +101,8 @@ jobs: - job: released displayName: Complete dependsOn: ["release", "docker"] - pool: x64-nano + pool: + vmImage: $(agentUbuntu) # This condition ensures that this (required) job passes if all of # the preceeding jobs either pass or are skipped # adapted from: From 31e02371a41a90ccca0dcb08897e25ea68eed9f6 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 29 Aug 2023 10:12:56 +0100 Subject: [PATCH 037/274] azp/ci: Prevent empty uploads (#29246) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/publish.yml | 3 +++ .azure-pipelines/stage/verify.yml | 2 ++ .azure-pipelines/stage/windows.yml | 6 ------ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index cc02f5a0e43a0..98143a23bb463 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -175,6 +175,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: bazel.distribution + publishTestResults: false stepsPre: - template: ../gpg.yml parameters: @@ -211,6 +212,7 @@ jobs: rbe: false artifactSuffix: ".arm64" bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" + publishTestResults: false tmpfsDockerDisabled: true stepsPre: - template: ../gpg.yml @@ -325,6 +327,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: release.signed + publishTestResults: false env: GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} stepsPre: diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index 3aa57a8b55620..f8f2e426fc08a 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -25,6 +25,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: verify_distro + publishTestResults: false env: ENVOY_DOCKER_IN_DOCKER: 1 @@ -47,6 +48,7 @@ jobs: ciTarget: verify_distro rbe: false artifactSuffix: ".arm64" + publishTestResults: false tmpfsDockerDisabled: true env: ENVOY_DOCKER_IN_DOCKER: 1 diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index d2884938ac9c2..98e7d76b4b9bf 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -91,12 +91,6 @@ jobs: WINDOWS_BUILD_TYPE: $(windowsBuildType) WINDOWS_IMAGE_BASE: $(windowsImageBase) WINDOWS_IMAGE_TAG: $(windowsImageTag) - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: "$(Build.StagingDirectory)/build_images" - artifactName: docker_windows - timeoutInMinutes: 10 - condition: not(canceled()) - job: released displayName: Complete From 2532d7df1e0e69e018dd2d207d11cb74697d0c81 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 29 Aug 2023 10:13:42 +0100 Subject: [PATCH 038/274] ci/github: Use diskspace hack from toolshed (#29271) Signed-off-by: Ryan Northey --- .github/actions/diskspace/action.yml | 27 --------------------------- .github/workflows/_ci.yml | 2 +- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 .github/actions/diskspace/action.yml diff --git a/.github/actions/diskspace/action.yml b/.github/actions/diskspace/action.yml deleted file mode 100644 index 8df3a6f89957b..0000000000000 --- a/.github/actions/diskspace/action.yml +++ /dev/null @@ -1,27 +0,0 @@ -inputs: - to_remove: - type: string - default: | - /opt/hostedtoolcache - /usr/local/lib/android - /usr/local/.ghcup - -runs: - using: composite - steps: - - id: remove_cruft - name: Cruft removal - run: | - echo "Disk space before cruft removal" - df -h - - TO_REMOVE=(${{ inputs.to_remove }}) - - for removal in "${TO_REMOVE[@]}"; do - echo "Removing: ${removal} ..." - sudo rm -rf "$removal" - done - - echo "Disk after before cruft removal" - df -h - shell: bash diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 11a61854caf74..27001d45f4987 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -115,7 +115,7 @@ jobs: run: git config --global --add safe.directory /__w/envoy/envoy - if: ${{ inputs.diskspace_hack }} - uses: ./.github/actions/diskspace + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.0.17 - run: | echo "disk space at beginning of build:" df -h From d32d6083a6b27b1b0151e0f8e4605c72ef45e990 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 Sep 2023 07:45:31 +0100 Subject: [PATCH 039/274] ci/windows: Fix cache key (#29274) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index 98e7d76b4b9bf..e9e400da52e7e 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -23,7 +23,7 @@ jobs: steps: - task: Cache@2 inputs: - key: '"windows.release" | $(cacheKeyBazelFiles)' + key: '"windows.release" | $(cacheKeyBazel)' path: $(Build.StagingDirectory)/repository_cache continueOnError: true - bash: ci/run_envoy_docker.sh ci/windows_ci_steps.sh From 48134a7fa0a33a5cb1fe6fe93deff3052077d0c4 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 Sep 2023 11:42:49 +0100 Subject: [PATCH 040/274] ci/checks: Increase concurrency (#29378) Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 2 -- .azure-pipelines/stages.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index d757dbb239782..8d766d900c1aa 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -88,7 +88,6 @@ stages: - env checkStageDeps: - env - concurrencyChecks: 10 macBuildStageDeps: - env windowsBuildStageDeps: @@ -105,7 +104,6 @@ stages: - env checkStageDeps: - env - concurrencyChecks: 10 macBuildStageDeps: - env windowsBuildStageDeps: diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 03f3458da412a..3177b976ef948 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -29,7 +29,7 @@ parameters: - name: concurrencyChecks displayName: "Check concurrency" type: number - default: 5 + default: 10 - name: concurrencyPrechecks displayName: "Prechecks concurrency" type: number From f8a08c8340cf5b45b40a93278d370127d88a77b9 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 5 Sep 2023 16:10:36 +0100 Subject: [PATCH 041/274] ci/format: Bazelify `check_format` script (#29397) Signed-off-by: Ryan Northey --- api/bazel/repositories.bzl | 1 + bazel/repositories.bzl | 8 ++++++ bazel/repository_locations.bzl | 11 +++++++ ci/format_pre.sh | 3 +- mobile/tools/check_format.sh | 2 +- tools/code_format/BUILD | 37 ++++++++++++++++++++---- tools/code_format/check_format.py | 40 ++++++++++++++++++-------- tools/code_format/envoy_build_fixer.py | 5 ++-- tools/local_fix_format.sh | 4 +-- 9 files changed, 86 insertions(+), 25 deletions(-) diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index 0bfd9b81063ce..e789fe67fd2be 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -24,6 +24,7 @@ def api_dependencies(): external_http_archive( name = "com_google_googleapis", ) + external_http_archive( name = "com_github_cncf_udpa", ) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index a33c90baa229d..5c1a84c0d3ba3 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -254,6 +254,7 @@ def envoy_dependencies(skip_targets = []): # semi-standard in the Bazel community, intended to avoid both duplicate # dependencies and name conflicts. _com_github_axboe_liburing() + _com_github_bazel_buildtools() _com_github_c_ares_c_ares() _com_github_circonus_labs_libcircllhist() _com_github_cyan4973_xxhash() @@ -390,6 +391,13 @@ def _com_github_axboe_liburing(): actual = "@envoy//bazel/foreign_cc:liburing", ) +def _com_github_bazel_buildtools(): + # TODO(phlax): Add binary download + # cf: https://github.com/bazelbuild/buildtools/issues/367 + external_http_archive( + name = "com_github_bazelbuild_buildtools", + ) + def _com_github_c_ares_c_ares(): external_http_archive( name = "com_github_c_ares_c_ares", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index c2538dd939f0d..64c56e76db13e 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -68,6 +68,17 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_apple/blob/{version}/LICENSE", ), + com_github_bazelbuild_buildtools = dict( + project_name = "Bazel build tools", + project_desc = "Developer tools for working with Google's bazel buildtool.", + project_url = "https://github.com/bazelbuild/buildtools", + version = "5.1.0", + sha256 = "e3bb0dc8b0274ea1aca75f1f8c0c835adbe589708ea89bf698069d0790701ea3", + release_date = "2022-04-14", + strip_prefix = "buildtools-{version}", + urls = ["https://github.com/bazelbuild/buildtools/archive/{version}.tar.gz"], + use_category = ["test_only"], + ), rules_fuzzing = dict( project_name = "Fuzzing Rules for Bazel", project_desc = "Bazel rules for fuzz tests", diff --git a/ci/format_pre.sh b/ci/format_pre.sh index c85da1c6ae3d4..f1a4c77fe079e 100755 --- a/ci/format_pre.sh +++ b/ci/format_pre.sh @@ -63,8 +63,7 @@ if [[ -n "$AZP_BRANCH" ]]; then fi CURRENT=check_format -echo "Running ${ENVOY_SRCDIR}/tools/code_format/check_format.py" -time "${ENVOY_SRCDIR}/tools/code_format/check_format.py" fix --fail_on_diff +bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:check_format -- fix --fail_on_diff if [[ "${#FAILED[@]}" -ne "0" ]]; then echo "${BASH_ERR_PREFIX}TESTS FAILED:" >&2 diff --git a/mobile/tools/check_format.sh b/mobile/tools/check_format.sh index 17336db2dc193..60009d7a437e3 100755 --- a/mobile/tools/check_format.sh +++ b/mobile/tools/check_format.sh @@ -47,4 +47,4 @@ FORMAT_ARGS+=( ./library/common/extensions ./test/java ./test/kotlin ./test/objective-c ./test/swift ./experimental/swift) -ENVOY_BAZEL_PREFIX=@envoy ../tools/code_format/check_format.py "${FORMAT_ARGS[@]}" +./bazelw run @envoy//tools/code_format:check_format -- --path "$PWD" "${FORMAT_ARGS[@]}" diff --git a/tools/code_format/BUILD b/tools/code_format/BUILD index 487a447a1563e..79712a5fd6aa5 100644 --- a/tools/code_format/BUILD +++ b/tools/code_format/BUILD @@ -1,11 +1,38 @@ load("//bazel:envoy_build_system.bzl", "envoy_package") +load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary") +load("@envoy_repo//:path.bzl", "PATH") licenses(["notice"]) # Apache 2 envoy_package() -exports_files([ - "check_format.py", - "header_order.py", - "envoy_build_fixer.py", -]) +py_library( + name = "envoy_build_fixer", + srcs = ["envoy_build_fixer.py"], +) + +py_library( + name = "header_order", + srcs = ["header_order.py"], +) + +py_binary( + name = "check_format", + srcs = ["check_format.py"], + args = [ + "--path=%s" % PATH, + "--buildifier_path=$(location @com_github_bazelbuild_buildtools//buildifier)", + "--buildozer_path=$(location @com_github_bazelbuild_buildtools//buildozer)", + ], + data = [ + ":config.yaml", + "@com_github_bazelbuild_buildtools//buildifier", + "@com_github_bazelbuild_buildtools//buildozer", + ], + deps = [ + requirement("pyyaml"), + ":envoy_build_fixer", + ":header_order", + ], +) diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 45cf81f6f3843..cb133d8c538fe 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -19,16 +19,16 @@ # As `pyyaml` is present in `envoy-build-ubuntu` it should be safe to use here. import yaml -import paths - logger = logging.getLogger(__name__) class FormatConfig: """Provides a format config object based on parsed YAML config.""" - def __init__(self, path: str) -> None: + def __init__(self, path: str, args, source_path) -> None: self.path = path + self.args = args + self.source_path = source_path def __getitem__(self, k): return self.config.__getitem__(k) @@ -36,12 +36,21 @@ def __getitem__(self, k): @cached_property def buildifier_path(self) -> str: """Path to the buildifer binary.""" - return paths.get_buildifier() + path = ( + os.path.join(self.source_path, self.args.buildifier_path) + if self.source_path else self.args.buildifier_path) + # v ugly hack + os.environ["BUILDIFIER_PATH"] = path + return path @cached_property def buildozer_path(self) -> str: """Path to the buildozer binary.""" - return paths.get_buildozer() + path = ( + os.path.join(self.source_path, self.args.buildozer_path) + if self.source_path else self.args.buildozer_path) + os.environ["BUILDOZER_PATH"] = path + return path @cached_property def clang_format_path(self) -> str: @@ -113,10 +122,11 @@ def _normalize( class FormatChecker: - def __init__(self, args): + def __init__(self, args, source_path): self.args = args self.config_path = args.config_path self.operation_type = args.operation_type + self.source_path = source_path self.target_path = args.target_path self.api_prefix = args.api_prefix self.envoy_build_rule_check = not args.skip_envoy_build_rule_check @@ -130,7 +140,7 @@ def build_fixer_check_excluded_paths(self): @cached_property def config(self) -> FormatConfig: - return FormatConfig(self.config_path) + return FormatConfig(self.config_path, self.args, self.source_path) @cached_property def include_dir_order(self): @@ -214,8 +224,8 @@ def executable_by_others(self, executable): # available. def check_tools(self): error_messages = [] - clang_format_abs_path = self.look_path(self.config.clang_format_path) + if clang_format_abs_path: if not self.executable_by_others(clang_format_abs_path): error_messages.append( @@ -245,7 +255,6 @@ def check_bazel_tool(name, path, var): "command {} exists, but cannot be executed by other " "users".format(path)) else: - error_messages.append( "Command {} not found. If you have {} installed, but the binary " "name is different or it's not available in $GOPATH/bin, please use " @@ -947,7 +956,8 @@ def check_format_visitor(self, arg, dir_name, names, fail_on_diff=False): for file_name in names: result = pool.apply_async( - self.check_format_return_trace_on_error, args=(dir_name + file_name, fail_on_diff)) + self.check_format_return_trace_on_error, + args=(os.path.join(dir_name, file_name), fail_on_diff)) result_list.append(result) # check_error_messages iterates over the list with error messages and prints @@ -965,7 +975,7 @@ def included_for_memcpy(self, file_path): def normalize_path(path): """Convert path to form ./path/to/dir/ for directories and ./path/to/file otherwise""" - if not path.startswith("./"): + if not path.startswith("./") and not path.startswith("/"): path = "./" + path isdir = os.path.isdir(path) @@ -988,6 +998,7 @@ def normalize_path(path): nargs="?", default=".", help="specify the root directory for the script to recurse over. Default '.'.") + parser.add_argument("--path", default=".", help="specify the root path.") parser.add_argument( "--config_path", default="./tools/code_format/config.yaml", @@ -1033,6 +1044,8 @@ def normalize_path(path): nargs="+", default=[], help="exclude paths from bazel_tools check.") + parser.add_argument("--buildifier_path", type=str, help="Path to buildifier executable.") + parser.add_argument("--buildozer_path", type=str, help="Path to buildozer executable.") parser.add_argument( "--include_dir_order", type=str, @@ -1040,7 +1053,10 @@ def normalize_path(path): help="specify the header block include directory order.") args = parser.parse_args() - format_checker = FormatChecker(args) + # TODO(phlax): completely rewrite file discovery in this file - its a mess + source_path = os.getcwd() + os.chdir(args.path) + format_checker = FormatChecker(args, source_path) excluded_prefixes = format_checker.config.paths["excluded"] if args.add_excluded_prefixes: diff --git a/tools/code_format/envoy_build_fixer.py b/tools/code_format/envoy_build_fixer.py index 1ac9762876231..9cfe99facf2f1 100755 --- a/tools/code_format/envoy_build_fixer.py +++ b/tools/code_format/envoy_build_fixer.py @@ -14,13 +14,12 @@ import sys import tempfile import pathlib -import paths # Where does Buildozer live? -BUILDOZER_PATH = paths.get_buildozer() +BUILDOZER_PATH = os.environ["BUILDOZER_PATH"] # Where does Buildifier live? -BUILDIFIER_PATH = paths.get_buildifier() +BUILDIFIER_PATH = os.environ["BUILDIFIER_PATH"] # Canonical Envoy license. LICENSE_STRING = 'licenses(["notice"]) # Apache 2\n\n' diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index c1cc3d6b6ea23..0715ca6466dfe 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -49,7 +49,7 @@ function format_one() { if [[ "$verbose" == "1" ]]; then set -x fi - ./tools/code_format/check_format.py fix "$1" + bazel run //tools/code_format:check_format -- fix "${1}" ./tools/spelling/check_spelling_pedantic.py fix "$1" ) } @@ -59,7 +59,7 @@ function format_all() { if [[ "$verbose" == "1" ]]; then set -x fi - ./tools/code_format/check_format.py fix + bazel run //tools/code_format:check_format -- fix ./tools/spelling/check_spelling_pedantic.py fix ) } From ad6cdcb4e016de2c3e144d88cce4e1c56af34af0 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 5 Sep 2023 17:49:28 +0100 Subject: [PATCH 042/274] deps: Bump `rules_rust` -> 0.27.0 (#29412) Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 64c56e76db13e..733ea5fd4f00f 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1395,13 +1395,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel rust rules", project_desc = "Bazel rust rules (used by Wasm)", project_url = "https://github.com/bazelbuild/rules_rust", - version = "262b6a5ea17ec91b3c07e37e82af0eb15dd6ceef", + version = "0.27.0", strip_prefix = "rules_rust-{version}", - sha256 = "e968b3d01c305282bd237bcdafe5e5141192e93eaefb22712e8bc4299e672b16", + sha256 = "d9a3981f4ef18ced850341bc05c7e2a506006a47a0207b6f7191f271cb893233", urls = ["https://github.com/bazelbuild/rules_rust/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.wasm.runtime.wasmtime"], - release_date = "2023-08-11", + release_date = "2023-08-31", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_rust/blob/{version}/LICENSE.txt", From 5c08e3de6311016fc131eba8e9bb9bef492c5c39 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 5 Sep 2023 21:20:17 +0100 Subject: [PATCH 043/274] deps: Bump `buildifier`/`buildozer` -> 6.3.3 (#29405) Signed-off-by: Ryan Northey --- bazel/external/quiche.BUILD | 6 +++--- bazel/foreign_cc/BUILD | 2 +- bazel/repository_locations.bzl | 8 ++++---- configs/BUILD | 6 +++--- contrib/kafka/filters/network/source/BUILD | 4 ++-- contrib/kafka/filters/network/test/BUILD | 4 ++-- .../network/test/broker/integration_test/BUILD | 4 ++-- .../network/test/mesh/integration_test/BUILD | 4 ++-- .../connection_balance/dlb/source/BUILD | 4 ++-- contrib/qat/BUILD | 2 +- contrib/vcl/source/BUILD | 2 +- distribution/BUILD | 2 +- distribution/binary/BUILD | 2 +- docs/BUILD | 4 ++-- source/common/common/BUILD | 10 +++++----- source/common/filesystem/BUILD | 2 +- source/common/grpc/BUILD | 8 ++++---- source/common/http/match_delegate/BUILD | 8 ++++---- source/common/http/matching/BUILD | 8 ++++---- source/common/network/BUILD | 8 ++++---- source/common/quic/BUILD | 10 +++++----- source/common/router/BUILD | 8 ++++---- source/common/ssl/matching/BUILD | 8 ++++---- source/common/watchdog/BUILD | 8 ++++---- source/exe/BUILD | 10 +++++----- source/extensions/BUILD | 4 ++-- source/extensions/common/wasm/BUILD | 12 ++++++------ .../listener_managers/listener_manager/BUILD | 18 +++++++++--------- .../quic/connection_id_generator/BUILD | 8 ++++---- source/extensions/quic/crypto_stream/BUILD | 8 ++++---- source/extensions/quic/proof_source/BUILD | 8 ++++---- .../quic/server_preferred_address/BUILD | 8 ++++---- source/extensions/udp_packet_writer/gso/BUILD | 2 +- test/config_test/BUILD | 6 +++--- test/extensions/filters/http/common/fuzz/BUILD | 2 +- .../network/thrift_proxy/driver/fbthrift/BUILD | 2 +- .../driver/generated/example/BUILD | 2 +- .../network/dns_resolver/apple/BUILD | 10 +++++----- test/fuzz/BUILD | 8 ++++---- test/server/BUILD | 4 ++-- test/server/config_validation/BUILD | 2 +- test/tools/router_check/BUILD | 2 +- test/tools/schema_validator/BUILD | 2 +- tools/api_proto_breaking_change_detector/BUILD | 2 +- tools/code/BUILD | 6 +++--- tools/code_format/BUILD | 4 ++-- tools/config_validation/BUILD | 2 +- tools/dependency/BUILD | 4 ++-- tools/deprecate_version/BUILD | 2 +- tools/distribution/BUILD | 2 +- tools/docs/BUILD | 4 ++-- tools/github/BUILD | 2 +- tools/gsutil/BUILD | 4 ++-- tools/proto_format/BUILD | 6 +++--- tools/protodoc/BUILD | 4 ++-- tools/protoprint/BUILD | 4 ++-- tools/protoxform/BUILD | 2 +- tools/type_whisperer/BUILD | 2 +- 58 files changed, 150 insertions(+), 150 deletions(-) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 41bcc3784c90a..1d04c6659d79f 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -1,4 +1,3 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load( "@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", @@ -10,6 +9,7 @@ load( "envoy_quiche_platform_impl_cc_library", "envoy_quiche_platform_impl_cc_test_library", ) +load("@rules_proto//proto:defs.bzl", "proto_library") licenses(["notice"]) # Apache 2 @@ -3551,15 +3551,15 @@ envoy_cc_library( srcs = select({ "@envoy//bazel:windows_x86_64": [], "//conditions:default": [ - "quiche/quic/core/io/event_loop_socket_factory.cc", "quiche/quic/core/io/event_loop_connecting_client_socket.cc", + "quiche/quic/core/io/event_loop_socket_factory.cc", ], }), hdrs = select({ "@envoy//bazel:windows_x86_64": [], "//conditions:default": [ - "quiche/quic/core/io/event_loop_socket_factory.h", "quiche/quic/core/io/event_loop_connecting_client_socket.h", + "quiche/quic/core/io/event_loop_socket_factory.h", ], }), copts = quiche_copts, diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index 67caf394a1076..3d3b13d969448 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -1,5 +1,5 @@ -load("//bazel:envoy_build_system.bzl", "envoy_cmake", "envoy_package") load("@rules_foreign_cc//foreign_cc:configure.bzl", "configure_make") +load("//bazel:envoy_build_system.bzl", "envoy_cmake", "envoy_package") licenses(["notice"]) # Apache 2 diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 733ea5fd4f00f..5fe48d6f00a94 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -72,11 +72,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel build tools", project_desc = "Developer tools for working with Google's bazel buildtool.", project_url = "https://github.com/bazelbuild/buildtools", - version = "5.1.0", - sha256 = "e3bb0dc8b0274ea1aca75f1f8c0c835adbe589708ea89bf698069d0790701ea3", - release_date = "2022-04-14", + version = "6.3.3", + sha256 = "42968f9134ba2c75c03bb271bd7bb062afb7da449f9b913c96e5be4ce890030a", + release_date = "2023-08-25", strip_prefix = "buildtools-{version}", - urls = ["https://github.com/bazelbuild/buildtools/archive/{version}.tar.gz"], + urls = ["https://github.com/bazelbuild/buildtools/archive/v{version}.tar.gz"], use_category = ["test_only"], ), rules_fuzzing = dict( diff --git a/configs/BUILD b/configs/BUILD index 17b5cf99b5fd6..ab37531ddba9b 100644 --- a/configs/BUILD +++ b/configs/BUILD @@ -1,9 +1,9 @@ +load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary") load( "//bazel:envoy_build_system.bzl", "envoy_package", ) -load("@rules_python//python:defs.bzl", "py_binary") -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 @@ -43,8 +43,8 @@ filegroup( "//bazel:disable_admin_functionality": [], "//conditions:default": [ "envoy-demo.yaml", - "freebind/freebind.yaml", "envoy-tap-config.yaml", + "freebind/freebind.yaml", ], }), ) diff --git a/contrib/kafka/filters/network/source/BUILD b/contrib/kafka/filters/network/source/BUILD index e0bf9ebed7983..ec50a777c50df 100644 --- a/contrib/kafka/filters/network/source/BUILD +++ b/contrib/kafka/filters/network/source/BUILD @@ -1,11 +1,11 @@ +load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") load( "//bazel:envoy_build_system.bzl", "envoy_cc_contrib_extension", "envoy_cc_library", "envoy_contrib_package", ) -load("@rules_python//python:defs.bzl", "py_binary", "py_library") -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 diff --git a/contrib/kafka/filters/network/test/BUILD b/contrib/kafka/filters/network/test/BUILD index 93b2d2e35a29b..f7bf15eba1515 100644 --- a/contrib/kafka/filters/network/test/BUILD +++ b/contrib/kafka/filters/network/test/BUILD @@ -1,11 +1,11 @@ +load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary") load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_cc_test_library", "envoy_contrib_package", ) -load("@rules_python//python:defs.bzl", "py_binary") -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 diff --git a/contrib/kafka/filters/network/test/broker/integration_test/BUILD b/contrib/kafka/filters/network/test/broker/integration_test/BUILD index dced1baa7909c..30444088ecbcd 100644 --- a/contrib/kafka/filters/network/test/broker/integration_test/BUILD +++ b/contrib/kafka/filters/network/test/broker/integration_test/BUILD @@ -1,9 +1,9 @@ +load("@base_pip3//:requirements.bzl", "requirement") load( "//bazel:envoy_build_system.bzl", "envoy_contrib_package", "envoy_py_test", ) -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 @@ -17,8 +17,8 @@ envoy_py_test( "@kafka_python_client//:all", ], data = [ - "//contrib/exe:envoy-static", "//bazel:remote_jdk11", + "//contrib/exe:envoy-static", "@kafka_server_binary//:all", ] + glob(["*.j2"]), flaky = True, diff --git a/contrib/kafka/filters/network/test/mesh/integration_test/BUILD b/contrib/kafka/filters/network/test/mesh/integration_test/BUILD index 3f0084aa6bc3f..3db16d2987563 100644 --- a/contrib/kafka/filters/network/test/mesh/integration_test/BUILD +++ b/contrib/kafka/filters/network/test/mesh/integration_test/BUILD @@ -1,9 +1,9 @@ +load("@base_pip3//:requirements.bzl", "requirement") load( "//bazel:envoy_build_system.bzl", "envoy_contrib_package", "envoy_py_test", ) -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 @@ -17,8 +17,8 @@ envoy_py_test( "@kafka_python_client//:all", ], data = [ - "//contrib/exe:envoy-static", "//bazel:remote_jdk11", + "//contrib/exe:envoy-static", "@kafka_server_binary//:all", ] + glob(["*.j2"]), flaky = True, diff --git a/contrib/network/connection_balance/dlb/source/BUILD b/contrib/network/connection_balance/dlb/source/BUILD index 4dbf6c997a271..b0952bb65138d 100644 --- a/contrib/network/connection_balance/dlb/source/BUILD +++ b/contrib/network/connection_balance/dlb/source/BUILD @@ -1,3 +1,4 @@ +load("@rules_foreign_cc//foreign_cc:defs.bzl", "make") load( "//bazel:envoy_build_system.bzl", "envoy_cc_contrib_extension", @@ -7,7 +8,6 @@ load( "//contrib:all_contrib_extensions.bzl", "envoy_contrib_linux_x86_64_constraints", ) -load("@rules_foreign_cc//foreign_cc:defs.bzl", "make") licenses(["notice"]) # Apache 2 @@ -37,8 +37,8 @@ envoy_cc_contrib_extension( ], }), deps = [ - "//envoy/registry", "//envoy/api:api_interface", + "//envoy/registry", "//envoy/server:factory_context_interface", "//envoy/server:filter_config_interface", "//source/common/common:logger_lib", diff --git a/contrib/qat/BUILD b/contrib/qat/BUILD index c56f6694b89f1..d435976e4953a 100644 --- a/contrib/qat/BUILD +++ b/contrib/qat/BUILD @@ -1,5 +1,5 @@ -load("//bazel:envoy_build_system.bzl", "envoy_contrib_package") load("@rules_foreign_cc//foreign_cc:configure.bzl", "configure_make") +load("//bazel:envoy_build_system.bzl", "envoy_contrib_package") load( "//contrib:all_contrib_extensions.bzl", "envoy_contrib_linux_x86_64_constraints", diff --git a/contrib/vcl/source/BUILD b/contrib/vcl/source/BUILD index 1233b3739b041..256ef6efa78da 100644 --- a/contrib/vcl/source/BUILD +++ b/contrib/vcl/source/BUILD @@ -1,3 +1,4 @@ +load("@base_pip3//:requirements.bzl", "requirement") load("@rules_cc//cc:defs.bzl", "cc_library") load( "//bazel:envoy_build_system.bzl", @@ -6,7 +7,6 @@ load( "envoy_cmake", "envoy_contrib_package", ) -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 diff --git a/distribution/BUILD b/distribution/BUILD index 5c260de7fe6bf..260463e320f17 100644 --- a/distribution/BUILD +++ b/distribution/BUILD @@ -1,6 +1,6 @@ +load("@envoy_repo//:version.bzl", "VERSION") load("//bazel:envoy_build_system.bzl", "envoy_package") load(":packages.bzl", "envoy_pkg_distros") -load("@envoy_repo//:version.bzl", "VERSION") licenses(["notice"]) # Apache 2 diff --git a/distribution/binary/BUILD b/distribution/binary/BUILD index 0c18f23247122..5d610f0d075db 100644 --- a/distribution/binary/BUILD +++ b/distribution/binary/BUILD @@ -1,6 +1,6 @@ -load("//bazel:envoy_build_system.bzl", "envoy_package") load("@rules_pkg//pkg:mappings.bzl", "pkg_files") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") +load("//bazel:envoy_build_system.bzl", "envoy_package") load("//distribution/binary:compiler.bzl", "bundled") licenses(["notice"]) # Apache 2 diff --git a/docs/BUILD b/docs/BUILD index 2517d3de973b2..80fc7b6e9842b 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -1,9 +1,9 @@ +load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files") +load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") load( "//bazel:envoy_build_system.bzl", "envoy_package", ) -load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files") -load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") licenses(["notice"]) # Apache 2 diff --git a/source/common/common/BUILD b/source/common/common/BUILD index 7c00df4467577..477f09950a323 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -1,3 +1,7 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_basic_cc_library", @@ -8,10 +12,6 @@ load( "envoy_package", "envoy_pch_library", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 @@ -211,7 +211,7 @@ envoy_cc_library( ":lock_guard_lib", ":macros", ":non_copyable", - "//source/common/protobuf:protobuf", + "//source/common/protobuf", ] + select({ "//bazel:android_logger": ["logger_impl_lib_android"], "//conditions:default": ["logger_impl_lib_standard"], diff --git a/source/common/filesystem/BUILD b/source/common/filesystem/BUILD index 34b812833eed5..af66e6d441df3 100644 --- a/source/common/filesystem/BUILD +++ b/source/common/filesystem/BUILD @@ -116,11 +116,11 @@ envoy_cc_library( deps = [ "//envoy/api:api_interface", "//envoy/event:dispatcher_interface", + "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/common:linked_object", "//source/common/common:minimal_logger_lib", "//source/common/common:utility_lib", - "//source/common/buffer:buffer_lib", "//source/common/network:default_socket_interface_lib", ] + select({ "//bazel:windows_x86_64": [ diff --git a/source/common/grpc/BUILD b/source/common/grpc/BUILD index 4c03865913af5..96d1ed06353d1 100644 --- a/source/common/grpc/BUILD +++ b/source/common/grpc/BUILD @@ -1,3 +1,7 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", @@ -5,10 +9,6 @@ load( "envoy_package", "envoy_select_google_grpc", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/common/http/match_delegate/BUILD b/source/common/http/match_delegate/BUILD index f860498782660..4cebb95ee8618 100644 --- a/source/common/http/match_delegate/BUILD +++ b/source/common/http/match_delegate/BUILD @@ -1,12 +1,12 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/common/http/matching/BUILD b/source/common/http/matching/BUILD index 1ae2fb53bcb1e..0738a6ebbb00a 100644 --- a/source/common/http/matching/BUILD +++ b/source/common/http/matching/BUILD @@ -1,12 +1,12 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/common/network/BUILD b/source/common/network/BUILD index bbe92972ec02b..dc73e67d73596 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -1,12 +1,12 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index 16ebece170396..65f752ec8b99d 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -1,13 +1,13 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", "envoy_select_enable_http_datagrams", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 @@ -506,8 +506,8 @@ envoy_cc_library( "//bazel:boringssl_fips": [], "//bazel:boringssl_disabled": [], "//conditions:default": [ - ":server_codec_lib", ":quic_transport_socket_factory_lib", + ":server_codec_lib", "//source/extensions/quic/crypto_stream:envoy_quic_crypto_server_stream_lib", "//source/extensions/quic/proof_source:envoy_quic_proof_source_factory_impl_lib", ], diff --git a/source/common/router/BUILD b/source/common/router/BUILD index d881a5d1ab000..1900065f68b0f 100644 --- a/source/common/router/BUILD +++ b/source/common/router/BUILD @@ -1,12 +1,12 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/common/ssl/matching/BUILD b/source/common/ssl/matching/BUILD index 25de1502208d1..983791df12d86 100644 --- a/source/common/ssl/matching/BUILD +++ b/source/common/ssl/matching/BUILD @@ -1,12 +1,12 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/common/watchdog/BUILD b/source/common/watchdog/BUILD index 2481ac8dab53e..d0cc4ff462a8f 100644 --- a/source/common/watchdog/BUILD +++ b/source/common/watchdog/BUILD @@ -1,12 +1,12 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/exe/BUILD b/source/exe/BUILD index f2b01f7b655db..32fdd616dd268 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -10,8 +10,8 @@ load( "envoy_select_enable_http3", "envoy_select_signal_trace", ) -load("//source/extensions:all_extensions.bzl", "envoy_all_core_extensions", "envoy_all_extensions") load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//source/extensions:all_extensions.bzl", "envoy_all_core_extensions", "envoy_all_extensions") licenses(["notice"]) # Apache 2 @@ -40,9 +40,9 @@ envoy_cc_library( "//source/common/stats:stats_lib", "//source/common/stats:thread_local_store_lib", "//source/server:drain_manager_lib", + "//source/server:listener_hooks_lib", "//source/server:options_lib", "//source/server:server_lib", - "//source/server:listener_hooks_lib", ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), @@ -71,11 +71,11 @@ envoy_cc_library( ":envoy_common_with_core_extensions_lib", ":platform_impl_lib", ":process_wide_lib", - "//source/common/thread_local:thread_local_lib", "//source/common/api:os_sys_calls_lib", "//source/common/common:compiler_requirements_lib", "//source/common/common:perf_annotation_lib", "//source/common/grpc:google_grpc_context_lib", + "//source/common/thread_local:thread_local_lib", "//source/server:hot_restart_lib", "//source/server:hot_restart_nop_lib", ] + envoy_select_signal_trace([ @@ -141,9 +141,9 @@ envoy_cc_library( "//source/common/stats:stats_lib", "//source/common/stats:thread_local_store_lib", "//source/server:drain_manager_lib", + "//source/server:listener_hooks_lib", "//source/server:options_lib", "//source/server:server_lib", - "//source/server:listener_hooks_lib", ] + envoy_all_core_extensions() + # TODO(rojkov): drop io_uring dependency when it's fully integrated. select({ @@ -266,8 +266,8 @@ envoy_cc_library( ":main_common_lib", "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", - "//source/common/common:win32_event_logger_impl_lib", "//source/common/common:thread_lib", + "//source/common/common:win32_event_logger_impl_lib", "//source/common/event:signal_lib", ], "//conditions:default": [], diff --git a/source/extensions/BUILD b/source/extensions/BUILD index 910abeaa47825..152c701894189 100644 --- a/source/extensions/BUILD +++ b/source/extensions/BUILD @@ -1,7 +1,7 @@ -load("//bazel:envoy_build_system.bzl", "envoy_extension_package") load("@envoy_api//bazel:utils.bzl", "json_data") -load(":extensions_build_config.bzl", "EXTENSIONS") +load("//bazel:envoy_build_system.bzl", "envoy_extension_package") load(":all_extensions.bzl", "envoy_all_extensions") +load(":extensions_build_config.bzl", "EXTENSIONS") licenses(["notice"]) # Apache 2 diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 0b9ad7e976288..ba67df990189f 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -78,29 +78,29 @@ envoy_cc_extension( deps = [ ":wasm_hdr", ":wasm_runtime_factory_interface", + "//envoy/server:lifecycle_notifier_interface", "//external:abseil_base", "//external:abseil_node_hash_map", "//external:zlib", - "//envoy/server:lifecycle_notifier_interface", "//source/common/buffer:buffer_lib", "//source/common/common:enum_to_int", "//source/common/common:safe_memcpy_lib", "//source/common/config:remote_data_fetcher_lib", "//source/common/http:message_lib", "//source/common/http:utility_lib", - "//source/common/tracing:http_tracer_lib", "//source/common/network/dns_resolver:dns_factory_util_lib", + "//source/common/tracing:http_tracer_lib", "//source/extensions/common/wasm/ext:declare_property_cc_proto", "//source/extensions/common/wasm/ext:envoy_null_vm_wasm_api", "//source/extensions/filters/common/expr:context_lib", - "@com_google_cel_cpp//eval/public/containers:field_access", - "@com_google_cel_cpp//eval/public/containers:field_backed_list_impl", - "@com_google_cel_cpp//eval/public/containers:field_backed_map_impl", - "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@com_google_cel_cpp//eval/public:builtin_func_registrar", "@com_google_cel_cpp//eval/public:cel_expr_builder_factory", "@com_google_cel_cpp//eval/public:cel_value", "@com_google_cel_cpp//eval/public:value_export_util", + "@com_google_cel_cpp//eval/public/containers:field_access", + "@com_google_cel_cpp//eval/public/containers:field_backed_list_impl", + "@com_google_cel_cpp//eval/public/containers:field_backed_map_impl", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@envoy_api//envoy/extensions/wasm/v3:pkg_cc_proto", "@proxy_wasm_cpp_host//:base_lib", "@proxy_wasm_cpp_host//:null_lib", diff --git a/source/extensions/listener_managers/listener_manager/BUILD b/source/extensions/listener_managers/listener_manager/BUILD index e5dcae1193ceb..ddb413ac53192 100644 --- a/source/extensions/listener_managers/listener_manager/BUILD +++ b/source/extensions/listener_managers/listener_manager/BUILD @@ -28,15 +28,10 @@ envoy_cc_extension( "//test:__subpackages__", ], deps = [ - ":connection_handler_lib", - "//source/server:listener_manager_factory_lib", - "//source/server:api_listener_lib", ":active_raw_udp_listener_config", - "//source/server:configuration_lib", - "//source/server:drain_manager_lib", + ":connection_handler_lib", ":filter_chain_manager_lib", ":lds_api_lib", - "//source/server:transport_socket_config_lib", "//envoy/access_log:access_log_interface", "//envoy/config:typed_metadata_interface", "//envoy/network:connection_interface", @@ -49,8 +44,8 @@ envoy_cc_extension( "//source/common/access_log:access_log_lib", "//source/common/common:basic_resource_lib", "//source/common/common:empty_string", - "//source/common/config:utility_lib", "//source/common/config:metadata_lib", + "//source/common/config:utility_lib", "//source/common/http:conn_manager_lib", "//source/common/init:manager_lib", "//source/common/init:target_lib", @@ -63,11 +58,16 @@ envoy_cc_extension( "//source/common/network:udp_packet_writer_handler_lib", "//source/common/network:utility_lib", "//source/common/protobuf:utility_lib", - "//source/common/stream_info:stream_info_lib", "//source/common/quic:quic_stat_names_lib", + "//source/common/stream_info:stream_info_lib", "//source/extensions/filters/network/http_connection_manager:config", - "//source/extensions/upstreams/http/generic:config", "//source/extensions/udp_packet_writer/default:config", + "//source/extensions/upstreams/http/generic:config", + "//source/server:api_listener_lib", + "//source/server:configuration_lib", + "//source/server:drain_manager_lib", + "//source/server:listener_manager_factory_lib", + "//source/server:transport_socket_config_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", diff --git a/source/extensions/quic/connection_id_generator/BUILD b/source/extensions/quic/connection_id_generator/BUILD index d379daeaea3b3..05f6fe9722406 100644 --- a/source/extensions/quic/connection_id_generator/BUILD +++ b/source/extensions/quic/connection_id_generator/BUILD @@ -1,13 +1,13 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_extension", "envoy_cc_library", "envoy_extension_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/extensions/quic/crypto_stream/BUILD b/source/extensions/quic/crypto_stream/BUILD index ed5a13caefb77..d358631954bf5 100644 --- a/source/extensions/quic/crypto_stream/BUILD +++ b/source/extensions/quic/crypto_stream/BUILD @@ -1,13 +1,13 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_extension", "envoy_cc_library", "envoy_extension_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/extensions/quic/proof_source/BUILD b/source/extensions/quic/proof_source/BUILD index f2d51ed0089a2..358e5123af8ee 100644 --- a/source/extensions/quic/proof_source/BUILD +++ b/source/extensions/quic/proof_source/BUILD @@ -1,13 +1,13 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_extension", "envoy_cc_library", "envoy_extension_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/extensions/quic/server_preferred_address/BUILD b/source/extensions/quic/server_preferred_address/BUILD index b8a8f65f44b26..9f2a46f283205 100644 --- a/source/extensions/quic/server_preferred_address/BUILD +++ b/source/extensions/quic/server_preferred_address/BUILD @@ -1,13 +1,13 @@ +load( + "@envoy_build_config//:extensions_build_config.bzl", + "LEGACY_ALWAYSLINK", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_extension", "envoy_cc_library", "envoy_extension_package", ) -load( - "@envoy_build_config//:extensions_build_config.bzl", - "LEGACY_ALWAYSLINK", -) licenses(["notice"]) # Apache 2 diff --git a/source/extensions/udp_packet_writer/gso/BUILD b/source/extensions/udp_packet_writer/gso/BUILD index dbaf963b5499a..d16cc337767d9 100644 --- a/source/extensions/udp_packet_writer/gso/BUILD +++ b/source/extensions/udp_packet_writer/gso/BUILD @@ -23,8 +23,8 @@ envoy_cc_extension( tags = ["nofips"], deps = [ "//envoy/config:typed_config_interface", - "//envoy/registry", "//envoy/network:udp_packet_writer_handler_interface", + "//envoy/registry", "@envoy_api//envoy/extensions/udp_packet_writer/v3:pkg_cc_proto", ] + envoy_select_enable_http3([ "//source/common/quic:udp_gso_batch_writer_lib", diff --git a/test/config_test/BUILD b/test/config_test/BUILD index 3fa4a6234c2f0..63ee81132676b 100644 --- a/test/config_test/BUILD +++ b/test/config_test/BUILD @@ -4,8 +4,8 @@ load( "envoy_cc_test_library", "envoy_package", ) -load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 @@ -55,12 +55,12 @@ envoy_cc_test_library( "//source/server/config_validation:server_lib", "//test/integration:integration_lib", "//test/mocks/server:instance_mocks", - "//test/mocks/server:worker_factory_mocks", "//test/mocks/server:listener_component_factory_mocks", + "//test/mocks/server:worker_factory_mocks", "//test/mocks/server:worker_mocks", "//test/mocks/ssl:ssl_mocks", - "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:simulated_time_system_lib", + "//test/test_common:threadsafe_singleton_injector_lib", ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), diff --git a/test/extensions/filters/http/common/fuzz/BUILD b/test/extensions/filters/http/common/fuzz/BUILD index 34b0a26c2e203..121a5be03d8c6 100644 --- a/test/extensions/filters/http/common/fuzz/BUILD +++ b/test/extensions/filters/http/common/fuzz/BUILD @@ -74,7 +74,7 @@ envoy_cc_fuzz_test( "//source/common/protobuf:utility_lib", "//source/extensions/upstreams/http/generic:config", "//test/config:utility_lib", - "@envoy_api//envoy/service/auth/v3:pkg_cc_proto", "@envoy_api//envoy/service/auth/v2alpha:pkg_cc_proto", + "@envoy_api//envoy/service/auth/v3:pkg_cc_proto", ] + envoy_all_http_filters(), ) diff --git a/test/extensions/filters/network/thrift_proxy/driver/fbthrift/BUILD b/test/extensions/filters/network/thrift_proxy/driver/fbthrift/BUILD index f3808b7b5606e..322e5e8c75660 100644 --- a/test/extensions/filters/network/thrift_proxy/driver/fbthrift/BUILD +++ b/test/extensions/filters/network/thrift_proxy/driver/fbthrift/BUILD @@ -1,6 +1,6 @@ +load("@base_pip3//:requirements.bzl", "requirement") load("@rules_python//python:defs.bzl", "py_library") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 diff --git a/test/extensions/filters/network/thrift_proxy/driver/generated/example/BUILD b/test/extensions/filters/network/thrift_proxy/driver/generated/example/BUILD index f7cec3338d58c..8ea20105b6a4c 100644 --- a/test/extensions/filters/network/thrift_proxy/driver/generated/example/BUILD +++ b/test/extensions/filters/network/thrift_proxy/driver/generated/example/BUILD @@ -1,6 +1,6 @@ +load("@base_pip3//:requirements.bzl", "requirement") load("@rules_python//python:defs.bzl", "py_library") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 diff --git a/test/extensions/network/dns_resolver/apple/BUILD b/test/extensions/network/dns_resolver/apple/BUILD index d949920ad9d9c..8f0aa1689da3d 100644 --- a/test/extensions/network/dns_resolver/apple/BUILD +++ b/test/extensions/network/dns_resolver/apple/BUILD @@ -17,20 +17,20 @@ envoy_cc_test( external_deps = ["abseil_synchronization"], deps = [ "//envoy/event:dispatcher_interface", + "//envoy/event:file_event_interface", "//envoy/network:dns_interface", + "//source/common/common:random_generator_lib", "//source/common/event:dispatcher_includes", - "//envoy/event:file_event_interface", - "//source/common/stats:isolated_store_lib", "//source/common/event:dispatcher_lib", "//source/common/network:address_lib", "//source/common/network/dns_resolver:dns_factory_util_lib", - "//source/common/common:random_generator_lib", + "//source/common/stats:isolated_store_lib", + "//test/mocks/event:event_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "//test/test_common:threadsafe_singleton_injector_lib", - "//test/mocks/event:event_mocks", ] + select({ "//bazel:apple": [ "//source/extensions/network/dns_resolver/apple:config", diff --git a/test/fuzz/BUILD b/test/fuzz/BUILD index df66c8862a780..a2e921ddab322 100644 --- a/test/fuzz/BUILD +++ b/test/fuzz/BUILD @@ -1,3 +1,7 @@ +load( + "@rules_fuzzing//fuzzing:cc_defs.bzl", + "cc_fuzzing_engine", +) load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", @@ -6,10 +10,6 @@ load( "envoy_proto_library", "envoy_select_signal_trace", ) -load( - "@rules_fuzzing//fuzzing:cc_defs.bzl", - "cc_fuzzing_engine", -) licenses(["notice"]) # Apache 2 diff --git a/test/server/BUILD b/test/server/BUILD index 7f082741ec644..62060da63fa5b 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -9,8 +9,8 @@ load( "envoy_select_admin_functionality", "envoy_select_hot_restart", ) -load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 @@ -266,8 +266,8 @@ envoy_cc_fuzz_test( "//source/common/thread_local:thread_local_lib", "//source/server:server_lib", "//test/integration:integration_lib", - "//test/mocks/server:options_mocks", "//test/mocks/server:hot_restart_mocks", + "//test/mocks/server:options_mocks", "//test/test_common:environment_lib", ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 816f1f97a3241..940c8ac0a34d9 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -1,6 +1,6 @@ load("//bazel:envoy_build_system.bzl", "envoy_cc_fuzz_test", "envoy_cc_test", "envoy_cc_test_library", "envoy_package", "envoy_proto_library") -load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 diff --git a/test/tools/router_check/BUILD b/test/tools/router_check/BUILD index 6dedf2221aaaf..2cebf70f2512b 100644 --- a/test/tools/router_check/BUILD +++ b/test/tools/router_check/BUILD @@ -5,8 +5,8 @@ load( "envoy_package", "envoy_proto_library", ) -load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 diff --git a/test/tools/schema_validator/BUILD b/test/tools/schema_validator/BUILD index 50dfb2e8fb352..4ea42313bd22f 100644 --- a/test/tools/schema_validator/BUILD +++ b/test/tools/schema_validator/BUILD @@ -4,8 +4,8 @@ load( "envoy_cc_test_library", "envoy_package", ) -load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 diff --git a/tools/api_proto_breaking_change_detector/BUILD b/tools/api_proto_breaking_change_detector/BUILD index 7093e55cd751a..6e67be8966598 100644 --- a/tools/api_proto_breaking_change_detector/BUILD +++ b/tools/api_proto_breaking_change_detector/BUILD @@ -1,5 +1,5 @@ -load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test") load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test") licenses(["notice"]) # Apache 2 diff --git a/tools/code/BUILD b/tools/code/BUILD index 1b800c6a6deeb..97f87721da641 100644 --- a/tools/code/BUILD +++ b/tools/code/BUILD @@ -1,12 +1,12 @@ -load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point") -load("@envoy_repo//:path.bzl", "PATH") load("@aspect_bazel_lib//lib:jq.bzl", "jq") +load("@envoy_repo//:path.bzl", "PATH") +load("//bazel:envoy_build_system.bzl", "envoy_package") load( "//test/extensions/filters/network/common/fuzz:config.bzl", "READFILTER_FUZZ_FILTERS", "READFILTER_NOFUZZ_FILTERS", ) +load("//tools/base:envoy_python.bzl", "envoy_entry_point") licenses(["notice"]) # Apache 2 diff --git a/tools/code_format/BUILD b/tools/code_format/BUILD index 79712a5fd6aa5..0e3d75117729b 100644 --- a/tools/code_format/BUILD +++ b/tools/code_format/BUILD @@ -1,7 +1,7 @@ -load("//bazel:envoy_build_system.bzl", "envoy_package") load("@base_pip3//:requirements.bzl", "requirement") -load("@rules_python//python:defs.bzl", "py_binary") load("@envoy_repo//:path.bzl", "PATH") +load("@rules_python//python:defs.bzl", "py_binary") +load("//bazel:envoy_build_system.bzl", "envoy_package") licenses(["notice"]) # Apache 2 diff --git a/tools/config_validation/BUILD b/tools/config_validation/BUILD index c88a96ca35b38..e2d3636c9abb0 100644 --- a/tools/config_validation/BUILD +++ b/tools/config_validation/BUILD @@ -1,5 +1,5 @@ -load("@rules_python//python:defs.bzl", "py_binary") load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary") licenses(["notice"]) # Apache 2 diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 5d0e7c4da622f..3db2d1dba5780 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -1,8 +1,8 @@ +load("@base_pip3//:requirements.bzl", "requirement") +load("@envoy_repo//:path.bzl", "PATH") load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_package") load("//tools/base:envoy_python.bzl", "envoy_entry_point") -load("@base_pip3//:requirements.bzl", "requirement") -load("@envoy_repo//:path.bzl", "PATH") licenses(["notice"]) # Apache 2 diff --git a/tools/deprecate_version/BUILD b/tools/deprecate_version/BUILD index 40bb0e4c09277..cefbed0623db1 100644 --- a/tools/deprecate_version/BUILD +++ b/tools/deprecate_version/BUILD @@ -1,5 +1,5 @@ -load("@rules_python//python:defs.bzl", "py_binary") load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary") licenses(["notice"]) # Apache 2 diff --git a/tools/distribution/BUILD b/tools/distribution/BUILD index 64f994ab46485..44e4006cdd982 100644 --- a/tools/distribution/BUILD +++ b/tools/distribution/BUILD @@ -1,6 +1,6 @@ +load("@base_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") load("//tools/base:envoy_python.bzl", "envoy_entry_point") -load("@base_pip3//:requirements.bzl", "requirement") licenses(["notice"]) # Apache 2 diff --git a/tools/docs/BUILD b/tools/docs/BUILD index 63765e15d2c41..edc5c7c2690e0 100644 --- a/tools/docs/BUILD +++ b/tools/docs/BUILD @@ -1,7 +1,7 @@ -load("@rules_python//python:defs.bzl", "py_binary") load("@base_pip3//:requirements.bzl", "requirement") -load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//tools/base:envoy_python.bzl", "envoy_entry_point") licenses(["notice"]) # Apache 2 diff --git a/tools/github/BUILD b/tools/github/BUILD index ae7eae1cf310d..f65c1d953e442 100644 --- a/tools/github/BUILD +++ b/tools/github/BUILD @@ -1,5 +1,5 @@ -load("@rules_python//python:defs.bzl", "py_binary") load("@base_pip3//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary") licenses(["notice"]) # Apache 2 diff --git a/tools/gsutil/BUILD b/tools/gsutil/BUILD index c02f0f643451e..c70e260c4854e 100644 --- a/tools/gsutil/BUILD +++ b/tools/gsutil/BUILD @@ -1,7 +1,7 @@ -load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//bazel:repositories_extra.bzl", "PYTHON_MINOR_VERSION") load("@base_pip3//:requirements.bzl", "requirement") load("@rules_python//python:defs.bzl", "py_binary", "py_library") +load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//bazel:repositories_extra.bzl", "PYTHON_MINOR_VERSION") licenses(["notice"]) # Apache 2 diff --git a/tools/proto_format/BUILD b/tools/proto_format/BUILD index 3c9e8025bb5ef..89eccc31bb423 100644 --- a/tools/proto_format/BUILD +++ b/tools/proto_format/BUILD @@ -1,10 +1,10 @@ -load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data") load("@aspect_bazel_lib//lib:jq.bzl", "jq") load("@envoy_repo//:path.bzl", "PATH") -load("@rules_python//python:defs.bzl", "py_binary") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") +load("@rules_python//python:defs.bzl", "py_binary") +load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data") licenses(["notice"]) # Apache 2 diff --git a/tools/protodoc/BUILD b/tools/protodoc/BUILD index 8c1f8ac652955..01ce510978c08 100644 --- a/tools/protodoc/BUILD +++ b/tools/protodoc/BUILD @@ -1,9 +1,9 @@ +load("@base_pip3//:requirements.bzl", "requirement") load("@com_google_protobuf//:protobuf.bzl", "py_proto_library") load("@rules_python//python:defs.bzl", "py_binary", "py_library") -load("@base_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/protodoc:protodoc.bzl", "protodoc_rule") load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_jinja_env", "envoy_py_data") +load("//tools/protodoc:protodoc.bzl", "protodoc_rule") licenses(["notice"]) # Apache 2 diff --git a/tools/protoprint/BUILD b/tools/protoprint/BUILD index 3f670d996ebb7..0760d9b0cd748 100644 --- a/tools/protoprint/BUILD +++ b/tools/protoprint/BUILD @@ -1,8 +1,8 @@ -load("//bazel:envoy_build_system.bzl", "envoy_package") load("@base_pip3//:requirements.bzl", "requirement") -load("@rules_python//python:defs.bzl", "py_binary") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") +load("@rules_python//python:defs.bzl", "py_binary") +load("//bazel:envoy_build_system.bzl", "envoy_package") load("//tools/base:envoy_python.bzl", "envoy_py_data") load("//tools/protoprint:protoprint.bzl", "protoprint_rule") diff --git a/tools/protoxform/BUILD b/tools/protoxform/BUILD index ed06fae55a9ac..73c95708fe52d 100644 --- a/tools/protoxform/BUILD +++ b/tools/protoxform/BUILD @@ -1,7 +1,7 @@ load("@aspect_bazel_lib//lib:jq.bzl", "jq") -load("@rules_python//python:defs.bzl", "py_binary", "py_library") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") load("//tools/protoxform:protoxform.bzl", "protoxform_rule") licenses(["notice"]) # Apache 2 diff --git a/tools/type_whisperer/BUILD b/tools/type_whisperer/BUILD index f83eecb2e3fc5..43d4b7cf0eaf9 100644 --- a/tools/type_whisperer/BUILD +++ b/tools/type_whisperer/BUILD @@ -2,8 +2,8 @@ load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", "envoy_proto_library") load("//tools/type_whisperer:api_build_file.bzl", "api_build_file") load("//tools/type_whisperer:file_descriptor_set_text.bzl", "file_descriptor_set_text") -load("//tools/type_whisperer:type_database.bzl", "type_database") load("//tools/type_whisperer:proto_cc_source.bzl", "proto_cc_source") +load("//tools/type_whisperer:type_database.bzl", "type_database") licenses(["notice"]) # Apache 2 From d17e870802c6ffa518b8b67a8f8e6fac8057a431 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 Sep 2023 10:51:10 +0100 Subject: [PATCH 044/274] tools/format: Cleanups (#29435) * tools/format: Remove unused script * readme: Update code format commands * bazel/ci: Prevent leaking of build tools into env * mobile/ci: Include format script in CI triggers Signed-off-by: Ryan Northey Signed-off-by: phlax --- bazel/README.md | 4 ++-- ci/build_setup.sh | 3 --- mobile/tools/what_to_run.sh | 2 +- tools/code_format/paths.py | 15 --------------- 4 files changed, 3 insertions(+), 21 deletions(-) delete mode 100644 tools/code_format/paths.py diff --git a/bazel/README.md b/bazel/README.md index 38cd9a9f0df35..fe497fe46b600 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -987,9 +987,9 @@ export PATH=$PATH:$PWD/bin/ Once this is set up, you can run clang-format without docker: ```shell -./tools/code_format/check_format.py check +bazel run //tools/code_format:check_format -- check ./tools/spelling/check_spelling_pedantic.py check -./tools/code_format/check_format.py fix +bazel run //tools/code_format:check_format -- fix ./tools/spelling/check_spelling_pedantic.py fix ``` diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 9dc4c1b0deb04..f5a6e95aea418 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -194,9 +194,6 @@ mkdir -p "${ENVOY_FAILED_TEST_LOGS}" export ENVOY_BUILD_PROFILE="${ENVOY_BUILD_DIR}"/generated/build-profile mkdir -p "${ENVOY_BUILD_PROFILE}" -export BUILDIFIER_BIN="${BUILDIFIER_BIN:-/usr/local/bin/buildifier}" -export BUILDOZER_BIN="${BUILDOZER_BIN:-/usr/local/bin/buildozer}" - if [[ "${ENVOY_BUILD_FILTER_EXAMPLE}" == "true" ]]; then # shellcheck source=ci/filter_example_setup.sh . "$(dirname "$0")"/filter_example_setup.sh diff --git a/mobile/tools/what_to_run.sh b/mobile/tools/what_to_run.sh index 939d23173b8e1..8daacd10892ea 100755 --- a/mobile/tools/what_to_run.sh +++ b/mobile/tools/what_to_run.sh @@ -5,7 +5,7 @@ set -euo pipefail BRANCH_NAME="$GITHUB_REF_NAME" BASE_COMMIT="$(git merge-base origin/main HEAD)" CHANGED_FILES="$(git diff "${BASE_COMMIT}" --name-only)" -CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml' +CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml^tools/code_format/check_format.py' # The logic in this file is roughly: # diff --git a/tools/code_format/paths.py b/tools/code_format/paths.py deleted file mode 100644 index 03530e66a3005..0000000000000 --- a/tools/code_format/paths.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -import os.path -import shutil - - -def get_buildifier(): - return os.getenv("BUILDIFIER_BIN") or ( - os.path.expandvars("$GOPATH/bin/buildifier") - if os.getenv("GOPATH") else shutil.which("buildifier")) - - -def get_buildozer(): - return os.getenv("BUILDOZER_BIN") or ( - os.path.expandvars("$GOPATH/bin/buildozer") - if os.getenv("GOPATH") else shutil.which("buildozer")) From fda5e0ba64af1b5422b1f4367476c925455d365e Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 7 Sep 2023 18:29:43 +0100 Subject: [PATCH 045/274] ci/cache: Use `.bazelrc` for docker cache key (#29492) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 2 +- .azure-pipelines/pipelines.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index c204ec5076fcf..edde8c5e0748a 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -11,7 +11,7 @@ parameters: # caching - name: cacheKeyDocker type: string - default: ".devcontainer/Dockerfile" + default: ".bazelrc" - name: cacheKeyVersion type: string default: $(cacheKeyVersion) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 8d766d900c1aa..6fe40d65f65fa 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -51,7 +51,7 @@ variables: - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker - value: ".devcontainer/Dockerfile" + value: ".bazelrc" # Docker build uses separate docker cache - name: cacheKeyDockerBuild # VERSION.txt is included to refresh Docker images for release From 9e9c1ab490ae4f761e31125c2b3eddbbc2e5013c Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 7 Sep 2023 19:33:20 +0100 Subject: [PATCH 046/274] ci/cache: Fix cache priming for Docker only (#29494) Signed-off-by: Ryan Northey --- .azure-pipelines/docker/prime_cache.sh | 13 ++++++++++++- .azure-pipelines/pipelines.yml | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/docker/prime_cache.sh b/.azure-pipelines/docker/prime_cache.sh index 8f8a0af6190c6..2ec003d16d951 100755 --- a/.azure-pipelines/docker/prime_cache.sh +++ b/.azure-pipelines/docker/prime_cache.sh @@ -33,6 +33,18 @@ fi echo "===================================================" echo +echo +echo "================ Docker fetch ======================" +if [[ "$DOCKER_RESTORED" != "true" ]]; then + echo "Fetching Docker" + ./ci/run_envoy_docker.sh uname -a + docker images +else + echo "Not fetching Docker as it was restored" +fi +echo "===================================================" +echo + echo echo "================ Bazel fetch ======================" # Fetch bazel dependencies @@ -45,7 +57,6 @@ fi echo "===================================================" echo -docker images df -h echo diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 6fe40d65f65fa..2325dbe4fc1ba 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -47,7 +47,7 @@ variables: - name: cacheKeyName value: envoy - name: cacheKeyVersion - value: v1 + value: v2 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker From b143955e916bb846f6c23bf1ece904d569f065d2 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 Sep 2023 19:14:03 +0100 Subject: [PATCH 047/274] mac/ci: Retry flakey bazel invokation (#29525) Signed-off-by: Ryan Northey --- ci/mac_ci_setup.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ci/mac_ci_setup.sh b/ci/mac_ci_setup.sh index ee2e3f029522d..2646a366e9572 100755 --- a/ci/mac_ci_setup.sh +++ b/ci/mac_ci_setup.sh @@ -29,20 +29,23 @@ function install { } function retry () { - local returns=1 i=1 - while ((i<=HOMEBREW_RETRY_ATTEMPTS)); do + local returns=1 i=1 attempts + attempts="${1}" + interval="${2}" + shift 2 + while ((i<=attempts)); do if "$@"; then returns=0 break else - sleep "$HOMEBREW_RETRY_INTERVAL"; + sleep "$interval"; ((i++)) fi done return "$returns" } -if ! retry brew update; then +if ! retry "$HOMEBREW_RETRY_ATTEMPTS" "$HOMEBREW_RETRY_INTERVAL" brew update; then # Do not exit early if update fails. echo "Failed to update homebrew" fi @@ -53,4 +56,4 @@ do is_installed "${DEP}" || install "${DEP}" done -bazel version +retry 5 2 bazel version From 4630d3553e7a6bfa92aca94daed77d7f3946700b Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 Sep 2023 20:40:30 +0100 Subject: [PATCH 048/274] ci/caching: Optimize bazel cache/fetch (#28977) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 69 +++++++++++++++- .azure-pipelines/cached.yml | 26 ++++-- .azure-pipelines/docker/create_cache.sh | 15 +++- .azure-pipelines/docker/load_caches.sh | 18 ++-- .azure-pipelines/docker/prepare_cache.sh | 9 +- .azure-pipelines/docker/prime_cache.sh | 4 +- .azure-pipelines/docker/save_cache.sh | 45 +++++----- .azure-pipelines/pipelines.yml | 10 +-- .azure-pipelines/stage/checks.yml | 25 +++--- .azure-pipelines/stage/linux.yml | 3 +- .azure-pipelines/stage/prechecks.yml | 1 + .azure-pipelines/stage/publish.yml | 48 +++++------ .azure-pipelines/stage/verify.yml | 10 ++- ci/do_ci.sh | 100 ++++++++++++++++------- 14 files changed, 259 insertions(+), 124 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index edde8c5e0748a..db6de8c31567a 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -2,7 +2,7 @@ parameters: - name: ciTarget displayName: "CI target" type: string - default: bazel.release + default: release - name: artifactSuffix displayName: "Suffix of artifact" type: string @@ -18,6 +18,9 @@ parameters: - name: pathCacheTemp type: string default: $(pathCacheTemp) +- name: cacheName + type: string + default: - name: tmpfsCacheDisabled type: string @@ -145,19 +148,25 @@ steps: - bash: | set -e CACHE_DIRS=( + "$(Build.StagingDirectory)/envoy" "$(Build.StagingDirectory)/.cache/" "$(Build.StagingDirectory)/bazel_root/install/" "$(Build.StagingDirectory)/repository_cache/" "$(Build.StagingDirectory)/bazel_root/base/external") sudo mkdir -p "${CACHE_DIRS[@]}" - sudo chown -R vsts:vsts "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ - echo "Created bazel cache directories: "${CACHE_DIRS[*]}"" + if id -u vsts &> /dev/null; then + sudo chown -R vsts:vsts "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ + else + sudo chown -R azure-pipelines:azure-pipelines "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ + fi + echo "Created bazel directories: "${CACHE_DIRS[*]}"" displayName: "Create bazel directories" - condition: and(succeeded(), eq('${{ parameters.managedAgent }}', true), eq('${{ parameters.tmpfsDockerDisabled }}', true)) + condition: and(succeeded(), eq('${{ parameters.tmpfsDockerDisabled }}', true)) # Caching - template: cached.yml parameters: + cacheName: "${{ parameters.cacheName }}" keyBazel: "${{ parameters.cacheKeyBazel }}" keyDocker: "${{ parameters.cacheKeyDocker }}" pathDockerBind: "${{ parameters.pathDockerBind }}" @@ -166,6 +175,36 @@ steps: tmpfsDisabled: "${{ parameters.tmpfsCacheDisabled }}" tmpfsDockerDisabled: "${{ parameters.tmpfsDockerDisabled }}" +- script: | + if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then + unset GOOGLE_BES_PROJECT_ID + fi + ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' + condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + workingDirectory: $(Build.SourcesDirectory) + env: + ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) + GITHUB_TOKEN: "${{ parameters.authGithub }}" + BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" + ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" + ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" + # Any PR or CI run in envoy-presubmit uses the fake SCM hash + ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: + # sha1sum of `ENVOY_PULL_REQUEST` + BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 + ${{ if parameters.rbe }}: + GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) + ENVOY_RBE: "1" + BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" + ${{ if eq(parameters.rbe, false) }}: + BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" + BAZEL_REMOTE_CACHE: $(LocalBuildCache) + ${{ each var in parameters.env }}: + ${{ var.key }}: ${{ var.value }} + displayName: "Fetch assets (${{ parameters.ciTarget }})" + - ${{ each step in parameters.stepsPre }}: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} @@ -173,6 +212,13 @@ steps: - bash: | echo "disk space at beginning of build:" df -h + if [[ -e "$(Build.StagingDirectory)/bazel_root/base/external" ]]; then + du -sh "$(Build.StagingDirectory)/bazel_root/base/external" + fi + if [[ -e "$(Build.StagingDirectory)/repository_cache" ]]; then + du -sh "$(Build.StagingDirectory)/repository_cache" + fi + displayName: "Check disk space at beginning" - bash: | @@ -228,6 +274,9 @@ steps: cp -a $hprof $(Build.StagingDirectory)/envoy/hprof done + du -sh "$(Build.StagingDirectory)"/bazel_root/base/external + du -sh "$(Build.StagingDirectory)"/repository_cache + cp -a "$(Build.StagingDirectory)/bazel_root/base/server/jvm.out" $(Build.StagingDirectory)/envoy if [[ "${{ parameters.artifactSuffix }}" == ".arm64" ]]; then @@ -247,6 +296,18 @@ steps: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} +- script: | + set -e + sudo .azure-pipelines/docker/save_cache.sh "$(Build.StagingDirectory)" /mnt/cache/all true true + if id -u vsts &> /dev/null; then + sudo chown -R vsts:vsts /mnt/cache/all + else + sudo chown -R azure-pipelines:azure-pipelines /mnt/cache/all + fi + + displayName: "Cache/save (${{ parameters.cacheName}})" + condition: and(succeeded(), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + - task: PublishTestResults@2 inputs: testResultsFiles: "**/bazel-out/**/testlogs/**/test.xml" diff --git a/.azure-pipelines/cached.yml b/.azure-pipelines/cached.yml index acbf9e4864914..f284a1fc99756 100644 --- a/.azure-pipelines/cached.yml +++ b/.azure-pipelines/cached.yml @@ -1,14 +1,14 @@ parameters: -- name: name - type: string - default: $(cacheKeyName) - name: arch type: string default: "" - name: version type: string default: $(cacheKeyVersion) +- name: cacheName + type: string + default: - name: keyDocker type: string @@ -45,20 +45,32 @@ steps: displayName: "Cache/prepare" - task: Cache@2 + condition: and(not(canceled()), ne('${{ parameters.cacheName }}', '')) + env: + VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" + displayName: "Cache (${{ parameters.cacheName }})" + inputs: + key: '${{ parameters.cacheName }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }} | ${{ parameters.keyBazel }}' + path: "${{ parameters.pathTemp }}/all" + cacheHitVar: CACHE_RESTORED + +- task: Cache@2 + condition: and(not(canceled()), not(failed()), or(ne(variables.CACHE_RESTORED, 'true'), eq('${{ parameters.cacheName }}', ''))) env: VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" displayName: "Cache (Docker)" inputs: - key: '${{ parameters.name }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }}' + key: '"${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }} | docker' path: "${{ parameters.pathTemp }}/docker" cacheHitVar: DOCKER_CACHE_RESTORED - task: Cache@2 + condition: and(not(canceled()), not(failed()), or(ne(variables.CACHE_RESTORED, 'true'), eq('${{ parameters.cacheName }}', ''))) env: VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" displayName: "Cache (Bazel)" inputs: - key: '${{ parameters.name }} | bazel | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazel }}' + key: '"${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazel }} | bazel' path: "${{ parameters.pathTemp }}/bazel" cacheHitVar: BAZEL_CACHE_RESTORED @@ -67,9 +79,9 @@ steps: env: DOCKER_RESTORED: $(DOCKER_CACHE_RESTORED) BAZEL_RESTORED: $(BAZEL_CACHE_RESTORED) - displayName: "Cache/prime (${{ parameters.name }})" + displayName: "Cache/prime (Docker/Bazel)" # TODO(phlax): figure if there is a way to test cache without downloading it - condition: and(not(canceled()), eq(${{ parameters.prime }}, true), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) + condition: and(not(canceled()), eq(${{ parameters.prime }}, true), eq('${{ parameters.cacheName }}', ''), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) # Load the caches for a job - script: sudo .azure-pipelines/docker/load_caches.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.pathDockerBind }}" "${{ parameters.tmpfsDockerDisabled }}" diff --git a/.azure-pipelines/docker/create_cache.sh b/.azure-pipelines/docker/create_cache.sh index 4079df028f4c2..e9d9f55b071c7 100755 --- a/.azure-pipelines/docker/create_cache.sh +++ b/.azure-pipelines/docker/create_cache.sh @@ -3,7 +3,8 @@ set -o pipefail CACHE_TARBALL="${1}" -shift +ROOT_DIR="${2}" +shift 2 echo "Exporting ${*} -> ${CACHE_TARBALL}" @@ -12,9 +13,15 @@ mkdir -p "$CACHE_PATH" CACHE_ARGS=() for path in "$@"; do - total="$(du -sh "$path" | cut -f1)" - echo "Adding cache dir (${path}): ${total}" - CACHE_ARGS+=(-C "$path" .) + if [[ "$ROOT_DIR" == "." ]]; then + total="$(du -sh "$path" | cut -f1)" + echo "Adding cache dir (${path}): ${total}" + CACHE_ARGS+=(-C "$path" .) + else + total="$(du -sh "${ROOT_DIR}/$path" | cut -f1)" + echo "Adding cache dir (${ROOT_DIR}/${path}): ${total}" + CACHE_ARGS+=(-C "$ROOT_DIR" "$path") + fi done tar cf - "${CACHE_ARGS[@]}" | zstd - -q -T0 -o "$CACHE_TARBALL" diff --git a/.azure-pipelines/docker/load_caches.sh b/.azure-pipelines/docker/load_caches.sh index 56e4089fb9448..73c03425cfd53 100755 --- a/.azure-pipelines/docker/load_caches.sh +++ b/.azure-pipelines/docker/load_caches.sh @@ -11,10 +11,15 @@ if [[ -z "$CACHE_PATH" ]]; then exit 1 fi -DOCKER_CACHE_PATH="${CACHE_PATH}/docker" -DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" +if [[ -e "${CACHE_PATH}/all" ]]; then + DOCKER_CACHE_PATH="${CACHE_PATH}/all" + BAZEL_CACHE_PATH="${CACHE_PATH}/all" +else + DOCKER_CACHE_PATH="${CACHE_PATH}/docker" + BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" +fi -BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" +DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" BAZEL_CACHE_TARBALL="${BAZEL_CACHE_PATH}/bazel.tar.zst" @@ -42,9 +47,7 @@ remount_docker () { extract_docker () { if [[ -e "${DOCKER_CACHE_TARBALL}" ]]; then echo "Extracting docker cache ${DOCKER_CACHE_TARBALL} -> /var/lib/docker ..." - ls -alh "$DOCKER_CACHE_TARBALL" zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C /var/lib/docker - touch /tmp/DOCKER_CACHE_RESTORED else echo "No Docker cache to restore, starting Docker with no data" fi @@ -54,6 +57,11 @@ extract_bazel () { if [[ -e "${BAZEL_CACHE_TARBALL}" ]]; then echo "Extracting bazel cache ${BAZEL_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR} ..." zstd --stdout -d "$BAZEL_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}" + if id -u vsts &> /dev/null; then + sudo chown -R vsts:vsts "${ENVOY_DOCKER_BUILD_DIR}" + else + sudo chown -R azure-pipelines:azure-pipelines "${ENVOY_DOCKER_BUILD_DIR}" + fi else echo "No bazel cache to restore, starting bazel with no data" fi diff --git a/.azure-pipelines/docker/prepare_cache.sh b/.azure-pipelines/docker/prepare_cache.sh index 792b9f8f56843..ff3a07ffbc934 100755 --- a/.azure-pipelines/docker/prepare_cache.sh +++ b/.azure-pipelines/docker/prepare_cache.sh @@ -3,7 +3,6 @@ DOCKER_CACHE_PATH="$1" NO_MOUNT_TMPFS="${2:-}" DOCKER_CACHE_OWNERSHIP="vsts:vsts" -TMPFS_SIZE=5G if [[ -z "$DOCKER_CACHE_PATH" ]]; then echo "prepare_docker_cache called without path arg" >&2 @@ -14,6 +13,14 @@ if ! id -u vsts &> /dev/null; then DOCKER_CACHE_OWNERSHIP=azure-pipelines fi +tmpfs_size () { + # Make this 2/3 of total memory + total_mem="$(grep MemTotal /proc/meminfo | cut -d' ' -f2- | xargs | cut -d' ' -f1)" + bc <<< "$total_mem"*2/3*1024 +} + +TMPFS_SIZE="$(tmpfs_size)" + echo "Creating cache directory (${DOCKER_CACHE_PATH}) ..." mkdir -p "${DOCKER_CACHE_PATH}" if [[ -z "$NO_MOUNT_TMPFS" ]]; then diff --git a/.azure-pipelines/docker/prime_cache.sh b/.azure-pipelines/docker/prime_cache.sh index 2ec003d16d951..368c9a8aa319d 100755 --- a/.azure-pipelines/docker/prime_cache.sh +++ b/.azure-pipelines/docker/prime_cache.sh @@ -65,11 +65,11 @@ echo "================ Save caches ======================" if [[ "$DOCKER_RESTORED" != "true" ]]; then echo "Stopping docker" sudo systemctl stop docker docker.socket - sudo ./.azure-pipelines/docker/create_cache.sh "${DOCKER_CACHE_TARBALL}" /var/lib/docker + sudo ./.azure-pipelines/docker/create_cache.sh "${DOCKER_CACHE_TARBALL}" . /var/lib/docker fi if [[ "$BAZEL_RESTORED" != "true" ]]; then - sudo ./.azure-pipelines/docker/create_cache.sh "${BAZEL_CACHE_TARBALL}" "${BAZEL_PATH}" + sudo ./.azure-pipelines/docker/create_cache.sh "${BAZEL_CACHE_TARBALL}" . "${BAZEL_PATH}" fi sudo chmod o+r -R "${CACHE_PATH}" echo "===================================================" diff --git a/.azure-pipelines/docker/save_cache.sh b/.azure-pipelines/docker/save_cache.sh index 462177b3f03f5..f51fa1006b7d5 100755 --- a/.azure-pipelines/docker/save_cache.sh +++ b/.azure-pipelines/docker/save_cache.sh @@ -1,37 +1,42 @@ #!/bin/bash -e set -o pipefail +ENVOY_DOCKER_BUILD_DIR="$1" +CACHE_PATH="$2" +NO_MOUNT_TMPFS="${3:-}" +CACHE_BAZEL="${4:-}" -DOCKER_CACHE_PATH="$1" -NO_MOUNT_TMPFS="${2:-}" - -if [[ -z "$DOCKER_CACHE_PATH" ]]; then +if [[ -z "$CACHE_PATH" ]]; then echo "prime_docker_cache called without path arg" >&2 exit 1 fi -if [[ -e /tmp/DOCKER_CACHE_RESTORED ]]; then - echo "Not saving cache as it was restored" - exit 0 -fi - -DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" +DOCKER_CACHE_TARBALL="${CACHE_PATH}/docker.tar.zst" +BAZEL_CACHE_TARBALL="${CACHE_PATH}/bazel.tar.zst" docker images echo "Stopping Docker ..." -systemctl stop docker +systemctl stop docker docker.socket -echo "Creating directory to save tarball: ${DOCKER_CACHE_PATH}" -mkdir -p "$DOCKER_CACHE_PATH" +echo "Creating directory to save tarball: ${CACHE_PATH}" +mkdir -p "$CACHE_PATH" if [[ -z "$NO_MOUNT_TMPFS" ]]; then - echo "Mount tmpfs directory: ${DOCKER_CACHE_PATH}" - mount -t tmpfs none "$DOCKER_CACHE_PATH" + echo "Mount tmpfs directory: ${CACHE_PATH}" + mount -t tmpfs none "$CACHE_PATH" fi -echo "Creating tarball: /var/lib/docker -> ${DOCKER_CACHE_TARBALL}" -tar cf - -C /var/lib/docker . | zstd - -T0 -o "$DOCKER_CACHE_TARBALL" - -echo "Docker cache tarball created: ${DOCKER_CACHE_TARBALL}" -ls -lh "$DOCKER_CACHE_TARBALL" +./.azure-pipelines/docker/create_cache.sh \ + "${DOCKER_CACHE_TARBALL}" \ + . \ + /var/lib/docker + +if [[ "$CACHE_BAZEL" == "true" ]]; then + ./.azure-pipelines/docker/create_cache.sh \ + "${BAZEL_CACHE_TARBALL}" \ + "${ENVOY_DOCKER_BUILD_DIR}" \ + bazel_root/install \ + bazel_root/base/external \ + repository_cache +fi diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 2325dbe4fc1ba..69e784d00eef2 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -44,20 +44,12 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) -- name: cacheKeyName - value: envoy - name: cacheKeyVersion - value: v2 + value: v0 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker value: ".bazelrc" -# Docker build uses separate docker cache -- name: cacheKeyDockerBuild - # VERSION.txt is included to refresh Docker images for release - value: '"publish_docker" | ci/Dockerfile-envoy | VERSION.txt' -- name: cacheKeyDockerBuildVersion - value: v0 - name: pathCacheTemp value: /mnt/cache diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index 09bec400b6ee3..50fdb0956cdae 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -43,43 +43,44 @@ jobs: matrix: # These are ordered by most time-consuming first. coverage: - CI_TARGET: "bazel.coverage" + CI_TARGET: "coverage" fuzz_coverage: - CI_TARGET: "bazel.fuzz_coverage" + CI_TARGET: "fuzz_coverage" compile_time_options: - CI_TARGET: "bazel.compile_time_options" + CI_TARGET: "compile_time_options" ENVOY_FILTER_EXAMPLE: true tsan: - CI_TARGET: "bazel.tsan" + CI_TARGET: "tsan" asan: - CI_TARGET: "bazel.asan" + CI_TARGET: "asan" ENVOY_FILTER_EXAMPLE: true # Disabled due to https://github.com/envoyproxy/envoy/pull/18218 # api_compat: - # CI_TARGET: "bazel.api_compat" + # CI_TARGET: "api_compat" gcc: - CI_TARGET: "bazel.gcc" + CI_TARGET: "gcc" msan: - CI_TARGET: "bazel.msan" + CI_TARGET: "msan" ENVOY_FILTER_EXAMPLE: true # # Temporarily disabled to facilitate release CI, should be resolved # as part of https://github.com/envoyproxy/envoy/issues/28566 # # clang_tidy: - # CI_TARGET: "bazel.clang_tidy" + # CI_TARGET: "clang_tidy" # REPO_FETCH_DEPTH: 0 # REPO_FETCH_TAGS: true # PUBLISH_TEST_RESULTS: false # PUBLISH_ENVOY: false api: - CI_TARGET: "bazel.api" + CI_TARGET: "api" timeoutInMinutes: 180 pool: envoy-x64-small steps: - template: ../bazel.yml parameters: ciTarget: $(CI_TARGET) + cacheName: $(CI_TARGET) envoyBuildFilterExample: $(ENVOY_FILTER_EXAMPLE) cacheTestResults: ${{ parameters.cacheTestResults }} managedAgent: false @@ -95,10 +96,10 @@ jobs: pathtoPublish: "$(Build.StagingDirectory)/tmp/lint-fixes" artifactName: "$(CI_TARGET).fixes" timeoutInMinutes: 10 - condition: and(failed(), eq(variables['CI_TARGET'], 'bazel.clang_tidy')) + condition: and(failed(), eq(variables['CI_TARGET'], 'clang_tidy')) - script: ci/run_envoy_docker.sh 'ci/do_ci.sh $(CI_TARGET)-upload' displayName: "Upload $(CI_TARGET) Report to GCS" - condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'bazel.coverage'), eq(variables['CI_TARGET'], 'bazel.fuzz_coverage'))) + condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'coverage'), eq(variables['CI_TARGET'], 'fuzz_coverage'))) env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index d35f67094b5f1..be4e4a6ada149 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -48,7 +48,8 @@ jobs: - template: ../bazel.yml parameters: managedAgent: ${{ parameters.managedAgent }} - ciTarget: bazel.release + ciTarget: release + cacheName: "release" bazelBuildExtraOptions: ${{ parameters.bazelBuildExtraOptions }} cacheTestResults: ${{ parameters.cacheTestResults }} cacheVersion: $(cacheKeyBazel) diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 6df51ab8d569c..408a322e45cc3 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -47,6 +47,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: $(CI_TARGET) + cacheName: $(CI_TARGET) cacheTestResults: ${{ parameters.cacheTestResults }} cacheVersion: $(cacheKeyBazel) publishEnvoy: false diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 98143a23bb463..b04013b69721b 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -101,18 +101,16 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/**/bin/*" + artifactName: "release" + itemPattern: "release/**/bin/*" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: ciTarget: docker-upload + cacheName: docker-upload publishEnvoy: false publishTestResults: false pathDockerBind: "" - cacheKeyDocker: "$(cacheKeyDockerBuild)" - cacheKeyVersion: "$(cacheKeyDockerBuildVersion)" - pathCacheTemp: /var/azpcache tmpfsCacheDisabled: true diskspaceHack: true env: @@ -128,12 +126,12 @@ jobs: mkdir -p linux/amd64 linux/arm64 # x64 - cp -a $(Build.StagingDirectory)/bazel.release/x64/bin/release.tar.zst linux/amd64/release.tar.zst - cp -a $(Build.StagingDirectory)/bazel.release/x64/bin/schema_validator_tool linux/amd64/schema_validator_tool + cp -a $(Build.StagingDirectory)/release/x64/bin/release.tar.zst linux/amd64/release.tar.zst + cp -a $(Build.StagingDirectory)/release/x64/bin/schema_validator_tool linux/amd64/schema_validator_tool # arm64 - cp -a $(Build.StagingDirectory)/bazel.release/arm64/bin/release.tar.zst linux/arm64/release.tar.zst - cp -a $(Build.StagingDirectory)/bazel.release/arm64/bin/schema_validator_tool linux/arm64/schema_validator_tool + cp -a $(Build.StagingDirectory)/release/arm64/bin/release.tar.zst linux/arm64/release.tar.zst + cp -a $(Build.StagingDirectory)/release/arm64/bin/schema_validator_tool linux/arm64/schema_validator_tool # Debug what files appear to have been downloaded find linux -type f -name "*" | xargs ls -l @@ -148,13 +146,6 @@ jobs: DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} DOCKER_BUILD_TIMEOUT: ${{ parameters.timeoutDockerBuild }} - stepsPost: - - script: | - set -e - sudo .azure-pipelines/docker/save_cache.sh /var/azpcache/docker true - sudo rm -rf /var/lib/docker - displayName: "Cache/save (publish_docker)" - condition: and(succeeded(), ne(variables.DOCKER_CACHE_RESTORED, 'true')) - job: package_x64 displayName: Linux debs (x64) @@ -169,12 +160,13 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/x64/bin/*" + artifactName: "release" + itemPattern: "release/x64/bin/*" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: - ciTarget: bazel.distribution + ciTarget: distribution + cacheName: distribution publishTestResults: false stepsPre: - template: ../gpg.yml @@ -201,14 +193,15 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/arm64/bin/*" + artifactName: "release" + itemPattern: "release/arm64/bin/*" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: managedAgent: false - ciTarget: bazel.distribution + ciTarget: distribution + cacheName: distribution rbe: false artifactSuffix: ".arm64" bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" @@ -239,6 +232,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: docs + cacheName: docs cacheVersion: $(cacheKeyBazel) publishEnvoy: false publishTestResults: false @@ -315,18 +309,19 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/**/bin/*" + artifactName: "release" + itemPattern: "release/**/bin/*" targetPath: $(Build.StagingDirectory) - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.distribution" - itemPattern: "bazel.distribution/**/packages.*.tar.gz" + artifactName: "distribution" + itemPattern: "distribution/**/packages.*.tar.gz" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: ciTarget: release.signed + cacheName: release-signed publishTestResults: false env: GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} @@ -370,6 +365,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: verify.trigger + cacheName: verify-trigger authGithub: "$(key.value)" cacheVersion: $(cacheKeyBazel) publishEnvoy: false diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index f8f2e426fc08a..2214bee7971e3 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -18,13 +18,14 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.distribution" - itemPattern: "bazel.distribution/x64/packages.x64.tar.gz" + artifactName: "distribution" + itemPattern: "distribution/x64/packages.x64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: ciTarget: verify_distro + cacheName: verify_distro publishTestResults: false env: ENVOY_DOCKER_IN_DOCKER: 1 @@ -38,14 +39,15 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.distribution" - itemPattern: "bazel.distribution/arm64/packages.arm64.tar.gz" + artifactName: "distribution" + itemPattern: "distribution/arm64/packages.arm64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: managedAgent: false ciTarget: verify_distro + cacheName: verify_distro rbe: false artifactSuffix: ".arm64" publishTestResults: false diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 0e5cec26719d0..e8bc752c41285 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -30,6 +30,44 @@ FETCH_TARGETS=( @envoy_api//... @envoy_build_tools//...) +FETCH_TARGETS=( + @bazel_tools//tools/jdk:remote_jdk11 + @com_github_bufbuild_buf//:bin/buf + @envoy_build_tools//... + //docs/... + //tools/proto_format/... + //tools/zstd + //tools/gsutil + //tools/code_format/...) + +FETCH_BUILD_TARGETS=( + @com_github_google_quiche//:ci_tests + //contrib/exe/... + //distribution/... + //source/exe/... + //test/tools/schema_validator/... + //test/...) + +retry () { + local n wait iterations + wait="${1}" + iterations="${2}" + shift 2 + n=0 + until [ "$n" -ge "$iterations" ]; do + "${@}" \ + && break + n=$((n+1)) + if [[ "$n" -lt "$iterations" ]]; then + sleep "$wait" + echo "Retrying ..." + else + echo "Fetch failed" + exit 1 + fi + done +} + if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then BUILD_ARCH_DIR="/linux/amd64" @@ -181,25 +219,27 @@ function run_ci_verify () { CI_TARGET=$1 shift +if [[ "$CI_TARGET" =~ bazel.* ]]; then + ORIG_CI_TARGET="$CI_TARGET" + CI_TARGET="$(echo "${CI_TARGET}" | cut -d. -f2-)" + echo "Using \`${ORIG_CI_TARGET}\` is deprecated, please use \`${CI_TARGET}\`" +fi + if [[ $# -ge 1 ]]; then COVERAGE_TEST_TARGETS=("$@") TEST_TARGETS=("$@") else # Coverage test will add QUICHE tests by itself. COVERAGE_TEST_TARGETS=("//test/...") - if [[ "$CI_TARGET" == "bazel.release" ]]; then + if [[ "${CI_TARGET}" == "release" ]]; then # We test contrib on release only. COVERAGE_TEST_TARGETS=("${COVERAGE_TEST_TARGETS[@]}" "//contrib/...") - elif [[ "${CI_TARGET}" == "bazel.msan" ]]; then + elif [[ "${CI_TARGET}" == "msan" ]]; then COVERAGE_TEST_TARGETS=("${COVERAGE_TEST_TARGETS[@]}" "-//test/extensions/...") fi TEST_TARGETS=("${COVERAGE_TEST_TARGETS[@]}" "@com_github_google_quiche//:ci_tests") fi -if [[ "$CI_TARGET" =~ bazel.* ]]; then - CI_TARGET="$(echo "${CI_TARGET}" | cut -d. -f2-)" -fi - case $CI_TARGET in api) # Use libstdc++ because the API booster links to prebuilt libclang*/libLLVM* installed in /opt/llvm/lib, @@ -509,9 +549,9 @@ case $CI_TARGET in # Extract the Envoy binary from the tarball mkdir -p distribution/custom if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then - ENVOY_RELEASE_TARBALL="/build/bazel.release/x64/bin/release.tar.zst" + ENVOY_RELEASE_TARBALL="/build/release/x64/bin/release.tar.zst" else - ENVOY_RELEASE_TARBALL="/build/bazel.release/arm64/bin/release.tar.zst" + ENVOY_RELEASE_TARBALL="/build/release/arm64/bin/release.tar.zst" fi bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ //tools/zstd \ @@ -567,30 +607,32 @@ case $CI_TARGET in cat bazel-bin/distribution/dockerhub/readme.md ;; - fetch) + fetch|fetch-*) + case $CI_TARGET in + fetch) + targets=("${FETCH_TARGETS[@]}") + ;; + fetch-release) + targets=("${FETCH_BUILD_TARGETS[@]}") + ;; + *) + exit 0 + ;; + esac setup_clang_toolchain - echo "Fetching ${FETCH_TARGETS[*]} ..." FETCH_ARGS=( --noshow_progress --noshow_loading_progress) - # TODO(phlax): separate out retry logic - n=0 - until [ "$n" -ge 10 ]; do - bazel fetch "${BAZEL_GLOBAL_OPTIONS[@]}" \ - "${FETCH_ARGS[@]}" \ - "${FETCH_TARGETS[@]}" \ - && break - n=$((n+1)) - if [[ "$n" -lt 10 ]]; then - sleep 15 - echo "Retrying fetch ..." - else - echo "Fetch failed" - exit 1 - fi - done + echo "Fetching ${targets[*]} ..." + retry 15 10 bazel \ + fetch \ + "${BAZEL_GLOBAL_OPTIONS[@]}" \ + "${FETCH_ARGS[@]}" \ + "${targets[@]}" ;; + + fix_proto_format) # proto_format.sh needs to build protobuf. setup_clang_toolchain @@ -724,7 +766,7 @@ case $CI_TARGET in setup_clang_toolchain # The default config expects these files mkdir -p distribution/custom - cp -a /build/bazel.*/*64 distribution/custom/ + cp -a /build/*/*64 distribution/custom/ bazel build "${BAZEL_BUILD_OPTIONS[@]}" //distribution:signed cp -a bazel-bin/distribution/release.signed.tar.zst "${BUILD_DIR}/envoy/" "${ENVOY_SRCDIR}/ci/upload_gcs_artifact.sh" "${BUILD_DIR}/envoy" release @@ -774,9 +816,9 @@ case $CI_TARGET in # this can be required if any python deps require compilation setup_clang_toolchain if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then - PACKAGE_BUILD=/build/bazel.distribution/x64/packages.x64.tar.gz + PACKAGE_BUILD=/build/distribution/x64/packages.x64.tar.gz else - PACKAGE_BUILD=/build/bazel.distribution/arm64/packages.arm64.tar.gz + PACKAGE_BUILD=/build/distribution/arm64/packages.arm64.tar.gz fi bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ //distribution:verify_packages \ From 336053ffb88f8cf39c96b0510dbe57c0646d38df Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 Sep 2023 20:42:41 +0100 Subject: [PATCH 049/274] tools/ci: Optimize local formatter (#29521) Signed-off-by: Ryan Northey --- ci/do_ci.sh | 1 - tools/local_fix_format.sh | 46 ++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index e8bc752c41285..67aeb96afae02 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -4,7 +4,6 @@ set -e - # TODO(phlax): Clarify and/or integrate SRCDIR and ENVOY_SRCDIR export SRCDIR="${SRCDIR:-$PWD}" export ENVOY_SRCDIR="${ENVOY_SRCDIR:-$PWD}" diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index 0715ca6466dfe..ebaf2d8929ce8 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -44,13 +44,13 @@ fi # Runs the formatting functions on the specified args, echoing commands # if -vergbose was supplied to the script. -function format_one() { +function format_some() { ( if [[ "$verbose" == "1" ]]; then set -x fi - bazel run //tools/code_format:check_format -- fix "${1}" - ./tools/spelling/check_spelling_pedantic.py fix "$1" + bazel run //tools/code_format:check_format -- fix "$@" + ./tools/spelling/check_spelling_pedantic.py fix "$@" ) } @@ -65,25 +65,27 @@ function format_all() { } if [[ $# -gt 0 && "$1" == "-all" ]]; then - echo "Checking all files in the repo...this may take a while." - format_all + echo "Checking all files in the repo...this may take a while." + format_all else - if [[ $# -gt 0 && "$1" == "-main" ]]; then - shift - echo "Checking all files that have changed since the main branch." - args=$(git diff main | grep ^diff | awk '{print $3}' | cut -c 3-) - elif [[ $# == 0 ]]; then - args=$(git status|grep -E '(modified:|added:)'|awk '{print $2}') - args+=$(git status|grep -E 'new file:'|awk '{print $3}') - else - args="$*" - fi + if [[ $# -gt 0 && "$1" == "-main" ]]; then + shift + echo "Checking all files that have changed since the main branch." + args=$(git diff main | grep ^diff | awk '{print $3}' | cut -c 3-) + elif [[ $# == 0 ]]; then + args=$(git status|grep -E '(modified:|added:)'|awk '{print $2}') + args+=$(git status|grep -E 'new file:'|awk '{print $3}') + else + args="$*" + fi + + if [[ -z "$args" ]]; then + echo No files selected. Bailing out. + exit 0 + fi + + _changes="$(echo "$args" | tr '\n' ' ')" + IFS=' ' read -ra changes <<< "$_changes" - if [[ "$args" == "" ]]; then - echo No files selected. Bailing out. - exit 0 - fi - for arg in $args; do - format_one "$arg" - done + format_some "${changes[@]}" fi From c8e3c89b834469cd372a3c1749b8d6a19322f426 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 7 Sep 2023 20:18:46 +0100 Subject: [PATCH 050/274] deps/tooling: Bump `envoy.code.check` -> 0.5.5 (#29488) Signed-off-by: Ryan Northey --- source/extensions/extensions_metadata.yaml | 4 ++++ tools/base/requirements.in | 2 +- tools/base/requirements.txt | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index f1d743fd6e66c..a22fdee316f83 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -184,6 +184,7 @@ envoy.filters.http.admission_control: - envoy.filters.http.upstream security_posture: unknown status: stable + status_upstream: alpha type_urls: - envoy.extensions.filters.http.admission_control.v3.AdmissionControl envoy.filters.http.alternate_protocols_cache: @@ -222,6 +223,7 @@ envoy.filters.http.buffer: - envoy.filters.http.upstream security_posture: robust_to_untrusted_downstream status: stable + status_upstream: stable type_urls: - envoy.extensions.filters.http.buffer.v3.Buffer - envoy.extensions.filters.http.buffer.v3.BufferPerRoute @@ -251,6 +253,7 @@ envoy.filters.http.upstream_codec: - envoy.filters.http.upstream security_posture: robust_to_untrusted_downstream_and_upstream status: stable + status_upstream: stable type_urls: - envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec envoy.filters.http.composite: @@ -525,6 +528,7 @@ envoy.filters.http.header_mutation: - envoy.filters.http.upstream security_posture: unknown status: alpha + status_upstream: alpha type_urls: - envoy.extensions.filters.http.header_mutation.v3.HeaderMutation - envoy.extensions.filters.http.header_mutation.v3.HeaderMutationPerRoute diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 86532c679c851..81e3d3926be2c 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -7,7 +7,7 @@ coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 envoy.base.utils>=0.4.11 -envoy.code.check>=0.5.4 +envoy.code.check>=0.5.7 envoy.dependency.check>=0.1.7 envoy.distribution.release>=0.0.9 envoy.distribution.repo>=0.0.8 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 70f6f21bac8bc..2420ae8201628 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -439,9 +439,9 @@ envoy-base-utils==0.4.11 \ # envoy-docs-sphinx-runner # envoy-github-release # envoy-gpg-sign -envoy-code-check==0.5.4 \ - --hash=sha256:b3c338a0e607960ea75eb8298e786548d317655ac4c89d89b259395684eaf134 \ - --hash=sha256:ec919ea1e5523c5ad669f6601bb58c8da77bc1891c8846950add3b563c629ac5 +envoy-code-check==0.5.7 \ + --hash=sha256:5cd2c5a6a9e4b85bc3342cea58f1b6200ebf5ef926e7b8320a1f73bd49145811 \ + --hash=sha256:628b8ff787278e130cc576302a9f383c3a0a645841e5f7323c72359dc59c2935 # via -r requirements.in envoy-dependency-check==0.1.8 \ --hash=sha256:ac9820e446bb44e05121e5c93c210f40ca37076580b0d082da2c63e7784c338a \ From 0c494cc2b19608ed1d5a6c5f10b904c947be4feb Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Fri, 8 Sep 2023 17:27:14 -0400 Subject: [PATCH 051/274] tools: Add option to skip bazel for format fix (#29528) tools: Add option to skip bazel for format fix Signed-off-by: Joshua Marantz Signed-off-by: Ryan Northey --- tools/local_fix_format.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index ebaf2d8929ce8..5c29c15c75932 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -35,6 +35,14 @@ if [[ $# -gt 0 && "$1" == "-run-build-setup" ]]; then . ci/build_setup.sh fi +use_bazel=1 +if [[ $# -gt 0 && "$1" == "-skip-bazel" ]]; then + echo -n "WARNING: not using bazel to invoke this script may result in " + echo "mismatched versions and incorrect formatting" + shift + use_bazel=0 +fi + if [[ $# -gt 0 && "$1" == "-verbose" ]]; then verbose=1 shift @@ -49,8 +57,13 @@ function format_some() { if [[ "$verbose" == "1" ]]; then set -x fi - bazel run //tools/code_format:check_format -- fix "$@" - ./tools/spelling/check_spelling_pedantic.py fix "$@" + if [[ "$use_bazel" == "1" ]]; then + bazel run //tools/code_format:check_format -- fix "$@" + else + for arg in "$@"; do + ./tools/spelling/check_spelling_pedantic.py fix "$arg" + done + fi ) } From 286dbe757798a64f57c710e79d878688567df388 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 11 Sep 2023 16:03:41 +0100 Subject: [PATCH 052/274] code/format: Fix/update and shift owner checks -> `envoy.code.check` (#29538) Signed-off-by: Ryan Northey --- BUILD | 2 + CODEOWNERS | 3 +- tools/base/requirements.in | 2 +- tools/base/requirements.txt | 6 +- tools/code/BUILD | 8 + tools/code_format/check_format.py | 560 +++++++++++++----------------- tools/local_fix_format.sh | 31 +- 7 files changed, 270 insertions(+), 342 deletions(-) diff --git a/BUILD b/BUILD index 8e5e07c3073c0..34c8e7a4b6334 100644 --- a/BUILD +++ b/BUILD @@ -6,6 +6,8 @@ exports_files([ ".clang-format", "pytest.ini", ".coveragerc", + "CODEOWNERS", + "OWNERS.md", ]) alias( diff --git a/CODEOWNERS b/CODEOWNERS index d169a56f97ad8..d3ac2b86305fa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -361,6 +361,7 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /contrib/vcl/ @florincoras @KfreeZ /contrib/hyperscan/ @zhxie @soulxu /contrib/language/ @realtimetodie @realtimetodie -/contrib/dlb/ @mattklein123 @daixiang0 +# TODO(phlax): move this extension (https://github.com/envoyproxy/envoy/issues/29550) +/contrib/network/connection_balance/dlb @mattklein123 @daixiang0 /contrib/qat/ @giantcroc @soulxu /contrib/generic_proxy/ @wbpcode @soulxu @zhaohuabing @rojkov @htuch diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 81e3d3926be2c..dac380272b350 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -7,7 +7,7 @@ coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 envoy.base.utils>=0.4.11 -envoy.code.check>=0.5.7 +envoy.code.check>=0.5.8 envoy.dependency.check>=0.1.7 envoy.distribution.release>=0.0.9 envoy.distribution.repo>=0.0.8 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 2420ae8201628..5c7a8a2b0f514 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -439,9 +439,9 @@ envoy-base-utils==0.4.11 \ # envoy-docs-sphinx-runner # envoy-github-release # envoy-gpg-sign -envoy-code-check==0.5.7 \ - --hash=sha256:5cd2c5a6a9e4b85bc3342cea58f1b6200ebf5ef926e7b8320a1f73bd49145811 \ - --hash=sha256:628b8ff787278e130cc576302a9f383c3a0a645841e5f7323c72359dc59c2935 +envoy-code-check==0.5.8 \ + --hash=sha256:03f32588cc9ed98ab6703cbca6f81df1527db71c3a0f962be6a6084ded40d528 \ + --hash=sha256:2b12c51098c78d393823cf055a54e9308c37321d769041f01a2f35b04074d6f3 # via -r requirements.in envoy-dependency-check==0.1.8 \ --hash=sha256:ac9820e446bb44e05121e5c93c210f40ca37076580b0d082da2c63e7784c338a \ diff --git a/tools/code/BUILD b/tools/code/BUILD index 97f87721da641..77466a7b49056 100644 --- a/tools/code/BUILD +++ b/tools/code/BUILD @@ -31,6 +31,8 @@ jq( envoy_entry_point( name = "check", args = [ + "--codeowners=$(location //:CODEOWNERS)", + "--owners=$(location //:OWNERS.md)", "--extensions_build_config=$(location :extensions_build_config)", "--extensions_fuzzed_count=%s" % FUZZ_FILTER_COUNT, "--path=%s" % PATH, @@ -39,6 +41,8 @@ envoy_entry_point( ], data = [ ":extensions_build_config", + "//:CODEOWNERS", + "//:OWNERS.md", "@com_github_aignas_rules_shellcheck//:shellcheck", "@go_sdk//:bin/gofmt", ], @@ -54,6 +58,8 @@ genrule( -l warn \ -v warn \ -x mobile/dist/envoy-pom.xml \ + --codeowners=$(location //:CODEOWNERS) \ + --owners=$(location //:OWNERS.md) \ --extensions_build_config=$(location :extensions_build_config) \ --extensions_fuzzed_count=%s \ --path=%s \ @@ -68,6 +74,8 @@ genrule( tools = [ ":check", ":extensions_build_config", + "//:CODEOWNERS", + "//:OWNERS.md", "//bazel:volatile-scm-hash", "@com_github_aignas_rules_shellcheck//:shellcheck", "@go_sdk//:bin/gofmt", diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index cb133d8c538fe..4933603ec1a6c 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -13,7 +13,7 @@ import traceback import shutil from functools import cached_property -from typing import Callable, Dict, List, Pattern, Tuple, Union +from typing import Callable, Dict, Iterator, List, Pattern, Tuple, Union # The way this script is currently used (ie no bazel) it relies on system deps. # As `pyyaml` is present in `envoy-build-ubuntu` it should be safe to use here. @@ -122,15 +122,109 @@ def _normalize( class FormatChecker: - def __init__(self, args, source_path): - self.args = args - self.config_path = args.config_path - self.operation_type = args.operation_type - self.source_path = source_path - self.target_path = args.target_path - self.api_prefix = args.api_prefix - self.envoy_build_rule_check = not args.skip_envoy_build_rule_check - self._include_dir_order = args.include_dir_order + def __init__(self, args): + self._args = args + # TODO(phlax): completely rewrite file discovery in this file - its a mess + self.source_path = os.getcwd() + if self.args.path: + os.chdir(self.args.path) + os.environ["BAZEL_EXECROOT"] = self.source_path + self._include_dir_order = self.args.include_dir_order + + @property + def api_prefix(self): + return self.args.api_prefix + + @property + def config_path(self): + return self.args.config_path + + @property + def envoy_build_rule_check(self): + return not self.args.skip_envoy_build_rule_check + + @property + def excluded_prefixes(self): + return ( + self.config.paths["excluded"] + tuple(self.args.add_excluded_prefixes) + if self.args.add_excluded_prefixes else self.config.paths["excluded"]) + + @property + def error_messages(self): + return [] + + @property + def operation_type(self): + return self.args.operation_type + + @cached_property + def args(self): + parser = argparse.ArgumentParser(description="Check or fix file format.") + parser.add_argument( + "operation_type", + type=str, + choices=["check", "fix"], + help="specify if the run should 'check' or 'fix' format.") + parser.add_argument( + "target_path", + nargs="*", + default=["."], + help="specify the root directory for the script to recurse over. Default '.'.") + parser.add_argument("--path", default=".", help="specify the root path.") + parser.add_argument( + "--config_path", + default="./tools/code_format/config.yaml", + help="specify the config path. Default './tools/code_format/config.yaml'.") + parser.add_argument( + "--fail_on_diff", + action="store_true", + help="exit with failure if running fix produces changes.") + parser.add_argument( + "--add-excluded-prefixes", type=str, nargs="+", help="exclude additional prefixes.") + parser.add_argument( + "-j", + "--num-workers", + type=int, + default=multiprocessing.cpu_count(), + help="number of worker processes to use; defaults to one per core.") + parser.add_argument( + "--api-prefix", type=str, default="./api/", help="path of the API tree.") + parser.add_argument( + "--skip_envoy_build_rule_check", + action="store_true", + help="skip checking for '@envoy//' prefix in build rules.") + parser.add_argument( + "--namespace_check", + type=str, + nargs="?", + default="Envoy", + help="specify namespace check string. Default 'Envoy'.") + parser.add_argument( + "--namespace_check_excluded_paths", + type=str, + nargs="+", + default=[], + help="exclude paths from the namespace_check.") + parser.add_argument( + "--build_fixer_check_excluded_paths", + type=str, + nargs="+", + default=[], + help="exclude paths from envoy_build_fixer check.") + parser.add_argument( + "--bazel_tools_check_excluded_paths", + type=str, + nargs="+", + default=[], + help="exclude paths from bazel_tools check.") + parser.add_argument("--buildifier_path", type=str, help="Path to buildifier executable.") + parser.add_argument("--buildozer_path", type=str, help="Path to buildozer executable.") + parser.add_argument( + "--include_dir_order", + type=str, + default="", + help="specify the header block include directory order.") + return parser.parse_args(self._args) @cached_property def build_fixer_check_excluded_paths(self): @@ -757,44 +851,38 @@ def fix_build_line(self, file_path, line, line_number): def fix_build_path(self, file_path): self.evaluate_lines(file_path, functools.partial(self.fix_build_line, file_path)) - error_messages = [] # TODO(htuch): Add API specific BUILD fixer script. - if not self.is_build_fixer_excluded_file(file_path) and not self.is_api_file( - file_path) and not self.is_starlark_file(file_path) and not self.is_workspace_file( - file_path): - if os.system("%s %s %s" % - (self.config.paths["build_fixer_py"], file_path, file_path)) != 0: - error_messages += ["envoy_build_fixer rewrite failed for file: %s" % file_path] - - if os.system("%s -lint=fix -mode=fix %s" % (self.config.buildifier_path, file_path)) != 0: - error_messages += ["buildifier rewrite failed for file: %s" % file_path] + if self._run_build_fixer(file_path): + fixer_command = f"{self.config.paths['build_fixer_py']} {file_path} {file_path}" + if os.system(fixer_command) != 0: + error_messages.append(f"envoy_build_fixer rewrite failed for file: {file_path}") + + buildifier_command = f"{self.config.buildifier_path} -lint=fix -mode=fix {file_path}" + if os.system(buildifier_command) != 0: + error_messages.append(f"buildifier rewrite failed for file: {file_path}") return error_messages def check_build_path(self, file_path): error_messages = [] - - if not self.is_build_fixer_excluded_file(file_path) and not self.is_api_file( - file_path) and not self.is_starlark_file(file_path) and not self.is_workspace_file( - file_path): - command = "%s %s | diff %s -" % ( - self.config.paths["build_fixer_py"], file_path, file_path) - error_messages += self.execute_command( - command, "envoy_build_fixer check failed", file_path) - - if self.is_build_file(file_path) and file_path.startswith(self.api_prefix + "envoy"): + if self._run_build_fixer(file_path): + command = f"{self.config.paths['build_fixer_py']} {file_path} | diff {file_path} -" + error_messages.extend( + self.execute_command(command, "envoy_build_fixer check failed", file_path)) + envoy_api_build_file = ( + self.is_build_file(file_path) and file_path.startswith(f"{self.api_prefix}envoy")) + if envoy_api_build_file: found = False for line in self.read_lines(file_path): if "api_proto_package(" in line: found = True break if not found: - error_messages += ["API build file does not provide api_proto_package()"] - - command = "%s -mode=diff %s" % (self.config.buildifier_path, file_path) - error_messages += self.execute_command(command, "buildifier check failed", file_path) - error_messages += self.check_file_contents(file_path, self.check_build_line) + error_messages.append("API build file does not provide api_proto_package()") + command = f"{self.config.buildifier_path} -mode=diff {file_path}" + error_messages.extend(self.execute_command(command, "buildifier check failed", file_path)) + error_messages.extend(self.check_file_contents(file_path, self.check_build_line)) return error_messages def fix_source_path(self, file_path): @@ -835,9 +923,9 @@ def execute_command(self, command, error_message, file_path, regex=None): return [] except subprocess.CalledProcessError as e: if (e.returncode != 0 and e.returncode != 1): - return ["ERROR: something went wrong while executing: %s" % e.cmd] + return [f"ERROR: something went wrong while executing: {e.cmd}"] # In case we can't find any line numbers, record an error message first. - error_messages = ["%s for file: %s" % (error_message, file_path)] + error_messages = [f"{error_message} for file: {file_path}\n{e}"] for line in e.output.decode('utf-8').splitlines(): for num in regex.findall(line): error_messages.append(" %s:%s" % (file_path, num)) @@ -861,28 +949,29 @@ def check_format(self, file_path, fail_on_diff=False): orig_error_messages = [] # Apply fixes first, if asked, and then run checks. If we wind up attempting to fix # an issue, but there's still an error, that's a problem. - try_to_fix = self.operation_type == "fix" - if self.is_build_file(file_path) or self.is_starlark_file( - file_path) or self.is_workspace_file(file_path): - if try_to_fix: + check_build_path = ( + self.is_build_file(file_path) or self.is_starlark_file(file_path) + or self.is_workspace_file(file_path)) + if check_build_path: + if self.operation_type == "fix": orig_error_messages = self.check_build_path(file_path) if orig_error_messages: - error_messages += self.fix_build_path(file_path) - error_messages += self.check_build_path(file_path) + error_messages.extend( + [*self.fix_build_path(file_path), *self.check_build_path(file_path)]) else: - error_messages += self.check_build_path(file_path) + error_messages.extend(self.check_build_path(file_path)) else: - if try_to_fix: + if self.operation_type == "fix": orig_error_messages = self.check_source_path(file_path) if orig_error_messages: - error_messages += self.fix_source_path(file_path) - error_messages += self.check_source_path(file_path) + error_messages.extend( + [*self.fix_source_path(file_path), *self.check_source_path(file_path)]) else: - error_messages += self.check_source_path(file_path) + error_messages.extend(self.check_source_path(file_path)) if error_messages: - return ["From %s" % file_path] + error_messages - if not error_messages and fail_on_diff: + return [f"From {file_path}", *error_messages] + if fail_on_diff: return orig_error_messages return error_messages @@ -893,58 +982,20 @@ def check_format_return_trace_on_error(self, file_path, fail_on_diff=False): except: return traceback.format_exc().split("\n") - def check_owners(self, dir_name, owned_directories, error_messages): - """Checks to make sure a given directory is present either in CODEOWNERS or OWNED_EXTENSIONS - Args: - dir_name: the directory being checked. - owned_directories: directories currently listed in CODEOWNERS. - error_messages: where to put an error message for new unowned directories. - """ - found = False - for owned in owned_directories: - if owned.startswith(dir_name) or dir_name.startswith(owned): - found = True - break - if not found: - error_messages.append( - "New directory %s appears to not have owners in CODEOWNERS" % dir_name) + def normalize_path(self, path): + """Convert path to form ./path/to/dir/ for directories and ./path/to/file otherwise""" + if not path.startswith(("./", "/")): + path = "./" + path - def check_format_visitor(self, arg, dir_name, names, fail_on_diff=False): - """Run check_format in parallel for the given files. - Args: - arg: a tuple (pool, result_list, owned_directories, error_messages) - pool and result_list are for starting tasks asynchronously. - owned_directories tracks directories listed in the CODEOWNERS file. - error_messages is a list of string format errors. - dir_name: the parent directory of the given files. - names: a list of file names. - """ + isdir = os.path.isdir(path) + if isdir and not path.endswith("/"): + path += "/" - # Unpack the multiprocessing.Pool process pool and list of results. Since - # python lists are passed as references, this is used to collect the list of - # async results (futures) from running check_format and passing them back to - # the caller. - pool, result_list, owned_directories, error_messages = arg - - # Sanity check CODEOWNERS. This doesn't need to be done in a multi-threaded - # manner as it is a small and limited list. - source_prefix = './source/' - core_extensions_full_prefix = './source/extensions/' - # Check to see if this directory is a subdir under /source/extensions - # Also ignore top level directories under /source/extensions since we don't - # need owners for source/extensions/access_loggers etc, just the subdirectories. - if dir_name.startswith( - core_extensions_full_prefix) and '/' in dir_name[len(core_extensions_full_prefix):]: - self.check_owners(dir_name[len(source_prefix):], owned_directories, error_messages) - - # For contrib extensions we track ownership at the top level only. - contrib_prefix = './contrib/' - if dir_name.startswith(contrib_prefix): - top_level = pathlib.PurePath('/', *pathlib.PurePath(dir_name).parts[:2], '/') - self.check_owners(str(top_level), owned_directories, error_messages) - - dir_name = normalize_path(dir_name) + return path + def check_format_visitor(self, pool, results, files): + """Run check_format in parallel for the given files. + """ # TODO(phlax): improve class/process handling - this is required because if these # are not cached before the class is sent into the pool, it only caches them on the # forked proc @@ -954,127 +1005,91 @@ def check_format_visitor(self, arg, dir_name, names, fail_on_diff=False): self.config.replacements self.config.dir_order - for file_name in names: - result = pool.apply_async( - self.check_format_return_trace_on_error, - args=(os.path.join(dir_name, file_name), fail_on_diff)) - result_list.append(result) + for filepath in files: + results.append( + pool.apply_async( + self.check_format_return_trace_on_error, + args=(filepath, self.args.fail_on_diff))) # check_error_messages iterates over the list with error messages and prints # errors and returns a bool based on whether there were any errors. - def check_error_messages(self, error_messages): - if error_messages: - for e in error_messages: - print("ERROR: %s" % e) + def check_error_messages(self): + if self.error_messages: + for e in self.error_messages: + print(f"ERROR: {e}") return True return False - def included_for_memcpy(self, file_path): - return file_path in self.config.paths["memcpy"]["include"] - - -def normalize_path(path): - """Convert path to form ./path/to/dir/ for directories and ./path/to/file otherwise""" - if not path.startswith("./") and not path.startswith("/"): - path = "./" + path + def pooled_check_format(self, files) -> list[str]: + pool = multiprocessing.Pool(processes=self.args.num_workers) + # For each file in target_path, start a new task in the pool and collect the + # results (results is passed by reference, and is used as an output). + results = [] + self.check_format_visitor(pool, results, files) + # Close the pool to new tasks, wait for all of the running tasks to finish, + # then collect the error messages. + pool.close() + pool.join() + return results - isdir = os.path.isdir(path) - if isdir and not path.endswith("/"): - path += "/" + @property + def target_paths(self) -> Iterator[str]: + _files = [] + for target in self.args.target_path: + if os.path.isfile(target): + # All of our `excluded_prefixes` start with "./", but the provided + # target path argument might not. Add it here if it is missing, + # and use that normalized path for both lookup and `check_format`. + normalized_target_path = self.normalize_path(target) + skip = ( + normalized_target_path.startswith(self.excluded_prefixes) + or not normalized_target_path.endswith(self.config.suffixes["included"])) + if not skip: + yield normalized_target_path + else: + for root, _, files in os.walk(target): + for filename in files: + file_path = os.path.join(root, filename) + check_file = ( + not file_path.startswith(self.excluded_prefixes) + and file_path.endswith(self.config.suffixes["included"]) and not ( + file_path.endswith(self.config.suffixes["proto"]) + and root.startswith(self.args.api_prefix))) + if check_file: + yield file_path + + def run_checks(self): + # these are needed curently to put the build tool paths into the env + self.config.buildifier_path + self.config.buildozer_path + self.check_visibility() + # We first run formatting on non-BUILD files, since the BUILD file format + # requires analysis of srcs/hdrs in the BUILD file, and we don't want these + # to be rewritten by other multiprocessing pooled processes. + results = [ + *self.pooled_check_format(f for f in self.target_paths if not self.is_build_file(f)), + *self.pooled_check_format(f for f in self.target_paths if self.is_build_file(f)) + ] + self.error_messages.extend(sum((r.get() for r in results), [])) - return path + if self.check_error_messages(): + if self.args.operation_type == "check": + print("ERROR: check format failed. run '//tools/code_format:check_format -- fix'") + else: + print("ERROR: check format failed. diff has been applied'") + sys.exit(1) + if self.args.operation_type == "check": + print("PASS") -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Check or fix file format.") - parser.add_argument( - "operation_type", - type=str, - choices=["check", "fix"], - help="specify if the run should 'check' or 'fix' format.") - parser.add_argument( - "target_path", - type=str, - nargs="?", - default=".", - help="specify the root directory for the script to recurse over. Default '.'.") - parser.add_argument("--path", default=".", help="specify the root path.") - parser.add_argument( - "--config_path", - default="./tools/code_format/config.yaml", - help="specify the config path. Default './tools/code_format/config.yaml'.") - parser.add_argument( - "--fail_on_diff", - action="store_true", - help="exit with failure if running fix produces changes.") - parser.add_argument( - "--add-excluded-prefixes", type=str, nargs="+", help="exclude additional prefixes.") - parser.add_argument( - "-j", - "--num-workers", - type=int, - default=multiprocessing.cpu_count(), - help="number of worker processes to use; defaults to one per core.") - parser.add_argument("--api-prefix", type=str, default="./api/", help="path of the API tree.") - parser.add_argument( - "--skip_envoy_build_rule_check", - action="store_true", - help="skip checking for '@envoy//' prefix in build rules.") - parser.add_argument( - "--namespace_check", - type=str, - nargs="?", - default="Envoy", - help="specify namespace check string. Default 'Envoy'.") - parser.add_argument( - "--namespace_check_excluded_paths", - type=str, - nargs="+", - default=[], - help="exclude paths from the namespace_check.") - parser.add_argument( - "--build_fixer_check_excluded_paths", - type=str, - nargs="+", - default=[], - help="exclude paths from envoy_build_fixer check.") - parser.add_argument( - "--bazel_tools_check_excluded_paths", - type=str, - nargs="+", - default=[], - help="exclude paths from bazel_tools check.") - parser.add_argument("--buildifier_path", type=str, help="Path to buildifier executable.") - parser.add_argument("--buildozer_path", type=str, help="Path to buildozer executable.") - parser.add_argument( - "--include_dir_order", - type=str, - default="", - help="specify the header block include directory order.") - args = parser.parse_args() - - # TODO(phlax): completely rewrite file discovery in this file - its a mess - source_path = os.getcwd() - os.chdir(args.path) - format_checker = FormatChecker(args, source_path) - - excluded_prefixes = format_checker.config.paths["excluded"] - if args.add_excluded_prefixes: - excluded_prefixes += tuple(args.add_excluded_prefixes) - - # Check whether all needed external tools are available. - ct_error_messages = format_checker.check_tools() - if format_checker.check_error_messages(ct_error_messages): - sys.exit(1) - - def check_visibility(error_messages): + def check_visibility(self): command = ( "git diff $(tools/git/last_github_commit.sh) -- source/extensions/* %s |grep '+.*visibility ='" - % "".join([f"':(exclude){c}' " for c in format_checker.config["visibility_excludes"]])) + % "".join([f"':(exclude){c}' " for c in self.config["visibility_excludes"]])) try: output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).strip() if output: - error_messages.append( + self.error_messages.append( "This change appears to add visibility rules. Please get senior maintainer " "approval to add an exemption to visibility_excludes in tools/code_format/config.yaml" ) @@ -1083,133 +1098,24 @@ def check_visibility(error_messages): shell=True, stderr=subprocess.STDOUT).strip() if output: - error_messages.append( + self.error_messages.append( "envoy_package is not allowed to be used in source/extensions BUILD files.") except subprocess.CalledProcessError as e: if (e.returncode != 0 and e.returncode != 1): - error_messages.append("Failed to check visibility with command %s" % command) - - def get_owners(): - with open('./OWNERS.md') as f: - maintainers = ["@UNOWNED"] - for line in f: - if "Senior extension maintainers" in line: - return maintainers - m = format_checker.config.re["maintainers"].search(line) - if m is not None: - maintainers.append("@" + m.group(1).lower()) - - # Returns the list of directories with owners listed in CODEOWNERS. May append errors to - # error_messages. - def owned_directories(error_messages): - owned = [] - try: - maintainers = get_owners() - - with open('./CODEOWNERS') as f: - for line in f: - # If this line is of the form "extensions/... @owner1 @owner2" capture the directory - # name and store it in the list of directories with documented owners. - m = format_checker.config.re["codeowners_extensions"].search(line) - if m is not None and not line.startswith('#'): - owned.append(m.group(1).strip()) - owners = format_checker.config.re["owner"].findall(m.group(2).strip()) - if len(owners) < 2: - error_messages.append( - "Extensions require at least 2 owners in CODEOWNERS:\n" - " {}".format(line)) - maintainer = len(set(owners).intersection(set(maintainers))) > 0 - if not maintainer: - error_messages.append( - "Extensions require at least one maintainer OWNER:\n" - " {}".format(line)) - - m = format_checker.config.re["codeowners_contrib"].search(line) - if m is not None and not line.startswith('#'): - stripped_path = m.group(1).strip() - if not stripped_path.endswith('/'): - error_messages.append( - "Contrib CODEOWNERS entry '{}' must end in '/'".format( - stripped_path)) - continue - - if not (stripped_path.count('/') == 3 or - (stripped_path.count('/') == 4 - and stripped_path.startswith('/contrib/common/'))): - error_messages.append( - "Contrib CODEOWNERS entry '{}' must be 2 directories deep unless in /contrib/common/ and then it can be 3 directories deep" - .format(stripped_path)) - continue - - owned.append(stripped_path) - owners = format_checker.config.re["owner"].findall(m.group(2).strip()) - if len(owners) < 2: - error_messages.append( - "Contrib extensions require at least 2 owners in CODEOWNERS:\n" - " {}".format(line)) - - return owned - except IOError: - return [] # for the check format tests. - - # Calculate the list of owned directories once per run. - error_messages = [] - owned_directories = owned_directories(error_messages) - - check_visibility(error_messages) - - if os.path.isfile(args.target_path): - # All of our `excluded_prefixes` start with "./", but the provided - # target path argument might not. Add it here if it is missing, - # and use that normalized path for both lookup and `check_format`. - normalized_target_path = normalize_path(args.target_path) - if not normalized_target_path.startswith( - excluded_prefixes) and normalized_target_path.endswith( - format_checker.config.suffixes["included"]): - error_messages += format_checker.check_format(normalized_target_path) - else: - results = [] + self.error_messages.append("Failed to check visibility with command %s" % command) + + def included_for_memcpy(self, file_path): + return file_path in self.config.paths["memcpy"]["include"] - def pooled_check_format(path_predicate): - pool = multiprocessing.Pool(processes=args.num_workers) - # For each file in target_path, start a new task in the pool and collect the - # results (results is passed by reference, and is used as an output). - for root, _, files in os.walk(args.target_path): - _files = [] - for filename in files: - file_path = os.path.join(root, filename) - check_file = ( - path_predicate(filename) and not file_path.startswith(excluded_prefixes) - and file_path.endswith(format_checker.config.suffixes["included"]) and not ( - file_path.endswith(format_checker.config.suffixes["proto"]) - and root.startswith(args.api_prefix))) - if check_file: - _files.append(filename) - if not _files: - continue - format_checker.check_format_visitor( - (pool, results, owned_directories, error_messages), root, _files, - args.fail_on_diff) - - # Close the pool to new tasks, wait for all of the running tasks to finish, - # then collect the error messages. - pool.close() - pool.join() + def _run_build_fixer(self, filepath: str) -> bool: + return ( + not self.is_build_fixer_excluded_file(filepath) and not self.is_api_file(filepath) + and not self.is_starlark_file(filepath) and not self.is_workspace_file(filepath)) - # We first run formatting on non-BUILD files, since the BUILD file format - # requires analysis of srcs/hdrs in the BUILD file, and we don't want these - # to be rewritten by other multiprocessing pooled processes. - pooled_check_format(lambda f: not format_checker.is_build_file(f)) - pooled_check_format(lambda f: format_checker.is_build_file(f)) - error_messages += sum((r.get() for r in results), []) +def main(*args): + FormatChecker(args).run_checks() - if format_checker.check_error_messages(error_messages): - if args.operation_type == "check": - print("ERROR: check format failed. run 'tools/code_format/check_format.py fix'") - else: - print("ERROR: check format failed. diff has been applied'") - sys.exit(1) - if args.operation_type == "check": - print("PASS") +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index 5c29c15c75932..b392610cc48aa 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -35,12 +35,22 @@ if [[ $# -gt 0 && "$1" == "-run-build-setup" ]]; then . ci/build_setup.sh fi + use_bazel=1 if [[ $# -gt 0 && "$1" == "-skip-bazel" ]]; then - echo -n "WARNING: not using bazel to invoke this script may result in " - echo "mismatched versions and incorrect formatting" - shift - use_bazel=0 + echo "WARNING: not using bazel to invoke this script may result in mismatched" \ + "versions and incorrect formatting" >&2 + shift + use_bazel=0 + + BUILDIFIER_BIN="$(command -v buildifier)" || { + echo "Local buildifier not found, exiting" >&2 + exit 1 + } + BUILDOZER_BIN="$(command -v buildozer)" || { + echo "Local buildozer not found, exiting" >&2 + exit 1 + } fi if [[ $# -gt 0 && "$1" == "-verbose" ]]; then @@ -52,19 +62,20 @@ fi # Runs the formatting functions on the specified args, echoing commands # if -vergbose was supplied to the script. -function format_some() { - ( +format_some () { if [[ "$verbose" == "1" ]]; then set -x fi + if [[ "$use_bazel" == "1" ]]; then - bazel run //tools/code_format:check_format -- fix "$@" + bazel run //tools/code_format:check_format fix "$@" + ./tools/spelling/check_spelling_pedantic.py fix "$@" else for arg in "$@"; do - ./tools/spelling/check_spelling_pedantic.py fix "$arg" + ./tools/code_format/check_format.py --buildozer_path "$BUILDOZER_BIN" --buildifier_path "$BUILDIFIER_BIN" fix "$arg" + ./tools/spelling/check_spelling_pedantic.py fix "$arg" done fi - ) } function format_all() { @@ -84,7 +95,7 @@ else if [[ $# -gt 0 && "$1" == "-main" ]]; then shift echo "Checking all files that have changed since the main branch." - args=$(git diff main | grep ^diff | awk '{print $3}' | cut -c 3-) + args=$(git diff --name-only main) elif [[ $# == 0 ]]; then args=$(git status|grep -E '(modified:|added:)'|awk '{print $2}') args+=$(git status|grep -E 'new file:'|awk '{print $3}') From d7fa856aa9944dc06aea03d864a2cd2805dfe765 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 11 Sep 2023 21:02:34 +0100 Subject: [PATCH 053/274] clang/tools: Use python distributed versions (#29455) Signed-off-by: Ryan Northey --- tools/base/requirements.in | 2 + tools/base/requirements.txt | 21 ++++++++ tools/clang-format/BUILD | 12 +++++ tools/clang-format/clang_format.bzl | 60 ++++++++++++++++++++++ tools/code_format/BUILD | 2 + tools/code_format/check_format.py | 79 ++++++++--------------------- tools/local_fix_format.sh | 9 +++- 7 files changed, 125 insertions(+), 60 deletions(-) create mode 100644 tools/clang-format/BUILD create mode 100644 tools/clang-format/clang_format.bzl diff --git a/tools/base/requirements.in b/tools/base/requirements.in index dac380272b350..e34f513c30539 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -2,6 +2,8 @@ abstracts>=0.0.12 aio.api.bazel aiohttp>=3.8.1 cffi>=1.15.0 +clang-format==14.0.6 +clang-tidy==14.0.6 colorama coloredlogs cryptography>=41.0.1 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 5c7a8a2b0f514..4a6c421c798dd 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -368,6 +368,27 @@ charset-normalizer==3.2.0 \ # via # aiohttp # requests +clang-format==14.0.6 \ + --hash=sha256:13f2d6d4a2af004a783c65f0921afa8f0384bffcdaf500b6c2cb542edeb0b4a5 \ + --hash=sha256:810c649ab97d208cd418c897d50ab6e958eb8d96854527edd80d0dd21a75e914 \ + --hash=sha256:aaf4edecc46a24f0b572b82cf5827e292ad1c137903427627c4d5f671668cc2b \ + --hash=sha256:bd400c47665dd19afc03f98e747f78ed828abab99c6a1b07e137b35c1cd3cc26 \ + --hash=sha256:c93580945f75de7e01996f1fb3cf67e4dc424f1c864e237c85614fb99a48c7a4 \ + --hash=sha256:d5c96b500d7f8b5d2db5b75ac035be387512850ad589cdc3019666b861382136 \ + --hash=sha256:d780c04334bca80f2b60d25bf53c37bd0618520ee295a7888a11f25bde114ac4 \ + --hash=sha256:d7c1c5e404c58e55f0170f01b3c5611dce6c119e62b5d1020347e0ad97d5a047 \ + --hash=sha256:dbfd60528eb3bb7d7cfe8576faa70845fbf93601f815ef75163d36606e87f388 + # via -r requirements.in +clang-tidy==14.0.6 \ + --hash=sha256:02bce40a56cc344e20d2f63bef6b85acf9837954559e0091804d6e748dfc0359 \ + --hash=sha256:173a757415108095b541eb9a2d0c222d41f5624e7bb5b98772476957228ce2c7 \ + --hash=sha256:4635f6553f9e3eb7a81fec29d15e4e70b49c1780f31a17550c11007fc9bba4b3 \ + --hash=sha256:5b56edb6b7215eb79fede7ab8a4f9b94454bdfe1091d026acc1afdc7696abb68 \ + --hash=sha256:7f75eb4839dc996dea494a07814b3a70200be75bc7d9acb54d3d5916f24bcd8d \ + --hash=sha256:c9ffcb91f17ee920fdd7a83f30484f3cb4c183f7b490d092373e4a6f2c82729d \ + --hash=sha256:d595b8e9a155d63b6b9dec0afa62725590626c9f0e945c3d9e448a28e0082b39 \ + --hash=sha256:fef62fb706adccef94128761ca0796973a196e2d60fb938a312cfa2bc59730bd + # via -r requirements.in colorama==0.4.6 \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 diff --git a/tools/clang-format/BUILD b/tools/clang-format/BUILD new file mode 100644 index 0000000000000..604edb37e7c99 --- /dev/null +++ b/tools/clang-format/BUILD @@ -0,0 +1,12 @@ +load("@base_pip3//:requirements.bzl", "requirement") +load("//bazel:envoy_build_system.bzl", "envoy_package") +load(":clang_format.bzl", "clang_format") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +clang_format( + name = "clang-format", + target = requirement("clang-format"), +) diff --git a/tools/clang-format/clang_format.bzl b/tools/clang-format/clang_format.bzl new file mode 100644 index 0000000000000..c21dc465ffd06 --- /dev/null +++ b/tools/clang-format/clang_format.bzl @@ -0,0 +1,60 @@ +# +# This fishes the clang-format binary out of the related python package. +# +# This is useful as using the binary through the python entry_point adds a lot of overhead. +# +# ```starlark +# +# load("@base_pip3//:requirements.bzl", "requirement") +# +# clang_format( +# name = "clang-format", +# target = requirement("clang-format"), +# ) +# +# ``` +# +# The exposed binary can also be run directly: +# +# ```console +# +# $ bazel run //tools/clang-format -- --version +# +# ``` +# + +def _clang_format_impl(ctx): + clang_bin = None + for file in ctx.attr.target[DefaultInfo].data_runfiles.files.to_list(): + if file.basename == "clang-format" and file.dirname.split("/").pop() == "bin": + clang_bin = file + break + + if not clang_bin: + fail("Unable to find clang-format file in package") + + output_file = ctx.actions.declare_file("clang-format") + args = ctx.actions.args() + args.add(clang_bin.path) + args.add(output_file.path) + ctx.actions.run( + outputs = [output_file], + inputs = [clang_bin], + arguments = [args], + executable = "cp", + mnemonic = "ClangFormatGetter", + ) + return [DefaultInfo( + executable = output_file, + files = depset([output_file]), + )] + +clang_format = rule( + implementation = _clang_format_impl, + attrs = { + "target": attr.label( + allow_files = True, + ), + }, + executable = True, +) diff --git a/tools/code_format/BUILD b/tools/code_format/BUILD index 0e3d75117729b..73b9872299c73 100644 --- a/tools/code_format/BUILD +++ b/tools/code_format/BUILD @@ -22,11 +22,13 @@ py_binary( srcs = ["check_format.py"], args = [ "--path=%s" % PATH, + "--clang_format_path=$(location //tools/clang-format)", "--buildifier_path=$(location @com_github_bazelbuild_buildtools//buildifier)", "--buildozer_path=$(location @com_github_bazelbuild_buildtools//buildozer)", ], data = [ ":config.yaml", + "//tools/clang-format", "@com_github_bazelbuild_buildtools//buildifier", "@com_github_bazelbuild_buildtools//buildozer", ], diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 4933603ec1a6c..66df2599166de 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -55,7 +55,10 @@ def buildozer_path(self) -> str: @cached_property def clang_format_path(self) -> str: """Path to the clang-format binary.""" - return os.getenv("CLANG_FORMAT", "clang-format-14") + path = ( + os.path.join(self.source_path, self.args.clang_format_path) + if self.source_path else self.args.clang_format_path) + return path @cached_property def config(self) -> Dict: @@ -128,7 +131,6 @@ def __init__(self, args): self.source_path = os.getcwd() if self.args.path: os.chdir(self.args.path) - os.environ["BAZEL_EXECROOT"] = self.source_path self._include_dir_order = self.args.include_dir_order @property @@ -217,6 +219,8 @@ def args(self): nargs="+", default=[], help="exclude paths from bazel_tools check.") + parser.add_argument( + "--clang_format_path", type=str, help="Path to clang-format executable.") parser.add_argument("--buildifier_path", type=str, help="Path to buildifier executable.") parser.add_argument("--buildozer_path", type=str, help="Path to buildozer executable.") parser.add_argument( @@ -314,55 +318,6 @@ def executable_by_others(self, executable): st = os.stat(os.path.expandvars(executable)) return bool(st.st_mode & stat.S_IXOTH) - # Check whether all needed external tools (clang-format, buildifier, buildozer) are - # available. - def check_tools(self): - error_messages = [] - clang_format_abs_path = self.look_path(self.config.clang_format_path) - - if clang_format_abs_path: - if not self.executable_by_others(clang_format_abs_path): - error_messages.append( - "command {} exists, but cannot be executed by other " - "users".format(self.config.clang_format_path)) - else: - error_messages.append( - "Command {} not found. If you have clang-format in version 12.x.x " - "installed, but the binary name is different or it's not available in " - "PATH, please use CLANG_FORMAT environment variable to specify the path. " - "Examples:\n" - " export CLANG_FORMAT=clang-format-14.0.0\n" - " export CLANG_FORMAT=/opt/bin/clang-format-14\n" - " export CLANG_FORMAT=/usr/local/opt/llvm@14/bin/clang-format".format( - self.config.clang_format_path)) - - def check_bazel_tool(name, path, var): - bazel_tool_abs_path = self.look_path(path) - if bazel_tool_abs_path: - if not self.executable_by_others(bazel_tool_abs_path): - error_messages.append( - "command {} exists, but cannot be executed by other " - "users".format(path)) - elif self.path_exists(path): - if not self.executable_by_others(path): - error_messages.append( - "command {} exists, but cannot be executed by other " - "users".format(path)) - else: - error_messages.append( - "Command {} not found. If you have {} installed, but the binary " - "name is different or it's not available in $GOPATH/bin, please use " - "{} environment variable to specify the path. Example:\n" - " export {}=`which {}`\n" - "If you don't have {} installed, you can install it by:\n" - " go install github.com/bazelbuild/buildtools/{}@latest".format( - path, name, var, var, name, name, name)) - - check_bazel_tool('buildifier', self.config.buildifier_path, 'BUILDIFIER_BIN') - check_bazel_tool('buildozer', self.config.buildozer_path, 'BUILDOZER_BIN') - - return error_messages - def check_namespace(self, file_path): for excluded_path in self.namespace_check_excluded_paths: if file_path.startswith(excluded_path): @@ -897,7 +852,6 @@ def fix_source_path(self, file_path): def check_source_path(self, file_path): error_messages = self.check_file_contents(file_path, self.check_source_line) - if not file_path.endswith(self.config.suffixes["proto"]): error_messages += self.check_namespace(file_path) command = ( @@ -906,8 +860,7 @@ def check_source_path(self, file_path): file_path)) error_messages += self.execute_command( command, "header_order.py check failed", file_path) - command = ("%s %s | diff %s -" % (self.config.clang_format_path, file_path, file_path)) - error_messages += self.execute_command(command, "clang-format check failed", file_path) + error_messages.extend(self.clang_format(file_path, check=True)) return error_messages # Example target outputs are: @@ -938,11 +891,19 @@ def fix_header_order(self, file_path): return ["header_order.py rewrite error: %s" % (file_path)] return [] - def clang_format(self, file_path): - command = "%s -i %s" % (self.config.clang_format_path, file_path) - if os.system(command) != 0: - return ["clang-format rewrite error: %s" % (file_path)] - return [] + def clang_format(self, file_path, check=False): + result = [] + command = ( + f"{self.config.clang_format_path} {file_path} | diff {file_path} -" + if check else f"{self.config.clang_format_path} -i {file_path}") + + if check: + result = self.execute_command(command, "clang-format check failed", file_path) + else: + if os.system(command) != 0: + result = [f"clang-format rewrite error: {file_path}"] + + return result def check_format(self, file_path, fail_on_diff=False): error_messages = [] diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index b392610cc48aa..832d80ae8b0de 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -43,6 +43,10 @@ if [[ $# -gt 0 && "$1" == "-skip-bazel" ]]; then shift use_bazel=0 + CLANG_FORMAT_BIN="$(command -v clang-format)" || { + echo "Local clang-format not found, exiting" >&2 + exit 1 + } BUILDIFIER_BIN="$(command -v buildifier)" || { echo "Local buildifier not found, exiting" >&2 exit 1 @@ -72,7 +76,10 @@ format_some () { ./tools/spelling/check_spelling_pedantic.py fix "$@" else for arg in "$@"; do - ./tools/code_format/check_format.py --buildozer_path "$BUILDOZER_BIN" --buildifier_path "$BUILDIFIER_BIN" fix "$arg" + ./tools/code_format/check_format.py \ + --clang_format_path "$CLANG_FORMAT_BIN" \ + --buildozer_path "$BUILDOZER_BIN" \ + --buildifier_path "$BUILDIFIER_BIN" fix "$arg" ./tools/spelling/check_spelling_pedantic.py fix "$arg" done fi From c3b9d8e6e1fb390ce14430c44b429abdb3c83100 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 13 Sep 2023 14:09:17 +0100 Subject: [PATCH 054/274] ci/cache: Optimize per-job caching (#29587) Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 2 +- ci/do_ci.sh | 79 +++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 69e784d00eef2..5c8e740463b11 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -45,7 +45,7 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) - name: cacheKeyVersion - value: v0 + value: v1 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 67aeb96afae02..79a11d10ba29a 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -18,34 +18,44 @@ echo "building for ${ENVOY_BUILD_ARCH}" cd "${SRCDIR}" -FETCH_TARGETS=( - //contrib/... - //distribution/... - //docs/... - //source/... - //test/... - //tools/... - @nodejs//... - @envoy_api//... - @envoy_build_tools//...) - +# Its better to fetch too little rather than too much, as whatever is +# actually used is what will be cached. +# Fetching is mostly for robustness rather than optimization. FETCH_TARGETS=( @bazel_tools//tools/jdk:remote_jdk11 - @com_github_bufbuild_buf//:bin/buf @envoy_build_tools//... - //docs/... - //tools/proto_format/... - //tools/zstd //tools/gsutil - //tools/code_format/...) - + //tools/zstd) FETCH_BUILD_TARGETS=( - @com_github_google_quiche//:ci_tests //contrib/exe/... //distribution/... - //source/exe/... - //test/tools/schema_validator/... + //source/exe/...) +FETCH_GCC_TARGETS=( + //source/exe/...) +# TODO(phlax): add this as a general cache +# this fetches a bit too much for some of the targets +# but its not really possible to filter their needs so move +# to a shared precache +FETCH_TEST_TARGETS=( + @nodejs//... //test/...) +FETCH_ALL_TEST_TARGETS=( + @com_github_google_quiche//:ci_tests + "${FETCH_TEST_TARGETS[@]}") +FETCH_API_TARGETS=( + @envoy_api//... + //tools/api_proto_plugin/... + //tools/protoprint/... + //tools/protoxform/... + //tools/type_whisperer/... + //tools/testdata/protoxform/...) +FETCH_DOCS_TARGETS+=( + //docs/...) +FETCH_FORMAT_TARGETS+=( + //tools/code_format/...) +FETCH_PROTO_TARGETS=( + @com_github_bufbuild_buf//:bin/buf + //tools/proto_format/...) retry () { local n wait iterations @@ -611,8 +621,31 @@ case $CI_TARGET in fetch) targets=("${FETCH_TARGETS[@]}") ;; + fetch-check_and_fix_proto_format) + targets=("${FETCH_PROTO_TARGETS[@]}") + ;; + fetch-docs) + targets=("${FETCH_DOCS_TARGETS[@]}") + ;; + fetch-format) + targets=("${FETCH_FORMAT_TARGETS[@]}") + ;; + fetch-gcc) + targets=("${FETCH_GCC_TARGETS[@]}") + ;; fetch-release) - targets=("${FETCH_BUILD_TARGETS[@]}") + targets=( + "${FETCH_BUILD_TARGETS[@]}" + "${FETCH_ALL_TEST_TARGETS[@]}") + ;; + fetch-*coverage) + targets=("${FETCH_TEST_TARGETS[@]}") + ;; + fetch-*san|fetch-compile_time_options) + targets=("${FETCH_ALL_TEST_TARGETS[@]}") + ;; + fetch-api) + targets=("${FETCH_API_TARGETS[@]}") ;; *) exit 0 @@ -630,8 +663,6 @@ case $CI_TARGET in "${targets[@]}" ;; - - fix_proto_format) # proto_format.sh needs to build protobuf. setup_clang_toolchain @@ -670,7 +701,7 @@ case $CI_TARGET in info) setup_clang_toolchain - bazel info "${BAZEL_GLOBAL_OPTIONS[@]}" + bazel info "${BAZEL_BUILD_OPTIONS[@]}" ;; msan) From 96ca0dd50cab752c8a473fe43f817177432de24b Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 14 Sep 2023 17:27:11 +0100 Subject: [PATCH 055/274] tools/format: Minor improvement for err handling (#29623) Signed-off-by: Ryan Northey --- tools/code_format/check_format.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 66df2599166de..e5d16c7092ad2 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -876,9 +876,11 @@ def execute_command(self, command, error_message, file_path, regex=None): return [] except subprocess.CalledProcessError as e: if (e.returncode != 0 and e.returncode != 1): - return [f"ERROR: something went wrong while executing: {e.cmd}"] + return [ + f"ERROR: something went wrong while executing: {e.cmd}\n{e.output.decode()}" + ] # In case we can't find any line numbers, record an error message first. - error_messages = [f"{error_message} for file: {file_path}\n{e}"] + error_messages = [f"{error_message} for file: {file_path}\n{e.output.decode()}"] for line in e.output.decode('utf-8').splitlines(): for num in regex.findall(line): error_messages.append(" %s:%s" % (file_path, num)) From 3edc2040e92e443294d7276620d8757965d42a9f Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 13 Sep 2023 22:11:42 +0100 Subject: [PATCH 056/274] git/hooks: Use `envoy.code.check` (#29605) Signed-off-by: Ryan Northey --- support/hooks/pre-push | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/support/hooks/pre-push b/support/hooks/pre-push index c07afe49bc181..5da7eac99f00a 100755 --- a/support/hooks/pre-push +++ b/support/hooks/pre-push @@ -84,7 +84,9 @@ do # TODO(mattklein123): Optimally we would be able to do this on a per-file basis. "$SCRIPT_DIR"/proto_format/proto_format.sh check || exit 1 - "$SCRIPT_DIR"/code_format/format_python_tools.sh check || exit 1 + bazel run //tools/code:check -- \ + -s main \ + -v warn || exit 1 # Check correctness of repositories definitions. echo " Checking repositories definitions" From 3ddb60b722cf7d12ab6ebfecba1ad42ef7c38203 Mon Sep 17 00:00:00 2001 From: Jacob Bohanon <57016439+jbohanon@users.noreply.github.com> Date: Thu, 7 Sep 2023 06:29:16 -0400 Subject: [PATCH 057/274] fix push hook to use the new bazel-y check format call (#29458) Signed-off-by: Jacob Bohanon Signed-off-by: Ryan Northey --- support/hooks/pre-push | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/hooks/pre-push b/support/hooks/pre-push index 5da7eac99f00a..0cc8c6e5665b7 100755 --- a/support/hooks/pre-push +++ b/support/hooks/pre-push @@ -72,7 +72,7 @@ do # either of these things aren't true, the check fails. for i in $(git diff --name-only "$RANGE" --diff-filter=ACMR --ignore-submodules=all 2>&1); do echo -ne " Checking format for $i - " - "$SCRIPT_DIR"/code_format/check_format.py check "$i" || exit 1 + bazel run //tools/code_format:check_format -- check "$i" || exit 1 # TODO(phlax): It seems this is not running in CI anymore and is now finding issues # in merged PRs. Unify this hook and format checks in CI when the new format tool is rolled From e2f38a8730c05da2c96c2ae4d63bb69fcb3c448e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 12 Sep 2023 17:30:52 +0100 Subject: [PATCH 058/274] git/hooks: Prevent pre-push from looping format check (#29580) Signed-off-by: Ryan Northey --- support/hooks/pre-push | 19 ++++++++++--------- tools/code_format/check_format.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/support/hooks/pre-push b/support/hooks/pre-push index 0cc8c6e5665b7..9d05d39373e28 100755 --- a/support/hooks/pre-push +++ b/support/hooks/pre-push @@ -70,16 +70,17 @@ do # `$CLANG_FORMAT` and `$BUILDIFY` are defined, or that the default values it # assumes for these variables correspond to real binaries on the system. If # either of these things aren't true, the check fails. - for i in $(git diff --name-only "$RANGE" --diff-filter=ACMR --ignore-submodules=all 2>&1); do - echo -ne " Checking format for $i - " - bazel run //tools/code_format:check_format -- check "$i" || exit 1 - # TODO(phlax): It seems this is not running in CI anymore and is now finding issues - # in merged PRs. Unify this hook and format checks in CI when the new format tool is rolled - # out. - # echo " Checking spelling for $i" - # "$SCRIPT_DIR"/spelling/check_spelling_pedantic.py check "$i" || exit 1 - done + _CHANGES=$(git diff --name-only "$RANGE" --diff-filter=ACMR --ignore-submodules=all 2>&1 | tr '\n' ' ') + IFS=' ' read -ra CHANGES <<< "$_CHANGES" + + echo -ne " Checking format for ${CHANGES[*]} - " + bazel run //tools/code_format:check_format -- check "${CHANGES[@]}" || exit 1 + # TODO(phlax): It seems this is not running in CI anymore and is now finding issues + # in merged PRs. Unify this hook and format checks in CI when the new format tool is rolled + # out. + # echo " Checking spelling for $i" + # "$SCRIPT_DIR"/spelling/check_spelling_pedantic.py check "${CHANGES[@]}" || exit 1 # TODO(mattklein123): Optimally we would be able to do this on a per-file basis. "$SCRIPT_DIR"/proto_format/proto_format.sh check || exit 1 diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index e5d16c7092ad2..bd7f9d8d59a09 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -151,7 +151,7 @@ def excluded_prefixes(self): self.config.paths["excluded"] + tuple(self.args.add_excluded_prefixes) if self.args.add_excluded_prefixes else self.config.paths["excluded"]) - @property + @cached_property def error_messages(self): return [] From 9357a6b7234fc5b53c958a515a03a23fbd642dd3 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 14 Sep 2023 17:47:01 +0100 Subject: [PATCH 059/274] tools/clang: Remove remaining host clang-format usage (+docs update) (#29624) Signed-off-by: Ryan Northey Signed-off-by: phlax --- bazel/README.md | 54 +-------------------------------- ci/build_setup.sh | 2 -- support/hooks/pre-push | 6 ---- tools/protoprint/BUILD | 1 + tools/protoprint/protoprint.bzl | 2 +- tools/protoprint/protoprint.py | 13 ++++---- 6 files changed, 9 insertions(+), 69 deletions(-) diff --git a/bazel/README.md b/bazel/README.md index fe497fe46b600..34b0a75239d25 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -930,61 +930,9 @@ TEST_TMPDIR=/tmp tools/gen_compilation_database.py # Running format linting without docker -The easiest way to run the clang-format check/fix commands is to run them via -docker, which helps ensure the right toolchain is set up. However you may prefer -to run clang-format scripts on your workstation directly: - * It's possible there is a speed advantage - * Docker itself can sometimes go awry and you then have to deal with that - * Type-ahead doesn't always work when waiting running a command through docker - -To run the tools directly, you must install the correct version of clang. This -may change over time, check the version of clang in the docker image. You must -also have 'buildifier' installed from the bazel distribution. - Note that if you run the `check_spelling.py` script you will need to have `aspell` installed. -Edit the paths shown here to reflect the installation locations on your system: - -```shell -export CLANG_FORMAT="$HOME/ext/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clang-format" -export BUILDIFIER_BIN="/usr/bin/buildifier" -``` - -A relatively easy way to use the correct `clang-format` in your host system is to copy the `clang-format` from the ci docker image. - -* Run the ci docker image - -```shell -ci/run_envoy_docker.sh bash -``` - -* Get the docker container ID - -```shell -dockerContainerID=$(docker ps | grep envoy-build-ubuntu | awk '{print $1}') -``` - -* Copy the `clang-format` to host machine - -```shell -docker cp $dockerContainerID:/opt/llvm/bin/clang-format clang-format-ci -``` - -* Ensure that the copied `clang-format` is the default one, by ensuring it is in `$PATH`: - -```shell -cp clang-format-ci /usr/local/bin/clang-format -``` - -Alternatively, if you are a non-root user, you can use a bin dir and add that to `$PATH` - -```shell -mkdir bin -mv clang-format-ci bin/clang-format -export PATH=$PATH:$PWD/bin/ -``` - -Once this is set up, you can run clang-format without docker: +You can run clang-format directly, without docker: ```shell bazel run //tools/code_format:check_format -- check diff --git a/ci/build_setup.sh b/ci/build_setup.sh index f5a6e95aea418..2d54fa423bc35 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -87,7 +87,6 @@ fi export ENVOY_TEST_TMPDIR="${ENVOY_TEST_TMPDIR:-$BUILD_DIR/tmp}" export LLVM_ROOT="${LLVM_ROOT:-/opt/llvm}" export PATH=${LLVM_ROOT}/bin:${PATH} -export CLANG_FORMAT="${CLANG_FORMAT:-clang-format}" if [[ -f "/etc/redhat-release" ]]; then BAZEL_BUILD_EXTRA_OPTIONS+=("--copt=-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1") @@ -138,7 +137,6 @@ BAZEL_BUILD_OPTIONS=( "${BAZEL_GLOBAL_OPTIONS[@]}" "--verbose_failures" "--experimental_generate_json_trace_profile" - "--action_env=CLANG_FORMAT" "${BAZEL_BUILD_EXTRA_OPTIONS[@]}" "${BAZEL_EXTRA_TEST_OPTIONS[@]}") diff --git a/support/hooks/pre-push b/support/hooks/pre-push index 9d05d39373e28..b90a98b87b5be 100755 --- a/support/hooks/pre-push +++ b/support/hooks/pre-push @@ -65,12 +65,6 @@ do # our `relpath` helper. SCRIPT_DIR="$(dirname "$(realpath "$0")")/../../tools" - # TODO(hausdorff): We should have a more graceful failure story when the - # user does not have all the tools set up correctly. This script assumes - # `$CLANG_FORMAT` and `$BUILDIFY` are defined, or that the default values it - # assumes for these variables correspond to real binaries on the system. If - # either of these things aren't true, the check fails. - _CHANGES=$(git diff --name-only "$RANGE" --diff-filter=ACMR --ignore-submodules=all 2>&1 | tr '\n' ' ') IFS=' ' read -ra CHANGES <<< "$_CHANGES" diff --git a/tools/protoprint/BUILD b/tools/protoprint/BUILD index 0760d9b0cd748..4a8cac10d7979 100644 --- a/tools/protoprint/BUILD +++ b/tools/protoprint/BUILD @@ -15,6 +15,7 @@ py_binary( srcs = ["protoprint.py"], data = [ "//:.clang-format", + "//tools/clang-format", "//tools/type_whisperer:api_type_db.pb_text", ], visibility = ["//visibility:public"], diff --git a/tools/protoprint/protoprint.bzl b/tools/protoprint/protoprint.bzl index 8e688ca0b6ce4..8a8c61c4f98c1 100644 --- a/tools/protoprint/protoprint.bzl +++ b/tools/protoprint/protoprint.bzl @@ -20,7 +20,7 @@ protoprint_aspect = api_proto_plugin_aspect( "//tools/protoprint", _protoprint_impl, use_type_db = True, - extra_inputs = ["//:.clang-format"], + extra_inputs = ["//:.clang-format", "//tools/clang-format"], ) def _protoprint_rule_impl(ctx): diff --git a/tools/protoprint/protoprint.py b/tools/protoprint/protoprint.py index a336d41e13345..5828fb0e10f1b 100644 --- a/tools/protoprint/protoprint.py +++ b/tools/protoprint/protoprint.py @@ -71,7 +71,7 @@ def extract_clang_proto_style(clang_format_text): return str(format_dict) -def clang_format(style, contents): +def clang_format(clang_format_path, style, contents): """Run proto-style oriented clang-format over given string. Args: @@ -80,11 +80,6 @@ def clang_format(style, contents): Returns: clang-formatted string """ - clang_format_path = os.getenv("CLANG_FORMAT", shutil.which("clang-format")) - if not clang_format_path: - if not os.path.exists("/opt/llvm/bin/clang-format"): - raise RuntimeError("Unable to find clang-format, sorry") - clang_format_path = "/opt/llvm/bin/clang-format" return subprocess.run( [clang_format_path, '--style=%s' % style, '--assume-filename=.proto'], input=contents.encode('utf-8'), @@ -592,6 +587,10 @@ def __init__(self, params): if params['type_db_path']: utils.load_type_db(params['type_db_path']) + self.clang_format_path = pathlib.Path(params["clang-format"]) + if not self.clang_format_path.exists(): + raise ProtoPrintError(f"Unable to find clang-format binary: {self.clang_format_path}") + self.clang_format_config = pathlib.Path(params[".clang-format"]) if not self.clang_format_config.exists(): raise ProtoPrintError(f"Unable to find .clang-format file: {self.clang_format_config}") @@ -739,6 +738,7 @@ def visit_file(self, file_proto, type_context, services, msgs, enums): formatted_enums = format_block('\n'.join(enums)) formatted_msgs = format_block('\n'.join(msgs)) return clang_format( + str(self.clang_format_path), extract_clang_proto_style(self.clang_format_config.read_text()), header + formatted_services + formatted_enums + formatted_msgs) @@ -756,7 +756,6 @@ def traverse_file(self, file_proto, visitor): def main(data=None): utils.load_protos() - plugin.plugin([plugin.direct_output_descriptor('.proto', ProtoFormatVisitor, want_params=True)], traverser=ProtoprintTraverser().traverse_file) From e6b5afe847355ff34300c2dd94092d28073e4e46 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 18 Sep 2023 11:57:01 +0100 Subject: [PATCH 060/274] ci/bazel: Fix bazelisk cache (#29666) Signed-off-by: Ryan Northey --- .azure-pipelines/docker/save_cache.sh | 1 + .azure-pipelines/pipelines.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.azure-pipelines/docker/save_cache.sh b/.azure-pipelines/docker/save_cache.sh index f51fa1006b7d5..f80f28d9f56be 100755 --- a/.azure-pipelines/docker/save_cache.sh +++ b/.azure-pipelines/docker/save_cache.sh @@ -36,6 +36,7 @@ if [[ "$CACHE_BAZEL" == "true" ]]; then ./.azure-pipelines/docker/create_cache.sh \ "${BAZEL_CACHE_TARBALL}" \ "${ENVOY_DOCKER_BUILD_DIR}" \ + .cache \ bazel_root/install \ bazel_root/base/external \ repository_cache diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 5c8e740463b11..6cb7ac6fff03d 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -45,7 +45,7 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) - name: cacheKeyVersion - value: v1 + value: v2 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker From 6cbb2b881c1475b2bfb25d5c3152c92a0e861d67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 09:50:09 +0100 Subject: [PATCH 061/274] build(deps): bump distroless/base-nossl-debian11 from `f10e1fb` to `a156aae` in /ci (#29338) build(deps): bump distroless/base-nossl-debian11 in /ci Bumps distroless/base-nossl-debian11 from `f10e1fb` to `a156aae`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian11 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 9ea1d1a06cceb..572a4f460124e 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -59,7 +59,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless # gcr.io/distroless/base-nossl-debian11:nonroot -FROM gcr.io/distroless/base-nossl-debian11:nonroot@sha256:f10e1fbf558c630a4b74a987e6c754d45bf59f9ddcefce090f6b111925996767 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian11:nonroot@sha256:a156aae8df01d39f2390021016c672bee4fb34f3a90759f4d9aa74c116ec142a AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From bccfd67be8c447993e7282678efe44d2aa9d719e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 19 Sep 2023 15:17:15 +0100 Subject: [PATCH 062/274] release/ci: Fix/update local Docker build (+docs update) (#29646) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/publish.yml | 27 ++-- .azure-pipelines/stage/windows.yml | 4 +- ci/README.md | 74 +++++----- ci/do_ci.sh | 130 ++++++++++++------ ci/docker_ci.sh | 38 ++--- ci/run_envoy_docker.sh | 3 + ci/test_docker_ci.sh | 6 +- .../start/building/local_docker_build.rst | 29 ++-- 8 files changed, 188 insertions(+), 123 deletions(-) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index b04013b69721b..fa3b0060f4cba 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -120,32 +120,23 @@ jobs: echo "disk space at beginning of Docker build:" df -h displayName: "Check disk space before Docker build" + # TODO(phlax): switch docker <> docker-upload as main task - bash: | set -e - - mkdir -p linux/amd64 linux/arm64 - - # x64 - cp -a $(Build.StagingDirectory)/release/x64/bin/release.tar.zst linux/amd64/release.tar.zst - cp -a $(Build.StagingDirectory)/release/x64/bin/schema_validator_tool linux/amd64/schema_validator_tool - - # arm64 - cp -a $(Build.StagingDirectory)/release/arm64/bin/release.tar.zst linux/arm64/release.tar.zst - cp -a $(Build.StagingDirectory)/release/arm64/bin/schema_validator_tool linux/arm64/schema_validator_tool - - # Debug what files appear to have been downloaded - find linux -type f -name "*" | xargs ls -l - - ci/docker_ci.sh + mkdir -p $(Build.StagingDirectory)/envoy + mv $(Build.StagingDirectory)/release/* $(Build.StagingDirectory)/envoy + ./ci/run_envoy_docker.sh 'ci/do_ci.sh docker' displayName: Build Docker images timeoutInMinutes: ${{ parameters.timeoutDockerPublish }} workingDirectory: $(Build.SourcesDirectory) env: - AZP_BRANCH: $(Build.SourceBranch) - AZP_SHA1: $(Build.SourceVersion) + CI_BRANCH: $(Build.SourceBranch) + CI_SHA1: $(Build.SourceVersion) DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} DOCKER_BUILD_TIMEOUT: ${{ parameters.timeoutDockerBuild }} + ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) + ENVOY_DOCKER_IN_DOCKER: 1 - job: package_x64 displayName: Linux debs (x64) @@ -237,7 +228,7 @@ jobs: publishEnvoy: false publishTestResults: false env: - AZP_BRANCH: $(Build.SourceBranch) + CI_BRANCH: $(Build.SourceBranch) stepsPost: - script: | diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index e9e400da52e7e..9be16bebcf5c6 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -84,8 +84,8 @@ jobs: ci/docker_ci.sh workingDirectory: $(Build.SourcesDirectory) env: - AZP_BRANCH: $(Build.SourceBranch) - AZP_SHA1: $(Build.SourceVersion) + CI_BRANCH: $(Build.SourceBranch) + CI_SHA1: $(Build.SourceVersion) DOCKERHUB_USERNAME: $(DockerUsername) DOCKERHUB_PASSWORD: $(DockerPassword) WINDOWS_BUILD_TYPE: $(windowsBuildType) diff --git a/ci/README.md b/ci/README.md index 143b8f3235991..2282bc57ca562 100644 --- a/ci/README.md +++ b/ci/README.md @@ -5,7 +5,7 @@ and an image based on Windows2019. ## Ubuntu Envoy image -The Ubuntu based Envoy Docker image at [`envoyproxy/envoy-build:`](https://hub.docker.com/r/envoyproxy/envoy-build/) is used for CI checks, +The Ubuntu based Envoy Docker image at [`envoyproxy/envoy-build-ubuntu:`](https://hub.docker.com/r/envoyproxy/envoy-build/) is used for CI checks, where `` is specified in [`envoy_build_sha.sh`](https://github.com/envoyproxy/envoy/blob/main/ci/envoy_build_sha.sh). Developers may work with the latest build image SHA in [envoy-build-tools](https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8) repo to provide a self-contained environment for building Envoy binaries and running tests that reflects the latest built Ubuntu Envoy image. @@ -13,9 +13,14 @@ Moreover, the Docker image at [`envoyproxy/envoy:dev-`](https://hub.docker The `` corresponds to the main commit at which the binary was compiled. Lastly, `envoyproxy/envoy:dev` contains an Envoy binary built from the latest tip of main that passed tests. -## Alpine Envoy image +## Distroless Envoy image + +Minimal images based on a [distroless](https://github.com/GoogleContainerTools/distroless) allow for quicker deployment of Envoy. + +The Distroless base image is only built with symbols stripped. + +## Debug Envoy image -Minimal images based on Alpine Linux allow for quicker deployment of Envoy. The Alpine base image is only built with symbols stripped. To get the binary with symbols, use the corresponding Ubuntu based debug image. The image is pushed with two different tags: `` and `latest`. Parallel to the Ubuntu images above, `` corresponds to the main commit at which the binary was compiled, and `latest` corresponds to a binary built from the latest tip of main that passed tests. @@ -81,7 +86,7 @@ ENVOY_DOCKER_PULL=true ./ci/run_envoy_docker.sh An example basic invocation to build a developer version of the Envoy static binary (using the Bazel `fastbuild` type) is: ```bash -./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.dev' +./ci/run_envoy_docker.sh './ci/do_ci.sh dev' ``` The Envoy binary can be found in `/tmp/envoy-docker-build/envoy/source/exe/envoy-fastbuild` on the Docker host. You @@ -89,13 +94,13 @@ can control this by setting `ENVOY_DOCKER_BUILD_DIR` in the environment, e.g. to generate the binary in `~/build/envoy/source/exe/envoy-fastbuild` you can run: ```bash -ENVOY_DOCKER_BUILD_DIR=~/build ./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.dev' +ENVOY_DOCKER_BUILD_DIR=~/build ./ci/run_envoy_docker.sh './ci/do_ci.sh dev' ``` For a release version of the Envoy binary you can run: ```bash -./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.release.server_only' +./ci/run_envoy_docker.sh './ci/do_ci.sh release.server_only' ``` The build artifact can be found in `/tmp/envoy-docker-build/envoy/source/exe/envoy` (or wherever @@ -104,7 +109,7 @@ The build artifact can be found in `/tmp/envoy-docker-build/envoy/source/exe/env For a debug version of the Envoy binary you can run: ```bash -./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.debug.server_only' +./ci/run_envoy_docker.sh './ci/do_ci.sh debug.server_only' ``` The build artifact can be found in `/tmp/envoy-docker-build/envoy/source/exe/envoy-debug` (or wherever @@ -119,33 +124,34 @@ the BAZEL_BUILD_EXTRA_OPTIONS environment variable The `./ci/run_envoy_docker.sh './ci/do_ci.sh '` targets are: -* `bazel.api` — build and run API tests under `-c fastbuild` with clang. -* `bazel.asan` — build and run tests under `-c dbg --config=clang-asan` with clang. -* `bazel.asan ` — build and run a specified test or test dir under `-c dbg --config=clang-asan` with clang. -* `bazel.debug` — build Envoy static binary and run tests under `-c dbg`. -* `bazel.debug ` — build Envoy static binary and run a specified test or test dir under `-c dbg`. -* `bazel.debug.server_only` — build Envoy static binary under `-c dbg`. -* `bazel.dev` — build Envoy static binary and run tests under `-c fastbuild` with clang. -* `bazel.dev ` — build Envoy static binary and run a specified test or test dir under `-c fastbuild` with clang. -* `bazel.dev.contrib` — build Envoy static binary with contrib and run tests under `-c fastbuild` with clang. -* `bazel.dev.contrib ` — build Envoy static binary with contrib and run a specified test or test dir under `-c fastbuild` with clang. -* `bazel.release` — build Envoy static binary and run tests under `-c opt` with clang. -* `bazel.release ` — build Envoy static binary and run a specified test or test dir under `-c opt` with clang. -* `bazel.release.server_only` — build Envoy static binary under `-c opt` with clang. -* `bazel.sizeopt` — build Envoy static binary and run tests under `-c opt --config=sizeopt` with clang. -* `bazel.sizeopt ` — build Envoy static binary and run a specified test or test dir under `-c opt --config=sizeopt` with clang. -* `bazel.sizeopt.server_only` — build Envoy static binary under `-c opt --config=sizeopt` with clang. -* `bazel.coverage` — build and run tests under `-c dbg` with gcc, generating coverage information in `$ENVOY_DOCKER_BUILD_DIR/envoy/generated/coverage/coverage.html`. -* `bazel.coverage ` — build and run a specified test or test dir under `-c dbg` with gcc, generating coverage information in `$ENVOY_DOCKER_BUILD_DIR/envoy/generated/coverage/coverage.html`. Specify `//contrib/...` to get contrib coverage. -* `bazel.msan` — build and run tests under `-c dbg --config=clang-msan` with clang. -* `bazel.msan ` — build and run a specified test or test dir under `-c dbg --config=clang-msan` with clang. -* `bazel.tsan` — build and run tests under `-c dbg --config=clang-tsan` with clang. -* `bazel.tsan ` — build and run a specified test or test dir under `-c dbg --config=clang-tsan` with clang. -* `bazel.fuzz` — build and run fuzz tests under `-c dbg --config=asan-fuzzer` with clang. -* `bazel.fuzz ` — build and run a specified fuzz test or test dir under `-c dbg --config=asan-fuzzer` with clang. If specifying a single fuzz test, must use the full target name with "_with_libfuzzer" for ``. -* `bazel.compile_time_options` — build Envoy and run tests with various compile-time options toggled to their non-default state, to ensure they still build. -* `bazel.compile_time_options ` — build Envoy and run a specified test or test dir with various compile-time options toggled to their non-default state, to ensure they still build. -* `bazel.clang_tidy ` — build and run clang-tidy specified source files, if no files specified, runs against the diff with the last GitHub commit. +* `api` — build and run API tests under `-c fastbuild` with clang. +* `asan` — build and run tests under `-c dbg --config=clang-asan` with clang. +* `asan ` — build and run a specified test or test dir under `-c dbg --config=clang-asan` with clang. +* `debug` — build Envoy static binary and run tests under `-c dbg`. +* `debug ` — build Envoy static binary and run a specified test or test dir under `-c dbg`. +* `debug.server_only` — build Envoy static binary under `-c dbg`. +* `docker` — build Docker images, expects `release` or `release.server_only` to have been run furst. +* `dev` — build Envoy static binary and run tests under `-c fastbuild` with clang. +* `dev ` — build Envoy static binary and run a specified test or test dir under `-c fastbuild` with clang. +* `dev.contrib` — build Envoy static binary with contrib and run tests under `-c fastbuild` with clang. +* `dev.contrib ` — build Envoy static binary with contrib and run a specified test or test dir under `-c fastbuild` with clang. +* `release` — build Envoy static binary and run tests under `-c opt` with clang. +* `release ` — build Envoy static binaries and run a specified test or test dir under `-c opt` with clang. +* `release.server_only` — build Envoy static binaries under `-c opt` with clang. +* `sizeopt` — build Envoy static binary and run tests under `-c opt --config=sizeopt` with clang. +* `sizeopt ` — build Envoy static binary and run a specified test or test dir under `-c opt --config=sizeopt` with clang. +* `sizeopt.server_only` — build Envoy static binary under `-c opt --config=sizeopt` with clang. +* `coverage` — build and run tests under `-c dbg` with gcc, generating coverage information in `$ENVOY_DOCKER_BUILD_DIR/envoy/generated/coverage/coverage.html`. +* `coverage ` — build and run a specified test or test dir under `-c dbg` with gcc, generating coverage information in `$ENVOY_DOCKER_BUILD_DIR/envoy/generated/coverage/coverage.html`. Specify `//contrib/...` to get contrib coverage. +* `msan` — build and run tests under `-c dbg --config=clang-msan` with clang. +* `msan ` — build and run a specified test or test dir under `-c dbg --config=clang-msan` with clang. +* `tsan` — build and run tests under `-c dbg --config=clang-tsan` with clang. +* `tsan ` — build and run a specified test or test dir under `-c dbg --config=clang-tsan` with clang. +* `fuzz` — build and run fuzz tests under `-c dbg --config=asan-fuzzer` with clang. +* `fuzz ` — build and run a specified fuzz test or test dir under `-c dbg --config=asan-fuzzer` with clang. If specifying a single fuzz test, must use the full target name with "_with_libfuzzer" for ``. +* `compile_time_options` — build Envoy and run tests with various compile-time options toggled to their non-default state, to ensure they still build. +* `compile_time_options ` — build Envoy and run a specified test or test dir with various compile-time options toggled to their non-default state, to ensure they still build. +* `clang_tidy ` — build and run clang-tidy specified source files, if no files specified, runs against the diff with the last GitHub commit. * `check_proto_format`— check configuration, formatting and build issues in API proto files. * `fix_proto_format`— fix configuration, formatting and build issues in API proto files. * `format`— run validation, linting and formatting tools. diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 79a11d10ba29a..4c2fb064b3344 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -580,21 +580,51 @@ case $CI_TARGET in fi ;; - docs) - setup_clang_toolchain - echo "generating docs..." - # Build docs. - "${ENVOY_SRCDIR}/docs/build.sh" - ;; - - docs-upload) - setup_clang_toolchain - "${ENVOY_SRCDIR}/ci/upload_gcs_artifact.sh" /source/generated/docs docs - ;; - - docs-publish-latest) - BUILD_SHA=$(git rev-parse HEAD) - curl -X POST -d "$BUILD_SHA" "$NETLIFY_TRIGGER_URL" + docker) + # This is limited to linux x86/arm64 and expects `release` or `release.server_only` to have + # been run first. + if ! docker ps &> /dev/null; then + echo "Unable to build with Docker. If you are running with ci/run_envoy_docker.sh" \ + "you should set ENVOY_DOCKER_IN_DOCKER=1" + exit 1 + fi + if [[ -z "$CI_SHA1" ]]; then + CI_SHA1="$(git rev-parse HEAD~1)" + export CI_SHA1 + fi + ENVOY_ARCH_DIR="$(dirname "${ENVOY_BUILD_DIR}")" + ENVOY_TARBALL_DIR="${ENVOY_TARBALL_DIR:-${ENVOY_ARCH_DIR}}" + _PLATFORMS=() + PLATFORM_NAMES=( + x64:linux/amd64 + arm64:linux/arm64) + # TODO(phlax): avoid copying bins + for platform_name in "${PLATFORM_NAMES[@]}"; do + path="$(echo "${platform_name}" | cut -d: -f1)" + platform="$(echo "${platform_name}" | cut -d: -f2)" + bin_folder="${ENVOY_TARBALL_DIR}/${path}/bin" + if [[ ! -e "${bin_folder}/release.tar.zst" ]]; then + continue + fi + _PLATFORMS+=("$platform") + if [[ -e "$platform" ]]; then + rm -rf "$platform" + fi + mkdir -p "${platform}" + cp -a "${bin_folder}"/* "$platform" + done + if [[ -z "${_PLATFORMS[*]}" ]]; then + echo "No tarballs found in ${ENVOY_TARBALL_DIR}, did you run \`release\` first?" >&2 + exit 1 + fi + PLATFORMS="$(IFS=, ; echo "${_PLATFORMS[*]}")" + export DOCKER_PLATFORM="$PLATFORMS" + if [[ -z "${DOCKERHUB_PASSWORD}" && "${#_PLATFORMS[@]}" -eq 1 ]]; then + # if you are not pushing the images and there is only one platform + # then load to Docker (ie local build) + export DOCKER_LOAD_IMAGES=1 + fi + "${ENVOY_SRCDIR}/ci/docker_ci.sh" ;; docker-upload) @@ -616,6 +646,23 @@ case $CI_TARGET in cat bazel-bin/distribution/dockerhub/readme.md ;; + docs) + setup_clang_toolchain + echo "generating docs..." + # Build docs. + "${ENVOY_SRCDIR}/docs/build.sh" + ;; + + docs-publish-latest) + BUILD_SHA=$(git rev-parse HEAD) + curl -X POST -d "$BUILD_SHA" "$NETLIFY_TRIGGER_URL" + ;; + + docs-upload) + setup_clang_toolchain + "${ENVOY_SRCDIR}/ci/upload_gcs_artifact.sh" /source/generated/docs docs + ;; + fetch|fetch-*) case $CI_TARGET in fetch) @@ -736,18 +783,24 @@ case $CI_TARGET in -- "${PUBLISH_ARGS[@]}" ;; - release) - # When testing memory consumption, we want to test against exact byte-counts - # where possible. As these differ between platforms and compile options, we - # define the 'release' builds as canonical and test them only in CI, so the - # toolchain is kept consistent. This ifdef is checked in - # test/common/stats/stat_test_utility.cc when computing - # Stats::TestUtil::MemoryTest::mode(). - if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then - BAZEL_BUILD_OPTIONS+=("--test_env=ENVOY_MEMORY_TEST_EXACT=true") + release|release.server_only) + if [[ "$CI_TARGET" == "release" ]]; then + # When testing memory consumption, we want to test against exact byte-counts + # where possible. As these differ between platforms and compile options, we + # define the 'release' builds as canonical and test them only in CI, so the + # toolchain is kept consistent. This ifdef is checked in + # test/common/stats/stat_test_utility.cc when computing + # Stats::TestUtil::MemoryTest::mode(). + if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then + BAZEL_BUILD_OPTIONS+=("--test_env=ENVOY_MEMORY_TEST_EXACT=true") + fi fi setup_clang_toolchain ENVOY_BINARY_DIR="${ENVOY_BUILD_DIR}/bin" + if [[ -e "${ENVOY_BINARY_DIR}" ]]; then + echo "Existing output directory found (${ENVOY_BINARY_DIR}), removing ..." + rm -rf "${ENVOY_BINARY_DIR}" + fi mkdir -p "$ENVOY_BINARY_DIR" # As the binary build package enforces compiler options, adding here to ensure the tests and distribution build # reuse settings and any already compiled artefacts, the bundle itself will always be compiled @@ -755,16 +808,18 @@ case $CI_TARGET in BAZEL_RELEASE_OPTIONS=( --stripopt=--strip-all -c opt) - # Run release tests - echo "Testing with:" - echo " targets: ${TEST_TARGETS[*]}" - echo " build options: ${BAZEL_BUILD_OPTIONS[*]}" - echo " release options: ${BAZEL_RELEASE_OPTIONS[*]}" - bazel_with_collection \ - test "${BAZEL_BUILD_OPTIONS[@]}" \ - --remote_download_minimal \ - "${BAZEL_RELEASE_OPTIONS[@]}" \ - "${TEST_TARGETS[@]}" + if [[ "$CI_TARGET" == "release" ]]; then + # Run release tests + echo "Testing with:" + echo " targets: ${TEST_TARGETS[*]}" + echo " build options: ${BAZEL_BUILD_OPTIONS[*]}" + echo " release options: ${BAZEL_RELEASE_OPTIONS[*]}" + bazel_with_collection \ + test "${BAZEL_BUILD_OPTIONS[@]}" \ + --remote_download_minimal \ + "${BAZEL_RELEASE_OPTIONS[@]}" \ + "${TEST_TARGETS[@]}" + fi # Build release binaries bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ "${BAZEL_RELEASE_OPTIONS[@]}" \ @@ -783,12 +838,7 @@ case $CI_TARGET in cp -a \ bazel-bin/test/tools/schema_validator/schema_validator_tool.stripped \ "${ENVOY_BINARY_DIR}/schema_validator_tool" - ;; - - release.server_only) - setup_clang_toolchain - echo "bazel release build..." - bazel_envoy_binary_build release + echo "Release files created in ${ENVOY_BINARY_DIR}" ;; release.signed) diff --git a/ci/docker_ci.sh b/ci/docker_ci.sh index 3845486acf07e..fdf6cdaf74b56 100755 --- a/ci/docker_ci.sh +++ b/ci/docker_ci.sh @@ -14,22 +14,23 @@ set -e # DOCKERHUB_PASSWORD=mypassword # ## Set these to simulate types of CI run -# AZP_SHA1=MOCKSHA -# AZP_BRANCH=refs/heads/main -# AZP_BRANCH=refs/heads/release/v1.43 -# AZP_BRANCH=refs/tags/v1.77.3 +# CI_SHA1=MOCKSHA +# CI_BRANCH=refs/heads/main +# CI_BRANCH=refs/heads/release/v1.43 +# CI_BRANCH=refs/tags/v1.77.3 ## # Workaround for https://github.com/envoyproxy/envoy/issues/26634 DOCKER_BUILD_TIMEOUT="${DOCKER_BUILD_TIMEOUT:-400}" +DOCKER_PLATFORM="${DOCKER_PLATFORM:-linux/arm64,linux/amd64}" function is_windows() { [[ -n "$DOCKER_FAKE_WIN" ]] || [[ "$(uname -s)" == *NT* ]] } if [[ -n "$DOCKER_CI_DRYRUN" ]]; then - AZP_SHA1="${AZP_SHA1:-MOCKSHA}" + CI_SHA1="${CI_SHA1:-MOCKSHA}" if is_windows; then WINDOWS_IMAGE_BASE="${WINDOWS_IMAGE_BASE:-mcr.microsoft.com/windows/fakecore}" @@ -50,7 +51,7 @@ fi if [[ "$ENVOY_VERSION" =~ $DEV_VERSION_REGEX ]]; then # Dev version IMAGE_POSTFIX="-dev" - IMAGE_NAME="${AZP_SHA1}" + IMAGE_NAME="${CI_SHA1}" else # Non-dev version IMAGE_POSTFIX="" @@ -58,12 +59,14 @@ else fi # Only push images for main builds, and non-dev release branch builds -if [[ -n "$DOCKERHUB_USERNAME" ]] && [[ -n "$DOCKERHUB_PASSWORD" ]]; then - if [[ "${AZP_BRANCH}" == "${MAIN_BRANCH}" ]]; then +if [[ -n "$DOCKER_LOAD_IMAGES" ]]; then + LOAD_IMAGES=1 +elif [[ -n "$DOCKERHUB_USERNAME" ]] && [[ -n "$DOCKERHUB_PASSWORD" ]]; then + if [[ "${CI_BRANCH}" == "${MAIN_BRANCH}" ]]; then echo "Pushing images for main." PUSH_IMAGES_TO_REGISTRY=1 - elif [[ "${AZP_BRANCH}" =~ ${RELEASE_BRANCH_REGEX} ]] && ! [[ "$ENVOY_VERSION" =~ $DEV_VERSION_REGEX ]]; then - echo "Pushing images for release branch ${AZP_BRANCH}." + elif [[ "${CI_BRANCH}" =~ ${RELEASE_BRANCH_REGEX} ]] && ! [[ "$ENVOY_VERSION" =~ $DEV_VERSION_REGEX ]]; then + echo "Pushing images for release branch ${CI_BRANCH}." PUSH_IMAGES_TO_REGISTRY=1 else echo 'Ignoring non-release branch for docker push.' @@ -72,7 +75,7 @@ else echo 'No credentials for docker push.' fi -ENVOY_DOCKER_IMAGE_DIRECTORY="${ENVOY_DOCKER_IMAGE_DIRECTORY:-${BUILD_STAGINGDIRECTORY:-.}/build_images}" +ENVOY_DOCKER_IMAGE_DIRECTORY="${ENVOY_DOCKER_IMAGE_DIRECTORY:-${BUILD_DIR:-.}/build_images}" # This prefix is altered for the private security images on setec builds. DOCKER_IMAGE_PREFIX="${DOCKER_IMAGE_PREFIX:-envoyproxy/envoy}" if [[ -z "$DOCKER_CI_DRYRUN" ]]; then @@ -84,7 +87,7 @@ config_env() { echo ">> BUILDX: install" echo "> docker run --rm --privileged tonistiigi/binfmt --install all" echo "> docker buildx rm multi-builder 2> /dev/null || :" - echo "> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64" + echo "> docker buildx create --use --name multi-builder --platform ${DOCKER_PLATFORM}" if [[ -n "$DOCKER_CI_DRYRUN" ]]; then return @@ -95,7 +98,7 @@ config_env() { # Remove older build instance docker buildx rm multi-builder 2> /dev/null || : - docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 + docker buildx create --use --name multi-builder --platform "${DOCKER_PLATFORM}" } if is_windows; then @@ -152,7 +155,7 @@ build_platforms() { elif [[ "${build_type}" == *-google-vrp ]]; then echo -n "linux/amd64" else - echo -n "linux/arm64,linux/amd64" + echo -n "$DOCKER_PLATFORM" fi } @@ -210,7 +213,10 @@ build_and_maybe_push_image () { args+=( "--sbom=false" "--provenance=false") - if [[ "${image_type}" =~ debug ]]; then + if [[ -n "$LOAD_IMAGES" ]]; then + action="BUILD+LOAD" + args+=("--load") + elif [[ "${image_type}" =~ debug ]]; then # For linux if its the debug image then push immediately for release branches, # otherwise just test the build if [[ -n "$PUSH_IMAGES_TO_REGISTRY" ]]; then @@ -341,7 +347,7 @@ tag_variants () { # Only push latest on main/dev builds. if [[ "$ENVOY_VERSION" =~ $DEV_VERSION_REGEX ]]; then - if [[ "${AZP_BRANCH}" == "${MAIN_BRANCH}" ]]; then + if [[ "${CI_BRANCH}" == "${MAIN_BRANCH}" ]]; then variant_type="latest" fi else diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 0fe264980d9c4..5b9dd3d0a06db 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -114,6 +114,8 @@ docker run --rm \ -e BAZEL_FAKE_SCM_REVISION \ -e BAZEL_REMOTE_CACHE \ -e BAZEL_STARTUP_EXTRA_OPTIONS \ + -e CI_BRANCH \ + -e CI_SHA1 \ -e CI_TARGET_BRANCH \ -e DOCKERHUB_USERNAME \ -e DOCKERHUB_PASSWORD \ @@ -135,6 +137,7 @@ docker run --rm \ -e ENVOY_HEAD_REF \ -e ENVOY_PUBLISH_DRY_RUN \ -e ENVOY_REPO \ + -e ENVOY_TARBALL_DIR \ -e SYSTEM_PULLREQUEST_PULLREQUESTNUMBER \ -e GCS_ARTIFACT_BUCKET \ -e GITHUB_TOKEN \ diff --git a/ci/test_docker_ci.sh b/ci/test_docker_ci.sh index 6bfa4479aa4b2..bd9748aa2b05f 100755 --- a/ci/test_docker_ci.sh +++ b/ci/test_docker_ci.sh @@ -54,7 +54,7 @@ _test () { fi export ENVOY_VERSION="${version}" - export AZP_BRANCH="$branch" + export CI_BRANCH="$branch" # this should be ignored if the non-push export DOCKERHUB_USERNAME=DHUSER export DOCKERHUB_PASSWORD=DHPASSWORD @@ -68,13 +68,13 @@ _test () { if [[ "$DOCKER_CI_TEST_COMMIT" ]]; then echo "COMMIT(${name}): > ${testdata}" - echo " DOCKER_FAKE_WIN=${DOCKER_FAKE_WIN} ENVOY_VERSION=${version} ENVOY_DOCKER_IMAGE_DIRECTORY=/non/existent/test/path AZP_BRANCH=${branch} DOCKER_CI_DRYRUN=1 ./ci/docker_ci.sh | grep -E \"^>\"" + echo " DOCKER_FAKE_WIN=${DOCKER_FAKE_WIN} ENVOY_VERSION=${version} ENVOY_DOCKER_IMAGE_DIRECTORY=/non/existent/test/path CI_BRANCH=${branch} DOCKER_CI_DRYRUN=1 ./ci/docker_ci.sh | grep -E \"^>\"" ./ci/docker_ci.sh | grep -E "^>" > "$testdata" return fi echo "TEST(${name}): <> ${testdata}" - echo " DOCKER_FAKE_WIN=${DOCKER_FAKE_WIN} ENVOY_VERSION=${version} ENVOY_DOCKER_IMAGE_DIRECTORY=/non/existent/test/path AZP_BRANCH=${branch} DOCKER_CI_DRYRUN=1 ./ci/docker_ci.sh | grep -E \"^>\"" + echo " DOCKER_FAKE_WIN=${DOCKER_FAKE_WIN} ENVOY_VERSION=${version} ENVOY_DOCKER_IMAGE_DIRECTORY=/non/existent/test/path CI_BRANCH=${branch} DOCKER_CI_DRYRUN=1 ./ci/docker_ci.sh | grep -E \"^>\"" generated="$(mktemp)" ./ci/docker_ci.sh | grep -E "^>" > "$generated" diff --git a/docs/root/start/building/local_docker_build.rst b/docs/root/start/building/local_docker_build.rst index 86147f26fc2ab..714437603dd16 100644 --- a/docs/root/start/building/local_docker_build.rst +++ b/docs/root/start/building/local_docker_build.rst @@ -7,6 +7,13 @@ Building an Envoy Docker image The following steps guide you through building your own Envoy binary, and putting that in a clean Ubuntu container. +.. tip:: + These instructions run commands in Docker using ``ci/run_envoy_docker.sh``. + + By default this will place bazel run files and any artefacts in ``/tmp/envoy-docker-build``. + + You can override this by setting the ``ENVOY_DOCKER_BUILD_DIR`` env var to a path of your choosing. + **Step 1: Build Envoy** Using ``envoyproxy/envoy-build`` you will compile Envoy. @@ -16,7 +23,7 @@ This image has all software needed to build Envoy. From your Envoy directory: $ pwd src/envoy - $ ./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.release' + $ ./ci/run_envoy_docker.sh './ci/do_ci.sh release' That command will take some time to run because it is compiling an Envoy binary and running tests. @@ -27,13 +34,11 @@ also build as follows: $ pwd src/envoy - $ ./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.release.server_only' - + $ ./ci/run_envoy_docker.sh './ci/do_ci.sh release.server_only' For more information on building and different build targets, please refer to :repo:`ci/README.md`. .. warning:: - These instructions for building Envoy use `envoyproxy/envoy-build-ubuntu `_ image. You will need 4-5GB of disk space to accommodate this image. @@ -42,17 +47,21 @@ For more information on building and different build targets, please refer to :r **Step 2: Build image with only Envoy binary** -In this step we'll build an image that only has the Envoy binary, and none -of the software used to build it.: +In this step we'll build the Envoy deployment images. + +.. note:: + The ``docker`` CI target expects a release tarball to have been built previously using one of the steps above. + +In order to build Docker inside the Envoy build image we need to set the env var ``ENVOY_DOCKER_IN_DOCKER`` .. code-block:: console $ pwd src/envoy/ - $ docker build -f ci/Dockerfile-envoy -t envoy . + $ ENVOY_DOCKER_IN_DOCKER=1 ./ci/run_envoy_docker.sh './ci/do_ci.sh docker' -Now you can use this ``envoy`` image to build the any of the sandboxes if you change -the ``FROM`` line in any Dockerfile. +Now you can use the Envoy image to build the any of the sandboxes by changing +the ``FROM`` line in a related Dockerfile. -This will be particularly useful if you are interested in modifying Envoy, and testing +This can be particularly useful if you are interested in modifying Envoy, and testing your changes. From e1e47ee59076858a392df891a26858dbf7fd6df2 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 5 Sep 2023 16:15:00 +0100 Subject: [PATCH 063/274] deps/build: Bump `envoy-build-ubuntu` and toolchains (#29269) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .bazelrc | 2 +- .devcontainer/Dockerfile | 2 +- .github/workflows/_env.yml | 6 +++--- bazel/repository_locations.bzl | 6 +++--- ci/envoy_build_sha.sh | 10 +++++++++- ci/run_envoy_docker.sh | 11 ++++++++++- examples/shared/build/Dockerfile | 2 +- mobile/third_party/rbe_configs/config/BUILD | 8 ++------ 8 files changed, 30 insertions(+), 17 deletions(-) diff --git a/.bazelrc b/.bazelrc index efeb63ccb4539..f7c8afed810a4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -337,7 +337,7 @@ build:compile-time-options --@envoy//source/extensions/filters/http/kill_request # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:41c5a05d708972d703661b702a63ef5060125c33 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7dd1f7df667dd..f831a18c5c070 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:41c5a05d708972d703661b702a63ef5060125c33 +FROM gcr.io/envoy-ci/envoy-build:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index d7263d96d99ea..0044cab515d77 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -12,13 +12,13 @@ on: default: envoyproxy/envoy-build-ubuntu build_image_sha: type: string - default: 50337314a150ed12447c87c1622eac6f611a069888722fb9a426e21ed161cc26 + default: 6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 build_image_mobile_sha: type: string - default: ca26ff05bd3f3a09468242faaf38ae48315e57f0a87c102352162f95ac620e6f + default: 2c9852c10f133f780a96286230b7e07582e1c99fe204943d4fa66567f8b850f6 build_image_tag: type: string - default: 41c5a05d708972d703661b702a63ef5060125c33 + default: 1f2f7ee78f894859de0fa7a415b0bedde1f6c560 check_mobile_run: type: boolean diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 5fe48d6f00a94..eaaa4dbe9fb33 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -102,11 +102,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "49a27300e7b480955d3a6000eea159ff52998b52", - sha256 = "67fbba8f4329e16f693f9fabaa6e430eddb3f27b80186df884d5b801208be8d9", + version = "b3e8ecd0f256b648a19d0f2146a966c2a6a0c0e7", + sha256 = "40ae52a50987feeef25510a37108aad621f4ba0ea7420d898cefd239ee56b178", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2023-05-16", + release_date = "2023-09-03", use_category = ["build"], license = "Apache-2.0", license_url = "https://github.com/envoyproxy/envoy-build-tools/blob/{version}/LICENSE", diff --git a/ci/envoy_build_sha.sh b/ci/envoy_build_sha.sh index e2923189e35e2..03d8eb936ab9c 100644 --- a/ci/envoy_build_sha.sh +++ b/ci/envoy_build_sha.sh @@ -1,4 +1,12 @@ #!/bin/bash -ENVOY_BUILD_SHA=$(grep envoyproxy/envoy-build-ubuntu "$(dirname "$0")"/../.bazelrc | sed -e 's#.*envoyproxy/envoy-build-ubuntu:\(.*\)#\1#' | uniq) + +ENVOY_BUILD_CONTAINER="$(grep envoyproxy/envoy-build-ubuntu "$(dirname "$0")"/../.bazelrc | sed -e 's#.*envoyproxy/envoy-build-ubuntu:\(.*\)#\1#' | uniq)" +ENVOY_BUILD_SHA="$(echo "${ENVOY_BUILD_CONTAINER}" | cut -d@ -f1)" +ENVOY_BUILD_CONTAINER_SHA="$(echo "${ENVOY_BUILD_CONTAINER}" | cut -d@ -f2)" + +if [[ -n "$ENVOY_BUILD_CONTAINER_SHA" ]]; then + ENVOY_BUILD_CONTAINER_SHA="${ENVOY_BUILD_CONTAINER_SHA:7}" +fi + [[ $(wc -l <<< "${ENVOY_BUILD_SHA}" | awk '{$1=$1};1') == 1 ]] || (echo ".bazelrc envoyproxy/envoy-build-ubuntu hashes are inconsistent!" && exit 1) diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 5b9dd3d0a06db..58b491dc8e40c 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -19,6 +19,10 @@ export GOPROXY="${go_proxy:-}" if is_windows; then [[ -z "${IMAGE_NAME}" ]] && IMAGE_NAME="envoyproxy/envoy-build-windows2019" + # Container networking is unreliable in the most recently built images, pin Windows to a known + # good container. This can create a mismatch between the host environment, and the toolchain + # environment. + ENVOY_BUILD_SHA=41c5a05d708972d703661b702a63ef5060125c33 # TODO(sunjayBhatia): Currently ENVOY_DOCKER_OPTIONS is ignored on Windows because # CI sets it to a Linux-specific value. Undo this once https://github.com/envoyproxy/envoy/issues/13272 # is resolved. @@ -65,7 +69,12 @@ fi # The IMAGE_ID defaults to the CI hash but can be set to an arbitrary image ID (found with 'docker # images'). -[[ -z "${IMAGE_ID}" ]] && IMAGE_ID="${ENVOY_BUILD_SHA}" +if [[ -z "${IMAGE_ID}" ]]; then + IMAGE_ID="${ENVOY_BUILD_SHA}" + if ! is_windows && [[ -n "$ENVOY_BUILD_CONTAINER_SHA" ]]; then + IMAGE_ID="${ENVOY_BUILD_SHA}@sha256:${ENVOY_BUILD_CONTAINER_SHA}" + fi +fi [[ -z "${ENVOY_DOCKER_BUILD_DIR}" ]] && ENVOY_DOCKER_BUILD_DIR="${DEFAULT_ENVOY_DOCKER_BUILD_DIR}" # Replace backslash with forward slash for Windows style paths ENVOY_DOCKER_BUILD_DIR="${ENVOY_DOCKER_BUILD_DIR//\\//}" diff --git a/examples/shared/build/Dockerfile b/examples/shared/build/Dockerfile index 8a4355983a6e5..438aec3f5b99d 100644 --- a/examples/shared/build/Dockerfile +++ b/examples/shared/build/Dockerfile @@ -1,4 +1,4 @@ -FROM envoyproxy/envoy-build-ubuntu:41c5a05d708972d703661b702a63ef5060125c33 +FROM envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 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/mobile/third_party/rbe_configs/config/BUILD b/mobile/third_party/rbe_configs/config/BUILD index 298016e321ed9..b83dadeaf4a30 100644 --- a/mobile/third_party/rbe_configs/config/BUILD +++ b/mobile/third_party/rbe_configs/config/BUILD @@ -42,9 +42,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - # Please update both the commented tag and the sha256 - # mobile-41c5a05d708972d703661b702a63ef5060125c33 - "container-image": "docker://envoyproxy/envoy-build-ubuntu@sha256:ca26ff05bd3f3a09468242faaf38ae48315e57f0a87c102352162f95ac620e6f", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205", "OSFamily": "Linux", "Pool": "linux", }, @@ -59,9 +57,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - # Please update both the commented tag and the sha256 - # mobile-41c5a05d708972d703661b702a63ef5060125c33 - "container-image": "docker://envoyproxy/envoy-build-ubuntu@sha256:ca26ff05bd3f3a09468242faaf38ae48315e57f0a87c102352162f95ac620e6f", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205", "OSFamily": "Linux", "Pool": "linux", # Necessary to workaround https://github.com/google/sanitizers/issues/916, otherwise, dangling threads in the From 3617c3b26f77ccd6da14b2035183a69908a27a62 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 20 Sep 2023 12:09:51 +0100 Subject: [PATCH 064/274] build/image: Bump to include skopeo (#29732) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .bazelrc | 2 +- .devcontainer/Dockerfile | 2 +- .github/workflows/_env.yml | 6 +++--- bazel/repository_locations.bzl | 6 +++--- examples/shared/build/Dockerfile | 2 +- mobile/third_party/rbe_configs/config/BUILD | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.bazelrc b/.bazelrc index f7c8afed810a4..f1de05e65846d 100644 --- a/.bazelrc +++ b/.bazelrc @@ -337,7 +337,7 @@ build:compile-time-options --@envoy//source/extensions/filters/http/kill_request # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f831a18c5c070..3859774ea0b0d 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 +FROM gcr.io/envoy-ci/envoy-build:94e5d873c145ae86f205117e76276161c9af4806@sha256:3c3d299423a878a219a333153726cddf7cc5e5ff30f596dc97bafba521f2f928 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 0044cab515d77..1ea1b0ad45f09 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -12,13 +12,13 @@ on: default: envoyproxy/envoy-build-ubuntu build_image_sha: type: string - default: 6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 + default: 8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 build_image_mobile_sha: type: string - default: 2c9852c10f133f780a96286230b7e07582e1c99fe204943d4fa66567f8b850f6 + default: 0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95 build_image_tag: type: string - default: 1f2f7ee78f894859de0fa7a415b0bedde1f6c560 + default: 94e5d873c145ae86f205117e76276161c9af4806 check_mobile_run: type: boolean diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index eaaa4dbe9fb33..c2e450a11b739 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -102,11 +102,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "b3e8ecd0f256b648a19d0f2146a966c2a6a0c0e7", - sha256 = "40ae52a50987feeef25510a37108aad621f4ba0ea7420d898cefd239ee56b178", + version = "fc1ab3e96cf275ecaac913be2a22bce4a74b9272", + sha256 = "75fff0c28766ccb4e625244e35c950eb071d4bfb4a443b387140e1c037eeb6cc", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2023-09-03", + release_date = "2023-09-20", use_category = ["build"], license = "Apache-2.0", license_url = "https://github.com/envoyproxy/envoy-build-tools/blob/{version}/LICENSE", diff --git a/examples/shared/build/Dockerfile b/examples/shared/build/Dockerfile index 438aec3f5b99d..8fed6f57e6ce7 100644 --- a/examples/shared/build/Dockerfile +++ b/examples/shared/build/Dockerfile @@ -1,4 +1,4 @@ -FROM envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 +FROM envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 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/mobile/third_party/rbe_configs/config/BUILD b/mobile/third_party/rbe_configs/config/BUILD index b83dadeaf4a30..558a7ce5f01b3 100644 --- a/mobile/third_party/rbe_configs/config/BUILD +++ b/mobile/third_party/rbe_configs/config/BUILD @@ -42,7 +42,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", "OSFamily": "Linux", "Pool": "linux", }, @@ -57,7 +57,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", "OSFamily": "Linux", "Pool": "linux", # Necessary to workaround https://github.com/google/sanitizers/issues/916, otherwise, dangling threads in the From 83e604abd8214f379617e6320d2255ea20ca0e1f Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 19 Sep 2023 19:48:40 +0100 Subject: [PATCH 065/274] docker/ci: Fix artefact path bug (#29710) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index fa3b0060f4cba..c6885d8762753 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -124,6 +124,7 @@ jobs: - bash: | set -e mkdir -p $(Build.StagingDirectory)/envoy + rm -rf $(Build.StagingDirectory)/envoy/* mv $(Build.StagingDirectory)/release/* $(Build.StagingDirectory)/envoy ./ci/run_envoy_docker.sh 'ci/do_ci.sh docker' displayName: Build Docker images From 0c53c81cb86f7bfe1b5463232b1d4450bce52847 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 22 Sep 2023 14:03:07 +0100 Subject: [PATCH 066/274] ci/docs: Assorted fixes and cleanups (#29772) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/prechecks.yml | 5 +++-- ci/format_pre.sh | 2 +- ci/run_envoy_docker.sh | 2 -- docs/build.sh | 13 ++----------- tools/proto_format/proto_format.sh | 2 +- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 408a322e45cc3..6f65efc45d944 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -41,8 +41,9 @@ jobs: CI_TARGET: "format" protobuf: CI_TARGET: "check_and_fix_proto_format" - publishing: - CI_TARGET: docs + ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + publishing: + CI_TARGET: docs steps: - template: ../bazel.yml parameters: diff --git a/ci/format_pre.sh b/ci/format_pre.sh index f1a4c77fe079e..f4627dea89d8b 100755 --- a/ci/format_pre.sh +++ b/ci/format_pre.sh @@ -57,7 +57,7 @@ CURRENT=spelling "${ENVOY_SRCDIR}/tools/spelling/check_spelling_pedantic.py" --mark check # TODO(phlax): move clang/buildifier checks to bazel rules (/aspects) -if [[ -n "$AZP_BRANCH" ]]; then +if [[ -n "$CI_BRANCH" ]]; then CURRENT=check_format_test "${ENVOY_SRCDIR}/tools/code_format/check_format_test_helper.sh" --log=WARN fi diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 58b491dc8e40c..b145fe3fca863 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -111,8 +111,6 @@ fi docker run --rm \ "${ENVOY_DOCKER_OPTIONS[@]}" \ "${VOLUMES[@]}" \ - -e AZP_BRANCH \ - -e AZP_COMMIT_SHA \ -e HTTP_PROXY \ -e HTTPS_PROXY \ -e NO_PROXY \ diff --git a/docs/build.sh b/docs/build.sh index 09af4f70d7fe5..110aa22ebec54 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -20,7 +20,7 @@ DEV_VERSION_REGEX="-dev$" BUILD_TYPE=html if [[ "$VERSION" =~ $DEV_VERSION_REGEX ]]; then - if [[ "$AZP_BRANCH" == "$MAIN_BRANCH" ]]; then + if [[ "$CI_BRANCH" == "$MAIN_BRANCH" ]]; then # no need to build html, just rst BUILD_TYPE=rst fi @@ -37,15 +37,6 @@ IFS=" " read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTION_LIST:-}" # We want the binary at the end BAZEL_BUILD_OPTIONS+=(--remote_download_toplevel) -if [[ "${AZP_BRANCH}" =~ ^refs/pull ]]; then - # For PRs use the unmerged PR commit in the version string. - # - # Staged/built docs still use the merged sha in the URL to distinguish builds - # - export BUILD_DOCS_SHA="${AZP_COMMIT_SHA}" - BAZEL_BUILD_OPTIONS+=("--action_env=BUILD_DOCS_SHA") -fi - if [[ -n "${CI_TARGET_BRANCH}" ]] || [[ -n "${SPHINX_QUIET}" ]]; then export SPHINX_RUNNER_ARGS="-v warn" BAZEL_BUILD_OPTIONS+=("--action_env=SPHINX_RUNNER_ARGS") @@ -56,7 +47,7 @@ if [[ "${BUILD_TYPE}" == "html" ]] || [[ -n "${DOCS_BUILD_HTML}" ]]; then BUILD_HTML=1 BUILD_HTML_TARGET="//docs:html" BUILD_HTML_TARBALL="bazel-bin/docs/html.tar.gz" - if [[ -n "${AZP_BRANCH}" ]] || [[ -n "${DOCS_BUILD_RELEASE}" ]]; then + if [[ -n "${CI_BRANCH}" ]] || [[ -n "${DOCS_BUILD_RELEASE}" ]]; then # CI build - use git sha BUILD_HTML_TARGET="//docs:html_release" BUILD_HTML_TARBALL="bazel-bin/docs/html_release.tar.gz" diff --git a/tools/proto_format/proto_format.sh b/tools/proto_format/proto_format.sh index f86fc7b7471b9..b60286ff98e26 100755 --- a/tools/proto_format/proto_format.sh +++ b/tools/proto_format/proto_format.sh @@ -30,7 +30,7 @@ bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" \ --ci # Dont run this in git hooks by default -if [[ -n "$AZP_BRANCH" ]] || [[ "${FORCE_PROTO_FORMAT}" == "yes" ]]; then +if [[ -n "$CI_BRANCH" ]] || [[ "${FORCE_PROTO_FORMAT}" == "yes" ]]; then echo "Run buf tests" cd api/ || exit 1 bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" @com_github_bufbuild_buf//:bin/buf lint From a29f4269fcbc8d9cd416870bac549870d2743b61 Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 24 Sep 2023 22:32:09 +0100 Subject: [PATCH 067/274] bazel/tooling: Use toolshed entry_point macro (#29786) This allows the tooling to be reused (website, build-tools, etc) It also fixes a few bugs that i rinsed out while working on it This PR retains the envoy version of the macro - mostly to minimize change, but it also allows some customizations which we may need With the upstream macro, if @pip3 is defined as a pip parse target it will default to using that so we may want to rename our target to avoid having to pass a custom entry_point target for any invokations and having to call its setup. Signed-off-by: Ryan Northey --- bazel/python_dependencies.bzl | 3 ++ bazel/repositories.bzl | 6 +++ bazel/repository_locations.bzl | 14 ++++++ tools/base/envoy_python.bzl | 78 ++++++++++------------------------ 4 files changed, 45 insertions(+), 56 deletions(-) diff --git a/bazel/python_dependencies.bzl b/bazel/python_dependencies.bzl index 0033a53645475..ea50bf30ba386 100644 --- a/bazel/python_dependencies.bzl +++ b/bazel/python_dependencies.bzl @@ -1,7 +1,10 @@ load("@rules_python//python:pip.bzl", "pip_parse") load("@python3_11//:defs.bzl", "interpreter") +load("@envoy_toolshed//:packages.bzl", "load_packages") def envoy_python_dependencies(): + # TODO(phlax): rename base_pip3 -> pip3 and remove this + load_packages() pip_parse( name = "base_pip3", python_interpreter_target = interpreter, diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 5c1a84c0d3ba3..42c4a72a16507 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -253,6 +253,7 @@ def envoy_dependencies(skip_targets = []): # The long repo names (`com_github_fmtlib_fmt` instead of `fmtlib`) are # semi-standard in the Bazel community, intended to avoid both duplicate # dependencies and name conflicts. + _toolshed() _com_github_axboe_liburing() _com_github_bazel_buildtools() _com_github_c_ares_c_ares() @@ -356,6 +357,11 @@ def envoy_dependencies(skip_targets = []): actual = "@bazel_tools//tools/cpp/runfiles", ) +def _toolshed(): + external_http_archive( + name = "envoy_toolshed", + ) + def _boringssl(): external_http_archive( name = "boringssl", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index c2e450a11b739..62be1eb3bbd5c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -79,6 +79,20 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/bazelbuild/buildtools/archive/v{version}.tar.gz"], use_category = ["test_only"], ), + envoy_toolshed = dict( + project_name = "envoy_toolshed", + project_desc = "Tooling, libraries, runners and checkers for Envoy proxy's CI", + project_url = "https://github.com/envoyproxy/toolshed", + version = "bazel-0.0.1", + sha256 = "ed9314a378d78b84741a7c275ca41e3c75c9216447d59efc4f8d091a26d9640f", + strip_prefix = "toolshed-{version}/bazel", + urls = ["https://github.com/envoyproxy/toolshed/archive/refs/tags/{version}.tar.gz"], + use_category = ["build"], + release_date = "2023-09-24", + cpe = "N/A", + license = "Apache-2.0", + license_url = "https://github.com/envoyproxy/envoy/blob/{version}/LICENSE", + ), rules_fuzzing = dict( project_name = "Fuzzing Rules for Bazel", project_desc = "Bazel rules for fuzz tests", diff --git a/tools/base/envoy_python.bzl b/tools/base/envoy_python.bzl index f88150fffc143..1d94771708a68 100644 --- a/tools/base/envoy_python.bzl +++ b/tools/base/envoy_python.bzl @@ -1,63 +1,29 @@ -load("@rules_python//python:defs.bzl", "py_binary", "py_library") -load("@base_pip3//:requirements.bzl", "requirement", base_entry_point = "entry_point") load("@aspect_bazel_lib//lib:jq.bzl", "jq") load("@aspect_bazel_lib//lib:yq.bzl", "yq") +load("@base_pip3//:requirements.bzl", "requirement", base_entry_point = "entry_point") +load("@envoy_toolshed//py:macros.bzl", "entry_point") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") def envoy_entry_point( name, pkg, - main = "//tools/base:entry_point.py", - entry_point = base_entry_point, + entry_point_script = "@envoy//tools/base:entry_point.py", + entry_point_alias = base_entry_point, script = None, data = None, deps = None, args = None, - envoy_prefix = "@envoy"): - """This macro provides the convenience of using an `entry_point` while - also being able to create a rule with associated `args` and `data`, as is - possible with the normal `py_binary` rule. - - We may wish to remove this macro should https://github.com/bazelbuild/rules_python/issues/600 - be resolved. - - The `script` and `pkg` args are passed directly to the `entry_point`. - - By default, the pip `entry_point` from `@base_pip3` is used. You can provide - a custom `entry_point` if eg you want to provide an `entry_point` with dev - requirements, or from some other requirements set. - - A `py_binary` is dynamically created to wrap the `entry_point` with provided - `args` and `data`. - """ - actual_entry_point = entry_point( - pkg = pkg, - script = script or pkg, - ) - entry_point_script = "%s%s" % (envoy_prefix, main) - entry_point_py = "entry_point_%s_main.py" % name - entry_point_wrapper = "entry_point_%s_wrapper" % name - entry_point_path = "$(location %s)" % entry_point_script - entry_point_alias = "$(location %s)" % actual_entry_point - - native.genrule( - name = entry_point_wrapper, - cmd = """ - sed s#_ENTRY_POINT_ALIAS_#%s# %s > \"$@\" - """ % (entry_point_alias, entry_point_path), - tools = [ - actual_entry_point, - entry_point_script, - ], - outs = [entry_point_py], - ) - - py_binary( + visibility = ["//visibility:public"]): + entry_point( name = name, - srcs = [entry_point_wrapper, actual_entry_point], - main = entry_point_py, - args = (args or []), - data = (data or []), - deps = (deps or []), + pkg = pkg, + script = script, + entry_point_script = entry_point_script, + entry_point_alias = entry_point_alias, + data = data, + deps = deps, + args = args, + visibility = visibility, ) def envoy_jinja_env( @@ -66,7 +32,7 @@ def envoy_jinja_env( filters = {}, env_kwargs = {}, deps = [], - entry_point = base_entry_point): + entry_point_alias = base_entry_point): """This provides a prebuilt jinja environment that can be imported as a module. Templates are compiled to a python module for faster loading, and the generated environment @@ -157,7 +123,7 @@ def envoy_jinja_env( pkg = "envoy.base.utils", script = "envoy.jinja_env", deps = deps, - entry_point = entry_point, + entry_point_alias = entry_point_alias, ) native.genrule( @@ -246,7 +212,7 @@ def envoy_genjson(name, srcs = [], yaml_srcs = [], filter = None, args = None): filter = filter, ) -def envoy_py_data(name, src, format = None, entry_point = base_entry_point): +def envoy_py_data(name, src, format = None, entry_point_alias = base_entry_point): """Preload JSON/YAML data as a python lib. Data is loaded to python and then dumped to a pickle file. @@ -298,7 +264,7 @@ def envoy_py_data(name, src, format = None, entry_point = base_entry_point): envoy_entry_point( name = name_entry_point, - entry_point = entry_point, + entry_point_alias = entry_point_alias, pkg = "envoy.base.utils", script = "envoy.data_env", ) @@ -348,7 +314,7 @@ def envoy_gencontent( "lstrip_blocks": True, }, template_deps = [], - entry_point = base_entry_point): + entry_point_alias = base_entry_point): '''Generate templated output from a Jinja template and JSON/Yaml sources. `srcs`, `yaml_srcs` and `**json_kwargs` are passed to `envoy_genjson`. @@ -387,14 +353,14 @@ def envoy_gencontent( envoy_py_data( name = "%s_data" % name, src = ":%s_json" % name, - entry_point = entry_point, + entry_point_alias = entry_point_alias, ) envoy_jinja_env( name = name_tpl, env_kwargs = template_kwargs, templates = [template], filters = template_filters, - entry_point = entry_point, + entry_point_alias = entry_point_alias, ) native.genrule( name = "%s_generate_content_py" % name, From 79f97ce9234e4ce2721a88fbb9d83e762bae270e Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 25 Sep 2023 17:17:31 +0100 Subject: [PATCH 068/274] azp/ci: Rename `bazel.yml` -> `ci.yml` (#29774) In a follow up i will add another template for bazel specifically and reduce/simplify some code Signed-off-by: Ryan Northey --- .azure-pipelines/{bazel.yml => ci.yml} | 0 .azure-pipelines/stage/checks.yml | 2 +- .azure-pipelines/stage/linux.yml | 2 +- .azure-pipelines/stage/prechecks.yml | 2 +- .azure-pipelines/stage/publish.yml | 12 ++++++------ .azure-pipelines/stage/verify.yml | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) rename .azure-pipelines/{bazel.yml => ci.yml} (100%) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/ci.yml similarity index 100% rename from .azure-pipelines/bazel.yml rename to .azure-pipelines/ci.yml diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index 50fdb0956cdae..f39eec4787d9c 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -77,7 +77,7 @@ jobs: timeoutInMinutes: 180 pool: envoy-x64-small steps: - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: $(CI_TARGET) cacheName: $(CI_TARGET) diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index be4e4a6ada149..acf1cca81ba06 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -45,7 +45,7 @@ jobs: timeoutInMinutes: ${{ parameters.timeoutBuild }} pool: ${{ parameters.pool }} steps: - - template: ../bazel.yml + - template: ../ci.yml parameters: managedAgent: ${{ parameters.managedAgent }} ciTarget: release diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 6f65efc45d944..6ef132a9b01a5 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -45,7 +45,7 @@ jobs: publishing: CI_TARGET: docs steps: - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: $(CI_TARGET) cacheName: $(CI_TARGET) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index c6885d8762753..4896267629a06 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -104,7 +104,7 @@ jobs: artifactName: "release" itemPattern: "release/**/bin/*" targetPath: $(Build.StagingDirectory) - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: docker-upload cacheName: docker-upload @@ -155,7 +155,7 @@ jobs: artifactName: "release" itemPattern: "release/x64/bin/*" targetPath: $(Build.StagingDirectory) - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: distribution cacheName: distribution @@ -189,7 +189,7 @@ jobs: itemPattern: "release/arm64/bin/*" targetPath: $(Build.StagingDirectory) - - template: ../bazel.yml + - template: ../ci.yml parameters: managedAgent: false ciTarget: distribution @@ -221,7 +221,7 @@ jobs: pool: vmImage: $(agentUbuntu) steps: - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: docs cacheName: docs @@ -310,7 +310,7 @@ jobs: artifactName: "distribution" itemPattern: "distribution/**/packages.*.tar.gz" targetPath: $(Build.StagingDirectory) - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: release.signed cacheName: release-signed @@ -354,7 +354,7 @@ jobs: pool: vmImage: $(agentUbuntu) steps: - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: verify.trigger cacheName: verify-trigger diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index 2214bee7971e3..c0d6f3091f412 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -22,7 +22,7 @@ jobs: itemPattern: "distribution/x64/packages.x64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - - template: ../bazel.yml + - template: ../ci.yml parameters: ciTarget: verify_distro cacheName: verify_distro @@ -43,7 +43,7 @@ jobs: itemPattern: "distribution/arm64/packages.arm64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - - template: ../bazel.yml + - template: ../ci.yml parameters: managedAgent: false ciTarget: verify_distro From a85672df6f877e40cfdddd793bef918617b462b5 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 26 Sep 2023 19:32:18 +0100 Subject: [PATCH 069/274] bazel/tooling: Use toolshed `json_data` macro (#29790) As this is used in the api as well as Envoy itself the dep has been moved there. Signed-off-by: phlax Signed-off-by: Ryan Northey --- api/bazel/BUILD | 2 +- api/bazel/repositories.bzl | 4 ++++ api/bazel/repository_locations.bzl | 14 ++++++++++++++ api/bazel/utils.bzl | 18 ------------------ bazel/BUILD | 2 +- bazel/api_binding.bzl | 1 - contrib/BUILD | 2 +- source/extensions/BUILD | 2 +- 8 files changed, 22 insertions(+), 23 deletions(-) delete mode 100644 api/bazel/utils.bzl diff --git a/api/bazel/BUILD b/api/bazel/BUILD index 63651c1e5a48e..5ac7a0e55c365 100644 --- a/api/bazel/BUILD +++ b/api/bazel/BUILD @@ -1,5 +1,5 @@ +load("@envoy_toolshed//:macros.bzl", "json_data") load("@io_bazel_rules_go//proto:compiler.bzl", "go_proto_compiler") -load(":utils.bzl", "json_data") load(":repository_locations.bzl", "REPOSITORY_LOCATIONS_SPEC") load(":repository_locations_utils.bzl", "load_repository_locations_spec") load( diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index e789fe67fd2be..c6ab16fc1fbfa 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -56,6 +56,10 @@ def api_dependencies(): name = "com_github_chrusty_protoc_gen_jsonschema", ) + external_http_archive( + name = "envoy_toolshed", + ) + PROMETHEUSMETRICS_BUILD_CONTENT = """ load("@envoy_api//bazel:api_build_system.bzl", "api_cc_py_proto_library") load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 24682a66ac10e..d36cd659146ec 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -151,4 +151,18 @@ REPOSITORY_LOCATIONS_SPEC = dict( use_category = ["build"], release_date = "2023-05-30", ), + envoy_toolshed = dict( + project_name = "envoy_toolshed", + project_desc = "Tooling, libraries, runners and checkers for Envoy proxy's CI", + project_url = "https://github.com/envoyproxy/toolshed", + version = "0.0.6", + sha256 = "7047db983e49290ac14b2733459d439a8a521ff49e95fbd0b185a692bd6a6d86", + strip_prefix = "toolshed-bazel-v{version}/bazel", + urls = ["https://github.com/envoyproxy/toolshed/archive/refs/tags/bazel-v{version}.tar.gz"], + use_category = ["build"], + release_date = "2023-09-24", + cpe = "N/A", + license = "Apache-2.0", + license_url = "https://github.com/envoyproxy/envoy/blob/bazel-v{version}/LICENSE", + ), ) diff --git a/api/bazel/utils.bzl b/api/bazel/utils.bzl deleted file mode 100644 index 0961f00eb446a..0000000000000 --- a/api/bazel/utils.bzl +++ /dev/null @@ -1,18 +0,0 @@ -load("@bazel_skylib//rules:write_file.bzl", "write_file") - -def json_data( - name, - data, - visibility = ["//visibility:public"], - **kwargs): - """Write a bazel object to a file - - The provided `data` object should be json serializable. - """ - write_file( - name = name, - out = "%s.json" % name, - content = json.encode(data).split("\n"), - visibility = visibility, - **kwargs - ) diff --git a/bazel/BUILD b/bazel/BUILD index 71db4ba301e45..edb5d4a6e1b6f 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -1,6 +1,6 @@ load("//bazel:envoy_build_system.bzl", "envoy_package") load("//bazel:envoy_internal.bzl", "envoy_select_force_libcpp") -load("@envoy_api//bazel:utils.bzl", "json_data") +load("@envoy_toolshed//:macros.bzl", "json_data") load("@bazel_skylib//lib:selects.bzl", "selects") load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load(":repository_locations.bzl", "REPOSITORY_LOCATIONS_SPEC") diff --git a/bazel/api_binding.bzl b/bazel/api_binding.bzl index 65ed382836fcd..8d46d4c1827b8 100644 --- a/bazel/api_binding.bzl +++ b/bazel/api_binding.bzl @@ -13,7 +13,6 @@ def _default_envoy_api_impl(ctx): ] for d in api_dirs: ctx.symlink(ctx.path(ctx.attr.envoy_root).dirname.get_child(ctx.attr.reldir).get_child(d), d) - ctx.symlink(ctx.path(ctx.attr.envoy_root).dirname.get_child("api").get_child("bazel").get_child("utils.bzl"), "utils.bzl") _default_envoy_api = repository_rule( implementation = _default_envoy_api_impl, diff --git a/contrib/BUILD b/contrib/BUILD index f6813770abcca..34a896d22e409 100644 --- a/contrib/BUILD +++ b/contrib/BUILD @@ -1,4 +1,4 @@ -load("@envoy_api//bazel:utils.bzl", "json_data") +load("@envoy_toolshed//:macros.bzl", "json_data") load(":contrib_build_config.bzl", "CONTRIB_EXTENSIONS") licenses(["notice"]) # Apache 2 diff --git a/source/extensions/BUILD b/source/extensions/BUILD index 152c701894189..dfb91fa3b1853 100644 --- a/source/extensions/BUILD +++ b/source/extensions/BUILD @@ -1,4 +1,4 @@ -load("@envoy_api//bazel:utils.bzl", "json_data") +load("@envoy_toolshed//:macros.bzl", "json_data") load("//bazel:envoy_build_system.bzl", "envoy_extension_package") load(":all_extensions.bzl", "envoy_all_extensions") load(":extensions_build_config.bzl", "EXTENSIONS") From 8c3070218d2a46ec606be13226b5007a75f70376 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 26 Sep 2023 20:18:39 +0100 Subject: [PATCH 070/274] docs/publishing: Use github trigger to update website (#29811) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .azure-pipelines/stage/publish.yml | 25 ------------------------- .azure-pipelines/stages.yml | 1 - .github/workflows/_stage_publish.yml | 14 ++++++++++++++ .github/workflows/envoy-publish.yml | 3 +++ ci/do_ci.sh | 5 ----- ci/run_envoy_docker.sh | 1 - 6 files changed, 17 insertions(+), 32 deletions(-) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 4896267629a06..532cff091012e 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -39,9 +39,6 @@ parameters: - name: authGPGKey type: string default: "" -- name: authNetlifyURL - type: string - default: "" - name: authDockerUser type: string default: "" @@ -247,28 +244,6 @@ jobs: DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} - # Trigger Netlify rebuild of latest docs - - script: | - ci/run_envoy_docker.sh 'ci/do_ci.sh docs-upload' - displayName: "Upload Docs to GCS" - condition: | - and(not(canceled()), - eq(${{ parameters.publishDocsLatest }}, 'true')) - env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} - GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs-publish-latest' - condition: | - and(not(canceled()), - eq(${{ parameters.publishDocsLatest }}, 'true')) - displayName: "Publish latest docs" - workingDirectory: $(Build.SourcesDirectory) - env: - NETLIFY_TRIGGER_URL: ${{ parameters.authNetlifyURL }} - # Publish docs to the website - task: InstallSSHKey@0 condition: | diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 3177b976ef948..462a2c5452530 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -137,7 +137,6 @@ stages: authGPGPassphrase: $(MaintainerGPGKeyPassphrase) authGPGKey: $(MaintainerGPGKeySecureFileDownloadPath) authGPGPath: $(MaintainerGPGKey.secureFilePath) - authNetlifyURL: $(NetlifyTriggerURL) authSSHDocsKeyPublic: $(DocsPublicKey) authSSHDocsKey: $(DocsPrivateKey) authSSHKeyPassphrase: $(SshDeployKeyPassphrase) diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 2b0dcca963ccd..846ad8e3bec4b 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -28,12 +28,26 @@ on: type: string given_ref: type: string + secrets: + ENVOY_CI_SYNC_APP_ID: + ENVOY_CI_SYNC_APP_KEY: concurrency: group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-publish cancel-in-progress: true jobs: + publish_docs: + if: ${{ inputs.trusted }} + steps: + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.0.18 + with: + app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} + key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" + ref: main + repository: "envoyproxy/envoy-website" + workflow: envoy-sync.yaml + publish_ci: if: ${{ ! inputs.trusted }} name: ${{ matrix.name || matrix.target }} diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 4eec30a211494..a64d4ba686278 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -57,6 +57,9 @@ jobs: repo_ref: ${{ needs.env.outputs.trusted != 'true' && inputs.ref || '' }} permissions: contents: write + secrets: + ENVOY_CI_SYNC_APP_ID: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} + ENVOY_CI_SYNC_APP_KEY: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} verify: uses: ./.github/workflows/_stage_verify.yml diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 4c2fb064b3344..55f49139e33c6 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -653,11 +653,6 @@ case $CI_TARGET in "${ENVOY_SRCDIR}/docs/build.sh" ;; - docs-publish-latest) - BUILD_SHA=$(git rev-parse HEAD) - curl -X POST -d "$BUILD_SHA" "$NETLIFY_TRIGGER_URL" - ;; - docs-upload) setup_clang_toolchain "${ENVOY_SRCDIR}/ci/upload_gcs_artifact.sh" /source/generated/docs docs diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index b145fe3fca863..c0a36e08df527 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -150,7 +150,6 @@ docker run --rm \ -e GITHUB_TOKEN \ -e GITHUB_APP_ID \ -e GITHUB_INSTALL_ID \ - -e NETLIFY_TRIGGER_URL \ -e BUILD_SOURCEBRANCHNAME \ -e BAZELISK_BASE_URL \ -e ENVOY_BUILD_ARCH \ From 0789ac619b8cc6ef72fbaec9e9d174dd8bf59a62 Mon Sep 17 00:00:00 2001 From: moderation Date: Tue, 26 Sep 2023 22:15:54 -0700 Subject: [PATCH 071/274] Dependencies: remove duplicate toolshed entry (#29823) Clean up erroneous toolshed dependency. Noop name update to libcirclhist. Signed-off-by: moderation Signed-off-by: Ryan Northey --- api/bazel/repository_locations.bzl | 2 +- bazel/repositories.bzl | 14 ++++---------- bazel/repository_locations.bzl | 25 +++++-------------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index d36cd659146ec..cb2d0d4b4c84f 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -158,7 +158,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( version = "0.0.6", sha256 = "7047db983e49290ac14b2733459d439a8a521ff49e95fbd0b185a692bd6a6d86", strip_prefix = "toolshed-bazel-v{version}/bazel", - urls = ["https://github.com/envoyproxy/toolshed/archive/refs/tags/bazel-v{version}.tar.gz"], + urls = ["https://github.com/envoyproxy/toolshed/archive/bazel-v{version}.tar.gz"], use_category = ["build"], release_date = "2023-09-24", cpe = "N/A", diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 42c4a72a16507..4176d00d65cd0 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -253,11 +253,10 @@ def envoy_dependencies(skip_targets = []): # The long repo names (`com_github_fmtlib_fmt` instead of `fmtlib`) are # semi-standard in the Bazel community, intended to avoid both duplicate # dependencies and name conflicts. - _toolshed() _com_github_axboe_liburing() _com_github_bazel_buildtools() _com_github_c_ares_c_ares() - _com_github_circonus_labs_libcircllhist() + _com_github_openhistogram_libcircllhist() _com_github_cyan4973_xxhash() _com_github_datadog_dd_trace_cpp() _com_github_mirror_tclap() @@ -357,11 +356,6 @@ def envoy_dependencies(skip_targets = []): actual = "@bazel_tools//tools/cpp/runfiles", ) -def _toolshed(): - external_http_archive( - name = "envoy_toolshed", - ) - def _boringssl(): external_http_archive( name = "boringssl", @@ -377,14 +371,14 @@ def _boringssl_fips(): build_file = "@envoy//bazel/external:boringssl_fips.BUILD", ) -def _com_github_circonus_labs_libcircllhist(): +def _com_github_openhistogram_libcircllhist(): external_http_archive( - name = "com_github_circonus_labs_libcircllhist", + name = "com_github_openhistogram_libcircllhist", build_file = "@envoy//bazel/external:libcircllhist.BUILD", ) native.bind( name = "libcircllhist", - actual = "@com_github_circonus_labs_libcircllhist//:libcircllhist", + actual = "@com_github_openhistogram_libcircllhist//:libcircllhist", ) def _com_github_axboe_liburing(): diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 62be1eb3bbd5c..5c9fd142833f2 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -79,20 +79,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/bazelbuild/buildtools/archive/v{version}.tar.gz"], use_category = ["test_only"], ), - envoy_toolshed = dict( - project_name = "envoy_toolshed", - project_desc = "Tooling, libraries, runners and checkers for Envoy proxy's CI", - project_url = "https://github.com/envoyproxy/toolshed", - version = "bazel-0.0.1", - sha256 = "ed9314a378d78b84741a7c275ca41e3c75c9216447d59efc4f8d091a26d9640f", - strip_prefix = "toolshed-{version}/bazel", - urls = ["https://github.com/envoyproxy/toolshed/archive/refs/tags/{version}.tar.gz"], - use_category = ["build"], - release_date = "2023-09-24", - cpe = "N/A", - license = "Apache-2.0", - license_url = "https://github.com/envoyproxy/envoy/blob/{version}/LICENSE", - ), rules_fuzzing = dict( project_name = "Fuzzing Rules for Bazel", project_desc = "Bazel rules for fuzz tests", @@ -242,19 +228,19 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "c-ares", license_url = "https://github.com/c-ares/c-ares/blob/cares-{underscore_version}/LICENSE.md", ), - com_github_circonus_labs_libcircllhist = dict( + com_github_openhistogram_libcircllhist = dict( project_name = "libcircllhist", - project_desc = "An implementation of Circonus log-linear histograms", - project_url = "https://github.com/circonus-labs/libcircllhist", + project_desc = "An implementation of OpenHistogram log-linear histograms", + project_url = "https://github.com/openhistogram/libcircllhist", version = "39f9db724a81ba78f5d037f1cae79c5a07107c8e", sha256 = "fd2492f6cc1f8734f8f57be8c2e7f2907e94ee2a4c02445ce59c4241fece144b", strip_prefix = "libcircllhist-{version}", - urls = ["https://github.com/circonus-labs/libcircllhist/archive/{version}.tar.gz"], + urls = ["https://github.com/openhistogram/libcircllhist/archive/{version}.tar.gz"], use_category = ["controlplane", "observability_core", "dataplane_core"], release_date = "2019-05-21", cpe = "N/A", license = "Apache-2.0", - license_url = "https://github.com/circonus-labs/libcircllhist/blob/{version}/LICENSE", + license_url = "https://github.com/openhistogram/libcircllhist/blob/{version}/LICENSE", ), com_github_cyan4973_xxhash = dict( project_name = "xxHash", @@ -520,7 +506,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "skywalking-data-collect-protocol", project_desc = "Data Collect Protocols of Apache SkyWalking", project_url = "https://github.com/apache/skywalking-data-collect-protocol", - name = "skywalking_data_collect_protocol", sha256 = "49bd689b9c1c0ea12064bd35581689cef7835e5ac15d335dc425fbfc2029aa90", urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v{version}.tar.gz"], strip_prefix = "skywalking-data-collect-protocol-{version}", From 53f8e8276c1e34e72a70bc16a736c35039b11aff Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 27 Sep 2023 07:49:55 +0100 Subject: [PATCH 072/274] github/ci: Fix publish workflow (#29829) Signed-off-by: Ryan Northey --- .github/workflows/_stage_publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 846ad8e3bec4b..526ab4807e19e 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -39,6 +39,7 @@ concurrency: jobs: publish_docs: if: ${{ inputs.trusted }} + runs-on: ubuntu-22.04 steps: - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.0.18 with: From 288c629d6270068ebf045e9cb43f9f0928c2936e Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 29 Sep 2023 17:41:52 +0100 Subject: [PATCH 073/274] protodoc: Fix rst checker and load lazily (#29874) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/prechecks.yml | 1 + .bazelrc | 2 ++ tools/protodoc/protodoc.py | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 6ef132a9b01a5..5e83ae21feadd 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -47,6 +47,7 @@ jobs: steps: - template: ../ci.yml parameters: + bazelBuildExtraOptions: --config=docs-ci ciTarget: $(CI_TARGET) cacheName: $(CI_TARGET) cacheTestResults: ${{ parameters.cacheTestResults }} diff --git a/.bazelrc b/.bazelrc index f1de05e65846d..a473dc4020da1 100644 --- a/.bazelrc +++ b/.bazelrc @@ -42,6 +42,8 @@ build --action_env=BAZEL_FAKE_SCM_REVISION --host_action_env=BAZEL_FAKE_SCM_REVI build --enable_platform_specific_config build --test_summary=terse +build:docs-ci --action_env=DOCS_RST_CHECK=1 --host_action_env=DOCS_RST_CHECK=1 + # TODO(keith): Remove once these 2 are the default build --incompatible_config_setting_private_default_visibility build --incompatible_enforce_config_setting_visibility diff --git a/tools/protodoc/protodoc.py b/tools/protodoc/protodoc.py index 651d9b78e7496..88ac1f7b66e7b 100755 --- a/tools/protodoc/protodoc.py +++ b/tools/protodoc/protodoc.py @@ -3,7 +3,9 @@ # for the underlying protos mentioned in this file. See # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html for Sphinx RST syntax. +import importlib import logging +import os import sys from collections import defaultdict from functools import cached_property, lru_cache @@ -17,8 +19,6 @@ from validate import validate_pb2 from xds.annotations.v3 import status_pb2 as xds_status_pb2 -from envoy.code.check.checker import BackticksCheck - from tools.api_proto_plugin import annotations, constants, plugin, visitor from tools.protodoc import jinja from tools.protodoc.data import data @@ -140,8 +140,8 @@ class RstFormatVisitor(visitor.Visitor): """ @cached_property - def backticks_check(self) -> BackticksCheck: - return BackticksCheck() + def backticks_check(self): + return importlib.import_module("envoy.code.check.checker").BackticksCheck() @property def contrib_extension_category_data(self): @@ -253,7 +253,7 @@ def visit_message(self, msg_proto, ctx, nested_msgs: Iterable, nested_enums: Ite if msg_proto.options.map_entry or self._hide(ctx.leading_comment.annotations): return '' name = normalize_type_context_name(ctx.name) - return self.tpl_content.render( + message = self.tpl_content.render( header=self.tpl_header.render( anchor=message_cross_ref_label(name), title=name, @@ -269,6 +269,13 @@ def visit_message(self, msg_proto, ctx, nested_msgs: Iterable, nested_enums: Ite nested_msgs=nested_msgs, nested_enums=nested_enums)) + if not os.environ.get("DOCS_RST_CHECK"): + return message + error = self.backticks_check(message) + if error: + logger.warning(f"Bad RST ({msg_proto.name}): {error}") + return message + @lru_cache def _comment(self, comment, show_wip_warning=False): """Format a comment string with additional RST for annotations. From d9486078d12ab51dc3a1680146c6955887b9624d Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 29 Sep 2023 19:05:16 +0100 Subject: [PATCH 074/274] ci/publishing: Remove postsubmit docs build (#29853) this is now handled downstream Signed-off-by: Ryan Northey --- .azure-pipelines/env.yml | 20 ------ .azure-pipelines/stage/publish.yml | 92 +++++----------------------- .azure-pipelines/stages.yml | 10 --- .github/workflows/_stage_publish.yml | 26 ++++---- 4 files changed, 31 insertions(+), 117 deletions(-) diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index ca6e5ecb97e47..c70c868965ba4 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -171,32 +171,18 @@ jobs: PUBLISH_GITHUB_RELEASE=$(run.packaging) PUBLISH_DOCKERHUB=false - PUBLISH_DOCS=false - PUBLISH_DOCS_LATEST=false - PUBLISH_DOCS_RELEASE=false if [[ "$ISSTABLEBRANCH" == True && -n "$POSTSUBMIT" && "$NOSYNC" != true ]]; then - # Build docs for publishing either latest or a release build - PUBLISH_DOCS=true # main if [[ "$ISMAIN" == True ]]; then # Update the Dockerhub README PUBLISH_DOCKERHUB=true - if [[ "$(state.isDev)" == true ]]; then - # Postsubmit on `main` trigger rebuild of latest docs - PUBLISH_DOCS_LATEST=true - fi # Not main, and not -dev elif [[ "$(state.isDev)" == false ]]; then if [[ "$(state.versionPatch)" -eq 0 ]]; then # A just-forked branch PUBLISH_GITHUB_RELEASE=false fi - # A stable release, publish docs to the release - PUBLISH_DOCS_RELEASE=true - else - # Postsubmit for non-main/release, skip publishing docs in this case - PUBLISH_DOCS=false fi fi @@ -207,9 +193,6 @@ jobs: echo "##vso[task.setvariable variable=githubRelease;isoutput=true]${PUBLISH_GITHUB_RELEASE}" echo "##vso[task.setvariable variable=dockerhub;isoutput=true]${PUBLISH_DOCKERHUB}" - echo "##vso[task.setvariable variable=docs;isoutput=true]${PUBLISH_DOCS}" - echo "##vso[task.setvariable variable=docsLatest;isoutput=true]${PUBLISH_DOCS_LATEST}" - echo "##vso[task.setvariable variable=docsRelease;isoutput=true]${PUBLISH_DOCS_RELEASE}" displayName: "Decide what to publish" workingDirectory: $(Build.SourcesDirectory) @@ -231,9 +214,6 @@ jobs: echo echo "env.outputs['publish.githubRelease']: $(publish.githubRelease)" echo "env.outputs['publish.dockerhub]: $(publish.dockerhub)" - echo "env.outputs['publish.docs]: $(publish.docs)" - echo "env.outputs['publish.docsLatest]: $(publish.docsLatest)" - echo "env.outputs['publish.docsRelease]: $(publish.docsRelease)" displayName: "Print build environment" diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 532cff091012e..da0cf2e0518d5 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -45,15 +45,6 @@ parameters: - name: authDockerPassword type: string default: "" -- name: authSSHDocsKey - type: string - default: "" -- name: authSSHDocsKeyPublic - type: string - default: "" -- name: authSSHKeyPassphrase - type: string - default: "" - name: runDocker displayName: "Run Docker" @@ -68,18 +59,6 @@ parameters: displayName: "Publish Dockerhub" type: string default: false -- name: publishDocs - displayName: "Publish Docs" - type: string - default: false -- name: publishDocsLatest - displayName: "Publish latest docs" - type: string - default: false -- name: publishDocsRelease - displayName: "Publish release docs" - type: string - default: false - name: publishGithubRelease displayName: "Publish Github release" type: string @@ -136,6 +115,22 @@ jobs: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_DOCKER_IN_DOCKER: 1 + stepsPost: + - script: | + ci/run_envoy_docker.sh 'ci/do_ci.sh dockerhub-publish' + condition: | + and(not(canceled()), succeeded(), + eq(${{ parameters.publishDockerhub }}, 'true')) + displayName: "Publish Dockerhub description and README" + env: + ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) + ENVOY_RBE: "1" + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" + GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} + GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} + DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} + DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} + - job: package_x64 displayName: Linux debs (x64) dependsOn: [] @@ -209,58 +204,6 @@ jobs: set -e rm -rf $(Build.StagingDirectory)/.gnupg -- job: docs - displayName: Publish docs - dependsOn: [] - condition: | - and(not(canceled()), - eq(${{ parameters.publishDocs }}, 'true')) - pool: - vmImage: $(agentUbuntu) - steps: - - template: ../ci.yml - parameters: - ciTarget: docs - cacheName: docs - cacheVersion: $(cacheKeyBazel) - publishEnvoy: false - publishTestResults: false - env: - CI_BRANCH: $(Build.SourceBranch) - stepsPost: - - - script: | - ci/run_envoy_docker.sh 'ci/do_ci.sh dockerhub-publish' - condition: | - and(not(canceled()), - eq(${{ parameters.publishDockerhub }}, 'true')) - displayName: "Publish Dockerhub description and README" - env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} - GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} - DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} - - # Publish docs to the website - - task: InstallSSHKey@0 - condition: | - and(not(canceled()), - eq(${{ parameters.publishDocsRelease }}, 'true')) - inputs: - hostName: $(authGithubSSHKeyPublic) - sshPublicKey: "${{ parameters.authSSHDocsKeyPublic }}" - sshPassphrase: "${{ parameters.authSSHKeyPassphrase }}" - sshKeySecureFile: "${{ parameters.authSSHDocsKey }}" - - script: docs/publish.sh - condition: | - and(not(canceled()), - eq(${{ parameters.publishDocsRelease }}, 'true')) - displayName: "Publish release docs" - workingDirectory: $(Build.SourcesDirectory) - - job: signed_release displayName: Signed binaries dependsOn: @@ -301,7 +244,7 @@ jobs: pathGPGConfiguredHome: /build/.gnupg pathGPGHome: $(Build.StagingDirectory)/.gnupg - job: success - dependsOn: ["docker", "docs", "signed_release"] + dependsOn: ["docker", "signed_release"] displayName: Success (linux artefacts) pool: vmImage: $(agentUbuntu) @@ -312,7 +255,6 @@ jobs: condition: | and( in(dependencies.docker.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), - in(dependencies.docs.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), in(dependencies.signed_release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')) steps: - checkout: none diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 462a2c5452530..dad053a7af180 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -57,7 +57,6 @@ stages: jobs: - template: env.yml - - stage: prechecks displayName: Prechecks dependsOn: ["env"] @@ -122,9 +121,6 @@ stages: RUN_PACKAGING: $[stageDependencies.env.repo.outputs['run.packaging']] PUBLISH_GITHUB_RELEASE: $[stageDependencies.env.repo.outputs['publish.githubRelease']] PUBLISH_DOCKERHUB: $[stageDependencies.env.repo.outputs['publish.dockerhub']] - PUBLISH_DOCS: $[stageDependencies.env.repo.outputs['publish.docs']] - PUBLISH_DOCS_LATEST: $[stageDependencies.env.repo.outputs['publish.docsLatest']] - PUBLISH_DOCS_RELEASE: $[stageDependencies.env.repo.outputs['publish.docsRelease']] jobs: - template: stage/publish.yml parameters: @@ -137,17 +133,11 @@ stages: authGPGPassphrase: $(MaintainerGPGKeyPassphrase) authGPGKey: $(MaintainerGPGKeySecureFileDownloadPath) authGPGPath: $(MaintainerGPGKey.secureFilePath) - authSSHDocsKeyPublic: $(DocsPublicKey) - authSSHDocsKey: $(DocsPrivateKey) - authSSHKeyPassphrase: $(SshDeployKeyPassphrase) bucketGCP: $(GcsArtifactBucket) timeoutDockerBuild: ${{ parameters.timeoutDockerBuild }} timeoutDockerPublish: ${{ parameters.timeoutDockerPublish }} runDocker: variables['RUN_DOCKER'] runPackaging: variables['RUN_PACKAGING'] - publishDocs: variables['PUBLISH_DOCS'] - publishDocsLatest: variables['PUBLISH_DOCS_LATEST'] - publishDocsRelease: variables['PUBLISH_DOCS_RELEASE'] publishDockerhub: variables['PUBLISH_DOCKERHUB'] publishGithubRelease: variables['PUBLISH_GITHUB_RELEASE'] diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 526ab4807e19e..83974e58dee68 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -37,18 +37,6 @@ concurrency: cancel-in-progress: true jobs: - publish_docs: - if: ${{ inputs.trusted }} - runs-on: ubuntu-22.04 - steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.0.18 - with: - app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} - key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" - ref: main - repository: "envoyproxy/envoy-website" - workflow: envoy-sync.yaml - publish_ci: if: ${{ ! inputs.trusted }} name: ${{ matrix.name || matrix.target }} @@ -105,3 +93,17 @@ jobs: run_pre_with: ${{ matrix.run_pre_with }} env: ${{ matrix.env }} trusted: true + + publish_docs: + if: ${{ inputs.trusted }} + runs-on: ubuntu-22.04 + needs: + - publish + steps: + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.0.18 + with: + app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} + key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" + ref: main + repository: ${ inputs.version_dev != '' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' } + workflow: envoy-sync.yaml From 3e37b14c99841128d45e2a04011f4ddc54ab67ff Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 29 Sep 2023 20:34:00 +0100 Subject: [PATCH 075/274] docs/build: Assorted fixes for building as external target (#29845) Signed-off-by: Ryan Northey Signed-off-by: phlax --- bazel/repositories.bzl | 2 +- docs/BUILD | 12 +++++++++--- docs/build.sh | 2 +- tools/base/requirements.in | 3 ++- tools/base/requirements.txt | 13 +++++++------ tools/docs/BUILD | 1 + tools/docs/generate_version_histories.py | 9 +++++---- tools/protodoc/protodoc.bzl | 2 +- 8 files changed, 27 insertions(+), 17 deletions(-) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 4176d00d65cd0..cce9b9165fa5d 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -114,7 +114,7 @@ genrule( name = "project", outs = ["project.json"], cmd = """ - $(location :get_project_json) . > $@ + $(location :get_project_json) $$(dirname $(location @envoy//:VERSION.txt)) > $@ """, tools = [ ":get_project_json", diff --git a/docs/BUILD b/docs/BUILD index 80fc7b6e9842b..265a8840986f7 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -119,7 +119,7 @@ genrule( srcs = [":empty_extensions.json"], outs = ["empty_protos_rst.tar.gz"], cmd = """ - $(location //tools/protodoc:generate_empty) \\ + $(location //tools/protodoc:generate_empty) \ $(location empty_extensions.json) $@ """, exec_tools = ["//tools/protodoc:generate_empty"], @@ -176,7 +176,7 @@ genrule( name = "version_histories", outs = ["version_histories.tar.gz"], cmd = """ - $(location //tools/docs:generate_version_histories) $@ + $(location //tools/docs:generate_version_histories) --path=$$(dirname $(location //:VERSION.txt)) $@ """, exec_tools = [ ":versions.yaml", @@ -243,7 +243,7 @@ genrule( --build_sha="$${BUILD_DOCS_SHA:-$${BUILD_SCM_REVISION}}" \ --docs_tag="$${BUILD_DOCS_TAG:-}" \ --version_file=$(location //:VERSION.txt) \ - --descriptor_path=$(location @envoy_api//:v3_proto_set) \\ + --descriptor_path=$(location @envoy_api//:v3_proto_set) \ $(location rst) \ $@ """, @@ -264,6 +264,7 @@ genrule( cmd = """ $(location //tools/docs:sphinx_runner) \ $${SPHINX_RUNNER_ARGS:-} \ + --build_sha="$${BUILD_DOCS_SHA:-}" \ --version_file=$(location //:VERSION.txt) \ --descriptor_path=$(location @envoy_api//:v3_proto_set) \ $(location :rst) \ @@ -276,3 +277,8 @@ genrule( "@envoy_api//:v3_proto_set", ], ) + +alias( + name = "docs", + actual = ":html", +) diff --git a/docs/build.sh b/docs/build.sh index 110aa22ebec54..70e1998e6bc74 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -45,7 +45,7 @@ fi # Building html/rst is determined by then needs of CI but can be overridden in dev. if [[ "${BUILD_TYPE}" == "html" ]] || [[ -n "${DOCS_BUILD_HTML}" ]]; then BUILD_HTML=1 - BUILD_HTML_TARGET="//docs:html" + BUILD_HTML_TARGET="//docs" BUILD_HTML_TARBALL="bazel-bin/docs/html.tar.gz" if [[ -n "${CI_BRANCH}" ]] || [[ -n "${DOCS_BUILD_RELEASE}" ]]; then # CI build - use git sha diff --git a/tools/base/requirements.in b/tools/base/requirements.in index e34f513c30539..c0c77a086bf56 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -1,5 +1,6 @@ abstracts>=0.0.12 aio.api.bazel +aio.api.github>=0.2.5 aiohttp>=3.8.1 cffi>=1.15.0 clang-format==14.0.6 @@ -8,7 +9,7 @@ colorama coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 -envoy.base.utils>=0.4.11 +envoy.base.utils>=0.4.12 envoy.code.check>=0.5.8 envoy.dependency.check>=0.1.7 envoy.distribution.release>=0.0.9 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 4a6c421c798dd..397a48db90cdb 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -25,10 +25,11 @@ aio-api-bazel==0.0.2 \ --hash=sha256:56e36463d236e477b7e282f2d870185a0b978b50e2c3803c1ebf8b8ac4b18f5b \ --hash=sha256:d3f563b7698e874437d80538a89dd4d79bc37de2e850c846330ae456e3f21dcc # via -r requirements.in -aio-api-github==0.2.4 \ - --hash=sha256:ccbc7c6c61b25994e87474d78c48549e9fbc98c2cc04314b50b80ba1f40fd521 \ - --hash=sha256:eccfccd1503f50384de3f6526bd780ca02107cb440a666b2c1ab978d99c7db5e +aio-api-github==0.2.5 \ + --hash=sha256:301a357209831ac2bc0fb5c79f8b8795a5363da5cabc2229f10155bdb6d42f5d \ + --hash=sha256:3532d0892e875e8bb6b188c0beba4e8bac9d5147e249ce987bb2beef1e7b711e # via + # -r requirements.in # envoy-base-utils # envoy-dependency-check aio-api-nist==0.0.3 \ @@ -446,9 +447,9 @@ docutils==0.19 \ # via # envoy-docs-sphinx-runner # sphinx -envoy-base-utils==0.4.11 \ - --hash=sha256:5ced696c470b4c3090e6fc3f74e7e33f5fe217e775b1fc1fb56dfc756b781fbe \ - --hash=sha256:97c79177bb89360b7772e58fb20c671c67bf0b6cdee74c0b9f8a80433f0370cc +envoy-base-utils==0.4.12 \ + --hash=sha256:2bafcb6be3c1223968c9ee90b7a33d6d93aee16fe99bef37410ea9e4bc1a957f \ + --hash=sha256:b9b409abe83be6911aa03cfe635ed1e2e2b9e44e7c8595b2b7e5c7aae7bf70fc # via # -r requirements.in # envoy-code-check diff --git a/tools/docs/BUILD b/tools/docs/BUILD index edc5c7c2690e0..672ba2a42b933 100644 --- a/tools/docs/BUILD +++ b/tools/docs/BUILD @@ -42,6 +42,7 @@ py_binary( envoy_entry_point( name = "sphinx_runner", pkg = "envoy.docs.sphinx_runner", + visibility = ["//visibility:public"], ) py_binary( diff --git a/tools/docs/generate_version_histories.py b/tools/docs/generate_version_histories.py index dd09075a50965..6bbb16f34ba03 100644 --- a/tools/docs/generate_version_histories.py +++ b/tools/docs/generate_version_histories.py @@ -6,7 +6,7 @@ from frozendict import frozendict import jinja2 -from packaging import version +from packaging import version as _version from aio.run import runner @@ -147,7 +147,7 @@ def jinja_env(self) -> jinja2.Environment: @cached_property def project(self) -> IProject: - return Project() + return Project(path=self.args.path) @cached_property def sections(self) -> frozendict: @@ -171,6 +171,7 @@ def version_history_tpl(self): def add_arguments(self, parser) -> None: super().add_arguments(parser) + parser.add_argument("--path") parser.add_argument("output_file") def minor_index_path(self, minor_version) -> pathlib.Path: @@ -192,7 +193,7 @@ async def write_version_histories(self) -> None: for changelog_version in self.project.changelogs: await self.write_version_history(changelog_version) - async def write_version_history(self, changelog_version: version.Version) -> None: + async def write_version_history(self, changelog_version: _version.Version) -> None: minor_version = utils.minor_version_for(changelog_version) root_path = self.tpath.joinpath(f"v{minor_version.base_version}") root_path.mkdir(parents=True, exist_ok=True) @@ -258,7 +259,7 @@ async def write_version_history_minor_indeces(self) -> None: await self.write_version_history_minor_index(minor_version, patches) async def write_version_history_minor_index( - self, minor_version: version.Version, patch_versions) -> None: + self, minor_version: _version.Version, patch_versions) -> None: skip_first = (self.project.is_dev and self.project.is_current(patch_versions[0])) if skip_first: patch_versions = patch_versions[1:] diff --git a/tools/protodoc/protodoc.bzl b/tools/protodoc/protodoc.bzl index 6ad1db8d97bd0..96f22e850c842 100644 --- a/tools/protodoc/protodoc.bzl +++ b/tools/protodoc/protodoc.bzl @@ -12,7 +12,7 @@ def _protodoc_impl(target, ctx): # # The aspect builds the transitive docs, so any .proto in the dependency graph # get docs created. -protodoc_aspect = api_proto_plugin_aspect("//tools/protodoc", _protodoc_impl) +protodoc_aspect = api_proto_plugin_aspect("@envoy//tools/protodoc", _protodoc_impl) def _protodoc_rule_impl(ctx): deps = [] From 5efcf0dac80a37b077d27ce0f8c54ae47c496107 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 2 Oct 2023 07:08:39 +0100 Subject: [PATCH 076/274] py/tools: Namespace repo packages to prevent conflict (#29787) Signed-off-by: Ryan Northey Signed-off-by: phlax --- BUILD | 7 ++ bazel/repositories.bzl | 6 ++ distribution/dockerhub/BUILD | 4 +- mobile/BUILD | 185 ----------------------------------- tools/BUILD | 3 + tools/base/envoy_python.bzl | 74 +++++++++++++- tools/code/BUILD | 4 +- tools/dependency/BUILD | 7 +- tools/distribution/BUILD | 6 +- tools/docs/BUILD | 25 ++--- tools/proto_format/BUILD | 9 +- tools/protodoc/BUILD | 13 +-- tools/protoprint/BUILD | 7 +- 13 files changed, 125 insertions(+), 225 deletions(-) delete mode 100644 mobile/BUILD diff --git a/BUILD b/BUILD index 34c8e7a4b6334..28d92d8c704a8 100644 --- a/BUILD +++ b/BUILD @@ -1,5 +1,12 @@ +load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//tools/base:envoy_python.bzl", "envoy_py_namespace") + licenses(["notice"]) # Apache 2 +envoy_package() + +envoy_py_namespace() + exports_files([ "VERSION.txt", "API_VERSION.txt", diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index cce9b9165fa5d..71667227f73c0 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -108,6 +108,7 @@ envoy_entry_point( name = "get_project_json", pkg = "envoy.base.utils", script = "envoy.project_data", + init_data = [":__init__.py"], ) genrule( @@ -132,6 +133,7 @@ envoy_entry_point( ], pkg = "envoy.base.utils", script = "envoy.project", + init_data = [":__init__.py"], ) envoy_entry_point( @@ -142,6 +144,7 @@ envoy_entry_point( ], pkg = "envoy.base.utils", script = "envoy.project", + init_data = [":__init__.py"], ) envoy_entry_point( @@ -152,6 +155,7 @@ envoy_entry_point( ], pkg = "envoy.base.utils", script = "envoy.project", + init_data = [":__init__.py"], ) envoy_entry_point( @@ -162,6 +166,7 @@ envoy_entry_point( ], pkg = "envoy.base.utils", script = "envoy.project", + init_data = [":__init__.py"], ) envoy_entry_point( @@ -172,6 +177,7 @@ envoy_entry_point( ], pkg = "envoy.base.utils", script = "envoy.project", + init_data = [":__init__.py"], ) ''') diff --git a/distribution/dockerhub/BUILD b/distribution/dockerhub/BUILD index cb48d42a20fd9..cd6321175ee6a 100644 --- a/distribution/dockerhub/BUILD +++ b/distribution/dockerhub/BUILD @@ -1,10 +1,12 @@ load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_gencontent") +load("//tools/base:envoy_python.bzl", "envoy_gencontent", "envoy_py_namespace") licenses(["notice"]) # Apache 2 envoy_package() +envoy_py_namespace() + envoy_gencontent( name = "readme", srcs = ["@envoy_repo//:project"], diff --git a/mobile/BUILD b/mobile/BUILD deleted file mode 100644 index 3972e1af9e43d..0000000000000 --- a/mobile/BUILD +++ /dev/null @@ -1,185 +0,0 @@ -load("@build_bazel_rules_android//android:rules.bzl", "aar_import") -load("@build_bazel_rules_apple//apple:apple.bzl", "apple_static_framework_import") -load("@io_bazel_rules_kotlin//kotlin/internal:toolchains.bzl", "define_kt_toolchain") -load( - "@com_github_buildbuddy_io_rules_xcodeproj//xcodeproj:defs.bzl", - "project_options", - "top_level_targets", - "xcode_schemes", - "xcodeproj", -) -load("//bazel:framework_imports_extractor.bzl", "framework_imports_extractor") - -licenses(["notice"]) # Apache 2 - -alias( - name = "ios_xcframework", - actual = "//library/swift:Envoy", - visibility = ["//visibility:public"], -) - -alias( - name = "ios_dist", - actual = "//library/swift:ios_framework", -) - -framework_imports_extractor( - name = "framework_imports", - framework = "//library/swift:ios_framework", -) - -apple_static_framework_import( - name = "envoy_mobile_ios", - framework_imports = [":framework_imports"], - sdk_dylibs = [ - "resolv.9", - "c++", - ], - sdk_frameworks = [ - "Network", - "SystemConfiguration", - "UIKit", - ], - visibility = ["//visibility:public"], -) - -alias( - name = "android_aar", - actual = "//library/kotlin/io/envoyproxy/envoymobile:envoy_aar", - visibility = ["//visibility:public"], -) - -aar_import( - name = "envoy_mobile_android", - aar = "//library/kotlin/io/envoyproxy/envoymobile:envoy_aar", - visibility = ["//visibility:public"], -) - -alias( - name = "android_dist", - actual = "//library/kotlin/io/envoyproxy/envoymobile:envoy_aar_with_artifacts", -) - -define_kt_toolchain( - name = "kotlin_toolchain", - jvm_target = "1.8", -) - -filegroup( - name = "kotlin_lint_config", - srcs = [".kotlinlint.yml"], - visibility = ["//visibility:public"], -) - -filegroup( - name = "editor_config", - srcs = [".editorconfig"], - visibility = ["//visibility:public"], -) - -genrule( - name = "kotlin_format", - srcs = ["//:editor_config"], - outs = ["kotlin_format.txt"], - cmd = """ - $(location @kotlin_formatter//file) --android "**/*.kt" \ - --reporter=plain --reporter=checkstyle,output=$@ \ - --editorconfig=$(location //:editor_config) - """, - tools = ["@kotlin_formatter//file"], -) - -genrule( - name = "kotlin_format_fix", - srcs = ["//:editor_config"], - outs = ["kotlin_format_fix.txt"], - cmd = """ - $(location @kotlin_formatter//file) -F --android "**/*.kt" \ - --reporter=plain --reporter=checkstyle,output=$@ \ - --editorconfig=$(location //:editor_config) - """, - tools = ["@kotlin_formatter//file"], -) - -xcodeproj( - name = "xcodeproj", - bazel_path = "./bazelw", - build_mode = "bazel", - project_name = "Envoy", - project_options = project_options( - indent_width = 2, - tab_width = 2, - ), - scheme_autogeneration_mode = "auto", # Switch to "all" to generate schemes for all deps - schemes = [ - xcode_schemes.scheme( - name = "Async Await App", - launch_action = xcode_schemes.launch_action("//examples/swift/async_await:app"), - ), - xcode_schemes.scheme( - name = "Hello World App", - launch_action = xcode_schemes.launch_action("//examples/swift/hello_world:app"), - ), - xcode_schemes.scheme( - name = "Hello World App (ObjC)", - launch_action = xcode_schemes.launch_action("//examples/objective-c/hello_world:app"), - ), - xcode_schemes.scheme( - name = "Baseline App", - launch_action = xcode_schemes.launch_action("//test/swift/apps/baseline:app"), - ), - xcode_schemes.scheme( - name = "Experimental App", - launch_action = xcode_schemes.launch_action("//test/swift/apps/experimental:app"), - ), - xcode_schemes.scheme( - name = "Swift Library", - build_action = xcode_schemes.build_action(["//library/swift:ios_lib"]), - ), - xcode_schemes.scheme( - name = "iOS Tests", - test_action = xcode_schemes.test_action([ - "//experimental/swift:quic_stream_test", - "//test/objective-c:envoy_bridge_utility_test", - "//test/swift/integration:test", - "//test/swift/stats:test", - "//test/swift:test", - ]), - ), - xcode_schemes.scheme( - name = "Swift C++ Interop Tests", - test_action = xcode_schemes.test_action([ - "//test/swift/cxx:test", - ]), - ), - xcode_schemes.scheme( - name = "Objective-C Library", - build_action = xcode_schemes.build_action(["//library/objective-c:envoy_engine_objc_lib"]), - test_action = xcode_schemes.test_action(["//test/objective-c:envoy_bridge_utility_test"]), - ), - ], - tags = ["manual"], - top_level_targets = [ - # Apps - top_level_targets( - labels = [ - "//examples/objective-c/hello_world:app", - "//examples/swift/async_await:app", - "//examples/swift/hello_world:app", - "//test/swift/apps/baseline:app", - "//test/swift/apps/experimental:app", - ], - target_environments = [ - "device", - "simulator", - ], - ), - # Tests - "//experimental/swift:quic_stream_test", - "//test/objective-c:envoy_bridge_utility_test", - "//test/swift/cxx:test", - "//test/swift/integration:test", - "//test/swift/stats:test", - "//test/swift:test", - ], -) diff --git a/tools/BUILD b/tools/BUILD index 5e578a3c82ca6..ec8b6d14f8e23 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -5,11 +5,14 @@ load( "envoy_package", "envoy_py_test_binary", ) +load("//tools/base:envoy_python.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 envoy_package() +envoy_py_namespace() + exports_files([ "gen_git_sha.sh", "check_repositories.sh", diff --git a/tools/base/envoy_python.bzl b/tools/base/envoy_python.bzl index 1d94771708a68..ec852e511b440 100644 --- a/tools/base/envoy_python.bzl +++ b/tools/base/envoy_python.bzl @@ -4,6 +4,56 @@ load("@base_pip3//:requirements.bzl", "requirement", base_entry_point = "entry_p load("@envoy_toolshed//py:macros.bzl", "entry_point") load("@rules_python//python:defs.bzl", "py_binary", "py_library") +ENVOY_PYTOOL_NAMESPACE = [ + ":py-init", + "//:py-init", + "//tools:py-init", +] + +def envoy_py_namespace(): + """Adding this to a build, injects a namespaced __init__.py, this allows namespaced + packages - eg envoy.base.utils to co-exist with packages created from the repo.""" + native.genrule( + name = "py-init-file", + outs = ["__init__.py"], + cmd = """ + echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" > $@ + """, + ) + py_library( + name = "py-init", + srcs = [":py-init-file"], + visibility = ["//visibility:public"], + ) + +def envoy_pytool_binary( + name, + data = None, + init_data = ENVOY_PYTOOL_NAMESPACE, + **kwargs): + """Wraps py_binary with envoy namespaced __init__.py files. + + If used outside of tools/${toolname}/BUILD you must specify the init_data.""" + py_binary( + name = name, + data = init_data + (data or []), + **kwargs + ) + +def envoy_pytool_library( + name, + data = None, + init_data = ENVOY_PYTOOL_NAMESPACE, + **kwargs): + """Wraps py_library with envoy namespaced __init__.py files. + + If used outside of tools/${toolname}/BUILD you must specify the init_data.""" + py_library( + name = name, + data = init_data + (data or []), + **kwargs + ) + def envoy_entry_point( name, pkg, @@ -11,6 +61,7 @@ def envoy_entry_point( entry_point_alias = base_entry_point, script = None, data = None, + init_data = ENVOY_PYTOOL_NAMESPACE, deps = None, args = None, visibility = ["//visibility:public"]): @@ -20,7 +71,7 @@ def envoy_entry_point( script = script, entry_point_script = entry_point_script, entry_point_alias = entry_point_alias, - data = data, + data = (data or []) + init_data, deps = deps, args = args, visibility = visibility, @@ -31,6 +82,8 @@ def envoy_jinja_env( templates, filters = {}, env_kwargs = {}, + init_data = ENVOY_PYTOOL_NAMESPACE, + data = [], deps = [], entry_point_alias = base_entry_point): """This provides a prebuilt jinja environment that can be imported as a module. @@ -152,9 +205,10 @@ def envoy_jinja_env( exec_tools = [name_templates], ) - py_library( + envoy_pytool_library( name = name, srcs = [name_env_py], + init_data = init_data, data = [name_templates], deps = [name_entry_point], ) @@ -212,7 +266,12 @@ def envoy_genjson(name, srcs = [], yaml_srcs = [], filter = None, args = None): filter = filter, ) -def envoy_py_data(name, src, format = None, entry_point_alias = base_entry_point): +def envoy_py_data( + name, + src, + init_data = ENVOY_PYTOOL_NAMESPACE, + format = None, + entry_point_alias = base_entry_point): """Preload JSON/YAML data as a python lib. Data is loaded to python and then dumped to a pickle file. @@ -293,9 +352,10 @@ def envoy_py_data(name, src, format = None, entry_point_alias = base_entry_point tools = [name_pickle], ) - py_library( + envoy_pytool_library( name = name, srcs = [name_env_py], + init_data = init_data, data = [name_pickle], deps = [name_entry_point, requirement("envoy.base.utils")], ) @@ -306,6 +366,7 @@ def envoy_gencontent( output, srcs = [], yaml_srcs = [], + init_data = ENVOY_PYTOOL_NAMESPACE, json_kwargs = {}, template_name = None, template_filters = {}, @@ -353,10 +414,12 @@ def envoy_gencontent( envoy_py_data( name = "%s_data" % name, src = ":%s_json" % name, + init_data = init_data, entry_point_alias = entry_point_alias, ) envoy_jinja_env( name = name_tpl, + init_data = init_data, env_kwargs = template_kwargs, templates = [template], filters = template_filters, @@ -377,10 +440,11 @@ def envoy_gencontent( outs = ["%s_generate_content.py" % name], tools = [":%s" % name_data, name_tpl, template], ) - py_binary( + envoy_pytool_binary( name = "%s_generate_content" % name, main = ":%s_generate_content.py" % name, srcs = [":%s_generate_content.py" % name], + init_data = init_data, deps = [ ":%s" % name_data, name_tpl, diff --git a/tools/code/BUILD b/tools/code/BUILD index 77466a7b49056..4de0a77a4b2ea 100644 --- a/tools/code/BUILD +++ b/tools/code/BUILD @@ -6,12 +6,14 @@ load( "READFILTER_FUZZ_FILTERS", "READFILTER_NOFUZZ_FILTERS", ) -load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace") licenses(["notice"]) # Apache 2 envoy_package() +envoy_py_namespace() + FUZZ_FILTER_COUNT = ( len(READFILTER_FUZZ_FILTERS) + len(READFILTER_NOFUZZ_FILTERS) diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 3db2d1dba5780..855ed61dc3860 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -1,13 +1,14 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@envoy_repo//:path.bzl", "PATH") -load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") licenses(["notice"]) # Apache 2 envoy_package() +envoy_py_namespace() + envoy_entry_point( name = "check", args = [ @@ -30,7 +31,7 @@ envoy_entry_point( pkg = "dependatool", ) -py_binary( +envoy_pytool_binary( name = "validate", srcs = ["validate.py"], args = [ diff --git a/tools/distribution/BUILD b/tools/distribution/BUILD index 44e4006cdd982..f809f3637d45f 100644 --- a/tools/distribution/BUILD +++ b/tools/distribution/BUILD @@ -1,11 +1,13 @@ load("@base_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") licenses(["notice"]) # Apache 2 envoy_package() +envoy_py_namespace() + envoy_entry_point( name = "release", pkg = "envoy.distribution.release", @@ -21,7 +23,7 @@ envoy_entry_point( pkg = "envoy.distribution.verify", ) -py_binary( +envoy_pytool_binary( name = "update_dockerhub_repository", srcs = ["update_dockerhub_repository.py"], data = ["//distribution/dockerhub:readme.md"], diff --git a/tools/docs/BUILD b/tools/docs/BUILD index 672ba2a42b933..7d2870c162ee2 100644 --- a/tools/docs/BUILD +++ b/tools/docs/BUILD @@ -1,13 +1,14 @@ load("@base_pip3//:requirements.bzl", "requirement") -load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") licenses(["notice"]) # Apache 2 envoy_package() -py_binary( +envoy_py_namespace() + +envoy_pytool_binary( name = "generate_extensions_security_rst", srcs = ["generate_extensions_security_rst.py"], deps = [ @@ -15,20 +16,16 @@ py_binary( ], ) -py_binary( +envoy_pytool_binary( name = "generate_external_deps_rst", - srcs = [ - "generate_external_deps_rst.py", - ], + srcs = ["generate_external_deps_rst.py"], args = ["$(location //bazel:all_repository_locations)"], data = ["//bazel:all_repository_locations"], ) -py_binary( +envoy_pytool_binary( name = "generate_api_rst", - srcs = [ - "generate_api_rst.py", - ], + srcs = ["generate_api_rst.py"], ) # The upstream lib is maintained here: @@ -45,11 +42,9 @@ envoy_entry_point( visibility = ["//visibility:public"], ) -py_binary( +envoy_pytool_binary( name = "generate_version_histories", - srcs = [ - "generate_version_histories.py", - ], + srcs = ["generate_version_histories.py"], deps = [ requirement("aio.run.runner"), requirement("envoy.base.utils"), diff --git a/tools/proto_format/BUILD b/tools/proto_format/BUILD index 89eccc31bb423..4a9bfc24e2f00 100644 --- a/tools/proto_format/BUILD +++ b/tools/proto_format/BUILD @@ -2,14 +2,15 @@ load("@aspect_bazel_lib//lib:jq.bzl", "jq") load("@envoy_repo//:path.bzl", "PATH") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") -load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data") +load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary") licenses(["notice"]) # Apache 2 envoy_package() +envoy_py_namespace() + # Files to include when building or comparing the normalized API API_FILES = [ "BUILD", @@ -130,7 +131,7 @@ genrule( tools = [":formatted_api"], ) -py_binary( +envoy_pytool_binary( name = "fetch_normalized_changes", srcs = ["fetch_normalized_changes.py"], args = [ @@ -165,7 +166,7 @@ genrule( ], ) -py_binary( +envoy_pytool_binary( name = "proto_sync", srcs = ["proto_sync.py"], args = [ diff --git a/tools/protodoc/BUILD b/tools/protodoc/BUILD index 01ce510978c08..f562c431ed3e0 100644 --- a/tools/protodoc/BUILD +++ b/tools/protodoc/BUILD @@ -1,15 +1,16 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@com_google_protobuf//:protobuf.bzl", "py_proto_library") -load("@rules_python//python:defs.bzl", "py_binary", "py_library") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_jinja_env", "envoy_py_data") +load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_jinja_env", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary", "envoy_pytool_library") load("//tools/protodoc:protodoc.bzl", "protodoc_rule") licenses(["notice"]) # Apache 2 envoy_package() -py_binary( +envoy_py_namespace() + +envoy_pytool_binary( name = "generate_empty", srcs = ["generate_empty.py"], visibility = ["//visibility:public"], @@ -30,7 +31,7 @@ envoy_py_data( src = "//docs:protodoc_manifest.yaml", ) -py_binary( +envoy_pytool_binary( name = "manifest_to_json", srcs = ["manifest_to_json.py"], args = ["$(location @envoy_api//:v3_proto_set)"], @@ -101,7 +102,7 @@ envoy_py_data( src = ":data_srcs", ) -py_binary( +envoy_pytool_binary( name = "protodoc", srcs = ["protodoc.py"], visibility = ["//visibility:public"], @@ -124,7 +125,7 @@ protodoc_rule( ], ) -py_library( +envoy_pytool_library( name = "rst_filters", srcs = ["rst_filters.py"], ) diff --git a/tools/protoprint/BUILD b/tools/protoprint/BUILD index 4a8cac10d7979..720e41c1a7ec4 100644 --- a/tools/protoprint/BUILD +++ b/tools/protoprint/BUILD @@ -1,16 +1,17 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") -load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_py_data") +load("//tools/base:envoy_python.bzl", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary") load("//tools/protoprint:protoprint.bzl", "protoprint_rule") licenses(["notice"]) # Apache 2 envoy_package() -py_binary( +envoy_py_namespace() + +envoy_pytool_binary( name = "protoprint", srcs = ["protoprint.py"], data = [ From b551561e1b918e943832fa2db1a8b014b0edf74d Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 2 Oct 2023 15:03:21 +0100 Subject: [PATCH 077/274] docs: Improve builds for Envoy and mobile (stamping) (#29887) Signed-off-by: Ryan Northey Signed-off-by: phlax --- api/CONTRIBUTING.md | 6 +-- bazel/BUILD | 8 ++-- bazel/get_workspace_status | 3 ++ ci/do_ci.sh | 16 +++++++- docs/BUILD | 8 +++- docs/README.md | 6 +-- docs/build.sh | 79 ++----------------------------------- mobile/docs/build.sh | 52 ------------------------ mobile/docs/conf.py | 13 +++--- tools/base/requirements.in | 5 +-- tools/base/requirements.txt | 24 ++++++----- 11 files changed, 59 insertions(+), 161 deletions(-) delete mode 100755 mobile/docs/build.sh diff --git a/api/CONTRIBUTING.md b/api/CONTRIBUTING.md index a1e61a7072c45..0ff244623984e 100644 --- a/api/CONTRIBUTING.md +++ b/api/CONTRIBUTING.md @@ -23,19 +23,19 @@ documentation. The documentation can be built locally in the root of https://github.com/envoyproxy/envoy via: ``` -docs/build.sh +ci/do_ci.sh docs ``` To skip configuration examples validation: ``` -SPHINX_SKIP_CONFIG_VALIDATION=true docs/build.sh +SPHINX_SKIP_CONFIG_VALIDATION=true ci/do_ci.sh docs ``` Or to use a hermetic Docker container: ``` -./ci/run_envoy_docker.sh './ci/do_ci.sh docs' +./ci/run_envoy_docker.sh 'ci/do_ci.sh docs' ``` This process builds RST documentation directly from the proto files, merges it with the static RST diff --git a/bazel/BUILD b/bazel/BUILD index edb5d4a6e1b6f..8b15825a9170b 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -41,8 +41,8 @@ genrule( outs = ["gnu_build_id.ldscript"], cmd = """ echo --build-id=0x$$( - grep BUILD_SCM_REVISION bazel-out/volatile-status.txt \\ - | sed 's/^BUILD_SCM_REVISION //') \\ + grep -E "^BUILD_SCM_REVISION" bazel-out/volatile-status.txt \ + | sed 's/^BUILD_SCM_REVISION //') \ > $@ """, # Undocumented attr to depend on workspace status files. @@ -55,8 +55,8 @@ genrule( name = "raw_build_id", outs = ["raw_build_id.ldscript"], cmd = """ - grep BUILD_SCM_REVISION bazel-out/volatile-status.txt \\ - | sed 's/^BUILD_SCM_REVISION //' \\ + grep -E "^BUILD_SCM_REVISION" bazel-out/volatile-status.txt \ + | sed 's/^BUILD_SCM_REVISION //' \ | tr -d '\\n' \\ > $@ """, diff --git a/bazel/get_workspace_status b/bazel/get_workspace_status index ca5159e6dea90..bc43475f01aca 100755 --- a/bazel/get_workspace_status +++ b/bazel/get_workspace_status @@ -23,6 +23,7 @@ if [ -f SOURCE_VERSION ] then echo "BUILD_SCM_REVISION $(cat SOURCE_VERSION)" + echo "ENVOY_BUILD_SCM_REVISION $(cat SOURCE_VERSION)" echo "STABLE_BUILD_SCM_REVISION $(cat SOURCE_VERSION)" echo "BUILD_SCM_STATUS Distribution" exit 0 @@ -30,11 +31,13 @@ fi if [[ -n "$BAZEL_FAKE_SCM_REVISION" ]]; then echo "BUILD_SCM_REVISION $BAZEL_FAKE_SCM_REVISION" + echo "ENVOY_BUILD_SCM_REVISION $BAZEL_FAKE_SCM_REVISION" echo "STABLE_BUILD_SCM_REVISION $BAZEL_FAKE_SCM_REVISION" else # The code below presents an implementation that works for git repository git_rev=$(git rev-parse HEAD) || exit 1 echo "BUILD_SCM_REVISION ${git_rev}" + echo "ENVOY_BUILD_SCM_REVISION ${git_rev}" echo "STABLE_BUILD_SCM_REVISION ${git_rev}" fi diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 55f49139e33c6..187acbc89e35c 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -650,7 +650,21 @@ case $CI_TARGET in setup_clang_toolchain echo "generating docs..." # Build docs. - "${ENVOY_SRCDIR}/docs/build.sh" + # We want the binary at the end + BAZEL_BUILD_OPTIONS+=(--remote_download_toplevel) + [[ -z "${DOCS_OUTPUT_DIR}" ]] && DOCS_OUTPUT_DIR=generated/docs + rm -rf "${DOCS_OUTPUT_DIR}" + mkdir -p "${DOCS_OUTPUT_DIR}" + if [[ -n "${CI_TARGET_BRANCH}" ]] || [[ -n "${SPHINX_QUIET}" ]]; then + export SPHINX_RUNNER_ARGS="-v warn" + BAZEL_BUILD_OPTIONS+=("--action_env=SPHINX_RUNNER_ARGS") + fi + if [[ -n "${DOCS_BUILD_RST}" ]]; then + bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" //docs:rst + cp bazel-bin/docs/rst.tar.gz "$DOCS_OUTPUT_DIR"/envoy-docs-rst.tar.gz + fi + bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" //docs:html + tar -xzf bazel-bin/docs/html.tar.gz -C "$DOCS_OUTPUT_DIR" ;; docs-upload) diff --git a/docs/BUILD b/docs/BUILD index 265a8840986f7..69548a4aafda4 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -236,11 +236,15 @@ pkg_tar( genrule( name = "html_release", outs = ["html_release.tar.gz"], + # BUILD_SHA must be set in release builds + # The Envoy workspace will provide this on stamped builds. For external builds + # you must either pass an env var or pass it through the workspace's status. cmd = """ . $(location //bazel:volatile_env) \ + && _BUILD_SHA=$${BUILD_DOCS_SHA:-$${ENVOY_BUILD_SCM_REVISION:-$${{BUILD_SCM_REVISION}}} \ && $(location //tools/docs:sphinx_runner) \ $${SPHINX_RUNNER_ARGS:-} \ - --build_sha="$${BUILD_DOCS_SHA:-$${BUILD_SCM_REVISION}}" \ + --build_sha="$$_BUILD_SHA" \ --docs_tag="$${BUILD_DOCS_TAG:-}" \ --version_file=$(location //:VERSION.txt) \ --descriptor_path=$(location @envoy_api//:v3_proto_set) \ @@ -280,5 +284,5 @@ genrule( alias( name = "docs", - actual = ":html", + actual = ":html_release", ) diff --git a/docs/README.md b/docs/README.md index 923ae33a4bb88..32f9301eaf25c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,14 +9,14 @@ In both cases, the generated output can be found in `generated/docs`. If you have an [existing Envoy development environment](https://github.com/envoyproxy/envoy/tree/main/bazel#quick-start-bazel-build-for-developers), you should have the necessary dependencies and requirements and be able to build the documentation directly. ```bash -./docs/build.sh +./ci/do_ci.sh docs ``` By default configuration examples are going to be validated during build. To disable validation, set `SPHINX_SKIP_CONFIG_VALIDATION` environment variable to `true`: ```bash -SPHINX_SKIP_CONFIG_VALIDATION=true docs/build.sh +SPHINX_SKIP_CONFIG_VALIDATION=true ./ci/do_ci.sh docs ``` ## Using the Docker build container to build the documentation @@ -27,7 +27,7 @@ image that is used in continuous integration. This can be done as follows: ``` -./ci/run_envoy_docker.sh 'docs/build.sh' +./ci/run_envoy_docker.sh './ci/do_ci.sh docs' ``` To use this method you will need a minimum of 4-5GB of disk space available to accommodate the build image. diff --git a/docs/build.sh b/docs/build.sh index 70e1998e6bc74..20089b3a2b6d0 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -1,78 +1,5 @@ #!/usr/bin/env bash -# set SPHINX_SKIP_CONFIG_VALIDATION environment variable to true to skip -# validation of configuration examples - -set -e - - -if [[ ! $(command -v bazel) ]]; then - # shellcheck disable=SC2016 - echo 'ERROR: bazel must be installed and available in "$PATH" to build docs' >&2 - exit 1 -fi - -VERSION="$(cat VERSION.txt)" -MAIN_BRANCH="refs/heads/main" -DEV_VERSION_REGEX="-dev$" - -# default is to build html only -BUILD_TYPE=html - -if [[ "$VERSION" =~ $DEV_VERSION_REGEX ]]; then - if [[ "$CI_BRANCH" == "$MAIN_BRANCH" ]]; then - # no need to build html, just rst - BUILD_TYPE=rst - fi -else - export BUILD_DOCS_TAG="v${VERSION}" - echo "BUILD AZP RELEASE BRANCH ${BUILD_DOCS_TAG}" - BAZEL_BUILD_OPTIONS+=("--action_env=BUILD_DOCS_TAG") -fi - -# This is for local RBE setup, should be no-op for builds without RBE setting in bazelrc files. -IFS=" " read -ra BAZEL_BUILD_OPTIONS <<< "${BAZEL_BUILD_OPTION_LIST:-}" -IFS=" " read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTION_LIST:-}" - -# We want the binary at the end -BAZEL_BUILD_OPTIONS+=(--remote_download_toplevel) - -if [[ -n "${CI_TARGET_BRANCH}" ]] || [[ -n "${SPHINX_QUIET}" ]]; then - export SPHINX_RUNNER_ARGS="-v warn" - BAZEL_BUILD_OPTIONS+=("--action_env=SPHINX_RUNNER_ARGS") -fi - -# Building html/rst is determined by then needs of CI but can be overridden in dev. -if [[ "${BUILD_TYPE}" == "html" ]] || [[ -n "${DOCS_BUILD_HTML}" ]]; then - BUILD_HTML=1 - BUILD_HTML_TARGET="//docs" - BUILD_HTML_TARBALL="bazel-bin/docs/html.tar.gz" - if [[ -n "${CI_BRANCH}" ]] || [[ -n "${DOCS_BUILD_RELEASE}" ]]; then - # CI build - use git sha - BUILD_HTML_TARGET="//docs:html_release" - BUILD_HTML_TARBALL="bazel-bin/docs/html_release.tar.gz" - fi -fi -if [[ "${BUILD_TYPE}" == "rst" ]] || [[ -n "${DOCS_BUILD_RST}" ]]; then - BUILD_RST=1 -fi - -# Build html/rst -if [[ -n "${BUILD_RST}" ]]; then - bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" //docs:rst -fi -if [[ -n "${BUILD_HTML}" ]]; then - bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" "$BUILD_HTML_TARGET" -fi - -[[ -z "${DOCS_OUTPUT_DIR}" ]] && DOCS_OUTPUT_DIR=generated/docs -rm -rf "${DOCS_OUTPUT_DIR}" -mkdir -p "${DOCS_OUTPUT_DIR}" - -# Save html/rst to output directory -if [[ -n "${BUILD_HTML}" ]]; then - tar -xzf "$BUILD_HTML_TARBALL" -C "$DOCS_OUTPUT_DIR" -fi -if [[ -n "${BUILD_RST}" ]]; then - cp bazel-bin/docs/rst.tar.gz "$DOCS_OUTPUT_DIR"/envoy-docs-rst.tar.gz -fi +# shellcheck disable=SC2016 +echo 'This script has been removed. Please use `ci/do_ci.sh docs` instead' >&2 +exit 1 diff --git a/mobile/docs/build.sh b/mobile/docs/build.sh deleted file mode 100755 index bec4b177bf55d..0000000000000 --- a/mobile/docs/build.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -set -e - -# shellcheck disable=SC1091 -. tools/shell_utils.sh - -# We need to set ENVOY_DOCS_VERSION_STRING and ENVOY_DOCS_RELEASE_LEVEL for Sphinx. -# We also validate that the tag and version match at this point if needed. - -# Docs for release tags are reserved for vX.Y.Z versions. -# vX.Y.Z.ddmmyy do not publish tagged docs. -VERSION_NUMBER=$(cat mobile/VERSION) -if [[ "$GITHUB_REF_TYPE" == "tag" ]] && [[ "${VERSION_NUMBER}" =~ ^[0-9]+\.[0-9]+\.[0-9]$ ]] -then - # Check the git tag matches the version number in the VERSION file. - if [ "v${VERSION_NUMBER}" != "${GITHUB_REF_NAME}" ]; then - echo "Given git tag does not match the VERSION file content:" - echo "${GITHUB_REF_NAME} vs $(cat mobile/VERSION)" - exit 1 - fi - # Check the version_history.rst contains current release version. - grep --fixed-strings "$VERSION_NUMBER" docs/root/intro/version_history.rst \ - || (echo "Git tag not found in version_history.rst" && exit 1) - - # Now that we now there is a match, we can use the tag. - export ENVOY_DOCS_VERSION_STRING="tag-$GITHUB_REF_NAME" - export ENVOY_DOCS_RELEASE_LEVEL=tagged - export ENVOY_BLOB_SHA="$GITHUB_REF_NAME" -else - BUILD_SHA=$(git rev-parse HEAD) - export ENVOY_DOCS_VERSION_STRING="${VERSION_NUMBER}"-"${BUILD_SHA:0:6}" - export ENVOY_DOCS_RELEASE_LEVEL=pre-release - export ENVOY_BLOB_SHA="$BUILD_SHA" -fi - -SCRIPT_DIR=$(dirname "$0") -BUILD_DIR=build_docs -[[ -z "${DOCS_OUTPUT_DIR}" ]] && DOCS_OUTPUT_DIR=generated/docs -[[ -z "${GENERATED_RST_DIR}" ]] && GENERATED_RST_DIR=generated/rst - -rm -rf "${DOCS_OUTPUT_DIR}" -mkdir -p "${DOCS_OUTPUT_DIR}" - -rm -rf "${GENERATED_RST_DIR}" -mkdir -p "${GENERATED_RST_DIR}" - -source_venv "$BUILD_DIR" -pip install -r "${SCRIPT_DIR}"/requirements.txt --no-deps --require-hashes - -rsync -av "${SCRIPT_DIR}"/root/ "${SCRIPT_DIR}"/conf.py "${GENERATED_RST_DIR}" -sphinx-build -W --keep-going -b html "${GENERATED_RST_DIR}" "${DOCS_OUTPUT_DIR}" diff --git a/mobile/docs/conf.py b/mobile/docs/conf.py index 80270f95c7af6..9618124b2704b 100644 --- a/mobile/docs/conf.py +++ b/mobile/docs/conf.py @@ -48,11 +48,11 @@ def setup(app): app.add_directive('substitution-code-block', SubstitutionCodeBlock) -if not os.environ.get('ENVOY_DOCS_RELEASE_LEVEL'): +if not (release_level := os.environ.get('ENVOY_DOCS_RELEASE_LEVEL')): raise Exception("ENVOY_DOCS_RELEASE_LEVEL env var must be defined") -release_level = os.environ['ENVOY_DOCS_RELEASE_LEVEL'] -blob_sha = os.environ['ENVOY_BLOB_SHA'] +if not (blob_sha := os.environ.get("ENVOY_BLOB_SHA")): + raise Exception("ENVOY_BLOB_SHA env var must be defined") # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -103,13 +103,12 @@ def setup(app): # |version| and |release|, also used in various other places throughout the # built documents. -if not os.environ.get('ENVOY_DOCS_VERSION_STRING'): +# The short X.Y version. +if not (version := os.environ.get("ENVOY_DOCS_VERSION_STRING")): raise Exception("ENVOY_DOCS_VERSION_STRING env var must be defined") -# The short X.Y version. -version = os.environ['ENVOY_DOCS_VERSION_STRING'] # The full version, including alpha/beta/rc tags. -release = os.environ['ENVOY_DOCS_VERSION_STRING'] +release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/tools/base/requirements.in b/tools/base/requirements.in index c0c77a086bf56..00dd34986e8dd 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -15,7 +15,7 @@ envoy.dependency.check>=0.1.7 envoy.distribution.release>=0.0.9 envoy.distribution.repo>=0.0.8 envoy.distribution.verify>=0.0.11 -envoy.docs.sphinx_runner>=0.2.5 +envoy.docs.sphinx_runner>=0.2.9 envoy.gpg.identity>=0.1.1 envoy.gpg.sign>=0.2.0 flake8>=6 @@ -41,6 +41,3 @@ thrift verboselogs yapf yarl>=1.7.2 - -# This has packaging issues, so use the github tarball until resolved (https://github.com/sphinx-contrib/jquery/issues/15) -https://github.com/sphinx-contrib/jquery/archive/refs/tags/v3.0.0.zip diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 397a48db90cdb..09c30e31d7631 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -447,6 +447,7 @@ docutils==0.19 \ # via # envoy-docs-sphinx-runner # sphinx + # sphinx-rtd-theme envoy-base-utils==0.4.12 \ --hash=sha256:2bafcb6be3c1223968c9ee90b7a33d6d93aee16fe99bef37410ea9e4bc1a957f \ --hash=sha256:b9b409abe83be6911aa03cfe635ed1e2e2b9e44e7c8595b2b7e5c7aae7bf70fc @@ -488,9 +489,9 @@ envoy-distribution-verify==0.0.11 \ envoy-docker-utils==0.0.2 \ --hash=sha256:a12cb57f0b6e204d646cbf94f927b3a8f5a27ed15f60d0576176584ec16a4b76 # via envoy-distribution-distrotest -envoy-docs-sphinx-runner==0.2.6 \ - --hash=sha256:045b23f182d9760df693e0c01f6bd0654d0c5690107945e262ce720a976920b7 \ - --hash=sha256:8166ff4b1b265efaf73db397d7ad0a1a743d71281ec233885c692c24f9349bd1 +envoy-docs-sphinx-runner==0.2.9 \ + --hash=sha256:1fa789b1d29ea929df67b07e5ca910d62e2057cd229719725030889da53b1a09 \ + --hash=sha256:4bfa1946104e263471d522b47d683e127124a5ad47334d69de4aea0eac282576 # via -r requirements.in envoy-github-abstract==0.0.22 \ --hash=sha256:2dd65e2f247a4947d0198b295c82716c13162e30c433b7625c27d59eee7bcf78 \ @@ -1221,12 +1222,18 @@ sphinx==7.1.0 \ # -r requirements.in # envoy-docs-sphinx-runner # sphinx-copybutton + # sphinx-rtd-theme # sphinxcontrib-httpdomain + # sphinxcontrib-jquery # sphinxext-rediraffe sphinx-copybutton==0.5.2 \ --hash=sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd \ --hash=sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e # via envoy-docs-sphinx-runner +sphinx-rtd-theme==2.0.0rc2 \ + --hash=sha256:d1270effe620df9164b1cd2d617909472a63531e21a716fd22d0fbcedf9d24ff \ + --hash=sha256:f04df9213acf421c3b42f4f39005c8bc68fc4696c5b4ed4ef13d1678369713f7 + # via envoy-docs-sphinx-runner sphinxcontrib-applehelp==1.0.4 \ --hash=sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228 \ --hash=sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e @@ -1243,11 +1250,12 @@ sphinxcontrib-httpdomain==1.8.1 \ --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \ --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b # via envoy-docs-sphinx-runner -sphinxcontrib.jquery @ https://github.com/sphinx-contrib/jquery/archive/refs/tags/v3.0.0.zip \ - --hash=sha256:562ad9ac0ac3d8f04a363eb3507ae4b2b856aa04aabab6df7543530fafb849ca +sphinxcontrib-jquery==4.1 \ + --hash=sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a \ + --hash=sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae # via - # -r requirements.in # envoy-docs-sphinx-runner + # sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 \ --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 @@ -1554,6 +1562,4 @@ zstandard==0.21.0 \ setuptools==68.0.0 \ --hash=sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f \ --hash=sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235 - # via - # -r requirements.in - # sphinxcontrib-jquery + # via -r requirements.in From b073af52d766be804978ab1dd1bde6dbff9dff48 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 2 Oct 2023 18:40:36 +0100 Subject: [PATCH 078/274] ci/tools: Add tarball unpacker (#29899) Signed-off-by: phlax Signed-off-by: Ryan Northey --- api/bazel/repository_locations.bzl | 6 ++-- ci/do_ci.sh | 10 ++++--- mobile/docs/publish.sh | 44 ------------------------------ tools/tarball/BUILD | 8 ++++++ 4 files changed, 17 insertions(+), 51 deletions(-) delete mode 100755 mobile/docs/publish.sh create mode 100644 tools/tarball/BUILD diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index cb2d0d4b4c84f..903b61f129aee 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -155,12 +155,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy_toolshed", project_desc = "Tooling, libraries, runners and checkers for Envoy proxy's CI", project_url = "https://github.com/envoyproxy/toolshed", - version = "0.0.6", - sha256 = "7047db983e49290ac14b2733459d439a8a521ff49e95fbd0b185a692bd6a6d86", + version = "0.0.10", + sha256 = "bdfcf0a23c18a99887ac25761aa56d85bedb6eda77c89f9f19e6142b812749b9", strip_prefix = "toolshed-bazel-v{version}/bazel", urls = ["https://github.com/envoyproxy/toolshed/archive/bazel-v{version}.tar.gz"], use_category = ["build"], - release_date = "2023-09-24", + release_date = "2023-10-02", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/envoyproxy/envoy/blob/bazel-v{version}/LICENSE", diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 187acbc89e35c..5fff08757ce55 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -650,8 +650,6 @@ case $CI_TARGET in setup_clang_toolchain echo "generating docs..." # Build docs. - # We want the binary at the end - BAZEL_BUILD_OPTIONS+=(--remote_download_toplevel) [[ -z "${DOCS_OUTPUT_DIR}" ]] && DOCS_OUTPUT_DIR=generated/docs rm -rf "${DOCS_OUTPUT_DIR}" mkdir -p "${DOCS_OUTPUT_DIR}" @@ -663,8 +661,12 @@ case $CI_TARGET in bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" //docs:rst cp bazel-bin/docs/rst.tar.gz "$DOCS_OUTPUT_DIR"/envoy-docs-rst.tar.gz fi - bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" //docs:html - tar -xzf bazel-bin/docs/html.tar.gz -C "$DOCS_OUTPUT_DIR" + DOCS_OUTPUT_DIR="$(realpath "$DOCS_OUTPUT_DIR")" + bazel "${BAZEL_STARTUP_OPTIONS[@]}" run \ + "${BAZEL_BUILD_OPTIONS[@]}" \ + --//tools/tarball:target=//docs:html \ + //tools/tarball:unpack \ + "$DOCS_OUTPUT_DIR" ;; docs-upload) diff --git a/mobile/docs/publish.sh b/mobile/docs/publish.sh deleted file mode 100755 index dc821274f9347..0000000000000 --- a/mobile/docs/publish.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -# This is run on every commit that GitHub Actions picks up. It assumes that docs have already been -# built via docs/build.sh. The push behavior differs depending on the nature of the commit: -# * Tag commit (e.g. v1.6.0): pushes docs to versioned location. -# * Main commit: pushes docs to latest. Note that envoy-mobile.github.io uses `master` rather than -# `main` because using `main` as the default branch currently results in 404s. -# * Otherwise: noop. - -set -e - -DOCS_DIR=generated/docs -CHECKOUT_DIR=../envoy-mobile-docs -BUILD_SHA="$(git rev-parse HEAD)" - -if [ "$GITHUB_REF_TYPE" == "tag" ] -then - PUBLISH_DIR="$CHECKOUT_DIR"/docs/envoy-mobile/"$GITHUB_REF_NAME" -elif [ "$GITHUB_REF_NAME" == "main" ] -then - PUBLISH_DIR="$CHECKOUT_DIR"/docs/envoy-mobile/latest -else - echo "Ignoring docs push" - exit 0 -fi - -echo 'cloning' -git clone git@github.com:envoy-mobile/envoy-mobile.github.io "$CHECKOUT_DIR" - -git -C "$CHECKOUT_DIR" fetch -git -C "$CHECKOUT_DIR" checkout -B master origin/master -rm -fr "$PUBLISH_DIR" -mkdir -p "$PUBLISH_DIR" -cp -r "$DOCS_DIR"/* "$PUBLISH_DIR" -cd "$CHECKOUT_DIR" - -git config user.name "envoy-mobile-docs(ci)" -git config user.email envoy-mobile-docs@users.noreply.github.com -echo 'add' -git add . -echo 'commit' -git commit -m "docs envoy-mobile@$BUILD_SHA" -echo 'push' -git push origin master diff --git a/tools/tarball/BUILD b/tools/tarball/BUILD new file mode 100644 index 0000000000000..069d4eee6b278 --- /dev/null +++ b/tools/tarball/BUILD @@ -0,0 +1,8 @@ +load("@envoy_toolshed//tarball:macros.bzl", "unpacker") + +licenses(["notice"]) # Apache 2 + +unpacker( + name = "unpack", + zstd = "//tools/zstd", +) From 04960817b48925555742adf35faddc20bc5fc6de Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 2 Oct 2023 20:16:29 +0100 Subject: [PATCH 079/274] ci/publishing: Minor fix for website publishing (#29903) Signed-off-by: Ryan Northey --- .github/workflows/_stage_publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 83974e58dee68..1d9227c8c8d42 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -105,5 +105,5 @@ jobs: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" ref: main - repository: ${ inputs.version_dev != '' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' } + repository: ${{ inputs.version_dev != '' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} workflow: envoy-sync.yaml From d12e6450abce4076598661b01d54e0153c6fc355 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 3 Oct 2023 13:46:13 +0100 Subject: [PATCH 080/274] verify/packages: Force rebuild of docker image (#29919) Signed-off-by: Ryan Northey --- distribution/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/BUILD b/distribution/BUILD index 260463e320f17..1b3b32a13d755 100644 --- a/distribution/BUILD +++ b/distribution/BUILD @@ -68,6 +68,7 @@ sh_binary( "$(location :distrotest.sh)", VERSION, "$(location :distros.yaml)", + "--rebuild", ], data = [ ":distros.yaml", From decd0f00a29517c3c34d9abda025b1e8c4e16062 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 3 Oct 2023 13:58:53 +0100 Subject: [PATCH 081/274] ci/website: Use commit sha when dispatching website build (#29918) Signed-off-by: Ryan Northey --- .github/workflows/_stage_publish.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 1d9227c8c8d42..58a366c9bd854 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -95,6 +95,13 @@ jobs: trusted: true publish_docs: + # For normal commits to Envoy main this will trigger an update in the website repo, + # which will update its envoy dep shas, and rebuild the website for the latest docs + # + # For commits that create a release, it instead triggers an update in the archive repo, + # which builds a static version of the docs for the release and commits it to the archive. + # In turn the archive repo triggers an update in the website so the new release docs are + # included in the published site if: ${{ inputs.trusted }} runs-on: ubuntu-22.04 needs: @@ -107,3 +114,5 @@ jobs: ref: main repository: ${{ inputs.version_dev != '' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} workflow: envoy-sync.yaml + inputs: | + commit_sha: ${{ inputs.version_dev != '' && github.sha || '' }} From fa096d646ee1ffb0fc2c237a97907c5f4c0b7b25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:08:28 +0000 Subject: [PATCH 082/274] build(deps): bump distroless/base-nossl-debian11 from `a156aae` to `1311462` in /ci (#29964) build(deps): bump distroless/base-nossl-debian11 in /ci Bumps distroless/base-nossl-debian11 from `a156aae` to `1311462`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian11 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 572a4f460124e..aa29a46fb4b64 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -59,7 +59,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless # gcr.io/distroless/base-nossl-debian11:nonroot -FROM gcr.io/distroless/base-nossl-debian11:nonroot@sha256:a156aae8df01d39f2390021016c672bee4fb34f3a90759f4d9aa74c116ec142a AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian11:nonroot@sha256:1311462d37ff75d7eafd7b3656f029a47fa465d5a7c8b4ce7956028e8b8fa5a8 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 70ac590bc737e6b018d52578060dd02dff3e9086 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 3 Oct 2023 19:47:37 +0100 Subject: [PATCH 083/274] ci/deps: Fix ci failure due to upstream repo issues (#29927) Signed-off-by: Ryan Northey --- tools/base/requirements.in | 2 +- tools/base/requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 00dd34986e8dd..365fc84ff42f0 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -11,7 +11,7 @@ cryptography>=41.0.1 dependatool>=0.2.2 envoy.base.utils>=0.4.12 envoy.code.check>=0.5.8 -envoy.dependency.check>=0.1.7 +envoy.dependency.check>=0.1.10 envoy.distribution.release>=0.0.9 envoy.distribution.repo>=0.0.8 envoy.distribution.verify>=0.0.11 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 09c30e31d7631..e37b804413c18 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -466,9 +466,9 @@ envoy-code-check==0.5.8 \ --hash=sha256:03f32588cc9ed98ab6703cbca6f81df1527db71c3a0f962be6a6084ded40d528 \ --hash=sha256:2b12c51098c78d393823cf055a54e9308c37321d769041f01a2f35b04074d6f3 # via -r requirements.in -envoy-dependency-check==0.1.8 \ - --hash=sha256:ac9820e446bb44e05121e5c93c210f40ca37076580b0d082da2c63e7784c338a \ - --hash=sha256:e92272ca1f4d850d3eb3bde3c22cff39c103e7850fbda8d1686814bfc8c45338 +envoy-dependency-check==0.1.10 \ + --hash=sha256:4a637e0ed7184791b495041f9baf44567a95cbb979e1e5f26f6a8c33f724cf9e \ + --hash=sha256:e6ae41249f298c865a357edcd8e4850354f222ea4f0dd629c737706b23670c75 # via -r requirements.in envoy-distribution-distrotest==0.0.10 \ --hash=sha256:83e912c48da22eb3e514fc1142247d33eb7ed0d59e94eca2ffbd178a26fbf808 \ From 3f9ec5d074362a5f403ca295b32c6dfc5263f93e Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 4 Oct 2023 15:12:35 +0100 Subject: [PATCH 084/274] ci/publish: Fix checked out ref for postsubmit (#29925) Currently when the publishing ci is triggered in postsubmit, it just uses the latest commit of the branch (main/release/etc) This fixes it to use the correct commit, and adds a check to ensure it is a commit of that branch. Signed-off-by: Ryan Northey Signed-off-by: phlax --- .github/workflows/_ci.yml | 23 ++++++++++++++--------- .github/workflows/_stage_publish.yml | 1 + .github/workflows/_stage_verify.yml | 2 +- .github/workflows/envoy-publish.yml | 4 ++-- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 27001d45f4987..60ba5b29cfbd2 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -96,21 +96,26 @@ jobs: with: image_tag: ${{ inputs.cache_build_image }} - # If the run is "trusted" (ie has access to secrets) then it should - # **not** set the ref and should use the code of the calling context. - - if: ${{ inputs.repo_ref && inputs.trusted }} - run: | - echo '`repo_ref` should not be set for trusted CI runs' - exit 1 - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout Envoy repository with: - fetch-depth: ${{ inputs.repo_fetch_depth }} + fetch-depth: ${{ ! inputs.trusted && inputs.repo_fetch_depth || 0 }} # WARNING: This allows untrusted code to run!!! # If this is set, then anything before or after in the job should be regarded as # compromised. ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + + # If we are in a trusted CI run then the provided commit _must_ be either the latest for + # this branch, or an antecdent. + - run: | + if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD; then + echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 + exit 1 + fi + git checkout "${{ inputs.repo_ref }}" + if: ${{ inputs.trusted }} + name: Check provided ref + - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 58a366c9bd854..8975169caf0a0 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -93,6 +93,7 @@ jobs: run_pre_with: ${{ matrix.run_pre_with }} env: ${{ matrix.env }} trusted: true + repo_ref: ${{ inputs.repo_ref }} publish_docs: # For normal commits to Envoy main this will trigger an update in the website repo, diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index a9dcf195c5db0..a1a40d2b5fd47 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -50,4 +50,4 @@ jobs: run_pre_with: ${{ matrix.run_pre_with }} env: ${{ matrix.env }} trusted: ${{ inputs.trusted }} - repo_ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + repo_ref: ${{ inputs.repo_ref }} diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index a64d4ba686278..f4123bc02efcc 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -54,7 +54,7 @@ jobs: trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} version_dev: ${{ needs.env.outputs.version_dev }} given_ref: ${{ inputs.ref }} - repo_ref: ${{ needs.env.outputs.trusted != 'true' && inputs.ref || '' }} + repo_ref: ${{ inputs.ref }} permissions: contents: write secrets: @@ -69,4 +69,4 @@ jobs: with: trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} given_ref: ${{ inputs.ref }} - repo_ref: ${{ needs.env.outputs.trusted != 'true' && needs.env.outputs.repo_ref || '' }} + repo_ref: ${{ needs.env.outputs.repo_ref }} From a72240517b0b704551ec17098f11d988ec6a847f Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 5 Oct 2023 11:01:06 +0100 Subject: [PATCH 085/274] ci/cache: Bump version to expire failing auth (#29975) Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 6cb7ac6fff03d..99f458d07abde 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -45,7 +45,7 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) - name: cacheKeyVersion - value: v2 + value: v3 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker From 842f0143939d38d11c55ac0e36aba04eddb974ed Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 5 Oct 2023 15:23:44 +0100 Subject: [PATCH 086/274] distribution/debs: Produce/test bookworm debs (#29972) Signed-off-by: Ryan Northey --- distribution/debian/packages.bzl | 2 +- distribution/distros.yaml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/distribution/debian/packages.bzl b/distribution/debian/packages.bzl index ac1834e69021e..866ff545b661a 100644 --- a/distribution/debian/packages.bzl +++ b/distribution/debian/packages.bzl @@ -10,7 +10,7 @@ def envoy_pkg_deb( description = "Envoy built for Debian/Ubuntu", preinst = "//distribution/debian:preinst", postinst = "//distribution/debian:postinst", - supported_distributions = "bullseye focal jammy", + supported_distributions = "bookworm bullseye focal jammy", architecture = select({ "//bazel:x86": "amd64", "//conditions:default": "arm64", diff --git a/distribution/distros.yaml b/distribution/distros.yaml index 6dc239ad27a1e..40c54e657a506 100644 --- a/distribution/distros.yaml +++ b/distribution/distros.yaml @@ -2,6 +2,10 @@ debian_bullseye: image: debian:bullseye-slim ext: bullseye.changes +debian_bookworm: + image: debian:bookworm-slim + ext: bookworm.changes + ubuntu_focal: image: ubuntu:20.04 ext: focal.changes From 541e2b992a5408d73feb36997ea86e7c3d8ec583 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 5 Oct 2023 15:38:14 +0100 Subject: [PATCH 087/274] ci/verify: Minor fix for postsubmit verify (#29981) Signed-off-by: Ryan Northey --- .github/workflows/envoy-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index f4123bc02efcc..8c31140a54b83 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -69,4 +69,4 @@ jobs: with: trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} given_ref: ${{ inputs.ref }} - repo_ref: ${{ needs.env.outputs.repo_ref }} + repo_ref: ${{ inputs.ref }} From 55e586a78891087fb498afac1df7142566b1bd7c Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 5 Oct 2023 15:57:46 +0100 Subject: [PATCH 088/274] distribution/debs: Add/test contrib debs (#29976) Signed-off-by: Ryan Northey --- ci/do_ci.sh | 6 +++++ distribution/debian/packages.bzl | 38 ++++++++++++++++++++++++++++++-- distribution/packages.bzl | 14 +++++++++--- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 5fff08757ce55..df59656ecc220 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -567,11 +567,17 @@ case $CI_TARGET in -- --stdout \ -d "$ENVOY_RELEASE_TARBALL" \ | tar xfO - envoy > distribution/custom/envoy + bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ + //tools/zstd \ + -- --stdout \ + -d "$ENVOY_RELEASE_TARBALL" \ + | tar xfO - envoy-contrib > distribution/custom/envoy-contrib # Build the packages bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ --remote_download_toplevel \ -c opt \ --//distribution:envoy-binary=//distribution:custom/envoy \ + --//distribution:envoy-contrib-binary=//distribution:custom/envoy-contrib \ //distribution:packages.tar.gz if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then cp -a bazel-bin/distribution/packages.tar.gz "${ENVOY_BUILD_DIR}/packages.x64.tar.gz" diff --git a/distribution/debian/packages.bzl b/distribution/debian/packages.bzl index 866ff545b661a..19ecc8a3fa5bb 100644 --- a/distribution/debian/packages.bzl +++ b/distribution/debian/packages.bzl @@ -49,7 +49,7 @@ def envoy_pkg_deb( output_group = "deb", ) -def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":envoy-bin-files", config = ":envoy-config"): +def envoy_pkg_debs(name, version, release_version, maintainer, bin_files, contrib_bin_files, config = ":envoy-config"): """Package the Envoy .debs with their .changes files. Packages are created for the version *and* the release version, eg @@ -57,7 +57,8 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env - envoy_1.21.0_amd64.deb - envoy-1.21_1.21.0_amd64.deb - This way packages are available for both "envoy" and "envoy-1.21" in package managers. + This way packages are available for both "envoy" and "envoy-1.21" in package managers, and users can install either + a specifically versioned package, or the latest for that minor version. """ # generate deb data for all packages @@ -71,6 +72,17 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env remap_paths = {"/copyright": "/usr/share/doc/envoy/copyright"}, ) + # generate deb data for all contrib packages + pkg_tar( + name = "contrib-deb-data", + srcs = [ + "//distribution/debian:copyright", + config, + contrib_bin_files, + ], + remap_paths = {"/copyright": "/usr/share/doc/envoy/copyright"}, + ) + # generate package for this patch version envoy_pkg_deb( name = "envoy", @@ -89,6 +101,24 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env maintainer = maintainer, ) + # generate contrib package for this patch version + envoy_pkg_deb( + name = "envoy-contrib", + data = ":contrib-deb-data", + version = version, + maintainer = maintainer, + ) + + # generate contrib package for this minor version + envoy_pkg_deb( + name = "envoy-contrib-%s" % release_version, + data = ":contrib-deb-data", + version = version, + conflicts = ["envoy"], + provides = ["envoy"], + maintainer = maintainer, + ) + pkg_tar( name = name, srcs = [ @@ -96,6 +126,10 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env "envoy.deb", "envoy-%s.changes" % release_version, "envoy-%s.deb" % release_version, + "envoy-contrib.changes", + "envoy-contrib.deb", + "envoy-contrib-%s.changes" % release_version, + "envoy-contrib-%s.deb" % release_version, ], extension = "tar", package_dir = "deb", diff --git a/distribution/packages.bzl b/distribution/packages.bzl index dacd54829fb8e..4690bb7ee90f3 100644 --- a/distribution/packages.bzl +++ b/distribution/packages.bzl @@ -12,6 +12,7 @@ def _release_version_for(version): def envoy_pkg_distros( name, envoy_bin = ":envoy-binary", + envoy_contrib_bin = ":envoy-contrib-binary", version = None, maintainer = None, config = "//configs:envoyproxy_io_proxy.yaml"): @@ -31,10 +32,19 @@ def envoy_pkg_distros( renames = {envoy_bin: "/usr/bin/envoy"}, ) + pkg_files( + name = "envoy-contrib-bin-files", + srcs = [envoy_contrib_bin], + attributes = pkg_attributes(mode = "0755"), + renames = {envoy_contrib_bin: "/usr/bin/envoy"}, + ) + # build debs envoy_pkg_debs( name = "debs", version = version, + bin_files = ":envoy-bin-files", + contrib_bin_files = ":envoy-contrib-bin-files", release_version = _release_version_for(version), maintainer = maintainer, ) @@ -43,9 +53,7 @@ def envoy_pkg_distros( pkg_tar( name = "distro_packages", extension = "tar", - deps = [ - ":debs", - ], + deps = [":debs"], ) # sign the packages From b2f7ca549f22d28194e9c8c51c1c7b9afa2e7300 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:35:05 +0000 Subject: [PATCH 089/274] build(deps): bump cryptography in /.github/actions/pr_notifier Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.2 to 41.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.2...41.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey --- .github/actions/pr_notifier/requirements.txt | 48 ++++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/actions/pr_notifier/requirements.txt b/.github/actions/pr_notifier/requirements.txt index f9dfcc84ad240..15c4922796916 100644 --- a/.github/actions/pr_notifier/requirements.txt +++ b/.github/actions/pr_notifier/requirements.txt @@ -138,30 +138,30 @@ charset-normalizer==3.1.0 \ --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab # via requests -cryptography==41.0.2 \ - --hash=sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711 \ - --hash=sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7 \ - --hash=sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd \ - --hash=sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e \ - --hash=sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58 \ - --hash=sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0 \ - --hash=sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d \ - --hash=sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83 \ - --hash=sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831 \ - --hash=sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766 \ - --hash=sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b \ - --hash=sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c \ - --hash=sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182 \ - --hash=sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f \ - --hash=sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa \ - --hash=sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4 \ - --hash=sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a \ - --hash=sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2 \ - --hash=sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76 \ - --hash=sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5 \ - --hash=sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee \ - --hash=sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f \ - --hash=sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14 +cryptography==41.0.4 \ + --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ + --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ + --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ + --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ + --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ + --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ + --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ + --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ + --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ + --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ + --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ + --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ + --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ + --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ + --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ + --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ + --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ + --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ + --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ + --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ + --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ + --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ + --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f # via pyjwt deprecated==1.2.13 \ --hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d \ From 793caeaa52656307a7c1bd1f802b18250b4b7650 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:35:31 +0000 Subject: [PATCH 090/274] build(deps): bump cryptography from 41.0.2 to 41.0.4 in /tools/base Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.2 to 41.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.2...41.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey --- tools/base/requirements.txt | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index e37b804413c18..e4671d8b058da 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -405,30 +405,30 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==41.0.2 \ - --hash=sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711 \ - --hash=sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7 \ - --hash=sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd \ - --hash=sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e \ - --hash=sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58 \ - --hash=sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0 \ - --hash=sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d \ - --hash=sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83 \ - --hash=sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831 \ - --hash=sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766 \ - --hash=sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b \ - --hash=sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c \ - --hash=sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182 \ - --hash=sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f \ - --hash=sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa \ - --hash=sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4 \ - --hash=sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a \ - --hash=sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2 \ - --hash=sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76 \ - --hash=sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5 \ - --hash=sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee \ - --hash=sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f \ - --hash=sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14 +cryptography==41.0.4 \ + --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ + --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ + --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ + --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ + --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ + --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ + --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ + --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ + --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ + --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ + --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ + --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ + --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ + --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ + --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ + --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ + --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ + --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ + --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ + --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ + --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ + --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ + --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f # via # -r requirements.in # pyjwt From 97c56cc546670f51cd709393303c344b46035ee7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:36:51 +0000 Subject: [PATCH 091/274] build(deps): bump urllib3 from 1.26.16 to 1.26.17 in /tools/base Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.16 to 1.26.17. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.16...1.26.17) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index e4671d8b058da..35462a1248650 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1295,9 +1295,9 @@ uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e # via gidgethub -urllib3==1.26.16 \ - --hash=sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f \ - --hash=sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14 +urllib3==1.26.17 \ + --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ + --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b # via # google-auth # requests From c3f35ce297107550fe0dc9bc31c6c9d011656a11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:07:52 +0000 Subject: [PATCH 092/274] build(deps): bump gitpython from 3.1.36 to 3.1.37 in /tools/base (#29945) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.36 to 3.1.37. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.36...3.1.37) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 35462a1248650..0c25be0a5c190 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -649,9 +649,9 @@ gitdb==4.0.10 \ --hash=sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a \ --hash=sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7 # via gitpython -gitpython==3.1.32 \ - --hash=sha256:8d9b8cb1e80b9735e8717c9362079d3ce4c6e5ddeebedd0361b228c3a67a62f6 \ - --hash=sha256:e3d59b1c2c6ebb9dfa7a184daf3b6dd4914237e7488a1730a6d8f6f5d0b4187f +gitpython==3.1.37 \ + --hash=sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33 \ + --hash=sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54 # via -r requirements.in google-api-core==2.11.1 \ --hash=sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a \ From bbace392a8a218c70ae6a0864f38f5c2f6af300d Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 6 Oct 2023 01:29:00 +0100 Subject: [PATCH 093/274] ci/verify: Increase tmpfs size when saving cache (#29985) This should fix a bad merge causing diskspace issues in the x64 verify job Signed-off-by: Ryan Northey --- .azure-pipelines/stage/verify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index c0d6f3091f412..f429feb4ff441 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -12,8 +12,7 @@ jobs: displayName: Debs (x64) condition: and(not(canceled()), succeeded(), ne(stageDependencies.env.repo.outputs['changed.mobileOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.docsOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.examplesOnly'], 'true')) timeoutInMinutes: 120 - pool: - vmImage: $(agentUbuntu) + pool: envoy-x64-small steps: - task: DownloadBuildArtifacts@0 inputs: @@ -27,6 +26,7 @@ jobs: ciTarget: verify_distro cacheName: verify_distro publishTestResults: false + tmpfsDockerDisabled: true env: ENVOY_DOCKER_IN_DOCKER: 1 From bb4b7869c206b8c61c93ae8c997ccd44bb6342b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:43:00 +0100 Subject: [PATCH 094/274] build(deps): bump urllib3 from 1.26.7 to 1.26.17 in /examples/grpc-bridge/client (#29914) build(deps): bump urllib3 in /examples/grpc-bridge/client Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.7 to 1.26.17. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.7...1.26.17) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- examples/grpc-bridge/client/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index b2bada3a01ab1..8c7b31340c018 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -133,7 +133,7 @@ requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via -r requirements.in -urllib3==1.26.7 \ - --hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece \ - --hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844 +urllib3==1.26.17 \ + --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ + --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b # via requests From 205059ff87d58fa5489c5658af810852c48c44b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:20:27 +0100 Subject: [PATCH 095/274] build(deps): bump urllib3 from 1.26.6 to 1.26.17 in /.github/actions/pr_notifier (#29913) build(deps): bump urllib3 in /.github/actions/pr_notifier Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.6 to 1.26.17. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.6...1.26.17) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/pr_notifier/requirements.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/actions/pr_notifier/requirements.txt b/.github/actions/pr_notifier/requirements.txt index 15c4922796916..fac4d708533d6 100644 --- a/.github/actions/pr_notifier/requirements.txt +++ b/.github/actions/pr_notifier/requirements.txt @@ -215,10 +215,12 @@ slack-sdk==3.21.3 \ --hash=sha256:20829bdc1a423ec93dac903470975ebf3bc76fd3fd91a4dadc0eeffc940ecb0c \ --hash=sha256:de3c07b92479940b61cd68c566f49fbc9974c8f38f661d26244078f3903bb9cc # via -r requirements.in -urllib3==1.26.6 \ - --hash=sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4 \ - --hash=sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f - # via requests +urllib3==1.26.17 \ + --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ + --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b + # via + # pygithub + # requests wrapt==1.12.1 \ --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 # via deprecated From a348a570eeeeffae291993304b3b5554fc7f26a6 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 6 Oct 2023 13:02:41 +0100 Subject: [PATCH 096/274] mobile/ci: Remove Gemfile/lock (#29998) Signed-off-by: Ryan Northey --- mobile/Gemfile | 3 -- mobile/Gemfile.lock | 99 --------------------------------------------- 2 files changed, 102 deletions(-) delete mode 100644 mobile/Gemfile delete mode 100644 mobile/Gemfile.lock diff --git a/mobile/Gemfile b/mobile/Gemfile deleted file mode 100644 index d1bf7c6fbeb06..0000000000000 --- a/mobile/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "cocoapods" diff --git a/mobile/Gemfile.lock b/mobile/Gemfile.lock deleted file mode 100644 index 31a996d9878e5..0000000000000 --- a/mobile/Gemfile.lock +++ /dev/null @@ -1,99 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.5) - rexml - activesupport (6.1.7.3) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - claide (1.1.0) - cocoapods (1.11.3) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) - addressable (~> 2.8) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix (~> 4.0) - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.6.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored2 (3.1.2) - concurrent-ruby (1.2.2) - escape (0.0.4) - ethon (0.15.0) - ffi (>= 1.15.0) - ffi (1.15.5) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - json (2.6.1) - minitest (5.18.0) - molinillo (0.8.0) - nanaimo (0.3.0) - nap (1.1.0) - netrc (0.11.0) - public_suffix (4.0.7) - rexml (3.2.5) - ruby-macho (2.5.1) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - xcodeproj (1.21.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - zeitwerk (2.6.7) - -PLATFORMS - arm64-darwin-21 - x86_64-darwin-19 - x86_64-linux - -DEPENDENCIES - cocoapods - -BUNDLED WITH - 2.3.8 From 97b0a632c45c637e1fbcd8c4bb823d542bcb5db0 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 1 Aug 2023 01:31:15 +0100 Subject: [PATCH 097/274] bazel: Switch `exec_tools` -> `tools` (#28729) Signed-off-by: Ryan Northey --- bazel/external/boringssl_fips.BUILD | 2 +- contrib/vcl/source/BUILD | 2 +- distribution/BUILD | 6 +++--- docs/BUILD | 24 ++++++++++++------------ tools/base/envoy_python.bzl | 2 +- tools/dependency/BUILD | 2 +- tools/proto_format/BUILD | 4 ++-- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/bazel/external/boringssl_fips.BUILD b/bazel/external/boringssl_fips.BUILD index 1af9f34b1f020..353b1b43292d3 100644 --- a/bazel/external/boringssl_fips.BUILD +++ b/bazel/external/boringssl_fips.BUILD @@ -30,5 +30,5 @@ genrule( "ssl/libssl.a", ], cmd = "$(location {}) $(location crypto/libcrypto.a) $(location ssl/libssl.a)".format("@envoy//bazel/external:boringssl_fips.genrule_cmd"), - exec_tools = ["@envoy//bazel/external:boringssl_fips.genrule_cmd"], + tools = ["@envoy//bazel/external:boringssl_fips.genrule_cmd"], ) diff --git a/contrib/vcl/source/BUILD b/contrib/vcl/source/BUILD index 256ef6efa78da..143f174a721ad 100644 --- a/contrib/vcl/source/BUILD +++ b/contrib/vcl/source/BUILD @@ -82,7 +82,7 @@ genrule( && find . -name "*.a" | xargs -I{} cp -a {} $$EXTERNAL_DIR \ && find . -name "vppcom.h" | xargs -I{} cp -a {} $$EXTERNAL_DIR """, - exec_tools = [":build"], + tools = [":build"], ) envoy_cc_library( diff --git a/distribution/BUILD b/distribution/BUILD index 1b3b32a13d755..24582fe3d8a80 100644 --- a/distribution/BUILD +++ b/distribution/BUILD @@ -114,12 +114,12 @@ genrule( -m arm64/envoy-contrib:bin/envoy-contrib-$${VERSION}-linux-aarch_64 \ --out $@ """ % VERSION, - exec_tools = [ + tags = ["no-remote"], + tools = [ ":arm64-packages", - ":x64-packages", ":arm64-release", + ":x64-packages", ":x64-release", "//tools/distribution:sign", ], - tags = ["no-remote"], ) diff --git a/docs/BUILD b/docs/BUILD index 69548a4aafda4..27a0249568b2d 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -71,7 +71,7 @@ genrule( $(location //source/extensions:extensions_metadata.yaml) \\ $(location //contrib:extensions_metadata.yaml) $@ """, - exec_tools = ["//tools/docs:generate_extensions_security_rst"], + tools = ["//tools/docs:generate_extensions_security_rst"], ) genrule( @@ -82,7 +82,7 @@ genrule( $(location //bazel:all_repository_locations) \ $@ """, - exec_tools = [ + tools = [ "//bazel:all_repository_locations", "//tools/docs:generate_external_deps_rst", ], @@ -108,7 +108,7 @@ genrule( cmd = """ cat $(location :v3_proto_srcs) $(location :xds_proto_srcs) > $@ """, - exec_tools = [ + tools = [ ":v3_proto_srcs", ":xds_proto_srcs", ], @@ -122,7 +122,7 @@ genrule( $(location //tools/protodoc:generate_empty) \ $(location empty_extensions.json) $@ """, - exec_tools = ["//tools/protodoc:generate_empty"], + tools = ["//tools/protodoc:generate_empty"], ) genrule( @@ -136,7 +136,7 @@ genrule( $(location //tools/docs:generate_api_rst) \\ $(location proto_srcs) $(locations //tools/protodoc:api_v3_protodoc) $@ """, - exec_tools = ["//tools/docs:generate_api_rst"], + tools = ["//tools/docs:generate_api_rst"], ) pkg_files( @@ -178,7 +178,7 @@ genrule( cmd = """ $(location //tools/docs:generate_version_histories) --path=$$(dirname $(location //:VERSION.txt)) $@ """, - exec_tools = [ + tools = [ ":versions.yaml", "//:VERSION.txt", "//changelogs", @@ -251,14 +251,14 @@ genrule( $(location rst) \ $@ """, - exec_tools = [ - "//bazel:volatile_env", - "//tools/docs:sphinx_runner", + stamp = 1, + tools = [ ":rst", "//:VERSION.txt", + "//bazel:volatile_env", + "//tools/docs:sphinx_runner", "@envoy_api//:v3_proto_set", ], - stamp = 1, ) # No git stamping, speeds up local dev switching branches @@ -274,10 +274,10 @@ genrule( $(location :rst) \ $@ """, - exec_tools = [ - "//tools/docs:sphinx_runner", + tools = [ ":rst", "//:VERSION.txt", + "//tools/docs:sphinx_runner", "@envoy_api//:v3_proto_set", ], ) diff --git a/tools/base/envoy_python.bzl b/tools/base/envoy_python.bzl index ec852e511b440..7578d54f19fe5 100644 --- a/tools/base/envoy_python.bzl +++ b/tools/base/envoy_python.bzl @@ -202,7 +202,7 @@ def envoy_jinja_env( > $@ """ % (template_arg, load_args), outs = [name_env_py], - exec_tools = [name_templates], + tools = [name_templates], ) envoy_pytool_library( diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 855ed61dc3860..5d5a388502854 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -68,7 +68,7 @@ genrule( --download_cves $@ \ --repository_locations=$(location //bazel:all_repository_locations) """, - exec_tools = [ + tools = [ ":cve_download", "//bazel:all_repository_locations", ], diff --git a/tools/proto_format/BUILD b/tools/proto_format/BUILD index 4a9bfc24e2f00..8ce574c9e623d 100644 --- a/tools/proto_format/BUILD +++ b/tools/proto_format/BUILD @@ -108,11 +108,11 @@ genrule( --build_file=$(location //tools/type_whisperer:api_build_file) \ --protoprinted=$(location //tools/protoprint:protoprinted) \ """, - exec_tools = [ + tools = [ ":format_api", ":xformed", - "//tools/type_whisperer:api_build_file", "//tools/protoprint:protoprinted", + "//tools/type_whisperer:api_build_file", ], ) From 9050542a4d6c8a58f3b2ed89092fc84264554dc0 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 9 Oct 2023 10:31:35 +0100 Subject: [PATCH 098/274] distribution/publishing: Add debs to releases (#29937) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/publish.yml | 6 ++++++ ci/do_ci.sh | 3 --- distribution/BUILD | 28 ++++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index da0cf2e0518d5..1eb1d57584cbe 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -243,6 +243,12 @@ jobs: authGPGKey: ${{ parameters.authGPGKey }} pathGPGConfiguredHome: /build/.gnupg pathGPGHome: $(Build.StagingDirectory)/.gnupg + - bash: | + set -e -o pipefail + mkdir -p distribution/custom + cp -a $(Build.StagingDirectory)/*/*64 distribution/custom/ + workingDirectory: $(Build.SourcesDirectory) + - job: success dependsOn: ["docker", "signed_release"] displayName: Success (linux artefacts) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index df59656ecc220..9cd420d38f6d3 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -861,9 +861,6 @@ case $CI_TARGET in release.signed) echo "Signing binary packages..." setup_clang_toolchain - # The default config expects these files - mkdir -p distribution/custom - cp -a /build/*/*64 distribution/custom/ bazel build "${BAZEL_BUILD_OPTIONS[@]}" //distribution:signed cp -a bazel-bin/distribution/release.signed.tar.zst "${BUILD_DIR}/envoy/" "${ENVOY_SRCDIR}/ci/upload_gcs_artifact.sh" "${BUILD_DIR}/envoy" release diff --git a/distribution/BUILD b/distribution/BUILD index 24582fe3d8a80..578f6de6f3b3f 100644 --- a/distribution/BUILD +++ b/distribution/BUILD @@ -97,6 +97,28 @@ label_flag( build_setting_default = "//distribution:custom/arm64/bin/release.tar.zst", ) +genrule( + name = "multi_arch_debs", + outs = ["multiarch-debs.tar.gz"], + # To ensure the debs tarball is not extracted and kept as a tarball, it is + # placed into a 2nd archive. + cmd = """ + tmpdir=$$(mktemp -d) \ + && tmpdir2=$$(mktemp -d) \ + && tar xf $(location :x64-packages) -C "$$tmpdir" \ + && tar xf $(location :arm64-packages) -C "$$tmpdir" \ + && rm "$${tmpdir}/signing.key" \ + && mv "$${tmpdir}/deb/"* "$${tmpdir}" \ + && rm -rf "$${tmpdir}/deb/" \ + && tar cf $$tmpdir2/debs.tar.gz -C "$${tmpdir}" . \ + && tar cf $@ -C "$${tmpdir2}" . \ + """, + tools = [ + ":arm64-packages", + ":x64-packages", + ], +) + genrule( name = "signed", outs = ["release.signed.tar.zst"], @@ -104,8 +126,7 @@ genrule( # Sign the packages VERSION=%s \ && $(location //tools/distribution:sign) \ - "deb.x64:$(location :x64-packages)" \ - "deb.arm64:$(location :arm64-packages)" \ + "bin:$(location :multi_arch_debs)" \ "x64:$(location :x64-release)" \ "arm64:$(location :arm64-release)" \ -m x64/envoy:bin/envoy-$${VERSION}-linux-x86_64 \ @@ -116,9 +137,8 @@ genrule( """ % VERSION, tags = ["no-remote"], tools = [ - ":arm64-packages", ":arm64-release", - ":x64-packages", + ":multi_arch_debs", ":x64-release", "//tools/distribution:sign", ], From fa3a1134d3e6a839ac902a043fcfa1eb0345eac6 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 9 Oct 2023 10:10:28 +0100 Subject: [PATCH 099/274] docker/build: Bump distroless -> debian 12 and pin ubuntu Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index aa29a46fb4b64..026bd59f3a5f1 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=20.04 +ARG BUILD_TAG=20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e3b2026b4fc2faba ARG ENVOY_VRP_BASE_IMAGE=envoy-base @@ -58,8 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -# gcr.io/distroless/base-nossl-debian11:nonroot -FROM gcr.io/distroless/base-nossl-debian11:nonroot@sha256:1311462d37ff75d7eafd7b3656f029a47fa465d5a7c8b4ce7956028e8b8fa5a8 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:54f30b80bb6a6b0185deff049fa35cc65d883b641ee655747db97ffd17432e00 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 481a9d54b8f5d73e16c6673eaea1a781c5e5705e Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Thu, 28 Sep 2023 16:11:58 +0000 Subject: [PATCH 100/274] Close HTTP connections that prematurely reset streams Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 9 +++ source/common/http/conn_manager_config.h | 1 + source/common/http/conn_manager_impl.cc | 65 +++++++++++++++++ source/common/http/conn_manager_impl.h | 23 ++++++ source/common/runtime/runtime_features.cc | 1 + .../multiplexed_integration_test.cc | 72 +++++++++++++++++++ 6 files changed, 171 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 32fc3bc047fd2..b5d45089f337c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -2,6 +2,15 @@ date: Pending behavior_changes: # *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +- area: http + change: | + Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key + ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream + reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, + with the default value of 500, determines the number of requests received from a connection before the check for premature + resets is applied. The connection is disconnected if more than 50% of resets are premature. + Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables + this check. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 52f8188c205c5..a07eb825f789b 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -66,6 +66,7 @@ namespace Http { COUNTER(downstream_rq_rejected_via_ip_detection) \ COUNTER(downstream_rq_response_before_rq_complete) \ COUNTER(downstream_rq_rx_reset) \ + COUNTER(downstream_rq_too_many_premature_resets) \ COUNTER(downstream_rq_timeout) \ COUNTER(downstream_rq_header_timeout) \ COUNTER(downstream_rq_too_large) \ diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 5a603aaed80b6..021c55bc4ccae 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1,5 +1,6 @@ #include "source/common/http/conn_manager_impl.h" +#include #include #include #include @@ -55,6 +56,11 @@ namespace Envoy { namespace Http { +const absl::string_view ConnectionManagerImpl::PrematureResetTotalStreamCountKey = + "overload.premature_reset_total_stream_count"; +const absl::string_view ConnectionManagerImpl::PrematureResetMinStreamLifetimeSecondsKey = + "overload.premature_reset_min_stream_lifetime_seconds"; + bool requestWasConnect(const RequestHeaderMapSharedPtr& headers, Protocol protocol) { if (!headers) { return false; @@ -267,6 +273,12 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream, bool check_for_def } void ConnectionManagerImpl::doDeferredStreamDestroy(ActiveStream& stream) { + if (!stream.state_.is_internally_destroyed_) { + ++closed_non_internally_destroyed_requests_; + if (isPrematureRstStream(stream)) { + ++number_premature_stream_resets_; + } + } if (stream.max_stream_duration_timer_ != nullptr) { stream.max_stream_duration_timer_->disableTimer(); stream.max_stream_duration_timer_ = nullptr; @@ -343,6 +355,7 @@ void ConnectionManagerImpl::doDeferredStreamDestroy(ActiveStream& stream) { if (connection_idle_timer_ && streams_.empty()) { connection_idle_timer_->enableTimer(config_.idleTimeout().value()); } + maybeDrainDueToPrematureResets(); } RequestDecoder& ConnectionManagerImpl::newStream(ResponseEncoder& response_encoder, @@ -607,6 +620,58 @@ void ConnectionManagerImpl::doConnectionClose( } } +bool ConnectionManagerImpl::isPrematureRstStream(const ActiveStream& stream) const { + // Check if the request was prematurely reset, by comparing its lifetime to the configured + // threshold. + ASSERT(!stream.state_.is_internally_destroyed_); + absl::optional duration = + stream.filter_manager_.streamInfo().currentDuration(); + + // Check if request lifetime is longer than the premature reset threshold. + if (duration) { + const uint64_t lifetime = std::chrono::duration_cast(*duration).count(); + const uint64_t min_lifetime = runtime_.snapshot().getInteger( + ConnectionManagerImpl::PrematureResetMinStreamLifetimeSecondsKey, 1); + if (lifetime > min_lifetime) { + return false; + } + } + + // If request has completed before configured threshold, also check if the Envoy proxied the + // response from the upstream. Requests without the response status were reset. + // TODO(RyanTheOptimist): Possibly support half_closed_local instead. + return !stream.filter_manager_.streamInfo().responseCode(); +} + +// Sends a GOAWAY if too many streams have been reset prematurely on this +// connection. +void ConnectionManagerImpl::maybeDrainDueToPrematureResets() { + if (!Runtime::runtimeFeatureEnabled( + "envoy.restart_features.send_goaway_for_premature_rst_streams") || + closed_non_internally_destroyed_requests_ == 0) { + return; + } + + const uint64_t limit = + runtime_.snapshot().getInteger(ConnectionManagerImpl::PrematureResetTotalStreamCountKey, 500); + + if (closed_non_internally_destroyed_requests_ < limit) { + return; + } + + if (static_cast(number_premature_stream_resets_) / + closed_non_internally_destroyed_requests_ < + .5) { + return; + } + + if (drain_state_ == DrainState::NotDraining) { + stats_.named_.downstream_rq_too_many_premature_resets_.inc(); + doConnectionClose(Network::ConnectionCloseType::Abort, absl::nullopt, + "too_many_premature_resets"); + } +} + void ConnectionManagerImpl::onGoAway(GoAwayErrorCode) { // Currently we do nothing with remote go away frames. In the future we can decide to no longer // push resources if applicable. diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index b82b1967a5115..dad494c953bc0 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -115,6 +115,14 @@ class ConnectionManagerImpl : Logger::Loggable, void setClearHopByHopResponseHeaders(bool value) { clear_hop_by_hop_response_headers_ = value; } bool clearHopByHopResponseHeaders() const { return clear_hop_by_hop_response_headers_; } + // This runtime key configures the number of streams which must be closed on a connection before + // envoy will potentially drain a connection due to excessive prematurely reset streams. + static const absl::string_view PrematureResetTotalStreamCountKey; + + // The minimum lifetime of a stream, in seconds, in order not to be considered + // prematurely closed. + static const absl::string_view PrematureResetMinStreamLifetimeSecondsKey; + private: struct ActiveStream; class MobileConnectionManagerImpl; @@ -544,6 +552,15 @@ class ConnectionManagerImpl : Logger::Loggable, void doConnectionClose(absl::optional close_type, absl::optional response_flag, absl::string_view details); + // Returns true if a RST_STREAM for the given stream is premature. Premature + // means the RST_STREAM arrived before response headers were sent and than + // the stream was alive for short period of time. This period is specified + // by the optional runtime value PrematureResetMinStreamLifetimeSecondsKey, + // or one second if that is not present. + bool isPrematureRstStream(const ActiveStream& stream) const; + // Sends a GOAWAY if both sufficient streams have been closed on a connection + // and at least half have been prematurely reset? + void maybeDrainDueToPrematureResets(); enum class DrainState { NotDraining, Draining, Closing }; @@ -584,6 +601,12 @@ class ConnectionManagerImpl : Logger::Loggable, bool clear_hop_by_hop_response_headers_{true}; // The number of requests accumulated on the current connection. uint64_t accumulated_requests_{}; + // The number of requests closed on the current connection which were + // not internally destroyed + uint64_t closed_non_internally_destroyed_requests_{}; + // The number of requests that received a premature RST_STREAM, according to + // the definition given in `isPrematureRstStream()`. + uint64_t number_premature_stream_resets_{0}; const std::string proxy_name_; // for Proxy-Status. const bool refresh_rtt_after_request_{}; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 8b75d6bbbfb1c..60d594c964319 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -91,6 +91,7 @@ RUNTIME_GUARD(envoy_reloadable_features_validate_grpc_header_before_log_grpc_sta RUNTIME_GUARD(envoy_reloadable_features_validate_upstream_headers); RUNTIME_GUARD(envoy_restart_features_explicit_wildcard_resource); RUNTIME_GUARD(envoy_restart_features_remove_runtime_singleton); +RUNTIME_GUARD(envoy_restart_features_send_goaway_for_premature_rst_streams); RUNTIME_GUARD(envoy_restart_features_udp_read_normalize_addresses); RUNTIME_GUARD(envoy_restart_features_use_apple_api_for_dns_lookups); diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 190e57065d1c6..b4260ff3fa9ec 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -25,6 +26,7 @@ #include "test/mocks/http/mocks.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -92,6 +94,15 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, MultiplexedIntegrationTest, {Http::CodecType::HTTP1})), HttpProtocolIntegrationTest::protocolTestParamsToString); +class MultiplexedIntegrationTestWithSimulatedTime : public Event::TestUsingSimulatedTime, + public MultiplexedIntegrationTest {}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, MultiplexedIntegrationTestWithSimulatedTime, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( + {Http::CodecType::HTTP2, Http::CodecType::HTTP3}, + {Http::CodecType::HTTP1})), + HttpProtocolIntegrationTest::protocolTestParamsToString); + TEST_P(MultiplexedIntegrationTest, RouterRequestAndResponseWithBodyNoBuffer) { testRouterRequestAndResponseWithBody(1024, 512, false, false); } @@ -1076,6 +1087,67 @@ TEST_P(MultiplexedIntegrationTest, GoAway) { EXPECT_EQ("200", response->headers().getStatusValue()); } +// TODO(rch): Add a unit test which covers internal redirect handling. +TEST_P(MultiplexedIntegrationTestWithSimulatedTime, GoAwayAfterTooManyResets) { + EXCLUDE_DOWNSTREAM_HTTP3; // Need to wait for the server to reset the stream + // before opening new one. + config_helper_.addRuntimeOverride("envoy.restart_features.send_goaway_for_premature_rst_streams", + "true"); + const int total_streams = 100; + config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", + absl::StrCat(total_streams)); + initialize(); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "GET"}, {":path", "/healthcheck"}, {":scheme", "http"}, {":authority", "host"}}; + codec_client_ = makeHttpConnection(lookupPort("http")); + for (int i = 0; i < total_streams; ++i) { + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_client_->sendReset(*request_encoder_); + ASSERT_TRUE(response->waitForReset()); + } + + // Envoy should disconnect client due to premature reset check + ASSERT_TRUE(codec_client_->waitForDisconnect()); + test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", total_streams); + test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); +} + +TEST_P(MultiplexedIntegrationTestWithSimulatedTime, DontGoAwayAfterTooManyResetsForLongStreams) { + EXCLUDE_DOWNSTREAM_HTTP3; // Need to wait for the server to reset the stream + // before opening new one. + config_helper_.addRuntimeOverride("envoy.restart_features.send_goaway_for_premature_rst_streams", + "true"); + const int total_streams = 100; + const int stream_lifetime_seconds = 2; + config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", + absl::StrCat(total_streams)); + + config_helper_.addRuntimeOverride("overload.premature_reset_min_stream_lifetime_seconds", + absl::StrCat(stream_lifetime_seconds)); + + initialize(); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "GET"}, {":path", "/healthcheck"}, {":scheme", "http"}, {":authority", "host"}}; + codec_client_ = makeHttpConnection(lookupPort("http")); + + std::string request_counter = "http.config_test.downstream_rq_total"; + std::string reset_counter = "http.config_test.downstream_rq_rx_reset"; + for (int i = 0; i < total_streams * 2; ++i) { + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + test_server_->waitForCounterEq(request_counter, i + 1); + timeSystem().advanceTimeWait(std::chrono::seconds(2 * stream_lifetime_seconds)); + codec_client_->sendReset(*request_encoder_); + ASSERT_TRUE(response->waitForReset()); + test_server_->waitForCounterEq(reset_counter, i + 1); + } +} + TEST_P(MultiplexedIntegrationTest, Trailers) { testTrailers(1024, 2048, false, false); } TEST_P(MultiplexedIntegrationTest, TrailersGiantBody) { From 2e4228b0ee73ae640c92e0974c91e251997a3d2f Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Sat, 30 Sep 2023 13:58:46 +0000 Subject: [PATCH 101/274] Limit on the number of HTTP requests processed from a connection in an I/O cycle Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 7 + source/common/http/conn_manager_impl.cc | 89 ++++++- source/common/http/conn_manager_impl.h | 25 +- test/common/http/conn_manager_impl_test_2.cc | 245 ++++++++++++++++++ .../http/conn_manager_impl_test_base.cc | 19 ++ .../common/http/conn_manager_impl_test_base.h | 2 + test/common/http/http2/http2_frame.cc | 12 +- test/common/http/http2/http2_frame.h | 14 +- .../multiplexed_integration_test.cc | 170 ++++++++++++ 9 files changed, 569 insertions(+), 14 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b5d45089f337c..86d0eac2339fc 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -11,6 +11,13 @@ behavior_changes: resets is applied. The connection is disconnected if more than 50% of resets are premature. Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables this check. +- area: http + change: | + Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed + from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This + mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other + connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. + By default this limit is disabled. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 021c55bc4ccae..bf04397084134 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -60,6 +60,10 @@ const absl::string_view ConnectionManagerImpl::PrematureResetTotalStreamCountKey "overload.premature_reset_total_stream_count"; const absl::string_view ConnectionManagerImpl::PrematureResetMinStreamLifetimeSecondsKey = "overload.premature_reset_min_stream_lifetime_seconds"; +// Runtime key for maximum number of requests that can be processed from a single connection per +// I/O cycle. Requests over this limit are deferred until the next I/O cycle. +const absl::string_view ConnectionManagerImpl::MaxRequestsPerIoCycle = + "http.max_requests_per_io_cycle"; bool requestWasConnect(const RequestHeaderMapSharedPtr& headers, Protocol protocol) { if (!headers) { @@ -116,6 +120,8 @@ ConnectionManagerImpl::ConnectionManagerImpl(ConnectionManagerConfig& config, /*node_id=*/local_info_.node().id(), /*server_name=*/config_.serverName(), /*proxy_status_config=*/config_.proxyStatusConfig())), + max_requests_during_dispatch_( + runtime_.snapshot().getInteger(ConnectionManagerImpl::MaxRequestsPerIoCycle, UINT32_MAX)), refresh_rtt_after_request_( Runtime::runtimeFeatureEnabled("envoy.reloadable_features.refresh_rtt_after_request")) {} @@ -128,6 +134,10 @@ const ResponseHeaderMap& ConnectionManagerImpl::continueHeader() { void ConnectionManagerImpl::initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) { read_callbacks_ = &callbacks; dispatcher_ = &callbacks.connection().dispatcher(); + if (max_requests_during_dispatch_ != UINT32_MAX) { + deferred_request_processing_callback_ = + dispatcher_->createSchedulableCallback([this]() -> void { onDeferredRequestProcessing(); }); + } stats_.named_.downstream_cx_total_.inc(); stats_.named_.downstream_cx_active_.inc(); @@ -454,6 +464,7 @@ void ConnectionManagerImpl::createCodec(Buffer::Instance& data) { } Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool) { + requests_during_dispatch_count_ = 0; if (!codec_) { // Http3 codec should have been instantiated by now. createCodec(data); @@ -1366,7 +1377,12 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt traceRequest(); } - filter_manager_.decodeHeaders(*request_headers_, end_stream); + if (!connection_manager_.shouldDeferRequestProxyingToNextIoCycle()) { + filter_manager_.decodeHeaders(*request_headers_, end_stream); + } else { + state_.deferred_to_next_io_iteration_ = true; + state_.deferred_end_stream_ = end_stream; + } // Reset it here for both global and overridden cases. resetIdleTimer(); @@ -1433,8 +1449,15 @@ void ConnectionManagerImpl::ActiveStream::decodeData(Buffer::Instance& data, boo connection_manager_.read_callbacks_->connection().dispatcher()); maybeEndDecode(end_stream); filter_manager_.streamInfo().addBytesReceived(data.length()); - - filter_manager_.decodeData(data, end_stream); + if (!state_.deferred_to_next_io_iteration_) { + filter_manager_.decodeData(data, end_stream); + } else { + if (!deferred_data_) { + deferred_data_ = std::make_unique(); + } + deferred_data_->move(data); + state_.deferred_end_stream_ = end_stream; + } } void ConnectionManagerImpl::ActiveStream::decodeTrailers(RequestTrailerMapPtr&& trailers) { @@ -1450,7 +1473,9 @@ void ConnectionManagerImpl::ActiveStream::decodeTrailers(RequestTrailerMapPtr&& return; } maybeEndDecode(true); - filter_manager_.decodeTrailers(*request_trailers_); + if (!state_.deferred_to_next_io_iteration_) { + filter_manager_.decodeTrailers(*request_trailers_); + } } void ConnectionManagerImpl::ActiveStream::decodeMetadata(MetadataMapPtr&& metadata_map) { @@ -2158,5 +2183,61 @@ void ConnectionManagerImpl::ActiveStream::resetStream(Http::StreamResetReason, a connection_manager_.doEndStream(*this); } +bool ConnectionManagerImpl::ActiveStream::onDeferredRequestProcessing() { + // TODO(yanavlasov): Merge this with the filter manager continueIteration() method + if (!state_.deferred_to_next_io_iteration_) { + return false; + } + state_.deferred_to_next_io_iteration_ = false; + bool end_stream = + state_.deferred_end_stream_ && deferred_data_ == nullptr && request_trailers_ == nullptr; + filter_manager_.decodeHeaders(*request_headers_, end_stream); + if (end_stream) { + return true; + } + if (deferred_data_ != nullptr) { + end_stream = state_.deferred_end_stream_ && request_trailers_ == nullptr; + filter_manager_.decodeData(*deferred_data_, end_stream); + } + if (request_trailers_ != nullptr) { + filter_manager_.decodeTrailers(*request_trailers_); + } + return true; +} + +bool ConnectionManagerImpl::shouldDeferRequestProxyingToNextIoCycle() { + // Do not defer this stream if stream deferral is disabled + if (deferred_request_processing_callback_ == nullptr) { + return false; + } + // Defer this stream if there are already deferred streams, so they are not + // processed out of order + if (deferred_request_processing_callback_->enabled()) { + return true; + } + ++requests_during_dispatch_count_; + bool defer = requests_during_dispatch_count_ > max_requests_during_dispatch_; + if (defer) { + deferred_request_processing_callback_->scheduleCallbackNextIteration(); + } + return defer; +} + +void ConnectionManagerImpl::onDeferredRequestProcessing() { + requests_during_dispatch_count_ = 1; // 1 stream is always let through + // Streams are inserted at the head of the list. As such process deferred + // streams at the back of the list first. + for (auto reverse_iter = streams_.rbegin(); reverse_iter != streams_.rend();) { + auto& stream_ptr = *reverse_iter; + // Move the iterator to the next item in case the `onDeferredRequestProcessing` call removes the + // stream from the list. + ++reverse_iter; + bool was_deferred = stream_ptr->onDeferredRequestProcessing(); + if (was_deferred && shouldDeferRequestProxyingToNextIoCycle()) { + break; + } + } +} + } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index dad494c953bc0..e79a6a81c0826 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -122,6 +122,7 @@ class ConnectionManagerImpl : Logger::Loggable, // The minimum lifetime of a stream, in seconds, in order not to be considered // prematurely closed. static const absl::string_view PrematureResetMinStreamLifetimeSecondsKey; + static const absl::string_view MaxRequestsPerIoCycle; private: struct ActiveStream; @@ -345,7 +346,8 @@ class ConnectionManagerImpl : Logger::Loggable, : codec_saw_local_complete_(false), codec_encode_complete_(false), on_reset_stream_called_(false), is_zombie_stream_(false), saw_connection_close_(false), successful_upgrade_(false), is_internally_destroyed_(false), - is_internally_created_(false), is_tunneling_(false), decorated_propagate_(true) {} + is_internally_created_(false), is_tunneling_(false), decorated_propagate_(true), + deferred_to_next_io_iteration_(false) {} // It's possibly for the codec to see the completed response but not fully // encode it. @@ -371,6 +373,14 @@ class ConnectionManagerImpl : Logger::Loggable, bool is_tunneling_ : 1; bool decorated_propagate_ : 1; + + // Indicates that sending headers to the filter manager is deferred to the + // next I/O cycle. If data or trailers are received when this flag is set + // they are deferred too. + // TODO(yanavlasov): encapsulate the entire state of deferred streams into a separate + // structure, so it can be atomically created and cleared. + bool deferred_to_next_io_iteration_ : 1; + bool deferred_end_stream_ : 1; }; bool canDestroyStream() const { @@ -418,6 +428,11 @@ class ConnectionManagerImpl : Logger::Loggable, // HTTP connection manager configuration, then the entire connection is closed. bool validateTrailers(); + // Dispatch deferred headers, body and trailers to the filter manager. + // Return true if this stream was deferred and dispatched pending headers, body and trailers (if + // present). Return false if this stream was not deferred. + bool onDeferredRequestProcessing(); + ConnectionManagerImpl& connection_manager_; OptRef connection_manager_tracing_config_; // TODO(snowp): It might make sense to move this to the FilterManager to avoid storing it in @@ -504,6 +519,8 @@ class ConnectionManagerImpl : Logger::Loggable, const Tracing::CustomTagMap* customTags() const override; bool verbose() const override; uint32_t maxPathTagLength() const override; + + std::unique_ptr deferred_data_; }; using ActiveStreamPtr = std::unique_ptr; @@ -562,6 +579,9 @@ class ConnectionManagerImpl : Logger::Loggable, // and at least half have been prematurely reset? void maybeDrainDueToPrematureResets(); + bool shouldDeferRequestProxyingToNextIoCycle(); + void onDeferredRequestProcessing(); + enum class DrainState { NotDraining, Draining, Closing }; ConnectionManagerConfig& config_; @@ -608,6 +628,9 @@ class ConnectionManagerImpl : Logger::Loggable, // the definition given in `isPrematureRstStream()`. uint64_t number_premature_stream_resets_{0}; const std::string proxy_name_; // for Proxy-Status. + uint32_t requests_during_dispatch_count_{0}; + const uint32_t max_requests_during_dispatch_{UINT32_MAX}; + Event::SchedulableCallbackPtr deferred_request_processing_callback_; const bool refresh_rtt_after_request_{}; }; diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 5daf2b1a45e6a..078bd1d85ab79 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -11,6 +11,7 @@ using testing::InvokeWithoutArgs; using testing::Mock; using testing::Ref; using testing::Return; +using testing::ReturnArg; using testing::ReturnRef; namespace Envoy { @@ -3767,5 +3768,249 @@ TEST_F(HttpConnectionManagerImplTest, NoProxyProtocolAdded) { // Clean up. filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } + +// Validate that deferred streams are processed with a variety of +// headers, data and trailer arriving in the same I/O cycle +TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { + const int kRequestsSentPerIOCycle = 100; + EXPECT_CALL(runtime_.snapshot_, getInteger(_, _)).WillRepeatedly(ReturnArg<1>()); + // Process 1 request per I/O cycle + auto* deferred_request_callback = enableStreamsPerIoLimit(1); + setup(false, ""); + + // Store the basic request encoder during filter chain setup. + std::vector> encoder_filters; + int decode_headers_call_count = 0; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + std::shared_ptr filter(new NiceMock()); + + // Each 4th request is headers only + EXPECT_CALL(*filter, decodeHeaders(_, i % 4 == 0 ? true : false)) + .WillRepeatedly(Invoke([&](RequestHeaderMap&, bool) -> FilterHeadersStatus { + ++decode_headers_call_count; + return FilterHeadersStatus::StopIteration; + })); + + // Each 1st request is headers and data only + // Each 2nd request is headers, data and trailers + if (i % 4 == 1 || i % 4 == 2) { + EXPECT_CALL(*filter, decodeData(_, i % 4 == 1 ? true : false)) + .WillOnce(Return(FilterDataStatus::StopIterationNoBuffer)); + } + + // Each 3rd request is headers and trailers (no data) + if (i % 4 == 2 || i % 4 == 3) { + EXPECT_CALL(*filter, decodeTrailers(_)).WillOnce(Return(FilterTrailersStatus::StopIteration)); + } + + EXPECT_CALL(*filter, setDecoderFilterCallbacks(_)); + encoder_filters.push_back(std::move(filter)); + } + + uint64_t random_value = 0; + EXPECT_CALL(random_, random()).WillRepeatedly(Invoke([&random_value]() { + return random_value++; + })); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .Times(kRequestsSentPerIOCycle) + .WillRepeatedly(Invoke([&encoder_filters](FilterChainManager& manager) -> bool { + static int index = 0; + int i = index++; + FilterFactoryCb factory([&encoder_filters, i](FilterChainFactoryCallbacks& callbacks) { + callbacks.addStreamDecoderFilter(encoder_filters[i]); + }); + manager.applyFilterFactoryCb({}, factory); + return true; + })); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)) + .Times(kRequestsSentPerIOCycle); + + std::vector> response_encoders(kRequestsSentPerIOCycle); + for (auto& encoder : response_encoders) { + EXPECT_CALL(encoder, getStream()).WillRepeatedly(ReturnRef(encoder.stream_)); + } + + EXPECT_CALL(*codec_, dispatch(_)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + decoder_ = &conn_manager_->newStream(response_encoders[i]); + + RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ + {":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; + + RequestTrailerMapPtr trailers{ + new TestRequestTrailerMapImpl{{"key1", "value1"}, {"key2", "value2"}}}; + + Buffer::OwnedImpl data("data"); + + switch (i % 4) { + case 0: + decoder_->decodeHeaders(std::move(headers), true); + break; + case 1: + decoder_->decodeHeaders(std::move(headers), false); + decoder_->decodeData(data, true); + break; + case 2: + decoder_->decodeHeaders(std::move(headers), false); + decoder_->decodeData(data, false); + decoder_->decodeTrailers(std::move(trailers)); + break; + case 3: + decoder_->decodeHeaders(std::move(headers), false); + decoder_->decodeTrailers(std::move(trailers)); + break; + } + } + + data.drain(4); + return Http::okStatus(); + })); + + // Kick off the incoming data. + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_TRUE(deferred_request_callback->enabled_); + // Only one request should go through the filter chain + ASSERT_EQ(decode_headers_call_count, 1); + + // Let other requests to go through the filter chain. Call expectations will fail + // if this is not the case. + int deferred_request_count = 0; + while (deferred_request_callback->enabled_) { + deferred_request_callback->invokeCallback(); + ++deferred_request_count; + } + + ASSERT_EQ(deferred_request_count, kRequestsSentPerIOCycle); + + for (auto& filter : encoder_filters) { + ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}}; + filter->callbacks_->streamInfo().setResponseCodeDetails(""); + filter->callbacks_->encodeHeaders(std::move(response_headers), true, "details"); + } + + EXPECT_EQ(kRequestsSentPerIOCycle, stats_.named_.downstream_rq_2xx_.value()); + EXPECT_EQ(kRequestsSentPerIOCycle, listener_stats_.downstream_rq_2xx_.value()); + EXPECT_EQ(kRequestsSentPerIOCycle, stats_.named_.downstream_rq_completed_.value()); + EXPECT_EQ(kRequestsSentPerIOCycle, listener_stats_.downstream_rq_completed_.value()); +} + +TEST_F(HttpConnectionManagerImplTest, StreamDeferralPreservesOrder) { + EXPECT_CALL(runtime_.snapshot_, getInteger(_, _)).WillRepeatedly(ReturnArg<1>()); + // Process 1 request per I/O cycle + auto* deferred_request_callback = enableStreamsPerIoLimit(1); + setup(false, ""); + + std::vector> encoder_filters; + int expected_request_id = 0; + const Http::LowerCaseString request_id_header(absl::string_view("request-id")); + // Two requests are processed in 2 I/O reads + const int TotalRequests = 2 * 2; + for (int i = 0; i < TotalRequests; ++i) { + std::shared_ptr filter(new NiceMock()); + + EXPECT_CALL(*filter, decodeHeaders(_, true)) + .WillRepeatedly(Invoke([&](RequestHeaderMap& headers, bool) -> FilterHeadersStatus { + // Check that requests are decoded in expected order + int request_id = 0; + ASSERT(absl::SimpleAtoi(headers.get(request_id_header)[0]->value().getStringView(), + &request_id)); + ASSERT(request_id == expected_request_id); + ++expected_request_id; + return FilterHeadersStatus::StopIteration; + })); + + EXPECT_CALL(*filter, setDecoderFilterCallbacks(_)); + encoder_filters.push_back(std::move(filter)); + } + + uint64_t random_value = 0; + EXPECT_CALL(random_, random()).WillRepeatedly(Invoke([&random_value]() { + return random_value++; + })); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .Times(TotalRequests) + .WillRepeatedly(Invoke([&encoder_filters](FilterChainManager& manager) -> bool { + static int index = 0; + int i = index++; + FilterFactoryCb factory([&encoder_filters, i](FilterChainFactoryCallbacks& callbacks) { + callbacks.addStreamDecoderFilter(encoder_filters[i]); + }); + manager.applyFilterFactoryCb({}, factory); + return true; + })); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(TotalRequests); + + std::vector> response_encoders(TotalRequests); + for (auto& encoder : response_encoders) { + EXPECT_CALL(encoder, getStream()).WillRepeatedly(ReturnRef(encoder.stream_)); + } + auto response_encoders_iter = response_encoders.begin(); + + int request_id = 0; + EXPECT_CALL(*codec_, dispatch(_)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { + // The second request should be deferred + for (int i = 0; i < 2; ++i) { + decoder_ = &conn_manager_->newStream(*response_encoders_iter); + ++response_encoders_iter; + + RequestHeaderMapPtr headers{ + new TestRequestHeaderMapImpl{{":authority", "host"}, + {":path", "/"}, + {":method", "GET"}, + {"request-id", absl::StrCat(request_id)}}}; + + ++request_id; + decoder_->decodeHeaders(std::move(headers), true); + } + + data.drain(4); + return Http::okStatus(); + })); + + // Kick off the incoming data. + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_TRUE(deferred_request_callback->enabled_); + // Only one request should go through the filter chain + ASSERT_EQ(expected_request_id, 1); + + // Test arrival of another request. New request is read from the socket before deferred callbacks. + Buffer::OwnedImpl fake_input2("1234"); + conn_manager_->onData(fake_input2, false); + + // No requests from the second read should go through as there are deferred stream present + ASSERT_EQ(expected_request_id, 1); + + // Let other requests to go through the filter chain. Call expectations will fail + // if this is not the case. + int deferred_request_count = 0; + while (deferred_request_callback->enabled_) { + deferred_request_callback->invokeCallback(); + ++deferred_request_count; + } + + ASSERT_EQ(deferred_request_count, TotalRequests); + + for (auto& filter : encoder_filters) { + ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}}; + filter->callbacks_->streamInfo().setResponseCodeDetails(""); + filter->callbacks_->encodeHeaders(std::move(response_headers), true, "details"); + } + + EXPECT_EQ(TotalRequests, stats_.named_.downstream_rq_2xx_.value()); + EXPECT_EQ(TotalRequests, listener_stats_.downstream_rq_2xx_.value()); + EXPECT_EQ(TotalRequests, stats_.named_.downstream_rq_completed_.value()); + EXPECT_EQ(TotalRequests, listener_stats_.downstream_rq_completed_.value()); +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/conn_manager_impl_test_base.cc b/test/common/http/conn_manager_impl_test_base.cc index 1c5053246e44f..641ca6fae0821 100644 --- a/test/common/http/conn_manager_impl_test_base.cc +++ b/test/common/http/conn_manager_impl_test_base.cc @@ -78,6 +78,7 @@ void HttpConnectionManagerImplMixin::setup(bool ssl, const std::string& server_n conn_manager_ = std::make_unique( *this, drain_close_, random_, http_context_, runtime_, local_info_, cluster_manager_, overload_manager_, test_time_.timeSystem()); + conn_manager_->initializeReadFilterCallbacks(filter_callbacks_); if (tracing) { @@ -370,5 +371,23 @@ void HttpConnectionManagerImplMixin::expectUhvTrailerCheck( })); } +Event::MockSchedulableCallback* +HttpConnectionManagerImplMixin::enableStreamsPerIoLimit(uint32_t limit) { + EXPECT_CALL(runtime_.snapshot_, getInteger("http.max_requests_per_io_cycle", _)) + .WillOnce(Return(limit)); + + // Expect HCM to create and set schedulable callback + auto* deferred_request_callback = + new Event::MockSchedulableCallback(&filter_callbacks_.connection_.dispatcher_); + EXPECT_CALL(*deferred_request_callback, enabled()) + .WillRepeatedly( + Invoke([deferred_request_callback]() { return deferred_request_callback->enabled_; })); + EXPECT_CALL(*deferred_request_callback, scheduleCallbackNextIteration()) + .WillRepeatedly( + Invoke([deferred_request_callback]() { deferred_request_callback->enabled_ = true; })); + + return deferred_request_callback; +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 99fdc93441185..10a4ff15b54ae 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -202,6 +202,8 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { HeaderValidator::TransformationResult transformation_result, bool expect_response = true); + Event::MockSchedulableCallback* enableStreamsPerIoLimit(uint32_t limit); + Envoy::Event::SimulatedTimeSystem test_time_; NiceMock route_config_provider_; std::shared_ptr route_config_{new NiceMock()}; diff --git a/test/common/http/http2/http2_frame.cc b/test/common/http/http2/http2_frame.cc index c5172938a804e..46ba3751ba242 100644 --- a/test/common/http/http2/http2_frame.cc +++ b/test/common/http/http2/http2_frame.cc @@ -341,7 +341,11 @@ Http2Frame Http2Frame::makeRequest(uint32_t stream_index, absl::string_view host makeNetworkOrderStreamId(stream_index)); frame.appendStaticHeader(StaticHeaderIndex::MethodGet); frame.appendStaticHeader(StaticHeaderIndex::SchemeHttps); - frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Path, path); + if (path.empty() || path == "/") { + frame.appendStaticHeader(StaticHeaderIndex::Path); + } else { + frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Path, path); + } frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Authority, host); frame.adjustPayloadSize(); return frame; @@ -365,7 +369,11 @@ Http2Frame Http2Frame::makePostRequest(uint32_t stream_index, absl::string_view makeNetworkOrderStreamId(stream_index)); frame.appendStaticHeader(StaticHeaderIndex::MethodPost); frame.appendStaticHeader(StaticHeaderIndex::SchemeHttps); - frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Path, path); + if (path.empty() || path == "/") { + frame.appendStaticHeader(StaticHeaderIndex::Path); + } else { + frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Path, path); + } frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Authority, host); frame.adjustPayloadSize(); return frame; diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index 7fdf510ea2562..5df3375c16616 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -253,6 +253,13 @@ class Http2Frame { ConstIterator end() const { return data_.end(); } bool empty() const { return data_.empty(); } + void appendHeaderWithoutIndexing(const Header& header); + // This method updates payload length in the HTTP2 header based on the size of the data_ + void adjustPayloadSize() { + ASSERT(size() >= HeaderSize); + setPayloadSize(size() - HeaderSize); + } + private: void buildHeader(Type type, uint32_t payload_size = 0, uint8_t flags = 0, uint32_t stream_id = 0); void setPayloadSize(uint32_t size); @@ -272,15 +279,8 @@ class Http2Frame { // Headers are directly encoded void appendStaticHeader(StaticHeaderIndex index); void appendHeaderWithoutIndexing(StaticHeaderIndex index, absl::string_view value); - void appendHeaderWithoutIndexing(const Header& header); void appendEmptyHeader(); - // This method updates payload length in the HTTP2 header based on the size of the data_ - void adjustPayloadSize() { - ASSERT(size() >= HeaderSize); - setPayloadSize(size() - HeaderSize); - } - DataContainer data_; }; diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index b4260ff3fa9ec..edce9a1838d98 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2130,6 +2130,176 @@ TEST_P(Http2FrameIntegrationTest, AccessLogOfWireBytesIfResponseSizeGreaterThanW tcp_client_->close(); } +TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequests) { + const int kRequestsSentPerIOCycle = 20; + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()); + } + tcp_client_->close(); +} + +TEST_P(Http2FrameIntegrationTest, MultipleRequests) { + const int kRequestsSentPerIOCycle = 20; + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = + Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a", + Http2Frame::DataFlags::EndStream); + absl::StrAppend(&buffer, std::string(data)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()); + } + tcp_client_->close(); +} + +TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailers) { + const int kRequestsSentPerIOCycle = 20; + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = + Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a"); + absl::StrAppend(&buffer, std::string(data)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto trailers = Http2Frame::makeEmptyHeadersFrame( + Http2Frame::makeClientStreamId(i), + static_cast(Http::Http2::orFlags( + Http2Frame::HeadersFlags::EndStream, Http2Frame::HeadersFlags::EndHeaders))); + trailers.appendHeaderWithoutIndexing({"k", "v"}); + trailers.adjustPayloadSize(); + absl::StrAppend(&buffer, std::string(trailers)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()); + } + tcp_client_->close(); +} + +TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequestsFollowedByReset) { + // This number of requests stays below premature reset detection. + const int kRequestsSentPerIOCycle = 20; + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto reset = Http2Frame::makeResetStreamFrame(Http2Frame::makeClientStreamId(i), + Http2Frame::ErrorCode::Cancel); + absl::StrAppend(&buffer, std::string(reset)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", + kRequestsSentPerIOCycle); + // Client should remain connected + ASSERT_TRUE(tcp_client_->connected()); + tcp_client_->close(); +} + +TEST_P(Http2FrameIntegrationTest, ResettingDeferredRequestsTriggersPrematureResetCheck) { + const int kRequestsSentPerIOCycle = 20; + // Set premature stream count to the number of streams we are about to send + config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", "20"); + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto reset = Http2Frame::makeResetStreamFrame(Http2Frame::makeClientStreamId(i), + Http2Frame::ErrorCode::Cancel); + absl::StrAppend(&buffer, std::string(reset)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + // Envoy should close the client connection due to too many premature resets + tcp_client_->waitForDisconnect(); + test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); +} + +TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { + // Use large number of requests to ensure close is detected while there are + // still some deferred streams. + const int kRequestsSentPerIOCycle = 1000; + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); + // Ensure premature reset detection does not get in the way + config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", "1001"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + ASSERT_TRUE(tcp_client_->connected()); + // Drop the downstream connection + tcp_client_->close(); + // Test that Envoy can clean-up deferred streams + test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", + kRequestsSentPerIOCycle); +} + INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FrameIntegrationTest, testing::ValuesIn(Http2FrameIntegrationTest::testParams()), frameIntegrationTestParamToString); From ea5fa06444cbc7a53067084e7dd01dae0e5473d4 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 11 Oct 2023 09:39:13 +0100 Subject: [PATCH 102/274] ci/env: Use correct checkout commit (#30085) Signed-off-by: Ryan Northey --- .github/workflows/_env.yml | 17 ++++++++++++++++- .github/workflows/_stage_publish.yml | 6 +++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 1ea1b0ad45f09..6abbdf220920c 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -136,7 +136,22 @@ jobs: - uses: actions/checkout@v3 name: Checkout Envoy repository with: - fetch-depth: ${{ ! inputs.check_mobile_run && 1 || 0 }} + fetch-depth: ${{ ! (inputs.check_mobile_run || inputs.trusted) && 1 || 0 }} + # WARNING: This allows untrusted code to run!!! + # If this is set, then anything before or after in the job should be regarded as + # compromised. + ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + # If we are in a trusted CI run then the provided commit _must_ be either the latest for + # this branch, or an antecdent. + - run: | + if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD; then + echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 + exit 1 + fi + git checkout "${{ inputs.repo_ref }}" + if: ${{ inputs.trusted }} + name: Check provided ref + - uses: ./.github/actions/env name: Generate environment variables id: env diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 8975169caf0a0..7765531c85eb1 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -80,7 +80,7 @@ jobs: ref: ${{ inputs.given_ref }} bucket: envoy-postsubmit env: | - if [[ '${{ inputs.version_dev }}' != '' ]]; then + if [[ '${{ inputs.version_dev }}' == 'dev' ]]; then export ENVOY_PUBLISH_DRY_RUN=1 fi uses: ./.github/workflows/_ci.yml @@ -113,7 +113,7 @@ jobs: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" ref: main - repository: ${{ inputs.version_dev != '' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} + repository: ${{ inputs.version_dev == 'dev' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} workflow: envoy-sync.yaml inputs: | - commit_sha: ${{ inputs.version_dev != '' && github.sha || '' }} + commit_sha: ${{ inputs.version_dev == 'dev' && github.sha || '' }} From 6b9db09c69965d5bfb37bdd29693f8b7f9e9e9ec Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Tue, 10 Oct 2023 21:05:11 +0000 Subject: [PATCH 103/274] repo: Release v1.27.1 - Resolve CVE-2023-44487 (https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) - Update Docker images to resolve glibc vulnerabilities **Full Changelog**: https://github.com/envoyproxy/envoy/compare/v1.27.0...v1.27.1 Docker images: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.1 Docs: https://www.envoyproxy.io/docs/envoy/v1.27.1/ Release notes: https://www.envoyproxy.io/docs/envoy/v1.27.1/version_history/v1.27/v1.27.1 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.24.11.yaml | 19 ++++++++++++++++ changelogs/1.25.10.yaml | 34 +++++++++++++++++++++++++++++ changelogs/1.26.5.yaml | 24 ++++++++++++++++++++ changelogs/current.yaml | 17 ++++----------- docs/inventories/v1.24/objects.inv | Bin 141751 -> 141778 bytes docs/inventories/v1.25/objects.inv | Bin 149776 -> 149818 bytes docs/inventories/v1.26/objects.inv | Bin 153757 -> 153855 bytes docs/inventories/v1.27/objects.inv | Bin 0 -> 159699 bytes docs/versions.yaml | 7 +++--- 10 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 changelogs/1.24.11.yaml create mode 100644 changelogs/1.25.10.yaml create mode 100644 changelogs/1.26.5.yaml create mode 100644 docs/inventories/v1.27/objects.inv diff --git a/VERSION.txt b/VERSION.txt index 8ea5f6d8e0fc1..08002f86cc8d9 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.1-dev +1.27.1 diff --git a/changelogs/1.24.11.yaml b/changelogs/1.24.11.yaml new file mode 100644 index 0000000000000..c5c5e55329bb6 --- /dev/null +++ b/changelogs/1.24.11.yaml @@ -0,0 +1,19 @@ +date: October 10, 2023 + +behavior_changes: +- area: http + change: | + Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key + ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream + reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, + with the default value of 500, determines the number of requests received from a connection before the check for premature + resets is applied. The connection is disconnected if more than 50% of resets are premature. + Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables + this check. +- area: http + change: | + Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed + from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This + mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other + connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. + By default this limit is disabled. diff --git a/changelogs/1.25.10.yaml b/changelogs/1.25.10.yaml new file mode 100644 index 0000000000000..087ad323021df --- /dev/null +++ b/changelogs/1.25.10.yaml @@ -0,0 +1,34 @@ +date: October 10, 2023 + +behavior_changes: +- area: http + change: | + Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key + ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream + reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, + with the default value of 500, determines the number of requests received from a connection before the check for premature + resets is applied. The connection is disconnected if more than 50% of resets are premature. + Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables + this check. +- area: http + change: | + Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed + from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This + mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other + connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. + By default this limit is disabled. +- area: http + change: | + Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed + from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This + mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other + connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. + By default this limit is disabled. + +bug_fixes: +- area: tls + change: | + fixed a bug where handshake may fail when both private key provider and cert validation are set. +- area: docker/publishing + change: | + Update base images to resolve various glibc vulnerabilities. diff --git a/changelogs/1.26.5.yaml b/changelogs/1.26.5.yaml new file mode 100644 index 0000000000000..5f248d665be67 --- /dev/null +++ b/changelogs/1.26.5.yaml @@ -0,0 +1,24 @@ +date: October 10, 2023 + +behavior_changes: +- area: http + change: | + Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key + ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream + reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, + with the default value of 500, determines the number of requests received from a connection before the check for premature + resets is applied. The connection is disconnected if more than 50% of resets are premature. + Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables + this check. +- area: http + change: | + Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed + from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This + mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other + connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. + By default this limit is disabled. + +bug_fixes: +- area: tls + change: | + fixed a bug where handshake may fail when both private key provider and cert validation are set. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 86d0eac2339fc..a6ce592912136 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,7 +1,6 @@ -date: Pending +date: October 11, 2023 behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - area: http change: | Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key @@ -19,21 +18,13 @@ behavior_changes: connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. By default this limit is disabled. -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* - bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: connection limit change: | fixed a use-after-free bug in the connection limit filter. - area: tls change: | fixed a bug where handshake may fail when both private key provider and cert validation are set. - -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - -new_features: - -deprecated: +- area: docker/publishing + change: | + Update base images to resolve various glibc vulnerabilities. diff --git a/docs/inventories/v1.24/objects.inv b/docs/inventories/v1.24/objects.inv index 986bfcc604afd3819b4c175b4f0a1c79f516b25e..4a3daae93885ef97995b0e00002d10f73ee3d9fd 100644 GIT binary patch delta 2614 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 diff --git a/docs/inventories/v1.25/objects.inv b/docs/inventories/v1.25/objects.inv index dd24b33b0039320969c1992e3871c50be0772a06..5fd23cdd958d31ff25ddd01180d26b366cfead68 100644 GIT binary patch delta 4053 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 Ee@zn2dH?_b delta 4011 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 R1h**)eDrTV`u}b2hJ3j>hSUH6 diff --git a/docs/inventories/v1.26/objects.inv b/docs/inventories/v1.26/objects.inv index c0b726818bb06ec647523d62aebb4b154ff60eb4..999a8f0cdf60e88a481c7f317baf8ed7eada6523 100644 GIT binary patch delta 139839 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༘ra;2Y1JvHvWgfj_ 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

bYVdzT0_%^B96=zQ!EQQ} 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 DNV%2b delta 139770 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

LCh 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ν}9NfHdw-1y>Qm(|h0u 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≤NQBaI~MY( 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߯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≧E3MURPG(=EJ4Km;e4;;Xy44lNB7T|@!AtWNnmti2fNni@V#H#@RjeRx3m3$B7dUMf8q@8sZ@ka^h&fA)U$*UIbQ1*%uol0Hr~c1(vARvY28p@!tSE z>$v>=A5cV~G&Q9O5vrvGP#q?23;$o8#SJFn#4-AhHOW6~4a*c5!nh1NjMJwcUO2+~ zlIgt!RNz|xu;$?UJW!*N>=Ps0zV98^K<-^Pih3-mmFUa3z?NBp2%j%@@i7+TNW+>e&UrAx>2ArxQ zPU`#HCy}{N`|nS!PUz!VC(T(+`=6<*Oz8hKNJ{=P?Y}iOFs8q*nxye@`gJ@HJEcuO zoK`M0|D{a~h1%ueNM3F@_?Gy!(`}rNkmemD){4<%axB+3`zY66V5_&jys)S?9*N@M z0Ewq>uh$YMxvu$x-xDuEQ(bKq>0?ilf=L8FURUz4!`g(2i-XA|o-)MVkX$kl+jlrNAO3@dOUA)!mnrPulm8cybA_QrPdCNR)k%c)|!E z5!{DQN<_Ywcv1?;=XO^WXhM>Vc!(ihZ#6y1b>_zyc)7~R^$HHG_Zh`y#VH?yc%NZz z44yHeREt}&lS4fj@pouR8XB2v@ZY+Sof9;!BL_Rlc5BBVL@mTm6VfBQ+;`w|Q$v=0rS2h#=)JI)!(VgyB%~WdBTp|BtS0h)o ztW&7sWIe(DFm*k&f1&n%aY69K)2cn}uU$ulKEs6aRkgMQ)`(wM#ito>#WDY<0j!Do z%KseZRd)GZjfU-8HP6y_%aq!p;V-21f%ooIgZiBIS%v!i`RK%H?dqpz*`!wyYy6sl zOIb&$j?Wh$4hR!hLw$x*h+{W^!{>9Qw;#+Tdw>LBIDP%G6`EHSKt50)`6p0VF)d)pz%+@yG}kZl}pQed04e-6V^y0tftY zH#jc07?;BL+-lV?-!d;HDRkc1`>-Af(hZQpS?nESoY!a@{Y3DNTgt`2Wt!MXwifTJ zE!G!JRov)$SbacwO-%6>H*N2c79ZV8B1zB62$$B=3XfazTIbU7ZJisz75>obP`RKU zBQQV_Uf}|S7R-}AZq@%j<0F4NQ`6d@7pJf1=JOrDy}xB{d%3=hgHDQkNoMl3c>ny;X0Hk_AG+<)($YC|iC3N6;?`k(-|v^?$3+o#YqGdt$?sGM z&$Qqn^g7()tEM^H;8oRl>$z5gb>nse%$^6e%?3o4OjI5*804*py1GpXyn8SYdGlt= ztGW8JoBZfwP;TwA{g*dP!4&b4Fc9{G)PlcR%#0}UhQX#UQmVRa%<$14Wg^unG zH$X)DD(pcZxdoHqE^tcQABP#>JoKXbWA)&2R+g4BN0$|`A}qb=^1Ad}Hp7gp3ZLV(6Z}8zbAr8^jQ+3B-43U% oK9+I-%;)}BHgsnftLB2Z?O(~h^mUvw;ji`BFK>XbEn~d@0|X22mH+?% literal 0 HcmV?d00001 diff --git a/docs/versions.yaml b/docs/versions.yaml index 6fed90d94e793..0d4dfd4188eeb 100644 --- a/docs/versions.yaml +++ b/docs/versions.yaml @@ -17,6 +17,7 @@ "1.21": 1.21.6 "1.22": 1.22.11 "1.23": 1.23.12 -"1.24": 1.24.10 -"1.25": 1.25.9 -"1.26": 1.26.4 +"1.24": 1.24.11 +"1.25": 1.25.10 +"1.26": 1.26.5 +"1.27": 1.27.0 From 164e8cb30a300e67258d855c05b150536eac86f1 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 11 Oct 2023 11:39:37 +0000 Subject: [PATCH 104/274] repo: Dev v1.27.2 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.1.yaml | 30 ++++++++++++++++++++++++++++++ changelogs/current.yaml | 39 +++++++++++++-------------------------- 3 files changed, 44 insertions(+), 27 deletions(-) create mode 100644 changelogs/1.27.1.yaml diff --git a/VERSION.txt b/VERSION.txt index 08002f86cc8d9..5b9b4efa580b7 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.1 +1.27.2-dev diff --git a/changelogs/1.27.1.yaml b/changelogs/1.27.1.yaml new file mode 100644 index 0000000000000..a6ce592912136 --- /dev/null +++ b/changelogs/1.27.1.yaml @@ -0,0 +1,30 @@ +date: October 11, 2023 + +behavior_changes: +- area: http + change: | + Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key + ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream + reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, + with the default value of 500, determines the number of requests received from a connection before the check for premature + resets is applied. The connection is disconnected if more than 50% of resets are premature. + Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables + this check. +- area: http + change: | + Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed + from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This + mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other + connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. + By default this limit is disabled. + +bug_fixes: +- area: connection limit + change: | + fixed a use-after-free bug in the connection limit filter. +- area: tls + change: | + fixed a bug where handshake may fail when both private key provider and cert validation are set. +- area: docker/publishing + change: | + Update base images to resolve various glibc vulnerabilities. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a6ce592912136..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,30 +1,17 @@ -date: October 11, 2023 +date: Pending behavior_changes: -- area: http - change: | - Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key - ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream - reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, - with the default value of 500, determines the number of requests received from a connection before the check for premature - resets is applied. The connection is disconnected if more than 50% of resets are premature. - Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables - this check. -- area: http - change: | - Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed - from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This - mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other - connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. - By default this limit is disabled. +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +minor_behavior_changes: +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: connection limit - change: | - fixed a use-after-free bug in the connection limit filter. -- area: tls - change: | - fixed a bug where handshake may fail when both private key provider and cert validation are set. -- area: docker/publishing - change: | - Update base images to resolve various glibc vulnerabilities. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: + +deprecated: From 200a6dd733add75af4ad8787a6a0565e1c2523e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:06:34 +0100 Subject: [PATCH 105/274] build(deps): bump distroless/base-nossl-debian12 from `54f30b8` to `bad3646` in /ci (#30048) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `54f30b8` to `bad3646`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 026bd59f3a5f1..37857b0469fcd 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:54f30b80bb6a6b0185deff049fa35cc65d883b641ee655747db97ffd17432e00 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:bad36468fcd4e6a96d961eab19ec794be3f86d97da4b75730673d63d8cad336d AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From e477510333338384b7e0926ab374016b4760e25e Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 07:23:20 +0100 Subject: [PATCH 106/274] ci/release: Dont run release tests/prechecks during actual release (#30120) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .azure-pipelines/env.yml | 7 +++++++ .azure-pipelines/stage/linux.yml | 12 +++++++++++- .azure-pipelines/stage/prechecks.yml | 16 +++++++++++++++- .azure-pipelines/stages.yml | 5 +++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index c70c868965ba4..20d86c42b05ca 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -148,6 +148,8 @@ jobs: RUN_CHECKS=true RUN_DOCKER=true RUN_PACKAGING=true + RUN_RELEASE_TESTS=true + if [[ "$(changed.mobileOnly)" == true || "$(changed.docsOnly)" == true ]]; then RUN_BUILD=false RUN_DOCKER=false @@ -156,10 +158,14 @@ jobs: RUN_CHECKS=false RUN_PACKAGING=false fi + if [[ "$ISSTABLEBRANCH" == True && -n "$POSTSUBMIT" && "$(state.isDev)" == false ]]; then + RUN_RELEASE_TESTS=false + fi echo "##vso[task.setvariable variable=build;isoutput=true]${RUN_BUILD}" echo "##vso[task.setvariable variable=checks;isoutput=true]${RUN_CHECKS}" echo "##vso[task.setvariable variable=docker;isoutput=true]${RUN_DOCKER}" echo "##vso[task.setvariable variable=packaging;isoutput=true]${RUN_PACKAGING}" + echo "##vso[task.setvariable variable=releaseTests;isoutput=true]${RUN_RELEASE_TESTS}" displayName: "Decide what to run" workingDirectory: $(Build.SourcesDirectory) @@ -211,6 +217,7 @@ jobs: echo "env.outputs['run.build']: $(run.build)" echo "env.outputs['run.checks']: $(run.checks)" echo "env.outputs['run.packaging']: $(run.packaging)" + echo "env.outputs['run.releaseTests]: $(run.releaseTests)" echo echo "env.outputs['publish.githubRelease']: $(publish.githubRelease)" echo "env.outputs['publish.dockerhub]: $(publish.dockerhub)" diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index acf1cca81ba06..3946910246bb8 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -11,6 +11,10 @@ parameters: displayName: "Artifact suffix" type: string default: +- name: runTests + displayName: "Run release tests" + type: string + default: true - name: rbe displayName: "Use RBE" type: boolean @@ -44,11 +48,17 @@ jobs: eq(${{ parameters.runBuild }}, 'true')) timeoutInMinutes: ${{ parameters.timeoutBuild }} pool: ${{ parameters.pool }} + variables: + - name: ciTarget + ${{ if eq(parameters.runTests, false) }}: + value: release.server_only + ${{ if ne(parameters.runTests, false) }}: + value: release steps: - template: ../ci.yml parameters: managedAgent: ${{ parameters.managedAgent }} - ciTarget: release + ciTarget: $(ciTarget) cacheName: "release" bazelBuildExtraOptions: ${{ parameters.bazelBuildExtraOptions }} cacheTestResults: ${{ parameters.cacheTestResults }} diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 5e83ae21feadd..846c97c723f18 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -25,11 +25,25 @@ parameters: type: string default: "" +# Timeout/s +- name: timeoutPrechecks + type: number + # Building the rst from protos can take a while even with RBE if there is + # a lot of change - eg protobuf changed, or a primitve proto changed. + default: 40 + +- name: runPrechecks + displayName: "Run prechecks" + type: string + default: true jobs: - job: prechecks displayName: Precheck - timeoutInMinutes: 30 + timeoutInMinutes: ${{ parameters.timeoutPrechecks }} + condition: | + and(not(canceled()), + eq(${{ parameters.runPrechecks }}, 'true')) pool: vmImage: $(agentUbuntu) variables: diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index dad053a7af180..1b535277e2da1 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -60,6 +60,8 @@ stages: - stage: prechecks displayName: Prechecks dependsOn: ["env"] + variables: + RUN_PRECHECKS: $[stageDependencies.env.repo.outputs['run.releaseTests']] jobs: - template: stage/prechecks.yml parameters: @@ -70,17 +72,20 @@ stages: authGPGKey: $(MaintainerGPGKeySecureFileDownloadPath) authGPGPath: $(MaintainerGPGKey.secureFilePath) bucketGCP: $(GcsArtifactBucket) + runPrechecks: variables['RUN_PRECHECKS'] - stage: linux_x64 displayName: Linux x64 dependsOn: ${{ parameters.buildStageDeps }} variables: RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] + RUN_TESTS: $[stageDependencies.env.repo.outputs['run.releaseTests']] jobs: - template: stage/linux.yml parameters: cacheTestResults: ${{ parameters.cacheTestResults }} runBuild: variables['RUN_BUILD'] + runTests: variables['RUN_TESTS'] tmpfsDockerDisabled: true - stage: linux_arm64 From 9513891077530a169d1c4a2e7d360c3dc4747308 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 09:07:53 +0100 Subject: [PATCH 107/274] github/ci: Switch prechecks to pull_request_target and fix (#30126) Signed-off-by: Ryan Northey --- .github/actions/env/action.yml | 37 +++++++++++++++++++++------ .github/workflows/_env.yml | 14 ++-------- .github/workflows/envoy-prechecks.yml | 4 +-- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/.github/actions/env/action.yml b/.github/actions/env/action.yml index b5d44c56d24f6..d30cab498dc57 100644 --- a/.github/actions/env/action.yml +++ b/.github/actions/env/action.yml @@ -82,13 +82,16 @@ outputs: runs: using: composite steps: - - - if: ${{ inputs.check_mobile_run != 'false' }} - id: should_run - name: 'Check what to run' - run: ./mobile/tools/what_to_run.sh - shell: bash - + # Pull request/targets are _never_ trusted. + # + # For dispatch events, only specified bots are trusted. + # + # Commits to a branch are always trusted. + # + # If code is trusted its not allowed to check out any + # non-ancestor commit of a stable branch. + # + # Untrusted code can check out any commit. - id: trusted name: 'Check if its a trusted run' run: | @@ -109,7 +112,7 @@ runs: TRUSTED= fi fi - if [[ "${{ github.event_name }}" == "pull_request" ]]; then + if [[ "${{ github.event_name }}" == "pull_request" || "${{ github.event_name }}" == "pull_request_target" ]]; then echo "Not trusted pull_request event" TRUSTED= fi @@ -120,6 +123,24 @@ runs: fi shell: bash + # If we are in a trusted CI run then the provided commit _must_ be either the latest for + # this branch, or an antecdent. + - run: | + if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD &> /dev/null; then + echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 + exit 1 + fi + git checkout "${{ inputs.repo_ref }}" + if: ${{ steps.trusted.outputs.trusted == 'true' && inputs.repo_ref }} + name: Check provided ref + shell: bash + + - if: ${{ inputs.check_mobile_run != 'false' }} + id: should_run + name: 'Check what to run' + run: ./mobile/tools/what_to_run.sh + shell: bash + - id: context name: 'CI context' run: | diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 6abbdf220920c..72423bfc084b8 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -136,21 +136,11 @@ jobs: - uses: actions/checkout@v3 name: Checkout Envoy repository with: - fetch-depth: ${{ ! (inputs.check_mobile_run || inputs.trusted) && 1 || 0 }} + fetch-depth: ${{ ! (inputs.check_mobile_run || ! startsWith(github.event_name, 'pull_request')) && 1 || 0 }} # WARNING: This allows untrusted code to run!!! # If this is set, then anything before or after in the job should be regarded as # compromised. - ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} - # If we are in a trusted CI run then the provided commit _must_ be either the latest for - # this branch, or an antecdent. - - run: | - if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD; then - echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 - exit 1 - fi - git checkout "${{ inputs.repo_ref }}" - if: ${{ inputs.trusted }} - name: Check provided ref + ref: ${{ startsWith(github.event_name, 'pull_request') && inputs.repo_ref || '' }} - uses: ./.github/actions/env name: Generate environment variables diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 67fff9920a8e7..85d5a52e525aa 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -8,7 +8,7 @@ on: branches: - main - release/v* - pull_request: + pull_request_target: paths: - '**/requirements*.txt' - '**/go.mod' @@ -29,7 +29,7 @@ jobs: check_mobile_run: false permissions: contents: read - statuses: write + containers: read prechecks: needs: From f6d5bd98d4d6770451af05be7c908b69da6f8db7 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 09:55:19 +0100 Subject: [PATCH 108/274] github/prechecks: Minor fix for workflow (#30138) Signed-off-by: Ryan Northey --- .github/workflows/envoy-prechecks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 85d5a52e525aa..5e5c8d0555532 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -29,7 +29,7 @@ jobs: check_mobile_run: false permissions: contents: read - containers: read + packages: read prechecks: needs: From c8f625155402e36bedb8c73478999e25bacfa3f7 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 10:30:39 +0100 Subject: [PATCH 109/274] github/prechecks: Add back statuses cred for now (#30140) Signed-off-by: Ryan Northey --- .github/workflows/envoy-prechecks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 5e5c8d0555532..4a20abdc435fe 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -30,6 +30,8 @@ jobs: permissions: contents: read packages: read + # TODO(phlax): figure out how to remove this + statuses: write prechecks: needs: From 86f79e79e3b00e0db3794c555b42339d5ed06556 Mon Sep 17 00:00:00 2001 From: Florian Mutter Date: Fri, 13 Oct 2023 15:02:42 +0200 Subject: [PATCH 110/274] tracing: fix Datadog span name (#29932) (#30186) Backport c3646f994e0296ed44ecac2869fa65e7f58bf986 Additional testing: Also, I ran the sources/extensions/tracers/datadog/demo both with and without these changes. Verified that the produced span's "operation name" before these changes is not as desired. Verified that the produced span's "operation name" after these changes is as desired. Desired: "Operation name" is "envoy.proxy", and "resource name" is the operation_name passed to startSpan. Risk Level: low Testing: See the modified unit test. Docs Changes: n/a Release Notes: updated Signed-off-by: David Goffredo --- changelogs/current.yaml | 3 +++ source/extensions/tracers/datadog/tracer.cc | 7 ++++++- test/extensions/tracers/datadog/tracer_test.cc | 10 +++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..38c1f0f5611a9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,9 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: tracing + change: | + Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/tracers/datadog/tracer.cc b/source/extensions/tracers/datadog/tracer.cc index 180a2c5a3c36e..ac898f317b481 100644 --- a/source/extensions/tracers/datadog/tracer.cc +++ b/source/extensions/tracers/datadog/tracer.cc @@ -86,7 +86,12 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext // The OpenTracing implementation ignored the `Tracing::Config` argument, // so we will as well. datadog::tracing::SpanConfig span_config; - span_config.name = operation_name; + // The `operation_name` parameter to this function more closely matches + // Datadog's concept of "resource name." Datadog's "span name," or "operation + // name," instead describes the category of operation being performed, which + // here we hard-code. + span_config.name = "envoy.proxy"; + span_config.resource = operation_name; span_config.start = estimateTime(stream_info.startTime()); datadog::tracing::Tracer& tracer = *thread_local_tracer.tracer; diff --git a/test/extensions/tracers/datadog/tracer_test.cc b/test/extensions/tracers/datadog/tracer_test.cc index 1247b8a44f28a..8a4e276fd3daa 100644 --- a/test/extensions/tracers/datadog/tracer_test.cc +++ b/test/extensions/tracers/datadog/tracer_test.cc @@ -116,9 +116,13 @@ TEST_F(DatadogTracerTest, SpanProperties) { ASSERT_TRUE(maybe_dd_span); const datadog::tracing::Span& dd_span = *maybe_dd_span; - // Verify that the span has the expected service name, operation name, start - // time, and sampling decision. - EXPECT_EQ("do.thing", dd_span.name()); + // Verify that the span has the expected service name, operation name, + // resource name, start time, and sampling decision. + // Note that the `operation_name` we specified above becomes the + // `resource_name()` of the resulting Datadog span, while the Datadog span's + // `name()` (operation name) is hard-coded to "envoy.proxy." + EXPECT_EQ("envoy.proxy", dd_span.name()); + EXPECT_EQ("do.thing", dd_span.resource_name()); EXPECT_EQ("envoy", dd_span.service_name()); ASSERT_TRUE(dd_span.trace_segment().sampling_decision()); EXPECT_EQ(int(datadog::tracing::SamplingPriority::USER_DROP), From 7d27881305e78be191d107ab6cbd26953624fef1 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 13 Oct 2023 14:54:42 +0100 Subject: [PATCH 111/274] [bp/1.27] Backport stack (2) (#30173) publishing workflow fixes/cleanups enable engflow RBE Signed-off-by: Ryan Northey --- .bazelrc | 12 ++++++++++++ .github/workflows/_ci.yml | 18 ++++++++++++++++++ .github/workflows/_env.yml | 14 -------------- .github/workflows/_stage_publish.yml | 15 +++++++++++---- .github/workflows/envoy-prechecks.yml | 6 ++++-- .github/workflows/envoy-publish.yml | 17 ++++++++++++++--- bazel/engflow-bazel-credential-helper.sh | 8 ++++++++ tools/base/requirements.in | 2 +- tools/base/requirements.txt | 6 +++--- 9 files changed, 71 insertions(+), 27 deletions(-) create mode 100755 bazel/engflow-bazel-credential-helper.sh diff --git a/.bazelrc b/.bazelrc index a473dc4020da1..1a512e1879450 100644 --- a/.bazelrc +++ b/.bazelrc @@ -489,6 +489,18 @@ build:rbe-engflow --remote_timeout=3600s build:rbe-engflow --bes_timeout=3600s build:rbe-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --google_default_credentials=false +build:rbe-envoy-engflow --remote_cache=grpcs://morganite.cluster.engflow.com +build:rbe-envoy-engflow --remote_executor=grpcs://morganite.cluster.engflow.com +build:rbe-envoy-engflow --bes_backend=grpcs://morganite.cluster.engflow.com/ +build:rbe-envoy-engflow --bes_results_url=https://morganite.cluster.engflow.com/invocation/ +build:rbe-envoy-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:rbe-envoy-engflow --grpc_keepalive_time=30s +build:rbe-envoy-engflow --remote_timeout=3600s +build:rbe-envoy-engflow --bes_timeout=3600s +build:rbe-envoy-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 + ############################################################################# # debug: Various Bazel debugging flags ############################################################################# diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 60ba5b29cfbd2..8359114dac36e 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -2,6 +2,9 @@ name: Envoy CI on: workflow_call: + secrets: + app_id: + app_key: inputs: target: required: true @@ -96,6 +99,20 @@ jobs: with: image_tag: ${{ inputs.cache_build_image }} + - name: Check workflow context + id: context + run: | + if [[ "${{ inputs.trusted }}" != "false" && -n "${{ secrets.app_id }}" && -n "${{ secrets.app_key }}" ]]; then + echo "use_appauth=true" >> $GITHUB_OUTPUT + fi + - if: ${{ steps.context.outputs.use_appauth == 'true' }} + name: Fetch token for app auth + id: appauth + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.18 + with: + app_id: ${{ secrets.app_id }} + key: ${{ secrets.app_key }} + - uses: actions/checkout@v4 name: Checkout Envoy repository with: @@ -104,6 +121,7 @@ jobs: # If this is set, then anything before or after in the job should be regarded as # compromised. ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + token: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} # If we are in a trusted CI run then the provided commit _must_ be either the latest for # this branch, or an antecdent. diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 72423bfc084b8..f400c6ffb2538 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -27,10 +27,6 @@ on: type: boolean default: false - start_check_status: - type: string - default: - repo_ref: type: string default: @@ -173,16 +169,6 @@ jobs: echo "PR: https://github.com/envoyproxy/envoy/pull/${{ steps.env.outputs.repo_ref_pr_number }}" fi - check: - if: ${{ inputs.start_check_status && github.event_name != 'pull_request' }} - uses: ./.github/workflows/_workflow-start.yml - permissions: - contents: read - statuses: write - with: - workflow_name: ${{ inputs.start_check_status }} - sha: ${{ inputs.repo_ref_sha }} - cache: if: ${{ inputs.prime_build_image }} uses: ./.github/workflows/_cache_docker.yml diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 7765531c85eb1..ef3ba7f01e5d6 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -26,11 +26,13 @@ on: default: '' repo_ref: type: string - given_ref: + sha: type: string secrets: ENVOY_CI_SYNC_APP_ID: ENVOY_CI_SYNC_APP_KEY: + ENVOY_CI_PUBLISH_APP_ID: + ENVOY_CI_PUBLISH_APP_KEY: concurrency: group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-publish @@ -48,7 +50,7 @@ jobs: name: github run_pre: ./.github/actions/publish/release/setup run_pre_with: | - ref: ${{ inputs.given_ref }} + ref: ${{ inputs.repo_ref }} bucket: envoy-pr env: | export ENVOY_PUBLISH_DRY_RUN=1 @@ -68,7 +70,8 @@ jobs: if: ${{ inputs.trusted }} name: ${{ matrix.name || matrix.target }} permissions: - contents: write + contents: read + packages: read strategy: fail-fast: false matrix: @@ -77,9 +80,10 @@ jobs: name: github run_pre: ./.github/actions/publish/release/setup run_pre_with: | - ref: ${{ inputs.given_ref }} + ref: ${{ inputs.repo_ref }} bucket: envoy-postsubmit env: | + export ENVOY_COMMIT=${{ inputs.sha }} if [[ '${{ inputs.version_dev }}' == 'dev' ]]; then export ENVOY_PUBLISH_DRY_RUN=1 fi @@ -94,6 +98,9 @@ jobs: env: ${{ matrix.env }} trusted: true repo_ref: ${{ inputs.repo_ref }} + secrets: + app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} publish_docs: # For normal commits to Envoy main this will trigger an update in the website repo, diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 4a20abdc435fe..d12715c918cbe 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -30,8 +30,6 @@ jobs: permissions: contents: read packages: read - # TODO(phlax): figure out how to remove this - statuses: write prechecks: needs: @@ -45,8 +43,12 @@ jobs: managed: true uses: ./.github/workflows/_ci.yml name: CI ${{ matrix.target }} + permissions: + contents: read + packages: read with: target: ${{ matrix.target }} rbe: ${{ matrix.rbe }} + bazel_extra: '--config=rbe-envoy-engflow' managed: ${{ matrix.managed }} cache_build_image: ${{ needs.env.outputs.build_image_ubuntu }} diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 8c31140a54b83..3c99a8a7ac3cb 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -35,31 +35,42 @@ jobs: with: check_mobile_run: false prime_build_image: true - start_check_status: Verify/examples repo_ref: ${{ inputs.ref }} repo_ref_sha: ${{ inputs.sha }} repo_ref_name: ${{ inputs.head_ref }} + permissions: + contents: read + packages: read + check: + if: ${{ github.event_name != 'pull_request' }} + uses: ./.github/workflows/_workflow-start.yml permissions: contents: read statuses: write + with: + workflow_name: Verify/examples + sha: ${{ inputs.sha }} publish: needs: - env + - check uses: ./.github/workflows/_stage_publish.yml name: Publish ${{ needs.env.outputs.repo_ref_title }} with: build_image_ubuntu: ${{ needs.env.outputs.build_image_ubuntu }} trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} version_dev: ${{ needs.env.outputs.version_dev }} - given_ref: ${{ inputs.ref }} repo_ref: ${{ inputs.ref }} permissions: - contents: write + contents: read + packages: read secrets: ENVOY_CI_SYNC_APP_ID: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} ENVOY_CI_SYNC_APP_KEY: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} + ENVOY_CI_PUBLISH_APP_ID: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + ENVOY_CI_PUBLISH_APP_KEY: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} verify: uses: ./.github/workflows/_stage_verify.yml diff --git a/bazel/engflow-bazel-credential-helper.sh b/bazel/engflow-bazel-credential-helper.sh new file mode 100755 index 0000000000000..c6c1bd339b624 --- /dev/null +++ b/bazel/engflow-bazel-credential-helper.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Bazel expects the helper to read stdin. +# See https://github.com/bazelbuild/bazel/pull/17666 +cat /dev/stdin > /dev/null + +# `GITHUB_TOKEN` is provided as a secret. +echo "{\"headers\":{\"Authorization\":[\"Bearer ${GITHUB_TOKEN}\"]}}" diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 365fc84ff42f0..32c3fcbd48362 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -9,7 +9,7 @@ colorama coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 -envoy.base.utils>=0.4.12 +envoy.base.utils>=0.4.16 envoy.code.check>=0.5.8 envoy.dependency.check>=0.1.10 envoy.distribution.release>=0.0.9 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 0c25be0a5c190..23f3914623e85 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -448,9 +448,9 @@ docutils==0.19 \ # envoy-docs-sphinx-runner # sphinx # sphinx-rtd-theme -envoy-base-utils==0.4.12 \ - --hash=sha256:2bafcb6be3c1223968c9ee90b7a33d6d93aee16fe99bef37410ea9e4bc1a957f \ - --hash=sha256:b9b409abe83be6911aa03cfe635ed1e2e2b9e44e7c8595b2b7e5c7aae7bf70fc +envoy-base-utils==0.4.16 \ + --hash=sha256:b1ad6684dcf525651b01ded26ebb9f8ee5900089c786dd58b7a50ed663dafe3e \ + --hash=sha256:edaf42b3ae24aa34bb8bbb41b5e2eb1c5b230207cb00ff5a47cf259d31c6c628 # via # -r requirements.in # envoy-code-check From be35697efdc3c0a57cd9e47dcbe16caf98475327 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 16 Oct 2023 09:01:57 +0100 Subject: [PATCH 112/274] build/image: Bump to `fdd65c62` Signed-off-by: Ryan Northey --- .bazelrc | 4 ++-- .devcontainer/Dockerfile | 2 +- .github/workflows/_env.yml | 6 +++--- bazel/repository_locations.bzl | 6 +++--- ci/run_envoy_docker.sh | 4 ---- examples/shared/build/Dockerfile | 2 +- mobile/third_party/rbe_configs/config/BUILD | 4 ++-- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.bazelrc b/.bazelrc index 1a512e1879450..b9d84f1124677 100644 --- a/.bazelrc +++ b/.bazelrc @@ -339,7 +339,7 @@ build:compile-time-options --@envoy//source/extensions/filters/http/kill_request # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker @@ -499,7 +499,7 @@ build:rbe-envoy-engflow --grpc_keepalive_time=30s build:rbe-envoy-engflow --remote_timeout=3600s build:rbe-envoy-engflow --bes_timeout=3600s build:rbe-envoy-engflow --bes_upload_mode=fully_async -build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 ############################################################################# # debug: Various Bazel debugging flags diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3859774ea0b0d..066695f4922a2 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:94e5d873c145ae86f205117e76276161c9af4806@sha256:3c3d299423a878a219a333153726cddf7cc5e5ff30f596dc97bafba521f2f928 +FROM gcr.io/envoy-ci/envoy-build:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:2a473cd9808182735d54e03b158975389948b9559b8e8fc624cfafbaf7059e62 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index f400c6ffb2538..a469aa3156b14 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -12,13 +12,13 @@ on: default: envoyproxy/envoy-build-ubuntu build_image_sha: type: string - default: 8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 + default: 06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 build_image_mobile_sha: type: string - default: 0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95 + default: f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e build_image_tag: type: string - default: 94e5d873c145ae86f205117e76276161c9af4806 + default: fdd65c6270a8507a18d5acd6cf19a18cb695e4fa check_mobile_run: type: boolean diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 5c9fd142833f2..7f83d0cd5f5aa 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -102,11 +102,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "fc1ab3e96cf275ecaac913be2a22bce4a74b9272", - sha256 = "75fff0c28766ccb4e625244e35c950eb071d4bfb4a443b387140e1c037eeb6cc", + version = "f727ec142156c8076384a35c0e2d51da3c1d7813", + sha256 = "72510592f34f3fd6269c5fdd2286465a05ce6ca438ac1faebfdb88ed309fe9da", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2023-09-20", + release_date = "2023-10-16", use_category = ["build"], license = "Apache-2.0", license_url = "https://github.com/envoyproxy/envoy-build-tools/blob/{version}/LICENSE", diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index c0a36e08df527..8fdf249bcf8e1 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -19,10 +19,6 @@ export GOPROXY="${go_proxy:-}" if is_windows; then [[ -z "${IMAGE_NAME}" ]] && IMAGE_NAME="envoyproxy/envoy-build-windows2019" - # Container networking is unreliable in the most recently built images, pin Windows to a known - # good container. This can create a mismatch between the host environment, and the toolchain - # environment. - ENVOY_BUILD_SHA=41c5a05d708972d703661b702a63ef5060125c33 # TODO(sunjayBhatia): Currently ENVOY_DOCKER_OPTIONS is ignored on Windows because # CI sets it to a Linux-specific value. Undo this once https://github.com/envoyproxy/envoy/issues/13272 # is resolved. diff --git a/examples/shared/build/Dockerfile b/examples/shared/build/Dockerfile index 8fed6f57e6ce7..37357200b93bf 100644 --- a/examples/shared/build/Dockerfile +++ b/examples/shared/build/Dockerfile @@ -1,4 +1,4 @@ -FROM envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +FROM envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 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/mobile/third_party/rbe_configs/config/BUILD b/mobile/third_party/rbe_configs/config/BUILD index 558a7ce5f01b3..77ce2843c8acd 100644 --- a/mobile/third_party/rbe_configs/config/BUILD +++ b/mobile/third_party/rbe_configs/config/BUILD @@ -42,7 +42,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e", "OSFamily": "Linux", "Pool": "linux", }, @@ -57,7 +57,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e", "OSFamily": "Linux", "Pool": "linux", # Necessary to workaround https://github.com/google/sanitizers/issues/916, otherwise, dangling threads in the From f1c8165c54ea4b10bb3786698766b0abe9548248 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 16 Oct 2023 11:42:37 +0100 Subject: [PATCH 113/274] ci: Run linux/win/mac ci immediately on release branches Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 99f458d07abde..8b3a86af2bd09 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -65,6 +65,13 @@ stages: # Presubmit/default - ${{ if eq(variables.pipelineDefault, true) }}: - template: stages.yml + parameters: + buildStageDeps: + - env + macBuildStageDeps: + - env + windowsBuildStageDeps: + - env # Scheduled run anywhere - ${{ if eq(variables.pipelineScheduled, true) }}: From d3c640f454f52a043c76562ac2b9892e735737bd Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 16 Oct 2023 16:03:33 +0100 Subject: [PATCH 114/274] bazel/ci: Cleanup flags and env vars (#30211) Signed-off-by: Ryan Northey --- .azure-pipelines/ci.yml | 106 ++++++++++++++++----------- .azure-pipelines/stage/checks.yml | 8 -- .azure-pipelines/stage/macos.yml | 8 +- .azure-pipelines/stage/prechecks.yml | 15 +--- .azure-pipelines/stage/publish.yml | 24 +++--- .bazelrc | 2 + ci/build_setup.sh | 8 -- ci/run_envoy_docker.sh | 8 +- ci/setup_cache.sh | 33 +++------ ci/upload_gcs_artifact.sh | 20 ++--- 10 files changed, 100 insertions(+), 132 deletions(-) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index db6de8c31567a..5e8b380cafad0 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -176,31 +176,68 @@ steps: tmpfsDockerDisabled: "${{ parameters.tmpfsDockerDisabled }}" - script: | - if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then - unset GOOGLE_BES_PROJECT_ID + ENVOY_SHARED_TMP_DIR=/tmp/bazel-shared + mkdir -p "$ENVOY_SHARED_TMP_DIR" + BAZEL_BUILD_EXTRA_OPTIONS="${{ parameters.bazelBuildExtraOptions }}" + if [[ "${{ parameters.rbe }}" == "True" ]]; then + # mktemp will create a tempfile with u+rw permission minus umask, it will not be readable by all + # users by default. + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -p "${ENVOY_SHARED_TMP_DIR}" -t gcp_service_account.XXXXXX.json) + bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" + BAZEL_BUILD_EXTRA_OPTIONS+=" ${{ parameters.bazelConfigRBE }} --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" + ENVOY_RBE=1 + if [[ "${{ parameters.bazelUseBES }}" == "True" ]]; then + BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" + fi + else + echo "using local build cache." + # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` + TARGET_BRANCH=$(echo "${CI_TARGET_BRANCH}" | cut -d/ -f2-) + BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" + if [[ "$BRANCH_NAME" == "merge" ]]; then + # Manually run PR commit - there is no easy way of telling which branch + # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` + BRANCH_NAME=main + fi + BAZEL_REMOTE_INSTANCE="branch/${BRANCH_NAME}" + echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." + BAZEL_BUILD_EXTRA_OPTIONS+=" --config=ci --config=cache-local --remote_instance_name=${BAZEL_REMOTE_INSTANCE} --remote_timeout=600" fi - ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' - condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + if [[ "${{ parameters.cacheTestResults }}" != "True" ]]; then + VERSION_DEV="$(cut -d- -f2 "VERSION.txt")" + # Use uncached test results for non-release scheduledruns. + if [[ $VERSION_DEV == "dev" ]]; then + BAZEL_EXTRA_TEST_OPTIONS+=" --nocache_test_results" + fi + fi + # Any PR or CI run in envoy-presubmit uses the fake SCM hash + if [[ "${{ variables['Build.Reason'] }}" == "PullRequest" || "${{ variables['Build.DefinitionName'] }}" == 'envoy-presubmit' ]]; then + # sha1sum of `ENVOY_PULL_REQUEST` + BAZEL_FAKE_SCM_REVISION=e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 + fi + echo "##vso[task.setvariable variable=BAZEL_BUILD_EXTRA_OPTIONS]${BAZEL_BUILD_EXTRA_OPTIONS}" + echo "##vso[task.setvariable variable=BAZEL_EXTRA_TEST_OPTIONS]${BAZEL_EXTRA_TEST_OPTIONS}" + echo "##vso[task.setvariable variable=BAZEL_FAKE_SCM_REVISION]${BAZEL_FAKE_SCM_REVISION}" + echo "##vso[task.setvariable variable=BAZEL_STARTUP_EXTRA_OPTIONS]${{ parameters.bazelStartupExtraOptions }}" + echo "##vso[task.setvariable variable=CI_TARGET_BRANCH]${CI_TARGET_BRANCH}" + echo "##vso[task.setvariable variable=ENVOY_BUILD_FILTER_EXAMPLE]${{ parameters.envoyBuildFilterExample }}" + echo "##vso[task.setvariable variable=ENVOY_DOCKER_BUILD_DIR]$(Build.StagingDirectory)" + echo "##vso[task.setvariable variable=ENVOY_RBE]${ENVOY_RBE}" + echo "##vso[task.setvariable variable=ENVOY_SHARED_TMP_DIR]${ENVOY_SHARED_TMP_DIR}" + echo "##vso[task.setvariable variable=GCP_SERVICE_ACCOUNT_KEY_PATH]${GCP_SERVICE_ACCOUNT_KEY_PATH}" + echo "##vso[task.setvariable variable=GITHUB_TOKEN]${{ parameters.authGithub }}" workingDirectory: $(Build.SourcesDirectory) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - GITHUB_TOKEN: "${{ parameters.authGithub }}" - BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" - # Any PR or CI run in envoy-presubmit uses the fake SCM hash - ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: - # sha1sum of `ENVOY_PULL_REQUEST` - BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 - ${{ if parameters.rbe }}: - GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" - ${{ if eq(parameters.rbe, false) }}: - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: $(LocalBuildCache) + displayName: "CI env ${{ parameters.ciTarget }}" + +- script: ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' + condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + workingDirectory: $(Build.SourcesDirectory) + env: ${{ each var in parameters.env }}: ${{ var.key }}: ${{ var.value }} displayName: "Fetch assets (${{ parameters.ciTarget }})" @@ -231,34 +268,10 @@ steps: displayName: "Enable IPv6" condition: ${{ parameters.managedAgent }} -- script: | - if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then - unset GOOGLE_BES_PROJECT_ID - fi - ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' +- script: ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' workingDirectory: $(Build.SourcesDirectory) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_BUILD_FILTER_EXAMPLE: ${{ parameters.envoyBuildFilterExample }} - GITHUB_TOKEN: "${{ parameters.authGithub }}" - BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" - ${{ if ne(parameters['cacheTestResults'], true) }}: - BAZEL_NO_CACHE_TEST_RESULTS: 1 - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" - # Any PR or CI run in envoy-presubmit uses the fake SCM hash - ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: - # sha1sum of `ENVOY_PULL_REQUEST` - BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 - ${{ if parameters.rbe }}: - GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" - ${{ if eq(parameters.rbe, false) }}: - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: $(LocalBuildCache) ${{ each var in parameters.env }}: ${{ var.key }}: ${{ var.value }} displayName: "Run CI script ${{ parameters.ciTarget }}" @@ -296,6 +309,13 @@ steps: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} +- bash: | + if [[ -n "$GCP_SERVICE_ACCOUNT_KEY_PATH" && -e "$GCP_SERVICE_ACCOUNT_KEY_PATH" ]]; then + echo "Removed key: ${GCP_SERVICE_ACCOUNT_KEY_PATH}" + rm -rf "$GCP_SERVICE_ACCOUNT_KEY_PATH" + fi + condition: not(canceled()) + - script: | set -e sudo .azure-pipelines/docker/save_cache.sh "$(Build.StagingDirectory)" /mnt/cache/all true true diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index f39eec4787d9c..8c03249e227b3 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -101,15 +101,7 @@ jobs: displayName: "Upload $(CI_TARGET) Report to GCS" condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'coverage'), eq(variables['CI_TARGET'], 'fuzz_coverage'))) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(Build.SourceBranchName)" - job: complete displayName: "Checks complete" diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml index 6089bd89ee896..4b7f99e718d31 100644 --- a/.azure-pipelines/stage/macos.yml +++ b/.azure-pipelines/stage/macos.yml @@ -27,9 +27,11 @@ jobs: - script: ./ci/mac_ci_steps.sh displayName: "Run Mac CI" env: - BAZEL_BUILD_EXTRA_OPTIONS: "--remote_download_toplevel --flaky_test_attempts=2" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: >- + --remote_download_toplevel + --flaky_test_attempts=2 + --remote_cache=grpcs://remotebuildexecution.googleapis.com + --remote_instance_name=projects/envoy-ci/instances/default_instance GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} ENVOY_RBE: 1 diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 846c97c723f18..b699a960eacec 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -99,7 +99,7 @@ jobs: authGPGKey: ${{ parameters.authGPGKey }} # GNUPGHOME inside the container pathGPGConfiguredHome: /build/.gnupg - pathGPGHome: /tmp/envoy-docker-build/.gnupg + pathGPGHome: $(Build.StagingDirectory)/.gnupg - bash: | set -e ci/run_envoy_docker.sh " @@ -107,7 +107,7 @@ jobs: && gpg --clearsign /tmp/authority \ && cat /tmp/authority.asc \ && gpg --verify /tmp/authority.asc" - rm -rf /tmp/envoy-docker-build/.gnupg + rm -rf $(Build.StagingDirectory)/.gnupg displayName: "Ensure container CI can sign with GPG" condition: and(not(canceled()), eq(variables['CI_TARGET'], 'docs')) @@ -129,10 +129,6 @@ jobs: ci/run_envoy_docker.sh 'ci/do_ci.sh dockerhub-readme' displayName: "Dockerhub publishing test" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') @@ -155,14 +151,9 @@ jobs: condition: and(failed(), eq(variables['CI_TARGET'], 'check_and_fix_proto_format')) # Publish docs - - script: | - ci/run_envoy_docker.sh 'ci/do_ci.sh docs-upload' + - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs-upload' displayName: "Upload Docs to GCS" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 1eb1d57584cbe..b361552e4e205 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -123,10 +123,6 @@ jobs: eq(${{ parameters.publishDockerhub }}, 'true')) displayName: "Publish Dockerhub description and README" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} @@ -277,6 +273,16 @@ jobs: pool: vmImage: $(agentUbuntu) steps: + - task: DownloadSecureFile@1 + name: WorkflowTriggerKey + displayName: 'Download workflow trigger key' + inputs: + secureFile: '${{ parameters.authGithubWorkflow }}' + - bash: | + set -e + KEY="$(cat $(WorkflowTriggerKey.secureFilePath) | base64 -w0)" + echo "##vso[task.setvariable variable=value;isoutput=true]$KEY" + name: key - template: ../ci.yml parameters: ciTarget: verify.trigger @@ -310,13 +316,3 @@ jobs: mkdir -p $(Build.StagingDirectory)/release.signed mv release.signed.tar.zst $(Build.StagingDirectory)/release.signed displayName: Fetch signed release - - task: DownloadSecureFile@1 - name: WorkflowTriggerKey - displayName: 'Download workflow trigger key' - inputs: - secureFile: '${{ parameters.authGithubWorkflow }}' - - bash: | - set -e - KEY="$(cat $(WorkflowTriggerKey.secureFilePath) | base64 -w0)" - echo "##vso[task.setvariable variable=value;isoutput=true]$KEY" - name: key diff --git a/.bazelrc b/.bazelrc index b9d84f1124677..46caa84309f85 100644 --- a/.bazelrc +++ b/.bazelrc @@ -221,6 +221,8 @@ build:fuzz-coverage --config=plain-fuzzer build:fuzz-coverage --run_under=@envoy//bazel/coverage:fuzz_coverage_wrapper.sh build:fuzz-coverage --test_tag_filters=-nocoverage +build:cache-local --remote_cache=grpc://localhost:9092 + # Remote execution: https://docs.bazel.build/versions/master/remote-execution.html build:rbe-toolchain --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 2d54fa423bc35..00f4c2c752278 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -119,14 +119,6 @@ bazel () { export _bazel export -f bazel -if [[ -n "$BAZEL_NO_CACHE_TEST_RESULTS" ]]; then - VERSION_DEV="$(cut -d- -f2 "${ENVOY_SRCDIR}/VERSION.txt")" - # Use uncached test results for non-release commits to a branch. - if [[ $VERSION_DEV == "dev" ]]; then - BAZEL_EXTRA_TEST_OPTIONS+=("--nocache_test_results") - fi -fi - # Use https://docs.bazel.build/versions/master/command-line-reference.html#flag--experimental_repository_cache_hardlinks # to save disk space. BAZEL_GLOBAL_OPTIONS=( diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 8fdf249bcf8e1..36c438bf01132 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -86,13 +86,13 @@ VOLUMES=( -v "${ENVOY_DOCKER_BUILD_DIR}":"${BUILD_DIR_MOUNT_DEST}" -v "${SOURCE_DIR}":"${SOURCE_DIR_MOUNT_DEST}") -if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" ]]; then +if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" || -n "$ENVOY_SHARED_TMP_DIR" ]]; then # Create a "shared" directory that has the same path in/outside the container # This allows the host docker engine to see artefacts using a temporary path created inside the container, # at the same path. # For example, a directory created with `mktemp -d --tmpdir /tmp/bazel-shared` can be mounted as a volume # from within the build container. - SHARED_TMP_DIR=/tmp/bazel-shared + SHARED_TMP_DIR="${ENVOY_SHARED_TMP_DIR:-/tmp/bazel-shared}" mkdir -p "${SHARED_TMP_DIR}" chmod +rwx "${SHARED_TMP_DIR}" VOLUMES+=(-v "${SHARED_TMP_DIR}":"${SHARED_TMP_DIR}") @@ -102,7 +102,6 @@ if [[ -n "${ENVOY_DOCKER_PULL}" ]]; then time docker pull "${ENVOY_BUILD_IMAGE}" fi - # Since we specify an explicit hash, docker-run will pull from the remote repo if missing. docker run --rm \ "${ENVOY_DOCKER_OPTIONS[@]}" \ @@ -124,10 +123,9 @@ docker run --rm \ -e DOCKERHUB_PASSWORD \ -e ENVOY_STDLIB \ -e BUILD_REASON \ - -e BAZEL_NO_CACHE_TEST_RESULTS \ -e BAZEL_REMOTE_INSTANCE \ - -e GOOGLE_BES_PROJECT_ID \ -e GCP_SERVICE_ACCOUNT_KEY \ + -e GCP_SERVICE_ACCOUNT_KEY_PATH \ -e NUM_CPUS \ -e ENVOY_BRANCH \ -e ENVOY_RBE \ diff --git a/ci/setup_cache.sh b/ci/setup_cache.sh index da0b189dd4a88..ca910ec1a090c 100755 --- a/ci/setup_cache.sh +++ b/ci/setup_cache.sh @@ -14,37 +14,22 @@ if [[ -n "${GCP_SERVICE_ACCOUNT_KEY:0:1}" ]]; then trap gcp_service_account_cleanup EXIT + echo "Setting GCP_SERVICE_ACCOUNT_KEY is deprecated, please place your decoded GCP key in " \ + "an exported/shared tmp directory and add it to BAZEL_BUILD_EXTRA_OPTIONS, eg: " >&2 + # shellcheck disable=SC2086 + echo "$ export ENVOY_SHARED_TMP_DIR=/tmp/envoy-shared" \ + "$ ENVOY_RBE_KEY_PATH=$(mktemp -p \"${ENVOY_SHARED_TMP_DIR}\" -t gcp_service_account.XXXXXX.json)" \ + "$ bash -c 'echo \"$(GcpServiceAccountKey)\"' | base64 --decode > \"${ENVOY_RBE_KEY_PATH}\"" \ + "$ export BAZEL_BUILD_EXTRA_OPTIONS+=\" --google_credentials=${ENVOY_RBE_KEY_PATH}\"" >&2 bash -c 'echo "${GCP_SERVICE_ACCOUNT_KEY}"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_FILE}" - export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_FILE}" - - if [[ -n "${GOOGLE_BES_PROJECT_ID}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" - fi - fi if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then + echo "Setting BAZEL_REMOTE_CACHE is deprecated, please use BAZEL_BUILD_EXTRA_OPTIONS " \ + "or use a user.bazelrc config " >&2 export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_cache=${BAZEL_REMOTE_CACHE}" echo "Set up bazel remote read/write cache at ${BAZEL_REMOTE_CACHE}." - - if [[ -z "${ENVOY_RBE}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_timeout=600" - echo "using local build cache." - # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` - TARGET_BRANCH="${CI_TARGET_BRANCH}" - if [[ "$TARGET_BRANCH" =~ ^origin/ ]]; then - TARGET_BRANCH=$(echo "$TARGET_BRANCH" | cut -d/ -f2-) - fi - BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" - if [[ "$BRANCH_NAME" == "merge" ]]; then - # Manually run PR commit - there is no easy way of telling which branch - # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` - BRANCH_NAME=main - fi - BAZEL_REMOTE_INSTANCE="branch/${BRANCH_NAME}" - fi - if [[ -n "${BAZEL_REMOTE_INSTANCE}" ]]; then export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_instance_name=${BAZEL_REMOTE_INSTANCE}" echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." diff --git a/ci/upload_gcs_artifact.sh b/ci/upload_gcs_artifact.sh index 6367184a408b5..339a4e98dc4dc 100755 --- a/ci/upload_gcs_artifact.sh +++ b/ci/upload_gcs_artifact.sh @@ -7,27 +7,17 @@ if [[ -z "${GCS_ARTIFACT_BUCKET}" ]]; then exit 1 fi -if [[ -z "${GCP_SERVICE_ACCOUNT_KEY}" ]]; then - echo "GCP key is not set, not uploading artifacts." - exit 1 -fi - read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTION_LIST:-}" read -ra BAZEL_BUILD_OPTIONS <<< "${BAZEL_BUILD_OPTION_LIST:-}" -remove_key () { - rm -rf "$KEYFILE" -} - -trap remove_key EXIT - -# Fail when service account key is not specified -KEYFILE="$(mktemp)" -bash -c 'echo ${GCP_SERVICE_ACCOUNT_KEY}' | base64 --decode > "$KEYFILE" +if [[ ! -s "${GCP_SERVICE_ACCOUNT_KEY_PATH}" ]]; then + echo "GCP key is not set, not uploading artifacts." + exit 1 +fi cat < ~/.boto [Credentials] -gs_service_key_file=${KEYFILE} +gs_service_key_file=${GCP_SERVICE_ACCOUNT_KEY_PATH} EOF SOURCE_DIRECTORY="$1" From 9f3e8a37d0d919938ac1ce930800dc62cab0129f Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 16 Oct 2023 10:10:02 -0400 Subject: [PATCH 115/274] HCM: Make reverse iteration resilient to element deletion (#30158) --------- Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 4 + source/common/http/conn_manager_impl.cc | 26 ++++-- test/common/http/http2/http2_frame.cc | 10 +++ test/common/http/http2/http2_frame.h | 3 +- test/integration/BUILD | 1 + .../local_reply_during_decoding_filter.cc | 6 +- .../multiplexed_integration_test.cc | 90 +++++++++++++++++++ 7 files changed, 130 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 38c1f0f5611a9..0ae4cf227f7e7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -11,6 +11,10 @@ bug_fixes: - area: tracing change: | Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index bf04397084134..f567d659f7022 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -2195,6 +2196,8 @@ bool ConnectionManagerImpl::ActiveStream::onDeferredRequestProcessing() { if (end_stream) { return true; } + // Filter manager will return early from decodeData and decodeTrailers if + // request has completed. if (deferred_data_ != nullptr) { end_stream = state_.deferred_end_stream_ && request_trailers_ == nullptr; filter_manager_.decodeData(*deferred_data_, end_stream); @@ -2224,19 +2227,26 @@ bool ConnectionManagerImpl::shouldDeferRequestProxyingToNextIoCycle() { } void ConnectionManagerImpl::onDeferredRequestProcessing() { + if (streams_.empty()) { + return; + } requests_during_dispatch_count_ = 1; // 1 stream is always let through // Streams are inserted at the head of the list. As such process deferred - // streams at the back of the list first. - for (auto reverse_iter = streams_.rbegin(); reverse_iter != streams_.rend();) { - auto& stream_ptr = *reverse_iter; - // Move the iterator to the next item in case the `onDeferredRequestProcessing` call removes the - // stream from the list. - ++reverse_iter; - bool was_deferred = stream_ptr->onDeferredRequestProcessing(); + // streams in the reverse order. + auto reverse_iter = std::prev(streams_.end()); + bool at_first_element = false; + do { + at_first_element = reverse_iter == streams_.begin(); + // Move the iterator to the previous item in case the `onDeferredRequestProcessing` call removes + // the stream from the list. + auto previous_element = std::prev(reverse_iter); + bool was_deferred = (*reverse_iter)->onDeferredRequestProcessing(); if (was_deferred && shouldDeferRequestProxyingToNextIoCycle()) { break; } - } + reverse_iter = previous_element; + // TODO(yanavlasov): see if `rend` can be used. + } while (!at_first_element); } } // namespace Http diff --git a/test/common/http/http2/http2_frame.cc b/test/common/http/http2/http2_frame.cc index 46ba3751ba242..319b3fc87380f 100644 --- a/test/common/http/http2/http2_frame.cc +++ b/test/common/http/http2/http2_frame.cc @@ -51,12 +51,22 @@ Http2Frame::ResponseStatus Http2Frame::responseStatus() const { return ResponseStatus::Ok; case StaticHeaderIndex::Status404: return ResponseStatus::NotFound; + case StaticHeaderIndex::Status500: + return ResponseStatus::InternalServerError; default: break; } return ResponseStatus::Unknown; } +uint32_t Http2Frame::streamId() const { + if (empty() || size() <= HeaderSize) { + return 0; + } + return (uint32_t(data_[5]) << 24) + (uint32_t(data_[6]) << 16) + (uint32_t(data_[7]) << 8) + + uint32_t(data_[8]); +} + void Http2Frame::buildHeader(Type type, uint32_t payload_size, uint8_t flags, uint32_t stream_id) { data_.assign(payload_size + HeaderSize, 0); setPayloadSize(payload_size); diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index 5df3375c16616..6349f5d94d845 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -121,7 +121,7 @@ class Http2Frame { Http11Required }; - enum class ResponseStatus { Unknown, Ok, NotFound }; + enum class ResponseStatus { Unknown, Ok, NotFound, InternalServerError }; struct Header { Header(absl::string_view key, absl::string_view value) : key_(key), value_(value) {} @@ -226,6 +226,7 @@ class Http2Frame { return false; } ResponseStatus responseStatus() const; + uint32_t streamId() const; // Copy HTTP2 header. The `header` parameter must at least be HeaderSize long. // Allocates payload size based on the value in the header. diff --git a/test/integration/BUILD b/test/integration/BUILD index 260c51d042c7e..f2b7b874f6bbb 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -504,6 +504,7 @@ envoy_cc_test( "//source/extensions/filters/http/buffer:config", "//source/extensions/load_balancing_policies/ring_hash:config", "//test/integration/filters:encode1xx_local_reply_config_lib", + "//test/integration/filters:local_reply_during_decoding_filter_lib", "//test/integration/filters:metadata_stop_all_filter_config_lib", "//test/integration/filters:on_local_reply_filter_config_lib", "//test/integration/filters:request_metadata_filter_config_lib", diff --git a/test/integration/filters/local_reply_during_decoding_filter.cc b/test/integration/filters/local_reply_during_decoding_filter.cc index 69d822e8dcca2..f29beb5723655 100644 --- a/test/integration/filters/local_reply_during_decoding_filter.cc +++ b/test/integration/filters/local_reply_during_decoding_filter.cc @@ -15,7 +15,11 @@ class LocalReplyDuringDecode : public Http::PassThroughFilter { public: constexpr static char name[] = "local-reply-during-decode"; - Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override { + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& request_headers, bool) override { + auto result = request_headers.get(Http::LowerCaseString("skip-local-reply")); + if (!result.empty() && result[0]->value() == "true") { + return Http::FilterHeadersStatus::Continue; + } decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError, "", nullptr, absl::nullopt, ""); return Http::FilterHeadersStatus::StopIteration; diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index edce9a1838d98..3aca9af441b40 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2183,6 +2183,43 @@ TEST_P(Http2FrameIntegrationTest, MultipleRequests) { tcp_client_->close(); } +// Validate the request completion during processing of deferred list works. +TEST_P(Http2FrameIntegrationTest, MultipleRequestsDecodeHeadersEndsRequest) { + const int kRequestsSentPerIOCycle = 20; + // The local-reply-during-decode will call sendLocalReply, completing them + // when processing headers. This will cause the ConnectionManagerImpl::ActiveRequest + // object to be removed from the streams_ list during the onDeferredRequestProcessing call. + config_helper_.addFilter("{ name: local-reply-during-decode }"); + // Process more than 1 deferred request at a time to validate the removal of elements from + // the list does not break reverse iteration. + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "3"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = + Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a", + Http2Frame::DataFlags::EndStream); + absl::StrAppend(&buffer, std::string(data)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + // The local-reply-during-decode filter sends 500 status to the client + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::InternalServerError, frame.responseStatus()); + } + tcp_client_->close(); +} + TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailers) { const int kRequestsSentPerIOCycle = 20; autonomous_upstream_ = true; @@ -2222,6 +2259,59 @@ TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailers) { tcp_client_->close(); } +// Validate the request completion during processing of headers in the deferred requests, +// is ok, when deferred data and trailers are also present. +TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailersDecodeHeadersEndsRequest) { + const int kRequestsSentPerIOCycle = 20; + autonomous_upstream_ = true; + config_helper_.addFilter("{ name: local-reply-during-decode }"); + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "6"); + beginSession(); + + std::string buffer; + // Make every 4th request to be reset by the local-reply-during-decode filter, this will give a + // good distribution of removed requests from the deferred sequence. + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, + {"no_trailers", "1"}, + {"skip-local-reply", i % 4 ? "true" : "false"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a"); + absl::StrAppend(&buffer, std::string(data)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto trailers = Http2Frame::makeEmptyHeadersFrame( + Http2Frame::makeClientStreamId(i), + static_cast(Http::Http2::orFlags( + Http2Frame::HeadersFlags::EndStream, Http2Frame::HeadersFlags::EndHeaders))); + trailers.appendHeaderWithoutIndexing({"k", "v"}); + trailers.adjustPayloadSize(); + absl::StrAppend(&buffer, std::string(trailers)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + uint32_t stream_id = frame.streamId(); + // Client stream indexes are multiples of 2 starting at 1 + if ((stream_id / 2) % 4) { + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()) + << " for stream=" << stream_id; + } else { + EXPECT_EQ(Http2Frame::ResponseStatus::InternalServerError, frame.responseStatus()) + << " for stream=" << stream_id; + } + } + tcp_client_->close(); +} + TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequestsFollowedByReset) { // This number of requests stays below premature reset detection. const int kRequestsSentPerIOCycle = 20; From 5ad31d0e2e9fc27485088f032a7406f1590f4a0f Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 17 Oct 2023 07:56:12 +0100 Subject: [PATCH 116/274] build/image: Fix sha (#30257) Signed-off-by: Ryan Northey --- .bazelrc | 4 ++-- .github/workflows/_env.yml | 2 +- examples/shared/build/Dockerfile | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index 46caa84309f85..6b080508f38cf 100644 --- a/.bazelrc +++ b/.bazelrc @@ -341,7 +341,7 @@ build:compile-time-options --@envoy//source/extensions/filters/http/kill_request # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker @@ -501,7 +501,7 @@ build:rbe-envoy-engflow --grpc_keepalive_time=30s build:rbe-envoy-engflow --remote_timeout=3600s build:rbe-envoy-engflow --bes_timeout=3600s build:rbe-envoy-engflow --bes_upload_mode=fully_async -build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 +build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e ############################################################################# # debug: Various Bazel debugging flags diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index a469aa3156b14..dd0ca05d204da 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -12,7 +12,7 @@ on: default: envoyproxy/envoy-build-ubuntu build_image_sha: type: string - default: 06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 + default: 3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e build_image_mobile_sha: type: string default: f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e diff --git a/examples/shared/build/Dockerfile b/examples/shared/build/Dockerfile index 37357200b93bf..1b0994c43501a 100644 --- a/examples/shared/build/Dockerfile +++ b/examples/shared/build/Dockerfile @@ -1,4 +1,4 @@ -FROM envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 +FROM envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ From fc387671393cd13f568b2c895d3091ef5240173e Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Tue, 17 Oct 2023 12:30:18 +0100 Subject: [PATCH 117/274] ci/github: Fix app auth publishing token (#30262) Signed-off-by: Ryan Northey --- .github/workflows/_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 8359114dac36e..8e857fab7c377 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -166,7 +166,7 @@ jobs: command_prefix: ${{ inputs.command_prefix }} command_ci: ${{ inputs.command_ci }} env: ${{ inputs.env }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} From ae07f9a11715245f7d25d2a13699c260c2ae8ebb Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 16 Oct 2023 20:19:55 +0000 Subject: [PATCH 118/274] repo: Release v1.27.2 Summary of changes: * Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, can cause a crash. **Docker images**: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.2 **Docs**: https://www.envoyproxy.io/docs/envoy/v1.27.2/ **Release notes**: https://www.envoyproxy.io/docs/envoy/v1.27.2/version_history/v1.27/v1.27.2 **Full changelog**: https://github.com/envoyproxy/envoy/compare/v1.27.1...v1.27.2 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.24.12.yaml | 7 +++++++ changelogs/1.25.11.yaml | 7 +++++++ changelogs/1.26.6.yaml | 10 ++++++++++ changelogs/current.yaml | 16 +--------------- docs/inventories/v1.24/objects.inv | Bin 141778 -> 141802 bytes docs/inventories/v1.25/objects.inv | Bin 149818 -> 149859 bytes docs/inventories/v1.26/objects.inv | Bin 153855 -> 153917 bytes docs/inventories/v1.27/objects.inv | Bin 159699 -> 159791 bytes docs/versions.yaml | 8 ++++---- 10 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 changelogs/1.24.12.yaml create mode 100644 changelogs/1.25.11.yaml create mode 100644 changelogs/1.26.6.yaml diff --git a/VERSION.txt b/VERSION.txt index 5b9b4efa580b7..457f0385465cb 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.2-dev +1.27.2 diff --git a/changelogs/1.24.12.yaml b/changelogs/1.24.12.yaml new file mode 100644 index 0000000000000..4beae10fad69d --- /dev/null +++ b/changelogs/1.24.12.yaml @@ -0,0 +1,7 @@ +date: October 16, 2023 + +bug_fixes: +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/1.25.11.yaml b/changelogs/1.25.11.yaml new file mode 100644 index 0000000000000..4beae10fad69d --- /dev/null +++ b/changelogs/1.25.11.yaml @@ -0,0 +1,7 @@ +date: October 16, 2023 + +bug_fixes: +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/1.26.6.yaml b/changelogs/1.26.6.yaml new file mode 100644 index 0000000000000..a5caeaa72fa50 --- /dev/null +++ b/changelogs/1.26.6.yaml @@ -0,0 +1,10 @@ +date: October 17, 2023 + +bug_fixes: +- area: tracing + change: | + Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0ae4cf227f7e7..91d3633c01549 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,13 +1,6 @@ -date: Pending - -behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* +date: October 16, 2023 bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: tracing change: | Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. @@ -15,10 +8,3 @@ bug_fixes: change: | Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, can cause a crash. - -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - -new_features: - -deprecated: diff --git a/docs/inventories/v1.24/objects.inv b/docs/inventories/v1.24/objects.inv index 4a3daae93885ef97995b0e00002d10f73ee3d9fd..dc548419533ff5ce944145fb3f3dbf158c33a663 100644 GIT binary patch delta 3224 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 Gc_@NC@dW+= diff --git a/docs/inventories/v1.25/objects.inv b/docs/inventories/v1.25/objects.inv index 5fd23cdd958d31ff25ddd01180d26b366cfead68..5e31b50384a4d5398afa903aba066019813d6346 100644 GIT binary patch delta 4077 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 delta 4053 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 Ha+ne#o}$gr diff --git a/docs/inventories/v1.26/objects.inv b/docs/inventories/v1.26/objects.inv index 999a8f0cdf60e88a481c7f317baf8ed7eada6523..477894d1f6bccd82887a88b242a0da1ebc5bf066 100644 GIT binary patch delta 8294 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 M&Aun;(^b diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index bedf4e67eb50b2c6d9b59f636aa6f925418458bf..4ebd9397af40885f3f5a5dc2e390d930f9cbc5da 100644 GIT binary patch delta 3851 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 N{bL{c{{v0pbnY6r3pM}% delta 3759 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 Date: Tue, 17 Oct 2023 13:45:49 +0100 Subject: [PATCH 119/274] repo: Dev v1.27.3 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.2.yaml | 10 ++++++++++ changelogs/current.yaml | 23 +++++++++++++++-------- 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 changelogs/1.27.2.yaml diff --git a/VERSION.txt b/VERSION.txt index 457f0385465cb..a759f59ddc72e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.2 +1.27.3-dev diff --git a/changelogs/1.27.2.yaml b/changelogs/1.27.2.yaml new file mode 100644 index 0000000000000..91d3633c01549 --- /dev/null +++ b/changelogs/1.27.2.yaml @@ -0,0 +1,10 @@ +date: October 16, 2023 + +bug_fixes: +- area: tracing + change: | + Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 91d3633c01549..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,10 +1,17 @@ -date: October 16, 2023 +date: Pending + +behavior_changes: +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +minor_behavior_changes: +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: tracing - change: | - Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. -- area: http - change: | - Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, - can cause a crash. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: + +deprecated: From 0ea97ffecf6e43884296ad2e0b4e947dd282e975 Mon Sep 17 00:00:00 2001 From: Keith Mattix II Date: Wed, 18 Oct 2023 08:06:55 -0500 Subject: [PATCH 120/274] Add release target to copy binary after build server_only (#30204) Signed-off-by: Keith Mattix II --- ci/README.md | 7 +++++++ ci/do_ci.sh | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/ci/README.md b/ci/README.md index 2282bc57ca562..7bfec04f0fda0 100644 --- a/ci/README.md +++ b/ci/README.md @@ -106,6 +106,13 @@ For a release version of the Envoy binary you can run: The build artifact can be found in `/tmp/envoy-docker-build/envoy/source/exe/envoy` (or wherever `$ENVOY_DOCKER_BUILD_DIR` points). +To enable the previous behavior of the `release.server_only` target where the final binary was copied to a tar.gz file +(e.g. envoy-binary.tar.gz), you can run: + + ```bash + ./ci/run_envoy_docker.sh './ci/do_ci.sh release.server_only.binary + ``` + For a debug version of the Envoy binary you can run: ```bash diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 9cd420d38f6d3..bec54b718462a 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -858,6 +858,12 @@ case $CI_TARGET in echo "Release files created in ${ENVOY_BINARY_DIR}" ;; + release.server_only.binary) + setup_clang_toolchain + echo "bazel release build..." + bazel_envoy_binary_build release + ;; + release.signed) echo "Signing binary packages..." setup_clang_toolchain From 65c8901709834a675581bade33ff80afc2bdebf4 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 19 Oct 2023 13:01:53 +0100 Subject: [PATCH 121/274] ci/rbe: Only enable BES where project is set (#30318) Signed-off-by: Ryan Northey --- .azure-pipelines/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index 5e8b380cafad0..0103648c1ad22 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -186,7 +186,7 @@ steps: bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" BAZEL_BUILD_EXTRA_OPTIONS+=" ${{ parameters.bazelConfigRBE }} --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" ENVOY_RBE=1 - if [[ "${{ parameters.bazelUseBES }}" == "True" ]]; then + if [[ "${{ parameters.bazelUseBES }}" == "True" && -n "${GOOGLE_BES_PROJECT_ID}" ]]; then BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" fi else From 452895108fc6cbaa15b2e079543690874955fcc1 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 23 Oct 2023 06:28:56 +0100 Subject: [PATCH 122/274] [bp/1.27] Backport stack (0) (#30371) * ci fixes/cleanups * test deflaking * dep/vuln update (nghttp2) Signed-off-by: Ryan Northey --- .azure-pipelines/env.yml | 3 ++- .azure-pipelines/stage/linux.yml | 17 ++++++++------ .azure-pipelines/stage/macos.yml | 8 +++++-- .azure-pipelines/stage/windows.yml | 19 ++++++++++++--- .azure-pipelines/stages.yml | 5 +++- .devcontainer/setup.sh | 4 +--- BUILD | 2 +- bazel/repository_locations.bzl | 6 ++--- ci/do_ci.sh | 2 -- ci/mac_ci_steps.sh | 3 --- ci/run_envoy_docker.sh | 2 +- ci/setup_cache.sh | 37 ------------------------------ ci/windows_ci_steps.sh | 3 --- distribution/dockerhub/BUILD | 3 ++- tools/BUILD | 2 +- tools/base/envoy_python.bzl | 16 ------------- tools/code/BUILD | 3 ++- tools/dependency/BUILD | 3 ++- tools/distribution/BUILD | 3 ++- tools/docs/BUILD | 3 ++- tools/proto_format/BUILD | 3 ++- tools/protodoc/BUILD | 3 ++- tools/protoprint/BUILD | 3 ++- tools/python/BUILD | 1 + tools/python/namespace.bzl | 17 ++++++++++++++ 25 files changed, 79 insertions(+), 92 deletions(-) delete mode 100755 ci/setup_cache.sh create mode 100644 tools/python/BUILD create mode 100644 tools/python/namespace.bzl diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index 20d86c42b05ca..c511ebc67a7b1 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -161,6 +161,7 @@ jobs: if [[ "$ISSTABLEBRANCH" == True && -n "$POSTSUBMIT" && "$(state.isDev)" == false ]]; then RUN_RELEASE_TESTS=false fi + echo "##vso[task.setvariable variable=build;isoutput=true]${RUN_BUILD}" echo "##vso[task.setvariable variable=checks;isoutput=true]${RUN_CHECKS}" echo "##vso[task.setvariable variable=docker;isoutput=true]${RUN_DOCKER}" @@ -217,7 +218,7 @@ jobs: echo "env.outputs['run.build']: $(run.build)" echo "env.outputs['run.checks']: $(run.checks)" echo "env.outputs['run.packaging']: $(run.packaging)" - echo "env.outputs['run.releaseTests]: $(run.releaseTests)" + echo "env.outputs['run.releaseTests']: $(run.releaseTests)" echo echo "env.outputs['publish.githubRelease']: $(publish.githubRelease)" echo "env.outputs['publish.dockerhub]: $(publish.dockerhub)" diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index 3946910246bb8..01adafd38369f 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -48,17 +48,20 @@ jobs: eq(${{ parameters.runBuild }}, 'true')) timeoutInMinutes: ${{ parameters.timeoutBuild }} pool: ${{ parameters.pool }} - variables: - - name: ciTarget - ${{ if eq(parameters.runTests, false) }}: - value: release.server_only - ${{ if ne(parameters.runTests, false) }}: - value: release steps: + - bash: | + if [[ "${{ parameters.runTests }}" == "false" ]]; then + CI_TARGET="release.server_only" + else + CI_TARGET="release" + fi + echo "${CI_TARGET}" + echo "##vso[task.setvariable variable=value;isoutput=true]${CI_TARGET}" + name: target - template: ../ci.yml parameters: managedAgent: ${{ parameters.managedAgent }} - ciTarget: $(ciTarget) + ciTarget: $(target.value) cacheName: "release" bazelBuildExtraOptions: ${{ parameters.bazelBuildExtraOptions }} cacheTestResults: ${{ parameters.cacheTestResults }} diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml index 4b7f99e718d31..fc990eafd737f 100644 --- a/.azure-pipelines/stage/macos.yml +++ b/.azure-pipelines/stage/macos.yml @@ -24,7 +24,12 @@ jobs: - script: ./ci/mac_ci_setup.sh displayName: "Install dependencies" - - script: ./ci/mac_ci_steps.sh + - bash: | + set -e + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -t gcp_service_account.XXXXXX.json) + bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" + BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" + ./ci/mac_ci_steps.sh displayName: "Run Mac CI" env: BAZEL_BUILD_EXTRA_OPTIONS: >- @@ -32,7 +37,6 @@ jobs: --flaky_test_attempts=2 --remote_cache=grpcs://remotebuildexecution.googleapis.com --remote_instance_name=projects/envoy-ci/instances/default_instance - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} ENVOY_RBE: 1 - task: PublishTestResults@2 diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index 9be16bebcf5c6..fa2729b822545 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -26,14 +26,27 @@ jobs: key: '"windows.release" | $(cacheKeyBazel)' path: $(Build.StagingDirectory)/repository_cache continueOnError: true - - bash: ci/run_envoy_docker.sh ci/windows_ci_steps.sh + + - bash: | + set -e + ENVOY_SHARED_TMP_DIR="C:\\Users\\VSSADM~1\\AppData\\Local\\Temp\\bazel-shared" + mkdir -p "$ENVOY_SHARED_TMP_DIR" + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -p "${ENVOY_SHARED_TMP_DIR}" -t gcp_service_account.XXXXXX.json) + bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" + export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" + export ENVOY_SHARED_TMP_DIR + ci/run_envoy_docker.sh ci/windows_ci_steps.sh displayName: "Run Windows msvc-cl CI" env: CI_TARGET: "windows" ENVOY_DOCKER_BUILD_DIR: "$(Build.StagingDirectory)" ENVOY_RBE: "true" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --config=remote-msvc-cl --jobs=$(RbeJobs) --flaky_test_attempts=2" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} + BAZEL_BUILD_EXTRA_OPTIONS: >- + --config=remote-ci + --config=rbe-google + --config=remote-msvc-cl + --jobs=$(RbeJobs) + --flaky_test_attempts=2 - task: PublishTestResults@2 inputs: diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 1b535277e2da1..c87f3996e6c28 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -84,8 +84,9 @@ stages: - template: stage/linux.yml parameters: cacheTestResults: ${{ parameters.cacheTestResults }} + # these are parsed differently and _must_ be expressed in this way runBuild: variables['RUN_BUILD'] - runTests: variables['RUN_TESTS'] + runTests: $(RUN_TESTS) tmpfsDockerDisabled: true - stage: linux_arm64 @@ -93,6 +94,7 @@ stages: dependsOn: ${{ parameters.buildStageDeps }} variables: RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] + RUN_TESTS: $[stageDependencies.env.repo.outputs['run.releaseTests']] jobs: - template: stage/linux.yml parameters: @@ -102,6 +104,7 @@ stages: timeoutBuild: 180 pool: envoy-arm-large runBuild: variables['RUN_BUILD'] + runTests: $(RUN_TESTS) bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" - stage: check diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index d2a54b474bb17..b50bb1190d661 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,10 +1,8 @@ #!/usr/bin/env bash -. ci/setup_cache.sh -trap - EXIT # Don't remove the key file written into a temporary file - BAZELRC_FILE=~/.bazelrc bazel/setup_clang.sh /opt/llvm +# TODO(phlax): use user.bazelrc # Use generated toolchain config because we know the base container is the one we're using in RBE. # Not using libc++ here because clangd will raise some tidy issue in libc++ header as of version 9. echo "build --config=rbe-toolchain-clang" >> ~/.bazelrc diff --git a/BUILD b/BUILD index 28d92d8c704a8..3b48868fd6f31 100644 --- a/BUILD +++ b/BUILD @@ -1,5 +1,5 @@ load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_py_namespace") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7f83d0cd5f5aa..94457b963dc60 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -456,12 +456,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Nghttp2", project_desc = "Implementation of HTTP/2 and its header compression algorithm HPACK in C", project_url = "https://nghttp2.org", - version = "1.55.1", - sha256 = "e12fddb65ae3218b4edc083501519379928eba153e71a1673b185570f08beb96", + version = "1.57.0", + sha256 = "1e3258453784d3b7e6cc48d0be087b168f8360b5d588c66bfeda05d07ad39ffd", strip_prefix = "nghttp2-{version}", urls = ["https://github.com/nghttp2/nghttp2/releases/download/v{version}/nghttp2-{version}.tar.gz"], use_category = ["controlplane", "dataplane_core"], - release_date = "2023-07-14", + release_date = "2023-10-10", cpe = "cpe:2.3:a:nghttp2:nghttp2:*", license = "MIT", license_url = "https://github.com/nghttp2/nghttp2/blob/v{version}/LICENSE", diff --git a/ci/do_ci.sh b/ci/do_ci.sh index bec54b718462a..484a1038474c7 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -8,8 +8,6 @@ set -e export SRCDIR="${SRCDIR:-$PWD}" export ENVOY_SRCDIR="${ENVOY_SRCDIR:-$PWD}" -# shellcheck source=ci/setup_cache.sh -. "$(dirname "$0")"/setup_cache.sh # shellcheck source=ci/build_setup.sh . "$(dirname "$0")"/build_setup.sh diff --git a/ci/mac_ci_steps.sh b/ci/mac_ci_steps.sh index 2ab857c72970a..dc779a665c713 100755 --- a/ci/mac_ci_steps.sh +++ b/ci/mac_ci_steps.sh @@ -11,9 +11,6 @@ trap finish EXIT echo "disk space at beginning of build:" df -h -# shellcheck source=ci/setup_cache.sh -. "$(dirname "$0")"/setup_cache.sh - read -ra BAZEL_BUILD_EXTRA_OPTIONS <<< "${BAZEL_BUILD_EXTRA_OPTIONS:-}" read -ra BAZEL_EXTRA_TEST_OPTIONS <<< "${BAZEL_EXTRA_TEST_OPTIONS:-}" diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 36c438bf01132..30edcf59ea5d3 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -86,7 +86,7 @@ VOLUMES=( -v "${ENVOY_DOCKER_BUILD_DIR}":"${BUILD_DIR_MOUNT_DEST}" -v "${SOURCE_DIR}":"${SOURCE_DIR_MOUNT_DEST}") -if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" || -n "$ENVOY_SHARED_TMP_DIR" ]]; then +if [[ -n "$ENVOY_DOCKER_IN_DOCKER" || -n "$ENVOY_SHARED_TMP_DIR" ]]; then # Create a "shared" directory that has the same path in/outside the container # This allows the host docker engine to see artefacts using a temporary path created inside the container, # at the same path. diff --git a/ci/setup_cache.sh b/ci/setup_cache.sh deleted file mode 100755 index ca910ec1a090c..0000000000000 --- a/ci/setup_cache.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -set -e - -if [[ -n "${GCP_SERVICE_ACCOUNT_KEY:0:1}" ]]; then - # mktemp will create a tempfile with u+rw permission minus umask, it will not be readable by all - # users by default. - GCP_SERVICE_ACCOUNT_KEY_FILE=$(mktemp -t gcp_service_account.XXXXXX.json) - - gcp_service_account_cleanup() { - echo "Deleting service account key file..." - rm -rf "${GCP_SERVICE_ACCOUNT_KEY_FILE}" - } - - trap gcp_service_account_cleanup EXIT - - echo "Setting GCP_SERVICE_ACCOUNT_KEY is deprecated, please place your decoded GCP key in " \ - "an exported/shared tmp directory and add it to BAZEL_BUILD_EXTRA_OPTIONS, eg: " >&2 - # shellcheck disable=SC2086 - echo "$ export ENVOY_SHARED_TMP_DIR=/tmp/envoy-shared" \ - "$ ENVOY_RBE_KEY_PATH=$(mktemp -p \"${ENVOY_SHARED_TMP_DIR}\" -t gcp_service_account.XXXXXX.json)" \ - "$ bash -c 'echo \"$(GcpServiceAccountKey)\"' | base64 --decode > \"${ENVOY_RBE_KEY_PATH}\"" \ - "$ export BAZEL_BUILD_EXTRA_OPTIONS+=\" --google_credentials=${ENVOY_RBE_KEY_PATH}\"" >&2 - bash -c 'echo "${GCP_SERVICE_ACCOUNT_KEY}"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_FILE}" - export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_FILE}" -fi - -if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then - echo "Setting BAZEL_REMOTE_CACHE is deprecated, please use BAZEL_BUILD_EXTRA_OPTIONS " \ - "or use a user.bazelrc config " >&2 - export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_cache=${BAZEL_REMOTE_CACHE}" - echo "Set up bazel remote read/write cache at ${BAZEL_REMOTE_CACHE}." - if [[ -n "${BAZEL_REMOTE_INSTANCE}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_instance_name=${BAZEL_REMOTE_INSTANCE}" - echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." - fi -fi diff --git a/ci/windows_ci_steps.sh b/ci/windows_ci_steps.sh index 58fd0a9a81d58..c16d7392602ac 100755 --- a/ci/windows_ci_steps.sh +++ b/ci/windows_ci_steps.sh @@ -11,9 +11,6 @@ trap finish EXIT echo "disk space at beginning of build:" df -h -# shellcheck source=ci/setup_cache.sh -. "$(dirname "$0")"/setup_cache.sh - [ -z "${ENVOY_SRCDIR}" ] && export ENVOY_SRCDIR=/c/source read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTIONS:-}" diff --git a/distribution/dockerhub/BUILD b/distribution/dockerhub/BUILD index cd6321175ee6a..599775efdf688 100644 --- a/distribution/dockerhub/BUILD +++ b/distribution/dockerhub/BUILD @@ -1,5 +1,6 @@ load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_gencontent", "envoy_py_namespace") +load("//tools/base:envoy_python.bzl", "envoy_gencontent") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/BUILD b/tools/BUILD index ec8b6d14f8e23..8dbb978d51ef4 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -5,7 +5,7 @@ load( "envoy_package", "envoy_py_test_binary", ) -load("//tools/base:envoy_python.bzl", "envoy_py_namespace") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/base/envoy_python.bzl b/tools/base/envoy_python.bzl index 7578d54f19fe5..a652943e4f48a 100644 --- a/tools/base/envoy_python.bzl +++ b/tools/base/envoy_python.bzl @@ -10,22 +10,6 @@ ENVOY_PYTOOL_NAMESPACE = [ "//tools:py-init", ] -def envoy_py_namespace(): - """Adding this to a build, injects a namespaced __init__.py, this allows namespaced - packages - eg envoy.base.utils to co-exist with packages created from the repo.""" - native.genrule( - name = "py-init-file", - outs = ["__init__.py"], - cmd = """ - echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" > $@ - """, - ) - py_library( - name = "py-init", - srcs = [":py-init-file"], - visibility = ["//visibility:public"], - ) - def envoy_pytool_binary( name, data = None, diff --git a/tools/code/BUILD b/tools/code/BUILD index 4de0a77a4b2ea..9c5fd6c0e3796 100644 --- a/tools/code/BUILD +++ b/tools/code/BUILD @@ -6,7 +6,8 @@ load( "READFILTER_FUZZ_FILTERS", "READFILTER_NOFUZZ_FILTERS", ) -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace") +load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 5d5a388502854..c97f59c223d5d 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -1,7 +1,8 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@envoy_repo//:path.bzl", "PATH") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/distribution/BUILD b/tools/distribution/BUILD index f809f3637d45f..cad485b87e1c8 100644 --- a/tools/distribution/BUILD +++ b/tools/distribution/BUILD @@ -1,6 +1,7 @@ load("@base_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/docs/BUILD b/tools/docs/BUILD index 7d2870c162ee2..290501c9f4495 100644 --- a/tools/docs/BUILD +++ b/tools/docs/BUILD @@ -1,6 +1,7 @@ load("@base_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/proto_format/BUILD b/tools/proto_format/BUILD index 8ce574c9e623d..597d2f191b5de 100644 --- a/tools/proto_format/BUILD +++ b/tools/proto_format/BUILD @@ -3,7 +3,8 @@ load("@envoy_repo//:path.bzl", "PATH") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/protodoc/BUILD b/tools/protodoc/BUILD index f562c431ed3e0..db4224fd4309b 100644 --- a/tools/protodoc/BUILD +++ b/tools/protodoc/BUILD @@ -1,8 +1,9 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@com_google_protobuf//:protobuf.bzl", "py_proto_library") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_jinja_env", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary", "envoy_pytool_library") +load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_jinja_env", "envoy_py_data", "envoy_pytool_binary", "envoy_pytool_library") load("//tools/protodoc:protodoc.bzl", "protodoc_rule") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/protoprint/BUILD b/tools/protoprint/BUILD index 720e41c1a7ec4..37659b927bcf5 100644 --- a/tools/protoprint/BUILD +++ b/tools/protoprint/BUILD @@ -2,8 +2,9 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_py_data", "envoy_pytool_binary") load("//tools/protoprint:protoprint.bzl", "protoprint_rule") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/python/BUILD b/tools/python/BUILD new file mode 100644 index 0000000000000..779d1695d3b7c --- /dev/null +++ b/tools/python/BUILD @@ -0,0 +1 @@ +licenses(["notice"]) # Apache 2 diff --git a/tools/python/namespace.bzl b/tools/python/namespace.bzl new file mode 100644 index 0000000000000..7be06755127b5 --- /dev/null +++ b/tools/python/namespace.bzl @@ -0,0 +1,17 @@ +load("@rules_python//python:defs.bzl", "py_library") + +def envoy_py_namespace(): + """Adding this to a build, injects a namespaced __init__.py, this allows namespaced + packages - eg envoy.base.utils to co-exist with packages created from the repo.""" + native.genrule( + name = "py-init-file", + outs = ["__init__.py"], + cmd = """ + echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" > $@ + """, + ) + py_library( + name = "py-init", + srcs = [":py-init-file"], + visibility = ["//visibility:public"], + ) From e2b528af262d2d442a35fb0f101815b339993d28 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 18 Oct 2023 10:24:35 -0400 Subject: [PATCH 123/274] Prevent recursion during premature reset check (#30270) Prevent doConnectionClose to be called recursively when connection with active requests is disconnected due to premature reset check. Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 3 +++ source/common/http/conn_manager_impl.cc | 2 +- test/integration/multiplexed_integration_test.cc | 5 ++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..e7847aa56d8ea 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,9 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index f567d659f7022..4b5c43b2e3b24 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -677,7 +677,7 @@ void ConnectionManagerImpl::maybeDrainDueToPrematureResets() { return; } - if (drain_state_ == DrainState::NotDraining) { + if (read_callbacks_->connection().state() == Network::Connection::State::Open) { stats_.named_.downstream_rq_too_many_premature_resets_.inc(); doConnectionClose(Network::ConnectionCloseType::Abort, absl::nullopt, "too_many_premature_resets"); diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 3aca9af441b40..f0468865e9b0d 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2368,7 +2368,7 @@ TEST_P(Http2FrameIntegrationTest, ResettingDeferredRequestsTriggersPrematureRese TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { // Use large number of requests to ensure close is detected while there are // still some deferred streams. - const int kRequestsSentPerIOCycle = 1000; + const int kRequestsSentPerIOCycle = 20000; config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); // Ensure premature reset detection does not get in the way config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", "1001"); @@ -2376,8 +2376,7 @@ TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { std::string buffer; for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { - auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/", - {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/"); absl::StrAppend(&buffer, std::string(request)); } From 6d3149d9834b6c1a5ea95f2830e37bfa1dc8bfd0 Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Wed, 18 Oct 2023 18:38:46 +0000 Subject: [PATCH 124/274] Lengthen the timeout Signed-off-by: Yan Avlasov --- test/integration/multiplexed_integration_test.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index f0468865e9b0d..f2e7df6b2b98f 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2385,8 +2385,9 @@ TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { // Drop the downstream connection tcp_client_->close(); // Test that Envoy can clean-up deferred streams - test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", - kRequestsSentPerIOCycle); + // Make the timeout longer to accommodate non optimized builds + test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", kRequestsSentPerIOCycle, + TestUtility::DefaultTimeout * 3); } INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FrameIntegrationTest, From 4a9a24caa72b1960750932096e7a30f63a20819b Mon Sep 17 00:00:00 2001 From: "Vikas Choudhary (vikasc)" Date: Sat, 21 Oct 2023 00:31:36 +0530 Subject: [PATCH 125/274] Fix intermittent cpu spike in grpc async client (#30123) In grpc async client, if timer expiry handler function gets fired where time to next_expiry is less than 1 sec, a loop gets created where timer gets enabled with 0 secs expiry again and again until 'now' becomes as next_expiry. This causes random cpu spikes. If there is HPA configured on cpu, this will result in scale up and scale down on proxies. I added some logs while debugging it. Sharing here might help understanding the issue more clearly: ``` AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:198] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer next_expire: 966826645315069, now 966825877367850 [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:208] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer enable timer: 0 [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:193] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:198] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer next_expire: 966826645315069, now 966825877389741 [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:208] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer enable timer: 0 ``` When this condition hits, logs get filled because of the loop I mentioned in the beginning. Fix is simple that in the `if` condition for expiry consider this round off thing as well. Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: Signed-off-by: Vikas Choudhary fix tests to match older codebase Signed-off-by: Vikas Choudhary Signed-off-by: Vikas Choudhary (vikasc) --- changelogs/current.yaml | 3 ++ .../common/grpc/async_client_manager_impl.cc | 13 +++-- .../grpc/async_client_manager_impl_test.cc | 51 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e7847aa56d8ea..9022d01ce408a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -11,6 +11,9 @@ bug_fixes: - area: http change: | Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: grpc + change: | + Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/grpc/async_client_manager_impl.cc b/source/common/grpc/async_client_manager_impl.cc index 949ae4bd75391..4b9dc4f861256 100644 --- a/source/common/grpc/async_client_manager_impl.cc +++ b/source/common/grpc/async_client_manager_impl.cc @@ -1,5 +1,7 @@ #include "source/common/grpc/async_client_manager_impl.h" +#include + #include "envoy/config/core/v3/grpc_service.pb.h" #include "envoy/stats/scope.h" @@ -187,13 +189,18 @@ void AsyncClientManagerImpl::RawAsyncClientCache::evictEntriesAndResetEvictionTi // Evict all the entries that have expired. while (!lru_list_.empty()) { MonotonicTime next_expire = lru_list_.back().accessed_time_ + EntryTimeoutInterval; - if (now >= next_expire) { + std::chrono::seconds time_to_next_expire_sec = + std::chrono::duration_cast(next_expire - now); + // since 'now' and 'next_expire' are in nanoseconds, the following condition is to + // check if the difference between them is less than 1 second. If we don't do this, the + // timer will be enabled with 0 seconds, which will cause the timer to fire immediately. + // This will cause cpu spike. + if (time_to_next_expire_sec.count() <= 0) { // Erase the expired entry. lru_map_.erase(lru_list_.back().config_); lru_list_.pop_back(); } else { - cache_eviction_timer_->enableTimer( - std::chrono::duration_cast(next_expire - now)); + cache_eviction_timer_->enableTimer(time_to_next_expire_sec); return; } } diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index 121f8cbc533d5..ee1d2008b20c2 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -1,3 +1,4 @@ +#include #include #include "envoy/config/core/v3/grpc_service.pb.h" @@ -105,6 +106,56 @@ TEST_F(RawAsyncClientCacheTest, GetExpiredButNotEvictedCacheEntry) { EXPECT_EQ(client_cache_.getCache(foo_service).get(), nullptr); } +class RawAsyncClientCacheTestBusyLoop : public testing::Test { +public: + RawAsyncClientCacheTestBusyLoop() { + timer_ = new Event::MockTimer(); + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb) { + return timer_; + })); + client_cache_ = std::make_unique(dispatcher_); + EXPECT_CALL(*timer_, enableTimer(testing::Not(std::chrono::milliseconds(0)), _)) + .Times(testing::AtLeast(1)); + } + + void waitForMilliSeconds(int ms) { + for (int i = 0; i < ms; i++) { + time_system_.advanceTimeAndRun(std::chrono::milliseconds(1), dispatcher_, + Event::Dispatcher::RunType::NonBlock); + } + } + +protected: + Event::SimulatedTimeSystem time_system_; + NiceMock dispatcher_; + Event::MockTimer* timer_; + std::unique_ptr client_cache_; +}; + +TEST_F(RawAsyncClientCacheTestBusyLoop, MultipleCacheEntriesEvictionBusyLoop) { + envoy::config::core::v3::GrpcService grpc_service; + RawAsyncClientSharedPtr foo_client = std::make_shared(); + // two entries are added to the cache + for (int i = 1; i <= 2; i++) { + grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(i)); + client_cache_->setCache(grpc_service, foo_client); + } + // waiting for 49.2 secs to make sure that for the entry which is not accessed, time to expire is + // less than 1 second, ~0.8 secs + waitForMilliSeconds(49200); + + // Access first cache entry to so that evictEntriesAndResetEvictionTimer() gets called. + // Since we are getting first entry, access time of first entry will be updated to current time. + grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(1)); + EXPECT_EQ(client_cache_->getCache(grpc_service).get(), foo_client.get()); + + // Verifying that though the time to expire for second entry ~0.8 sec, it is considered as expired + // to avoid the busy loop which could happen if timer gets enabled with 0(0.8 rounded off to 0) + // duration. + grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(2)); + EXPECT_EQ(client_cache_->getCache(grpc_service).get(), nullptr); +} + class AsyncClientManagerImplTest : public testing::Test { public: AsyncClientManagerImplTest() From 2827b0b720fd46c964d2eb6c1f36832244181c8a Mon Sep 17 00:00:00 2001 From: "Vikas Choudhary (vikasc)" Date: Wed, 18 Oct 2023 06:02:31 +0530 Subject: [PATCH 126/274] Check upstreamInfo's filter state as well in grpc access logs (#30057) Commit Message: At client side, if metadata exchange filter is an upstream filter, received filter state gets stored in the upstream streaminfo. While emitting access logs, streaminfo passed to the extractCommonAccessLogProperties() is the downstream side streaminfo. filter_state_objects_to_log keys must be searched in the usptream streaminfo's filter state as well. Additional Description: Risk Level: low Testing: done Docs Changes: no Release Notes: yes Signed-off-by: Vikas Choudhary Signed-off-by: Vikas Choudhary (vikasc) --- changelogs/current.yaml | 4 + .../grpc/grpc_access_log_utils.cc | 34 ++++-- .../grpc/grpc_access_log_utils.h | 3 + test/extensions/access_loggers/grpc/BUILD | 1 + .../grpc/grpc_access_log_utils_test.cc | 104 ++++++++++++++++++ 5 files changed, 136 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9022d01ce408a..71616eba4b605 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -5,6 +5,10 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: access_log + change: | + When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried + to find the keys configured in filter_state_objects_to_log. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc index 7155a13ee5fea..9a25f65dadfba 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc @@ -2,6 +2,7 @@ #include "envoy/data/accesslog/v3/accesslog.pb.h" #include "envoy/extensions/access_loggers/grpc/v3/als.pb.h" +#include "envoy/stream_info/filter_state.h" #include "envoy/upstream/upstream.h" #include "source/common/network/utility.h" @@ -300,16 +301,11 @@ void Utility::extractCommonAccessLogProperties( } for (const auto& key : config.filter_state_objects_to_log()) { - if (auto state = stream_info.filterState().getDataReadOnlyGeneric(key); state != nullptr) { - ProtobufTypes::MessagePtr serialized_proto = state->serializeAsProto(); - if (serialized_proto != nullptr) { - auto& filter_state_objects = *common_access_log.mutable_filter_state_objects(); - ProtobufWkt::Any& any = filter_state_objects[key]; - if (dynamic_cast(serialized_proto.get()) != nullptr) { - any.Swap(dynamic_cast(serialized_proto.get())); - } else { - any.PackFrom(*serialized_proto); - } + if (!(extractFilterStateData(stream_info.filterState(), key, common_access_log))) { + if (stream_info.upstreamInfo().has_value() && + stream_info.upstreamInfo()->upstreamFilterState() != nullptr) { + extractFilterStateData(*(stream_info.upstreamInfo()->upstreamFilterState()), key, + common_access_log); } } } @@ -342,6 +338,24 @@ void Utility::extractCommonAccessLogProperties( common_access_log.set_access_log_type(access_log_type); } +bool extractFilterStateData(const StreamInfo::FilterState& filter_state, const std::string& key, + envoy::data::accesslog::v3::AccessLogCommon& common_access_log) { + if (auto state = filter_state.getDataReadOnlyGeneric(key); state != nullptr) { + ProtobufTypes::MessagePtr serialized_proto = state->serializeAsProto(); + if (serialized_proto != nullptr) { + auto& filter_state_objects = *common_access_log.mutable_filter_state_objects(); + ProtobufWkt::Any& any = filter_state_objects[key]; + if (dynamic_cast(serialized_proto.get()) != nullptr) { + any.Swap(dynamic_cast(serialized_proto.get())); + } else { + any.PackFrom(*serialized_proto); + } + } + return true; + } + return false; +} + } // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h index 9f4f1e07fbd5d..beec1e719a8f5 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h @@ -24,6 +24,9 @@ class Utility { const StreamInfo::StreamInfo& stream_info); }; +bool extractFilterStateData(const StreamInfo::FilterState& filter_state, const std::string& key, + envoy::data::accesslog::v3::AccessLogCommon& common_access_log); + } // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions diff --git a/test/extensions/access_loggers/grpc/BUILD b/test/extensions/access_loggers/grpc/BUILD index 423c8734457f8..05d5477338a7e 100644 --- a/test/extensions/access_loggers/grpc/BUILD +++ b/test/extensions/access_loggers/grpc/BUILD @@ -35,6 +35,7 @@ envoy_extension_cc_test( extension_names = ["envoy.access_loggers.http_grpc"], deps = [ "//source/extensions/access_loggers/grpc:grpc_access_log_utils", + "//source/extensions/filters/common/expr:cel_state_lib", "//test/mocks/local_info:local_info_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", diff --git a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc index 782cf5178956f..372b7512e631b 100644 --- a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc +++ b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc @@ -1,6 +1,9 @@ #include "envoy/data/accesslog/v3/accesslog.pb.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/stream_info/filter_state_impl.h" #include "source/extensions/access_loggers/grpc/grpc_access_log_utils.h" +#include "source/extensions/filters/common/expr/cel_state.h" #include "test/mocks/stream_info/mocks.h" @@ -10,6 +13,8 @@ namespace AccessLoggers { namespace GrpcCommon { namespace { +using Filters::Common::Expr::CelStatePrototype; +using Filters::Common::Expr::CelStateType; using testing::_; using testing::Return; @@ -53,6 +58,105 @@ TEST(UtilityResponseFlagsToAccessLogResponseFlagsTest, All) { EXPECT_EQ(common_access_log_expected.DebugString(), common_access_log.DebugString()); } +// key is present only in downstream streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromDownstream) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("downstream_peer"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto state = std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + state->setValue("value_from_downstream_peer"); + stream_info.filter_state_->setData("downstream_peer", std::move(state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("downstream_peer"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("downstream_peer"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["downstream_peer"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_downstream_peer"); +} + +// key is present only in the upstream streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromUpstream) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("upstream_peer"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto state = std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + auto filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); + state->setValue("value_from_upstream_peer"); + filter_state->setData("upstream_peer", std::move(state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("upstream_peer"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("upstream_peer"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["upstream_peer"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_upstream_peer"); +} + +// key is present in both the streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, + FilterStateFromDownstreamIfSameKeyInBothStreamInfo) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("same_key"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto downstream_state = + std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + downstream_state->setValue("value_from_downstream_peer"); + stream_info.filter_state_->setData("same_key", std::move(downstream_state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + auto upstream_state = + std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + auto filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); + upstream_state->setValue("value_from_upstream_peer"); + filter_state->setData("same_key", std::move(upstream_state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("same_key"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("same_key"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["same_key"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_downstream_peer"); +} + } // namespace } // namespace GrpcCommon } // namespace AccessLoggers From dcfd6f7efa3df573d9bbe6b8a2191441887e9bd0 Mon Sep 17 00:00:00 2001 From: Keith Mattix II Date: Thu, 26 Oct 2023 12:20:35 -0500 Subject: [PATCH 127/274] Add var to force docker save (#30502) Signed-off-by: Keith Mattix II --- ci/do_ci.sh | 2 +- ci/run_envoy_docker.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 484a1038474c7..aaf9be5d4d7e1 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -623,7 +623,7 @@ case $CI_TARGET in fi PLATFORMS="$(IFS=, ; echo "${_PLATFORMS[*]}")" export DOCKER_PLATFORM="$PLATFORMS" - if [[ -z "${DOCKERHUB_PASSWORD}" && "${#_PLATFORMS[@]}" -eq 1 ]]; then + if [[ -z "${DOCKERHUB_PASSWORD}" && "${#_PLATFORMS[@]}" -eq 1 && -z $ENVOY_DOCKER_SAVE_IMAGE ]]; then # if you are not pushing the images and there is only one platform # then load to Docker (ie local build) export DOCKER_LOAD_IMAGES=1 diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 30edcf59ea5d3..a8a5fe1bf5760 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -121,6 +121,7 @@ docker run --rm \ -e CI_TARGET_BRANCH \ -e DOCKERHUB_USERNAME \ -e DOCKERHUB_PASSWORD \ + -e ENVOY_DOCKER_SAVE_IMAGE \ -e ENVOY_STDLIB \ -e BUILD_REASON \ -e BAZEL_REMOTE_INSTANCE \ From c658fab86a174fda07961e196edb97f9da9b5a50 Mon Sep 17 00:00:00 2001 From: David Goffredo Date: Wed, 8 Nov 2023 09:55:50 -0500 Subject: [PATCH 128/274] datadog: honor extracted sampling decisions (backport #30577 onto v1.27) (#30750) Signed-off-by: David Goffredo --- changelogs/current.yaml | 4 + source/extensions/tracers/datadog/tracer.cc | 39 ++-- source/extensions/tracers/datadog/tracer.h | 21 ++- .../extensions/tracers/datadog/tracer_test.cc | 168 ++++++++++++++++++ 4 files changed, 213 insertions(+), 19 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 71616eba4b605..29a3cd3f5f832 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -18,6 +18,10 @@ bug_fixes: - area: grpc change: | Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug that caused the Datadog tracing extension to drop traces that + should be kept on account of an extracted sampling decision. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/tracers/datadog/tracer.cc b/source/extensions/tracers/datadog/tracer.cc index ac898f317b481..c3acc38a15eff 100644 --- a/source/extensions/tracers/datadog/tracer.cc +++ b/source/extensions/tracers/datadog/tracer.cc @@ -20,6 +20,7 @@ #include "datadog/sampling_priority.h" #include "datadog/span_config.h" #include "datadog/trace_segment.h" +#include "datadog/tracer_config.h" namespace Envoy { namespace Extensions { @@ -94,8 +95,27 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext span_config.resource = operation_name; span_config.start = estimateTime(stream_info.startTime()); - datadog::tracing::Tracer& tracer = *thread_local_tracer.tracer; TraceContextReader reader{trace_context}; + datadog::tracing::Span span = + extract_or_create_span(*thread_local_tracer.tracer, span_config, reader); + + // If we did not extract a sampling decision, and if Envoy is telling us to + // drop the trace, then we treat that as a "user drop" (manual override). + // + // If Envoy is telling us to keep the trace, then we leave it up to the + // tracer's internal sampler (which might decide to drop the trace anyway). + if (!span.trace_segment().sampling_decision().has_value() && !tracing_decision.traced) { + span.trace_segment().override_sampling_priority( + int(datadog::tracing::SamplingPriority::USER_DROP)); + } + + return std::make_unique(std::move(span)); +} + +datadog::tracing::Span +Tracer::extract_or_create_span(datadog::tracing::Tracer& tracer, + const datadog::tracing::SpanConfig& span_config, + const datadog::tracing::DictReader& reader) { datadog::tracing::Expected maybe_span = tracer.extract_span(reader, span_config); if (datadog::tracing::Error* error = maybe_span.if_error()) { @@ -111,23 +131,10 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext int(error->code), error->message); } - maybe_span = tracer.create_span(span_config); - } - - ASSERT(maybe_span); - datadog::tracing::Span& span = *maybe_span; - - // If Envoy is telling us to drop the trace, then we treat that as a - // "user drop" (manual override). - // - // If Envoy is telling us to keep the trace, then we leave it up to the - // tracer's internal sampler (which might decide to drop the trace anyway). - if (!tracing_decision.traced) { - span.trace_segment().override_sampling_priority( - int(datadog::tracing::SamplingPriority::USER_DROP)); + return tracer.create_span(span_config); } - return std::make_unique(std::move(span)); + return std::move(*maybe_span); } } // namespace Datadog diff --git a/source/extensions/tracers/datadog/tracer.h b/source/extensions/tracers/datadog/tracer.h index 9e822cb9a0df3..670f382fc3053 100644 --- a/source/extensions/tracers/datadog/tracer.h +++ b/source/extensions/tracers/datadog/tracer.h @@ -10,7 +10,18 @@ #include "source/extensions/tracers/datadog/tracer_stats.h" #include "datadog/tracer.h" -#include "datadog/tracer_config.h" + +namespace datadog { +namespace tracing { + +class DictReader; +class FinalizedTracerConfig; +class Span; +struct SpanConfig; +struct TracerConfig; + +} // namespace tracing +} // namespace datadog namespace Envoy { namespace Extensions { @@ -73,8 +84,8 @@ class Tracer : public Tracing::Driver, private Logger::Loggable thread_local_slot_; }; diff --git a/test/extensions/tracers/datadog/tracer_test.cc b/test/extensions/tracers/datadog/tracer_test.cc index 8a4e276fd3daa..7cd37a8a33291 100644 --- a/test/extensions/tracers/datadog/tracer_test.cc +++ b/test/extensions/tracers/datadog/tracer_test.cc @@ -1,3 +1,5 @@ +#include + #include "envoy/tracing/trace_reason.h" #include "source/common/tracing/null_span_impl.h" @@ -7,11 +9,14 @@ #include "test/mocks/stream_info/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "datadog/error.h" #include "datadog/expected.h" +#include "datadog/optional.h" +#include "datadog/propagation_style.h" #include "datadog/sampling_priority.h" #include "datadog/trace_segment.h" #include "datadog/tracer_config.h" @@ -23,6 +28,30 @@ namespace Tracers { namespace Datadog { namespace { +class EnvVarGuard { +public: + EnvVarGuard(const std::string& name, const std::string& value) : name_(name) { + if (const char* const previous = std::getenv(name.c_str())) { + previous_value_ = previous; + } + const int overwrite = 1; // Yes, overwrite it. + TestEnvironment::setEnvVar(name, value, overwrite); + } + + ~EnvVarGuard() { + if (previous_value_) { + const int overwrite = 1; // Yes, overwrite it. + TestEnvironment::setEnvVar(name_, *previous_value_, overwrite); + } else { + TestEnvironment::unsetEnvVar(name_); + } + } + +private: + std::string name_; + datadog::tracing::Optional previous_value_; +}; + class DatadogTracerTest : public testing::Test { public: DatadogTracerTest() { @@ -203,6 +232,145 @@ TEST_F(DatadogTracerTest, ExtractionFailure) { ASSERT_TRUE(maybe_dd_span); } +TEST_F(DatadogTracerTest, EnvoySamplingVersusExtractedSampling) { + // Verify that sampling decisions extracted from incoming requests are honored + // regardless of the sampling decision made by Envoy (i.e. `bool + // Tracing::Decision::traced`). + // + // We test two styles of extraction: OpenTelemetry's W3C "tracecontext" style + // and Datadog's "datadog" style. When trace context is extracted in either of + // these styles, a sampling decision might be present. If a sampling decision + // is present, then the resulting sampling priority in the extracted trace + // must be the same as that which was extracted. + // + // If a sampling decision is not present in the extracted trace context, then + // an Envoy decision of "drop" is honored. An Envoy decision of "keep" + // delegates the sampling decision to the underlying Datadog tracer, which + // will not make a sampling decision immediately. + + struct Case { + int line; + datadog::tracing::Optional extracted_sampling_priority; + bool envoy_decision_keep; + datadog::tracing::PropagationStyle extraction_style; + // `resulting_sampling_priority` is the sampling priority that results from + // trace context extraction. + // It's not necessarily the sampling priority that would be sent to the + // Datadog Agent. + // If `resulting_sampling_priority` is null, then that means that the tracer + // does not make an initial sampling decision, though it will make one by + // the time is sends spans to the Datadog Agent or injects trace context + // into an outgoing request. + datadog::tracing::Optional resulting_sampling_priority; + } cases[] = { + {__LINE__, datadog::tracing::nullopt, true, datadog::tracing::PropagationStyle::DATADOG, + datadog::tracing::nullopt}, + // Note that the `resulting_sampling_priority` in this case is an artifact + // of "traceparent" always containing a sampling decision in its flags. See + // the main body of the test, below, for more information. + {__LINE__, datadog::tracing::nullopt, true, datadog::tracing::PropagationStyle::W3C, 0}, + // This is the only case, at least in this test, where Envoy's decision + // affects the resulting sampling priority. + {__LINE__, datadog::tracing::nullopt, false, datadog::tracing::PropagationStyle::DATADOG, -1}, + {__LINE__, datadog::tracing::nullopt, false, datadog::tracing::PropagationStyle::W3C, 0}, + + {__LINE__, -1, true, datadog::tracing::PropagationStyle::DATADOG, -1}, + {__LINE__, -1, true, datadog::tracing::PropagationStyle::W3C, -1}, + {__LINE__, -1, false, datadog::tracing::PropagationStyle::DATADOG, -1}, + {__LINE__, -1, false, datadog::tracing::PropagationStyle::W3C, -1}, + + {__LINE__, 0, true, datadog::tracing::PropagationStyle::DATADOG, 0}, + {__LINE__, 0, true, datadog::tracing::PropagationStyle::W3C, 0}, + {__LINE__, 0, false, datadog::tracing::PropagationStyle::DATADOG, 0}, + {__LINE__, 0, false, datadog::tracing::PropagationStyle::W3C, 0}, + + {__LINE__, 1, true, datadog::tracing::PropagationStyle::DATADOG, 1}, + {__LINE__, 1, true, datadog::tracing::PropagationStyle::W3C, 1}, + {__LINE__, 1, false, datadog::tracing::PropagationStyle::DATADOG, 1}, + {__LINE__, 1, false, datadog::tracing::PropagationStyle::W3C, 1}, + + {__LINE__, 2, true, datadog::tracing::PropagationStyle::DATADOG, 2}, + {__LINE__, 2, true, datadog::tracing::PropagationStyle::W3C, 2}, + {__LINE__, 2, false, datadog::tracing::PropagationStyle::DATADOG, 2}, + {__LINE__, 2, false, datadog::tracing::PropagationStyle::W3C, 2}, + }; + + for (const Case& test_case : cases) { + std::ostringstream failure_context; + failure_context << "Failure occurred for test entry on line " << test_case.line; + + std::string style_name; + if (test_case.extraction_style == datadog::tracing::PropagationStyle::DATADOG) { + style_name = "datadog"; + } else { + ASSERT_EQ(test_case.extraction_style, datadog::tracing::PropagationStyle::W3C) + << failure_context.str(); + style_name = "tracecontext"; + } + + EnvVarGuard guard{"DD_TRACE_PROPAGATION_STYLE", style_name}; + datadog::tracing::TracerConfig config; + config.defaults.service = "envoy"; + Tracer tracer("fake_cluster", "test_host", config, cluster_manager_, *store_.rootScope(), + thread_local_slot_allocator_); + + Tracing::Decision envoy_decision; + envoy_decision.reason = Tracing::Reason::Sampling; + envoy_decision.traced = test_case.envoy_decision_keep; + + const std::string operation_name = "do.thing"; + + Tracing::TestTraceContextImpl context{{}}; + if (test_case.extraction_style == datadog::tracing::PropagationStyle::DATADOG) { + context.context_map_["x-datadog-trace-id"] = "123"; + context.context_map_["x-datadog-parent-id"] = "456"; + if (test_case.extracted_sampling_priority) { + context.context_map_["x-datadog-sampling-priority"] = + std::to_string(*test_case.extracted_sampling_priority); + } + } else { + ASSERT_EQ(test_case.extraction_style, datadog::tracing::PropagationStyle::W3C) + << failure_context.str(); + std::string flags; + if (test_case.extracted_sampling_priority) { + const int priority = *test_case.extracted_sampling_priority; + flags = priority <= 0 ? "00" : "01"; + context.context_map_["tracestate"] = "dd=s:" + std::to_string(priority); + } else { + // There's no such thing as the absence of a sampling decision with + // "traceparent," so default to "drop." + flags = "00"; + } + context.context_map_["traceparent"] = + "00-0000000000000000000000000000007b-00000000000001c8-" + flags; + } + + const Tracing::SpanPtr span = tracer.startSpan(Tracing::MockConfig{}, context, stream_info_, + operation_name, envoy_decision); + ASSERT_TRUE(span) << failure_context.str(); + const auto as_dd_span_wrapper = dynamic_cast(span.get()); + EXPECT_NE(nullptr, as_dd_span_wrapper) << failure_context.str(); + + const datadog::tracing::Optional& maybe_dd_span = + as_dd_span_wrapper->impl(); + ASSERT_TRUE(maybe_dd_span) << failure_context.str(); + const datadog::tracing::Span& dd_span = *maybe_dd_span; + + const datadog::tracing::Optional decision = + dd_span.trace_segment().sampling_decision(); + if (test_case.resulting_sampling_priority) { + // We expect that the tracer made a sampling decision immediately, and + // that it has the expected sampling priority. + ASSERT_NE(datadog::tracing::nullopt, decision) << failure_context.str(); + EXPECT_EQ(*test_case.resulting_sampling_priority, decision->priority) + << failure_context.str(); + } else { + // We expect that the tracer did not immediately make a sampling decision. + EXPECT_EQ(datadog::tracing::nullopt, decision) << failure_context.str(); + } + } +} + } // namespace } // namespace Datadog } // namespace Tracers From 00a78942e343cee953df07ad6930ed33625c328a Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 Nov 2023 05:26:02 +0100 Subject: [PATCH 129/274] [bp/1.27] Datadog: restore "resource.name" tag (#30503) (#30892) This is the backport of #30503 Risk Level: low Testing: local unit tests Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Fixes: #30235 Signed-off-by: David Goffredo --- .../tracers/datadog/demo/docker-compose.yaml | 2 ++ source/extensions/tracers/datadog/demo/envoy | 2 +- source/extensions/tracers/datadog/span.cc | 16 ++++++++++++++-- test/extensions/tracers/datadog/span_test.cc | 12 +++++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/source/extensions/tracers/datadog/demo/docker-compose.yaml b/source/extensions/tracers/datadog/demo/docker-compose.yaml index 40b81aa75c333..1f42ec52daa19 100644 --- a/source/extensions/tracers/datadog/demo/docker-compose.yaml +++ b/source/extensions/tracers/datadog/demo/docker-compose.yaml @@ -8,9 +8,11 @@ services: dd-agent: volumes: - '/var/run/docker.sock:/var/run/docker.sock:ro' + - '/run/user:/run/user:ro' - '/proc/:/host/proc/:ro' - '/sys/fs/cgroup/:/host/sys/fs/cgroup:ro' environment: + - DOCKER_HOST - DD_API_KEY - DD_APM_ENABLED=true - DD_LOG_LEVEL=ERROR diff --git a/source/extensions/tracers/datadog/demo/envoy b/source/extensions/tracers/datadog/demo/envoy index 2a29dc40f391c..a84b13ed97e03 100755 --- a/source/extensions/tracers/datadog/demo/envoy +++ b/source/extensions/tracers/datadog/demo/envoy @@ -1,4 +1,4 @@ #!/bin/sh here=$(dirname "$0") -"$(bazelisk info bazel-genfiles)"/source/exe/envoy-static --config-path "$here"/envoy.yaml "$@" +"$here"/../../../../../bazel-bin/source/exe/envoy-static --config-path "$here"/envoy.yaml "$@" diff --git a/source/extensions/tracers/datadog/span.cc b/source/extensions/tracers/datadog/span.cc index 45922a20e620f..d027419152187 100644 --- a/source/extensions/tracers/datadog/span.cc +++ b/source/extensions/tracers/datadog/span.cc @@ -41,7 +41,9 @@ void Span::setOperation(absl::string_view operation) { return; } - span_->set_name(operation); + // What Envoy calls the operation name more closely corresponds to what + // Datadog calls the resource name. + span_->set_resource_name(operation); } void Span::setTag(absl::string_view name, absl::string_view value) { @@ -49,7 +51,17 @@ void Span::setTag(absl::string_view name, absl::string_view value) { return; } - span_->set_tag(name, value); + // The special "resource.name" tag is a holdover from when the Datadog tracer + // was OpenTracing-based, and so there was no way to set the Datadog resource + // name directly. + // In Envoy, it's still the case that there's no way to set the Datadog + // resource name directly; so, here if the tag name is "resource.name", we + // actually set the resource name instead of setting a tag. + if (name == "resource.name") { + span_->set_resource_name(value); + } else { + span_->set_tag(name, value); + } } void Span::log(SystemTime, const std::string&) { diff --git a/test/extensions/tracers/datadog/span_test.cc b/test/extensions/tracers/datadog/span_test.cc index 70b4fafd93525..aec618037cb55 100644 --- a/test/extensions/tracers/datadog/span_test.cc +++ b/test/extensions/tracers/datadog/span_test.cc @@ -128,7 +128,10 @@ TEST_F(DatadogTracerSpanTest, SetOperation) { ASSERT_NE(nullptr, data_ptr); const datadog::tracing::SpanData& data = *data_ptr; - EXPECT_EQ("gastric bypass", data.name); + // Setting the operation name actually sets the resource name, because Envoy's + // notion of operation name more closely matches Datadog's notion of resource + // name. + EXPECT_EQ("gastric bypass", data.resource); } TEST_F(DatadogTracerSpanTest, SetTag) { @@ -136,6 +139,7 @@ TEST_F(DatadogTracerSpanTest, SetTag) { span.setTag("foo", "bar"); span.setTag("boom", "bam"); span.setTag("foo", "new"); + span.setTag("resource.name", "vespene gas"); span.finishSpan(); ASSERT_EQ(1, collector_->chunks.size()); @@ -152,6 +156,12 @@ TEST_F(DatadogTracerSpanTest, SetTag) { found = data.tags.find("boom"); ASSERT_NE(data.tags.end(), found); EXPECT_EQ("bam", found->second); + + // The "resource.name" tag is special. It doesn't set a tag, but instead the + // span's resource name. + found = data.tags.find("resource.name"); + ASSERT_EQ(data.tags.end(), found); + EXPECT_EQ("vespene gas", data.resource); } TEST_F(DatadogTracerSpanTest, InjectContext) { From 502913c4f07f4e6690f9d8f072fdb18bf80a7839 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 9 Nov 2023 06:12:42 +0800 Subject: [PATCH 130/274] buffer: separate the BufferFragement release and drain tracker (#28770) Fixes #28760 Signed-off-by: He Jie Xu --- source/common/buffer/buffer_impl.h | 21 +++++++++- test/common/buffer/owned_impl_test.cc | 42 ++++++++++++++++++- .../user_space/io_handle_impl_test.cc | 20 +++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/source/common/buffer/buffer_impl.h b/source/common/buffer/buffer_impl.h index 058657dc5abd3..efaf6fa5ef388 100644 --- a/source/common/buffer/buffer_impl.h +++ b/source/common/buffer/buffer_impl.h @@ -90,7 +90,7 @@ class Slice { : capacity_(fragment.size()), storage_(nullptr), base_(static_cast(const_cast(fragment.data()))), reservable_(fragment.size()) { - addDrainTracker([&fragment]() { fragment.done(); }); + releasor_ = [&fragment]() { fragment.done(); }; } Slice(Slice&& rhs) noexcept { @@ -101,6 +101,7 @@ class Slice { reservable_ = rhs.reservable_; drain_trackers_ = std::move(rhs.drain_trackers_); account_ = std::move(rhs.account_); + releasor_.swap(rhs.releasor_); rhs.capacity_ = 0; rhs.base_ = nullptr; @@ -119,6 +120,11 @@ class Slice { reservable_ = rhs.reservable_; drain_trackers_ = std::move(rhs.drain_trackers_); account_ = std::move(rhs.account_); + if (releasor_) { + releasor_(); + } + releasor_ = rhs.releasor_; + rhs.releasor_ = nullptr; rhs.capacity_ = 0; rhs.base_ = nullptr; @@ -129,7 +135,12 @@ class Slice { return *this; } - ~Slice() { callAndClearDrainTrackersAndCharges(); } + ~Slice() { + callAndClearDrainTrackersAndCharges(); + if (releasor_) { + releasor_(); + } + } /** * @return true if the data in the slice is mutable @@ -307,6 +318,9 @@ class Slice { void transferDrainTrackersTo(Slice& destination) { destination.drain_trackers_.splice(destination.drain_trackers_.end(), drain_trackers_); ASSERT(drain_trackers_.empty()); + // The releasor needn't to be transferred, and actually if there is releasor, this + // slice can't coalesce. Then there won't be a chance to calling this method. + ASSERT(releasor_ == nullptr); } /** @@ -397,6 +411,9 @@ class Slice { /** Account associated with this slice. This may be null. When * coalescing with another slice, we do not transfer over their account. */ BufferMemoryAccountSharedPtr account_; + + /** The releasor for the BufferFragment */ + std::function releasor_; }; class OwnedImpl; diff --git a/test/common/buffer/owned_impl_test.cc b/test/common/buffer/owned_impl_test.cc index c575f5ad9719e..85fbdcf7a61f6 100644 --- a/test/common/buffer/owned_impl_test.cc +++ b/test/common/buffer/owned_impl_test.cc @@ -77,6 +77,44 @@ TEST_F(OwnedImplTest, AddBufferFragmentWithCleanup) { EXPECT_TRUE(release_callback_called_); } +TEST_F(OwnedImplTest, MoveBufferFragment) { + Buffer::OwnedImpl buffer1; + testing::MockFunction + release_callback_tracker; + std::string frag_input("a"); + BufferFragmentImpl frag(frag_input.c_str(), frag_input.size(), + release_callback_tracker.AsStdFunction()); + buffer1.addBufferFragment(frag); + + Buffer::OwnedImpl buffer2; + buffer2.move(buffer1); + + EXPECT_EQ(0, buffer1.length()); + EXPECT_EQ(1, buffer2.length()); + + EXPECT_CALL(release_callback_tracker, Call(_, _, _)); + buffer2.drain(buffer2.length()); +} + +TEST_F(OwnedImplTest, MoveBufferFragmentWithReleaseDrainTracker) { + Buffer::OwnedImpl buffer1; + testing::MockFunction + release_callback_tracker; + std::string frag_input("a"); + BufferFragmentImpl frag(frag_input.c_str(), frag_input.size(), + release_callback_tracker.AsStdFunction()); + buffer1.addBufferFragment(frag); + + Buffer::OwnedImpl buffer2; + buffer2.move(buffer1, true); + + EXPECT_EQ(0, buffer1.length()); + EXPECT_EQ(1, buffer2.length()); + + EXPECT_CALL(release_callback_tracker, Call(_, _, _)); + buffer2.drain(buffer2.length()); +} + TEST_F(OwnedImplTest, AddEmptyFragment) { char input[] = "hello world"; BufferFragmentImpl frag1(input, 11, [](const void*, size_t, const BufferFragmentImpl*) {}); @@ -667,10 +705,10 @@ TEST_F(OwnedImplTest, LinearizeDrainTracking) { testing::MockFunction done_tracker; EXPECT_CALL(tracker1, Call()); EXPECT_CALL(drain_tracker, Call(3 * LargeChunk + 108 * SmallChunk, 16384)); - EXPECT_CALL(release_callback_tracker, Call(_, _, _)); EXPECT_CALL(tracker2, Call()); - EXPECT_CALL(release_callback_tracker2, Call(_, _, _)); + EXPECT_CALL(release_callback_tracker, Call(_, _, _)); EXPECT_CALL(tracker3, Call()); + EXPECT_CALL(release_callback_tracker2, Call(_, _, _)); EXPECT_CALL(drain_tracker, Call(2 * LargeChunk + 107 * SmallChunk, 16384)); EXPECT_CALL(drain_tracker, Call(LargeChunk + 106 * SmallChunk, 16384)); EXPECT_CALL(tracker4, Call()); diff --git a/test/extensions/io_socket/user_space/io_handle_impl_test.cc b/test/extensions/io_socket/user_space/io_handle_impl_test.cc index 17aeb8b7a99c6..b3bf35512ef81 100644 --- a/test/extensions/io_socket/user_space/io_handle_impl_test.cc +++ b/test/extensions/io_socket/user_space/io_handle_impl_test.cc @@ -412,6 +412,26 @@ TEST_F(IoHandleImplTest, ShutDownOptionsNotSupported) { ASSERT_DEBUG_DEATH(io_handle_peer_->shutdown(ENVOY_SHUT_RDWR), ""); } +// This test is ensure the memory created by BufferFragment won't be released +// after the write. +TEST_F(IoHandleImplTest, WriteBufferFragement) { + Buffer::OwnedImpl buf("a"); + bool released = false; + auto buf_frag = Buffer::OwnedBufferFragmentImpl::create( + std::string(255, 'b'), [&released](const Buffer::OwnedBufferFragmentImpl* fragment) { + released = true; + delete fragment; + }); + buf.addBufferFragment(*buf_frag.release()); + + auto result = io_handle_->write(buf); + EXPECT_FALSE(released); + EXPECT_EQ(0, buf.length()); + io_handle_peer_->read(buf, absl::nullopt); + buf.drain(buf.length()); + EXPECT_TRUE(released); +} + TEST_F(IoHandleImplTest, WriteByMove) { Buffer::OwnedImpl buf("0123456789"); auto result = io_handle_peer_->write(buf); From 1cea1f307036be956cb8d5aa94077c61013c71bd Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Wed, 15 Nov 2023 12:02:22 +0000 Subject: [PATCH 131/274] add changelog Signed-off-by: He Jie Xu --- changelogs/current.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 29a3cd3f5f832..7a71d5669308f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -12,6 +12,10 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. - area: http change: | Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. From 06929b3d57be22fdd8ec6dd0170e6a6bcdef3f50 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 30 Oct 2023 08:45:01 +0000 Subject: [PATCH 132/274] deps/tooling: Make cve data preload optional (#30589) Signed-off-by: Ryan Northey --- .github/workflows/check-deps.yml | 2 +- .github/workflows/envoy-prechecks.yml | 54 --------------------------- ci/do_ci.sh | 1 + tools/dependency/BUILD | 25 +++++++++++-- 4 files changed, 23 insertions(+), 59 deletions(-) delete mode 100644 .github/workflows/envoy-prechecks.yml diff --git a/.github/workflows/check-deps.yml b/.github/workflows/check-deps.yml index 9bb8055cb624a..b652f213d9079 100644 --- a/.github/workflows/check-deps.yml +++ b/.github/workflows/check-deps.yml @@ -35,6 +35,6 @@ jobs: TODAY_DATE=$(date -u -I"date") export TODAY_DATE bazel run //tools/dependency:check --action_env=TODAY_DATE -- -c release_issues --fix - bazel run //tools/dependency:check --action_env=TODAY_DATE -- -c cves -w error + bazel run --//tools/dependency:preload_cve_data //tools/dependency:check --action_env=TODAY_DATE -- -c cves -w error env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml deleted file mode 100644 index d12715c918cbe..0000000000000 --- a/.github/workflows/envoy-prechecks.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Envoy/prechecks - -permissions: - contents: read - -on: - push: - branches: - - main - - release/v* - pull_request_target: - paths: - - '**/requirements*.txt' - - '**/go.mod' - - '**/*.bzl' - - 'WORKSPACE' - - '.github/workflows/envoy-prechecks.yml' - - '.github/workflows/_*.yml' - -concurrency: - group: ${{ github.event.inputs.head_ref || github.run_id }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - uses: ./.github/workflows/_env.yml - with: - prime_build_image: true - check_mobile_run: false - permissions: - contents: read - packages: read - - prechecks: - needs: - - env - strategy: - fail-fast: false - matrix: - include: - - target: deps - rbe: false - managed: true - uses: ./.github/workflows/_ci.yml - name: CI ${{ matrix.target }} - permissions: - contents: read - packages: read - with: - target: ${{ matrix.target }} - rbe: ${{ matrix.rbe }} - bazel_extra: '--config=rbe-envoy-engflow' - managed: ${{ matrix.managed }} - cache_build_image: ${{ needs.env.outputs.build_image_ubuntu }} diff --git a/ci/do_ci.sh b/ci/do_ci.sh index aaf9be5d4d7e1..c77ddd36f7b0c 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -518,6 +518,7 @@ case $CI_TARGET in TODAY_DATE=$(date -u -I"date") export TODAY_DATE bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/dependency:check \ + --//tools/dependency:preload_cve_data \ --action_env=TODAY_DATE \ -- -v warn \ -c cves release_dates releases diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index c97f59c223d5d..7969cd51d6295 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -1,4 +1,5 @@ load("@base_pip3//:requirements.bzl", "requirement") +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load("@envoy_repo//:path.bzl", "PATH") load("//bazel:envoy_build_system.bzl", "envoy_package") load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") @@ -10,18 +11,34 @@ envoy_package() envoy_py_namespace() +bool_flag( + name = "preload_cve_data", + build_setting_default = False, +) + +config_setting( + name = "preloaded_cve_data", + flag_values = { + ":preload_cve_data": "true", + }, +) + envoy_entry_point( name = "check", args = [ "--repository_locations=$(location //bazel:all_repository_locations)", "--cve_config=$(location :cve.yaml)", - "--cve_data=$(location :cve_data)", - ], + ] + select({ + ":preloaded_cve_data": ["--cve_data=$(location :cve_data)"], + "//conditions:default": [], + }), data = [ ":cve.yaml", - ":cve_data", "//bazel:all_repository_locations", - ], + ] + select({ + ":preloaded_cve_data": [":cve_data"], + "//conditions:default": [], + }), pkg = "envoy.dependency.check", deps = [requirement("orjson")], ) From 27a1448cd44d3c0a77f0a1ce4b4ecdfa2bdf36db Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 22 Oct 2023 15:25:39 +0100 Subject: [PATCH 133/274] bazel/deps: Add updater tool (#30368) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .github/workflows/envoy-dependency.yml | 171 +++++++++++++++++++++++++ api/bazel/repository_locations.bzl | 6 +- bazel/BUILD | 34 ++++- bazel/version_update_post.sh | 78 +++++++++++ 4 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/envoy-dependency.yml create mode 100644 bazel/version_update_post.sh diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml new file mode 100644 index 0000000000000..50582d9e7393f --- /dev/null +++ b/.github/workflows/envoy-dependency.yml @@ -0,0 +1,171 @@ +name: Envoy/dependency + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + task: + description: Select a task + required: true + default: bazel + type: choice + options: + - bazel + - bazel-api + - build-image + dependency: + description: Dependency to update (if applicable) + version: + description: Version to set (optional) + pr: + type: boolean + default: true + pr_message: + description: Additional message for PR, eg to fix an issue (optional) + +concurrency: + group: ${{ github.run_id }}-${{ github.workflow }} + cancel-in-progress: true + + +jobs: + update_bazel: + if: startsWith(inputs.task, 'bazel') + name: >- + Update dep + (${{ inputs.pr && 'PR/' || '' }}${{ inputs.task == 'bazel' && 'bazel' || 'bazel/api' }}/${{ inputs.dependency }}/${{ inputs.version }}) + runs-on: ubuntu-22.04 + steps: + - id: checkout + name: Checkout Envoy repository + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.0.25 + with: + app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} + app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} + - id: version + name: Shorten (possible) SHA + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.0.25 + with: + string: ${{ inputs.version }} + length: 7 + min: 40 + - run: | + echo "Updating(${TASK}): ${DEPENDENCY} -> ${VERSION}" + bazel run --config=ci //bazel:${TARGET} $DEPENDENCY $VERSION + name: Update dependency + env: + DEPENDENCY: ${{ inputs.dependency }} + VERSION: ${{ inputs.version }} + TARGET: ${{ inputs.task == 'bazel' && 'update' || 'api-update' }} + TASK: ${{ inputs.task == 'bazel' && 'bazel' || 'api/bazel' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.0.25 + name: Upload diff + with: + name: ${{ inputs.dependency }}-${{ steps.version.outputs.string }} + - name: Create a PR + if: ${{ inputs.pr }} + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.0.25 + with: + base: main + body: | + Created by Envoy dependency bot for @${{ github.actor }} + + ${{ inputs.pr_message }} + branch: >- + dependency/${{ inputs.task }}/${{ inputs.dependency }}/${{ steps.version.outputs.string }} + committer-name: publish-envoy[bot] + committer-email: 140627008+publish-envoy[bot]@users.noreply.github.com + title: >- + ${{ inputs.task == 'bazel' && 'deps' || 'deps/api' }}: Bump `${{ inputs.dependency }}` + -> ${{ steps.version.outputs.string }} + GITHUB_TOKEN: ${{ steps.checkout.outputs.token }} + + update_build_image: + if: github.event.inputs.task == 'build-image' + name: Update build image (PR) + runs-on: ubuntu-22.04 + steps: + - name: Fetch token for app auth + id: appauth + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.23 + with: + app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} + key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} + - uses: actions/checkout@v4 + name: Checkout Envoy repository + with: + path: envoy + fetch-depth: 0 + token: ${{ steps.appauth.outputs.token }} + - uses: actions/checkout@v4 + name: Checkout Envoy build tools repository + with: + repository: envoyproxy/envoy-build-tools + path: build-tools + fetch-depth: 0 + - run: | + shas=( + tag + sha + mobile_sha + gcr_sha) + for sha in "${shas[@]}"; do + current_sha=$(bazel run //tools/dependency:build-image-sha "$sha") + echo "${sha}=${current_sha}" >> "$GITHUB_OUTPUT" + done + id: current + name: Current SHAs + working-directory: envoy + - run: | + # get current build image version + CONTAINER_TAG=$(git log -1 --pretty=format:"%H" "./docker") + echo "tag=${CONTAINER_TAG}" >> "$GITHUB_OUTPUT" + echo "tag_short=${CONTAINER_TAG::7}" >> "$GITHUB_OUTPUT" + id: build-tools + name: Build image SHA + working-directory: build-tools + + - name: Check Docker SHAs + id: build-images + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.0.23 + with: + images: | + sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} + mobile_sha: envoyproxy/envoy-build-ubuntu:mobile-${{ steps.build-tools.outputs.tag }} + gcr_sha: gcr.io/envoy-ci/envoy-build:${{ steps.build-tools.outputs.tag }} + + - run: | + SHA_REPLACE=( + "$CURRENT_ENVOY_TAG:$ENVOY_TAG" + "$CURRENT_ENVOY_SHA:${OUTPUT_sha}" + "$CURRENT_ENVOY_MOBILE_SHA:${OUTPUT_mobile_sha}" + "$CURRENT_ENVOY_GCR_SHA:${OUTPUT_gcr_sha}") + echo "replace=${SHA_REPLACE[*]}" >> "$GITHUB_OUTPUT" + name: Find SHAs to replace + id: shas + env: + ENVOY_TAG: ${{ steps.build-tools.outputs.tag }} + CURRENT_ENVOY_TAG: ${{ steps.current.outputs.tag }} + CURRENT_ENVOY_SHA: ${{ steps.current.outputs.sha }} + CURRENT_ENVOY_MOBILE_SHA: ${{ steps.current.outputs.mobile_sha }} + CURRENT_ENVOY_GCR_SHA: ${{ steps.current.outputs.gcr_sha }} + - run: | + echo "${SHA_REPLACE}" | xargs bazel run @envoy_toolshed//sha:replace "${PWD}" + env: + SHA_REPLACE: ${{ steps.shas.outputs.replace }} + name: Update SHAs + working-directory: envoy + - name: Create a PR + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.0.23 + with: + base: main + body: Created by Envoy dependency bot + branch: dependency-envoy/build-image/latest + committer-name: publish-envoy[bot] + committer-email: 140627008+publish-envoy[bot]@users.noreply.github.com + title: 'deps: Bump build images -> `${{ steps.build-tools.outputs.tag_short }}`' + GITHUB_TOKEN: ${{ steps.appauth.outputs.token }} + working-directory: envoy diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 903b61f129aee..b250c62482857 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -155,12 +155,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy_toolshed", project_desc = "Tooling, libraries, runners and checkers for Envoy proxy's CI", project_url = "https://github.com/envoyproxy/toolshed", - version = "0.0.10", - sha256 = "bdfcf0a23c18a99887ac25761aa56d85bedb6eda77c89f9f19e6142b812749b9", + version = "0.1.1", + sha256 = "ee759b57270a2747f3f2a3d6ecaad63b834dd9887505a9f1c919d72429dbeffd", strip_prefix = "toolshed-bazel-v{version}/bazel", urls = ["https://github.com/envoyproxy/toolshed/archive/bazel-v{version}.tar.gz"], use_category = ["build"], - release_date = "2023-10-02", + release_date = "2023-10-21", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/envoyproxy/envoy/blob/bazel-v{version}/LICENSE", diff --git a/bazel/BUILD b/bazel/BUILD index 8b15825a9170b..d520ed2e94fb9 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -1,11 +1,12 @@ -load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//bazel:envoy_internal.bzl", "envoy_select_force_libcpp") -load("@envoy_toolshed//:macros.bzl", "json_data") load("@bazel_skylib//lib:selects.bzl", "selects") load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") -load(":repository_locations.bzl", "REPOSITORY_LOCATIONS_SPEC") load("@envoy_api//bazel:repository_locations.bzl", API_REPOSITORY_LOCATIONS_SPEC = "REPOSITORY_LOCATIONS_SPEC") load("@envoy_api//bazel:repository_locations_utils.bzl", "load_repository_locations_spec", "merge_dicts") +load("@envoy_toolshed//:macros.bzl", "json_data") +load("@envoy_toolshed//dependency:macros.bzl", "updater") +load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//bazel:envoy_internal.bzl", "envoy_select_force_libcpp") +load(":repository_locations.bzl", "REPOSITORY_LOCATIONS_SPEC") licenses(["notice"]) # Apache 2 @@ -882,3 +883,28 @@ cc_library( name = "python_headers", visibility = ["//visibility:public"], ) + +# These can be run as follows: +# +# $ bazel run //bazel:update ENVOY_DEP NEW_VERSION +# $ bazel run //bazel:api-update API_DEP NEW_VERSION +updater( + name = "update", + data = ["//tools/dependency:check"], + dependencies = ":repository_locations", + post_script = ":version_update_post.sh", + pydict = True, + tags = ["skip_on_windows"], + version_file = ":repository_locations.bzl", +) + +updater( + name = "api-update", + data = ["//tools/dependency:check"], + dependencies = "@envoy_api//bazel:repository_locations", + post_script = ":version_update_post.sh", + pydict = True, + tags = ["skip_on_windows"], + version_file = "@envoy_api//bazel:repository_locations.bzl", + version_path_replace = "external/envoy_api:api", +) diff --git a/bazel/version_update_post.sh b/bazel/version_update_post.sh new file mode 100644 index 0000000000000..ac877c1861f30 --- /dev/null +++ b/bazel/version_update_post.sh @@ -0,0 +1,78 @@ +#!/bin/bash -e + +set -o pipefail + + +EXISTING_DATE="$("${JQ}" -r ".${DEP}.release_date" "${DEP_DATA}")" +DATE_SEARCH="release_date = \"${EXISTING_DATE}\"," +DEP_CHECK="${DEP_CHECK:-tools/dependency/check}" + +find_date_line () { + local match match_ln date_match_ln + # This needs to find the correct date to replace + match="$(\ + grep -n "${DEP_SEARCH}" "${VERSION_FILE}" \ + | cut -d: -f-2)" + match_ln="$(\ + echo "${match}" \ + | cut -d: -f1)" + match_ln="$((match_ln + 1))" + date_match_ln="$(\ + tail -n "+${match_ln}" "${VERSION_FILE}" \ + | grep -n "${DATE_SEARCH}" \ + | head -n1 \ + | cut -d: -f1)" + date_match_ln="$((match_ln + date_match_ln - 1))" + printf '%s' "$date_match_ln" +} + +update_date () { + local match_ln search replace + match_ln="$1" + search="$2" + replace="$3" + echo "Updating date(${match_ln}): ${search} -> ${replace}" + sed -i "${match_ln}s/${search}/${replace}/" "$VERSION_FILE" +} + +get_new_date () { + # create a repository_locations with just the dep and with updated version + tmpfile="$(mktemp)" + # shellcheck disable=SC2016 + "$JQ" --arg new_version "$VERSION" \ + --arg existing_version "$EXISTING_VERSION" \ + --arg dep "$DEP" \ + 'if has($dep) then .[$dep].version = $new_version | .[$dep].urls |= map(gsub($existing_version; $new_version)) else . end' \ + "$DEP_DATA" > "$tmpfile" + output="$(\ + "$DEP_CHECK" \ + --repository_locations="$tmpfile" \ + --path "${BUILD_WORKSPACE_DIRECTORY}" \ + -c release_dates 2>&1)" + echo "$output" \ + | grep -E "^Mismatch" \ + | grep "$DEP" \ + | cut -d= -f2 \ + | xargs || { + cat "$tmpfile" >&2 + echo "$output" >&2 + rm "$tmpfile" + exit 1 + } + rm "$tmpfile" +} + +post_version_update () { + local date_ln new_date + if [[ "$EXISTING_VERSION" == "$VERSION" ]]; then + echo "Nothing to update" >&2 + exit 0 + fi + date_ln="$(find_date_line)" + new_date="$(get_new_date)" + if [[ -z "$new_date" ]]; then + echo "Unable to retrieve date" >&2 + exit 1 + fi + update_date "$date_ln" "$EXISTING_DATE" "$new_date" +} From 999ffb72229f9d1fcde61be063133f41882d8998 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 11 Dec 2023 07:50:48 +0000 Subject: [PATCH 134/274] github/ci: Update branch to new framework Signed-off-by: Ryan Northey --- .azure-pipelines/stage/publish.yml | 1 + .github/actions/do_ci/action.yml | 90 ------ .github/actions/env/action.yml | 196 ------------- .github/actions/pr_notifier/pr_notifier.py | 266 ------------------ .github/actions/pr_notifier/requirements.in | 2 - .github/actions/pr_notifier/requirements.txt | 226 --------------- .../actions/publish/release/setup/action.yml | 26 -- .../actions/verify/examples/setup/action.yml | 37 --- .github/config.yml | 166 +++++++++++ .github/workflows/_cache_docker.yml | 43 --- .github/workflows/_ci.yml | 183 ------------ .github/workflows/_env.yml | 178 ------------ .github/workflows/_stage_publish.yml | 126 --------- .github/workflows/_stage_verify.yml | 53 ---- .github/workflows/_workflow-start.yml | 50 ---- .github/workflows/commands.yml | 27 -- .github/workflows/envoy-publish.yml | 83 ------ .github/workflows/request.yml | 39 +++ .github/workflows/workflow-complete.yml | 62 ---- ci/do_ci.sh | 23 +- 20 files changed, 218 insertions(+), 1659 deletions(-) delete mode 100644 .github/actions/do_ci/action.yml delete mode 100644 .github/actions/env/action.yml delete mode 100644 .github/actions/pr_notifier/pr_notifier.py delete mode 100644 .github/actions/pr_notifier/requirements.in delete mode 100644 .github/actions/pr_notifier/requirements.txt delete mode 100644 .github/actions/publish/release/setup/action.yml delete mode 100644 .github/actions/verify/examples/setup/action.yml create mode 100644 .github/config.yml delete mode 100644 .github/workflows/_cache_docker.yml delete mode 100644 .github/workflows/_ci.yml delete mode 100644 .github/workflows/_env.yml delete mode 100644 .github/workflows/_stage_publish.yml delete mode 100644 .github/workflows/_stage_verify.yml delete mode 100644 .github/workflows/_workflow-start.yml delete mode 100644 .github/workflows/commands.yml delete mode 100644 .github/workflows/envoy-publish.yml create mode 100644 .github/workflows/request.yml delete mode 100644 .github/workflows/workflow-complete.yml diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index b361552e4e205..30e62ebc362c9 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -292,6 +292,7 @@ jobs: publishEnvoy: false publishTestResults: false env: + ENVOY_REPO: $(Build.Repository.Name) ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: ENVOY_HEAD_REF: "$(Build.SourceBranch)" ENVOY_BRANCH: "$(System.PullRequest.TargetBranch)" diff --git a/.github/actions/do_ci/action.yml b/.github/actions/do_ci/action.yml deleted file mode 100644 index 5a024feede039..0000000000000 --- a/.github/actions/do_ci/action.yml +++ /dev/null @@ -1,90 +0,0 @@ -inputs: - target: - required: true - type: string - rbe: - type: boolean - default: true - managed: - type: boolean - default: true - - auth_bazel_rbe: - type: string - default: '' - - bazel_extra: - type: string - default: - bazel_local_cache: - type: string - default: - bazel_rbe_cache: - type: string - default: grpcs://remotebuildexecution.googleapis.com - bazel_rbe_instance: - type: string - default: projects/envoy-ci/instances/default_instance - bazel_rbe_jobs: - type: number - default: 75 - - command_prefix: - type: string - default: ./ci/run_envoy_docker.sh - command_ci: - type: string - default: ./ci/do_ci.sh - - env: - type: string - - GITHUB_TOKEN: - required: true - -runs: - using: composite - steps: - - id: do_ci - name: 'Run CI target ${{ inputs.target }}' - run: | - if [[ "${#INPUT_ENV}" -ne 0 ]]; then - SOURCETMP="$(mktemp)" - # TODO(phlax): Fix escaping - echo "${{ inputs.env }}" > "$SOURCETMP" - . "$SOURCETMP" - rm -rf "$SOURCETMP" - fi - if [[ "${{ inputs.rbe }}" == 'true' ]]; then - export ENVOY_RBE=1 - export GCP_SERVICE_ACCOUNT_KEY=${{ inputs.auth_bazel_rbe }} - export BAZEL_BUILD_EXTRA_OPTIONS="--config=remote-ci --jobs=${{ inputs.bazel_rbe_jobs }} ${{ inputs.bazel_extra }}" - export BAZEL_REMOTE_CACHE=${{ inputs.bazel_rbe_cache }}" - export BAZEL_REMOTE_INSTANCE=${{ inputs.bazel_rbe_instance }}" - else - export BAZEL_BUILD_EXTRA_OPTIONS="--config=ci ${{ inputs.bazel_extra }}" - export BAZEL_REMOTE_CACHE="${{ inputs.bazel_local_cache }}" - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - export BAZEL_REMOTE_INSTANCE_BRANCH="${{ github.event.base.ref }}" - else - export BAZEL_REMOTE_INSTANCE_BRANCH="${{ github.ref }}" - fi - fi - - if [[ -n "${{ inputs.command_prefix }}" ]]; then - ${{ inputs.command_prefix }} '${{ inputs.command_ci }} ${{ inputs.target }}' - else - ${{ inputs.command_ci }} ${{ inputs.target }} - fi - - if [[ ${{ github.event_name }} == "pull_request" ]]; then - export BAZEL_FAKE_SCM_REVISION=e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 - export CI_TARGET_BRANCH="${{ github.event.base.ref }}" - else - export CI_TARGET_BRANCH="${{ github.ref }}" - fi - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} - ENVOY_DOCKER_BUILD_DIR: ${{ runner.temp }} - INPUT_ENV: ${{ inputs.env }} diff --git a/.github/actions/env/action.yml b/.github/actions/env/action.yml deleted file mode 100644 index d30cab498dc57..0000000000000 --- a/.github/actions/env/action.yml +++ /dev/null @@ -1,196 +0,0 @@ -inputs: - build_image_tag: - type: string - required: true - build_image_repo: - type: string - required: true - build_image_mobile_sha: - type: string - required: true - build_image_sha: - type: string - required: true - - repo_ref: - type: string - repo_ref_sha: - type: string - repo_ref_name: - type: string - - trusted_bots: - type: string - default: | - trigger-release-envoy[bot] - - check_mobile_run: - type: boolean - default: true - -outputs: - build_image_ubuntu: - value: ${{ steps.build.outputs.build_image_ubuntu }} - build_image_ubuntu_mobile: - value: ${{ steps.build.outputs.build_image_ubuntu_mobile }} - - mobile_android_build: - value: ${{ steps.should_run.outputs.mobile_android_build }} - mobile_android_build_all: - value: ${{ steps.should_run.outputs.mobile_android_build_all }} - mobile_android_tests: - value: ${{ steps.should_run.outputs.mobile_android_tests }} - mobile_asan: - value: ${{ steps.should_run.outputs.mobile_asan }} - mobile_cc_tests: - value: ${{ steps.should_run.outputs.mobile_cc_tests }} - mobile_compile_time_options: - value: ${{ steps.should_run.outputs.mobile_compile_time_options }} - mobile_coverage: - value: ${{ steps.should_run.outputs.mobile_coverage }} - mobile_formatting: - value: ${{ steps.should_run.outputs.mobile_formatting }} - mobile_ios_build: - value: ${{ steps.should_run.outputs.mobile_ios_build }} - mobile_ios_build_all: - value: ${{ steps.should_run.outputs.mobile_ios_build_all }} - mobile_ios_tests: - value: ${{ steps.should_run.outputs.mobile_ios_tests }} - mobile_release_validation: - value: ${{ steps.should_run.outputs.mobile_release_validation }} - mobile_tsan: - value: ${{ steps.should_run.outputs.mobile_tsan }} - repo_ref: - value: ${{ steps.context.outputs.repo_ref }} - repo_ref_name: - value: ${{ steps.context.outputs.repo_ref_name }} - repo_ref_pr_number: - value: ${{ steps.context.outputs.repo_ref_pr_number }} - repo_ref_sha: - value: ${{ steps.context.outputs.repo_ref_sha }} - repo_ref_sha_short: - value: ${{ steps.context.outputs.repo_ref_sha_short }} - repo_ref_title: - value: ${{ steps.context.outputs.repo_ref_title }} - trusted: - value: ${{ steps.trusted.outputs.trusted }} - version_dev: - value: ${{ steps.context.outputs.version_dev }} - version_patch: - value: ${{ steps.context.outputs.version_patch }} - -runs: - using: composite - steps: - # Pull request/targets are _never_ trusted. - # - # For dispatch events, only specified bots are trusted. - # - # Commits to a branch are always trusted. - # - # If code is trusted its not allowed to check out any - # non-ancestor commit of a stable branch. - # - # Untrusted code can check out any commit. - - id: trusted - name: 'Check if its a trusted run' - run: | - TRUSTED=1 - ACTOR="${{ github.actor }}" - if [[ "$ACTOR" =~ \[bot\] ]]; then - TRUSTED_BOT= - TRUSTED_BOTS=(${{ inputs.trusted_bots }}) - for bot in ${TRUSTED_BOTS[@]}; do - if [[ "$bot" == "$ACTOR" ]]; then - # Trusted bot account, ie non-PR - TRUSTED_BOT=1 - break - fi - done - if [[ -z "$TRUSTED_BOT" ]]; then - echo "Not trusted bot account" - TRUSTED= - fi - fi - if [[ "${{ github.event_name }}" == "pull_request" || "${{ github.event_name }}" == "pull_request_target" ]]; then - echo "Not trusted pull_request event" - TRUSTED= - fi - if [[ -n "$TRUSTED" ]]; then - echo "trusted=true" >> "$GITHUB_OUTPUT" - else - echo "trusted=false" >> "$GITHUB_OUTPUT" - fi - shell: bash - - # If we are in a trusted CI run then the provided commit _must_ be either the latest for - # this branch, or an antecdent. - - run: | - if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD &> /dev/null; then - echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 - exit 1 - fi - git checkout "${{ inputs.repo_ref }}" - if: ${{ steps.trusted.outputs.trusted == 'true' && inputs.repo_ref }} - name: Check provided ref - shell: bash - - - if: ${{ inputs.check_mobile_run != 'false' }} - id: should_run - name: 'Check what to run' - run: ./mobile/tools/what_to_run.sh - shell: bash - - - id: context - name: 'CI context' - run: | - if grep dev VERSION.txt; then - VERSION_DEV="$(cat VERSION.txt | cut -d- -f2)" - else - VERSION_DEV="" - fi - VERSION_PATCH="$(cat VERSION.txt | cut -d- -f1 | rev | cut -d. -f1 | rev)" - # TODO: strip merge from pr names - REF_NAME=${{ inputs.repo_ref_name || github.ref_name }} - if [[ "$REF_NAME" =~ ^refs/pull/ ]]; then - REF_NAME="${REF_NAME:10}" - REF_PR_NUMBER="$(echo "${REF_NAME}" | cut -d/ -f1)" - elif [[ "${{ github.event_name }}" == 'pull_request' ]]; then - REF_PR_NUMBER="$(echo "${REF_NAME}" | cut -d/ -f1)" - fi - echo "SET PR NUMBER: ${REF_PR_NUMBER}" - - REF="${{ steps.trusted.outputs.trusted != 'true' && inputs.repo_ref || '' }}" - REF_SHA=${{ inputs.repo_ref_sha || github.event.pull_request.head.sha || github.sha }} - REF_SHA_SHORT="${REF_SHA:0:7}" - REF_TITLE=( - "${{ steps.trusted.outputs.trusted == 'true' && 'postsubmit' || 'pr' }}/" - "${REF_NAME}" - "@${REF_SHA_SHORT}") - REF_TITLE="$(printf %s "${REF_TITLE[@]}" $'\n')" - { - echo "repo_ref=$REF" - echo "repo_ref_name=$REF_NAME" - echo "repo_ref_pr_number=$REF_PR_NUMBER" - echo "repo_ref_sha=$REF_SHA" - echo "repo_ref_title=$REF_TITLE" - echo "repo_ref_sha_short=$REF_SHA_SHORT" - echo "version_dev=$VERSION_DEV" - echo "version_patch=$VERSION_PATCH" - } >> "$GITHUB_OUTPUT" - shell: bash - - - id: build - name: 'Check current build images' - run: | - { - echo "build_image_ubuntu=${BUILD_IMAGE_UBUNTU_REPO}:${BUILD_IMAGE_UBUNTU}@sha256:${BUILD_IMAGE_UBUNTU_SHA}" - echo "build_image_ubuntu_mobile=${BUILD_IMAGE_UBUNTU_REPO}:mobile-${BUILD_IMAGE_UBUNTU}@sha256:${BUILD_IMAGE_UBUNTU_MOBILE_SHA}" - } >> "$GITHUB_OUTPUT" - env: - # TODO(phlax): derive these from a config file - BUILD_IMAGE_UBUNTU_REPO: ${{ inputs.build_image_repo }} - BUILD_IMAGE_UBUNTU: ${{ inputs.build_image_tag }} - BUILD_IMAGE_UBUNTU_SHA: ${{ inputs.build_image_sha }} - BUILD_IMAGE_UBUNTU_MOBILE_SHA: ${{ inputs.build_image_mobile_sha }} - shell: bash diff --git a/.github/actions/pr_notifier/pr_notifier.py b/.github/actions/pr_notifier/pr_notifier.py deleted file mode 100644 index 5ad39556efe36..0000000000000 --- a/.github/actions/pr_notifier/pr_notifier.py +++ /dev/null @@ -1,266 +0,0 @@ -# Script for collecting PRs in need of review, and informing maintainers via -# slack. -# -# By default this runs in "developer mode" which means that it collects PRs -# associated with maintainers and API reviewers, and spits them out (badly -# formatted) to the command line. -# -# .github/workflows/pr_notifier.yml runs the script with --cron_job -# which instead sends the collected PRs to the various slack channels. -# -# NOTE: Slack IDs can be found in the user's full profile from within Slack. - -from __future__ import print_function - -import argparse -import datetime -import os -import sys - -import github -from slack_sdk import WebClient -from slack_sdk.errors import SlackApiError - -MAINTAINERS = { - 'alyssawilk': 'U78RP48V9', - 'mattklein123': 'U5CALEVSL', - 'lizan': 'U79E51EQ6', - 'snowp': 'U93KTPQP6', - 'ggreenway': 'U78MBV869', - 'htuch': 'U78E7055Z', - 'zuercher': 'U78J72Q82', - 'phlax': 'U017PLM0GNQ', - 'jmarantz': 'U80HPLBPG', - 'ravenblackx': 'U02MJHFEX35', - 'yanavlasov': 'UJHLR5KFS', - 'RyanTheOptimist': 'U01SW3JC8GP', - 'adisuissa': 'UT17EMMTP', - 'KBaichoo': 'U016ZPU8KBK', - 'wbpcode': 'U017KF5C0Q6', - 'kyessenov': 'U7KTRAA8M', - 'keith': 'UGS5P90CF', - 'abeyad': 'U03CVM7GPM1', -} - -# First pass reviewers who are not maintainers should get -# notifications but not result in a PR not getting assigned a -# maintainer owner. -FIRST_PASS = { - 'dmitri-d': 'UB1883Q5S', - 'tonya11en': 'U989BG2CW', - 'esmet': 'U01BCGBUUAE', - 'mathetake': 'UG9TD2FSB', -} - -# Only notify API reviewers who aren't maintainers. -# Maintainers are already notified of pending PRs. -API_REVIEWERS = { - 'markdroth': 'UMN8K55A6', - 'adisuissa': 'UT17EMMTP', -} - - -def get_slo_hours(): - # on Monday, allow for 24h + 48h - if datetime.date.today().weekday() == 0: - return 72 - return 24 - - -# Return true if the PR has a waiting tag, false otherwise. -def is_waiting(labels): - for label in labels: - if label.name == 'waiting' or label.name == 'waiting:any': - return True - return False - - -def is_contrib(labels): - return any(label.name == "contrib" for label in labels) - - -# Return true if the PR has an API tag, false otherwise. -def is_api(labels): - for label in labels: - if label.name == 'api': - return True - return False - - -# Generate a pr message, bolding the time if it's out-SLO -def pr_message(pr_age, pr_url, pr_title, delta_days, delta_hours): - if pr_age < datetime.timedelta(hours=get_slo_hours()): - return "<%s|%s> has been waiting %s days %s hours\n" % ( - pr_url, pr_title, delta_days, delta_hours) - else: - return "<%s|%s> has been waiting *%s days %s hours*\n" % ( - pr_url, pr_title, delta_days, delta_hours) - - -# Adds reminder lines to the appropriate assignee to review the assigned PRs -# Returns true if one of the assignees is in the primary_assignee_map, false otherwise. -def add_reminders( - assignees, assignees_and_prs, message, primary_assignee_map, first_pass_assignee_map): - has_primary_assignee = False - for assignee_info in assignees: - assignee = assignee_info.login - if assignee in primary_assignee_map: - has_primary_assignee = True - elif assignee not in first_pass_assignee_map: - continue - if assignee not in assignees_and_prs.keys(): - assignees_and_prs[ - assignee] = "Hello, %s, here are your PR reminders for the day \n" % assignee - assignees_and_prs[assignee] = assignees_and_prs[assignee] + message - return has_primary_assignee - - -# Returns true if the PR needs an LGTM from an API shephard. -def needs_api_review(labels, repo, pr_info): - # API reviews should always have the label, so don't bother doing an RPC if - # it's not tagged (this helps avoid github rate limiting) - if not (is_api(labels)): - return False - # repokitten tags each commit as pending unless there has been an API LGTM - # since the latest API changes. If this PR is tagged pendding it needs an - # API review, otherwise it's set. - status = repo.get_commit(pr_info.head.sha).get_statuses() - return status[0].state == "pending" if status.totalCount else False - - -def track_prs(github_token): - git = github.Github(github_token) - - repo = git.get_repo('envoyproxy/envoy') - - # The list of PRs which are not waiting, but are well within review SLO - recent_prs = [] - # A dict of maintainer : outstanding_pr_string to be sent to slack - maintainers_and_prs = {} - # A placeholder for unassigned PRs, to be sent to #maintainers eventually - maintainers_and_prs['unassigned'] = "" - # A dict of shephard : outstanding_pr_string to be sent to slack - api_review_and_prs = {} - # Out-SLO PRs to be sent to #envoy-maintainer-oncall - stalled_prs = "" - - # Snag all PRs, including drafts - for pr_info in repo.get_pulls("open", "updated", "desc"): - labels = pr_info.labels - assignees = pr_info.assignees - # If the PR is waiting, continue. - if is_waiting(labels): - continue - # Drafts are not covered by our SLO (repokitteh warns of this) - if pr_info.draft: - continue - # Don't warn for dependabot. - if pr_info.user.login == 'dependabot[bot]': - continue - - # Update the time based on the time zone delta from github's - pr_age = pr_info.updated_at - datetime.timedelta(hours=4) - delta = datetime.datetime.now() - pr_age - delta_days = delta.days - delta_hours = delta.seconds // 3600 - - # If we get to this point, the review may be in SLO - nudge if it's in - # SLO, nudge in bold if not. - message = pr_message(delta, pr_info.html_url, pr_info.title, delta_days, delta_hours) - - if (needs_api_review(labels, repo, pr_info)): - add_reminders(pr_info.assignees, api_review_and_prs, message, API_REVIEWERS, []) - - # If the PR has been out-SLO for over a day, inform on-call - if delta > datetime.timedelta(hours=get_slo_hours() + 36): - stalled_prs = stalled_prs + message - - # Add a reminder to each maintainer-assigner on the PR. - has_maintainer_assignee = add_reminders( - pr_info.assignees, maintainers_and_prs, message, MAINTAINERS, FIRST_PASS) - - # If there was no maintainer, track it as unassigned. - if not has_maintainer_assignee and not is_contrib(labels): - maintainers_and_prs['unassigned'] = maintainers_and_prs['unassigned'] + message - - # Return the dict of {maintainers : PR notifications}, - # the dict of {api-shephards-who-are-not-maintainers: PR notifications}, - # and stalled PRs - return maintainers_and_prs, api_review_and_prs, stalled_prs - - -def post_to_assignee(client, assignees_and_messages, assignees_map): - # Post updates to individual assignees - for key in assignees_and_messages: - message = assignees_and_messages[key] - - # Only send messages if we have the slack UID - if key not in assignees_map: - continue - uid = assignees_map[key] - - # Ship messages off to slack. - try: - print(assignees_and_messages[key]) - response = client.conversations_open(users=uid, text="hello") - channel_id = response["channel"]["id"] - client.chat_postMessage(channel=channel_id, text=message) - except SlackApiError as e: - print("Unexpected error %s", e.response["error"]) - - -def post_to_oncall(client, unassigned_prs, out_slo_prs): - # Post updates to #envoy-maintainer-oncall - unassigned_prs = maintainers_and_messages['unassigned'] - try: - client.chat_postMessage( - channel='#envoy-maintainer-oncall', - text=("*'Unassigned' PRs* (PRs with no maintainer assigned)\n%s" % unassigned_prs)) - client.chat_postMessage( - channel='#envoy-maintainer-oncall', - text=("*Stalled PRs* (PRs with review out-SLO, please address)\n%s" % out_slo_prs)) - issue_link = "https://github.com/envoyproxy/envoy/issues?q=is%3Aissue+is%3Aopen+label%3Atriage" - client.chat_postMessage( - channel='#envoy-maintainer-oncall', - text=( - "*Untriaged Issues* (please tag and cc area experts)\n<%s|%s>" % - (issue_link, issue_link))) - except SlackApiError as e: - print("Unexpected error %s", e.response["error"]) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument( - '--cron_job', - action="store_true", - help="true if this is run by the daily cron job, false if run manually by a developer") - args = parser.parse_args() - - github_token = os.getenv('GITHUB_TOKEN') - if not github_token: - print('Missing GITHUB_TOKEN: please check github workflow configuration') - sys.exit(1) - - slack_bot_token = os.getenv('SLACK_BOT_TOKEN') - if not slack_bot_token: - print( - 'Missing SLACK_BOT_TOKEN: please export token from https://api.slack.com/apps/A023NPQQ33K/oauth?' - ) - sys.exit(1) - - maintainers_and_messages, shephards_and_messages, stalled_prs = track_prs(github_token) - - if not args.cron_job: - print(maintainers_and_messages) - print("\n\n\n") - print(shephards_and_messages) - print("\n\n\n") - print(stalled_prs) - exit(0) - - client = WebClient(token=slack_bot_token) - post_to_oncall(client, maintainers_and_messages['unassigned'], stalled_prs) - post_to_assignee(client, shephards_and_messages, API_REVIEWERS) - post_to_assignee(client, maintainers_and_messages, MAINTAINERS) - post_to_assignee(client, maintainers_and_messages, FIRST_PASS) diff --git a/.github/actions/pr_notifier/requirements.in b/.github/actions/pr_notifier/requirements.in deleted file mode 100644 index b27ccacba25ae..0000000000000 --- a/.github/actions/pr_notifier/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -pygithub -slack_sdk diff --git a/.github/actions/pr_notifier/requirements.txt b/.github/actions/pr_notifier/requirements.txt deleted file mode 100644 index fac4d708533d6..0000000000000 --- a/.github/actions/pr_notifier/requirements.txt +++ /dev/null @@ -1,226 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile --generate-hashes .github/actions/pr_notifier/requirements.txt -# -certifi==2023.7.22 \ - --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ - --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 - # via requests -cffi==1.14.5 \ - --hash=sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813 \ - --hash=sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373 \ - --hash=sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69 \ - --hash=sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f \ - --hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \ - --hash=sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05 \ - --hash=sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea \ - --hash=sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee \ - --hash=sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0 \ - --hash=sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396 \ - --hash=sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7 \ - --hash=sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f \ - --hash=sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73 \ - --hash=sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315 \ - --hash=sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76 \ - --hash=sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1 \ - --hash=sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49 \ - --hash=sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed \ - --hash=sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892 \ - --hash=sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482 \ - --hash=sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058 \ - --hash=sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5 \ - --hash=sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53 \ - --hash=sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045 \ - --hash=sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3 \ - --hash=sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55 \ - --hash=sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5 \ - --hash=sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e \ - --hash=sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c \ - --hash=sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369 \ - --hash=sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827 \ - --hash=sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053 \ - --hash=sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa \ - --hash=sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4 \ - --hash=sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322 \ - --hash=sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132 \ - --hash=sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62 \ - --hash=sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa \ - --hash=sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0 \ - --hash=sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396 \ - --hash=sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e \ - --hash=sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991 \ - --hash=sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6 \ - --hash=sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc \ - --hash=sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1 \ - --hash=sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406 \ - --hash=sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333 \ - --hash=sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d \ - --hash=sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c - # via - # cryptography - # pynacl -charset-normalizer==3.1.0 \ - --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \ - --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \ - --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \ - --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \ - --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \ - --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \ - --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \ - --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \ - --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \ - --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \ - --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \ - --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \ - --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \ - --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \ - --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \ - --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \ - --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \ - --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \ - --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \ - --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \ - --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \ - --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \ - --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \ - --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \ - --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \ - --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \ - --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \ - --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \ - --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \ - --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \ - --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \ - --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \ - --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \ - --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \ - --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \ - --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \ - --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \ - --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \ - --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \ - --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \ - --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \ - --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \ - --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \ - --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \ - --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \ - --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \ - --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \ - --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \ - --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \ - --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \ - --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \ - --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \ - --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \ - --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \ - --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \ - --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \ - --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \ - --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \ - --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \ - --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \ - --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \ - --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \ - --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \ - --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \ - --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \ - --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \ - --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \ - --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \ - --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \ - --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \ - --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \ - --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \ - --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \ - --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ - --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab - # via requests -cryptography==41.0.4 \ - --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ - --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ - --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ - --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ - --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ - --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ - --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ - --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ - --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ - --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ - --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ - --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ - --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ - --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ - --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ - --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ - --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ - --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ - --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ - --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ - --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ - --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ - --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f - # via pyjwt -deprecated==1.2.13 \ - --hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d \ - --hash=sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d - # via pygithub -idna==2.10 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 - # via requests -pycparser==2.20 \ - --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ - --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 - # via cffi -pygithub==1.59.0 \ - --hash=sha256:126bdbae72087d8d038b113aab6b059b4553cb59348e3024bb1a1cae406ace9e \ - --hash=sha256:6e05ff49bac3caa7d1d6177a10c6e55a3e20c85b92424cc198571fd0cf786690 - # via -r requirements.in -pyjwt[crypto]==2.4.0 \ - --hash=sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf \ - --hash=sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba - # via pygithub -pynacl==1.4.0 \ - --hash=sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4 \ - --hash=sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4 \ - --hash=sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574 \ - --hash=sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d \ - --hash=sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634 \ - --hash=sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25 \ - --hash=sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f \ - --hash=sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505 \ - --hash=sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122 \ - --hash=sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7 \ - --hash=sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420 \ - --hash=sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f \ - --hash=sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96 \ - --hash=sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6 \ - --hash=sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6 \ - --hash=sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514 \ - --hash=sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff \ - --hash=sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80 - # via pygithub -requests==2.31.0 \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 - # via pygithub -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via pynacl -slack-sdk==3.21.3 \ - --hash=sha256:20829bdc1a423ec93dac903470975ebf3bc76fd3fd91a4dadc0eeffc940ecb0c \ - --hash=sha256:de3c07b92479940b61cd68c566f49fbc9974c8f38f661d26244078f3903bb9cc - # via -r requirements.in -urllib3==1.26.17 \ - --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ - --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b - # via - # pygithub - # requests -wrapt==1.12.1 \ - --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 - # via deprecated diff --git a/.github/actions/publish/release/setup/action.yml b/.github/actions/publish/release/setup/action.yml deleted file mode 100644 index 4e0935710d2db..0000000000000 --- a/.github/actions/publish/release/setup/action.yml +++ /dev/null @@ -1,26 +0,0 @@ -inputs: - ref: - type: string - required: true - bucket: - type: string - required: true - -runs: - using: composite - steps: - - id: url - run: | - echo "base=https://storage.googleapis.com/${{ inputs.bucket }}/${REF:0:7}/release" \ - >> "$GITHUB_OUTPUT" - env: - REF: ${{ inputs.ref }} - shell: bash - - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.0.10 - id: fetch - with: - url: "${{ steps.url.outputs.base }}/release.signed.tar.zst" - - run: | - mkdir -p ${{ runner.temp }}/release.signed - mv ${{ steps.fetch.outputs.path }} ${{ runner.temp }}/release.signed - shell: bash diff --git a/.github/actions/verify/examples/setup/action.yml b/.github/actions/verify/examples/setup/action.yml deleted file mode 100644 index 18f3205721ce1..0000000000000 --- a/.github/actions/verify/examples/setup/action.yml +++ /dev/null @@ -1,37 +0,0 @@ -inputs: - ref: - type: string - required: true - bucket: - type: string - default: envoy-pr - -runs: - using: composite - steps: - - id: url - run: | - echo "base=https://storage.googleapis.com/${{ inputs.bucket }}/${REF:0:7}/docker" \ - >> "$GITHUB_OUTPUT" - env: - REF: ${{ inputs.ref }} - shell: bash - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.0.10 - with: - url: "${{ steps.url.outputs.base }}/envoy.tar" - variant: dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.0.10 - with: - url: "${{ steps.url.outputs.base }}/envoy-contrib.tar" - variant: contrib-dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.0.10 - with: - url: "${{ steps.url.outputs.base }}/envoy-google-vrp.tar" - variant: google-vrp-dev - - run: docker images | grep envoy - shell: bash - - run: | - export DEBIAN_FRONTEND=noninteractive - sudo apt-get -qq update -y - sudo apt-get -qq install -y --no-install-recommends expect - shell: bash diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 0000000000000..54f7de70d8f35 --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,166 @@ +agent-ubuntu: ubuntu-22.04 +build-image: + # Authoritative configuration for build image/s + repo: envoyproxy/envoy-build-ubuntu + sha: 3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e + mobile-sha: f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e + # this is authoritative, but is not currently used in github ci + gcr-sha: 2a473cd9808182735d54e03b158975389948b9559b8e8fc624cfafbaf7059e62 + tag: fdd65c6270a8507a18d5acd6cf19a18cb695e4fa + +config: + envoy: + icon: >- + [![](https://avatars.githubusercontent.com/u/30125649?s=24&v=4)](#) + +checks: + # Checks: this configures which _checks_ will be activated or skipped + # + # The configured _names_ need to match the checks configured for the repo + # + # Any check that is marked as `required` but is not triggered by the run + # config above in a given CI run is marked as `skipped` + # + # For example if macos is marked as `required: true` but then has a path + # selection that means its doesnt run the check will be `skipped` and pass + prechecks: + name: Envoy/Prechecks + on-run: + - precheck-deps + required: true + # yamllint disable rule:line-length + advice: + general: | + ### Ensuring your commits are signed off + + You can set up DCO using Envoy's git hooks. + + ### Git hooks + + To set this up, do the following: + + ```console + $ ./support/bootstrap + ``` + + If you only want the DCO check you can do the following to disable the + other hooks + + ```console + $ echo NO_VERIFY=1 > .env + ``` + deps: | + ### Advice on updating dependencies + + General information about Envoy's depdendencies [can be found here](https://github.com/envoyproxy/envoy/blob/main/DEPENDENCY_POLICY.md) + format: | + ### Advice on correct formatting + + Envoy ensures a minimum standard for all files in the repository. + + You are strongly advised to heed the following CI notice: + + ```console + Please fix your editor to ensure: + + - no trailing whitespace + - no preceding mixed tabs/spaces + - all files end with a newline + ``` + # yamllint enable rule:line-length + publish: + name: >- + Envoy/Publish and verify + on-run: + - publish + - verify + required: true + +run: + precheck-deps: + paths: + - .bazelrc + - .bazelversion + - .github/config.yml + - "**/*.bzl" + - "**/requirements.txt" + publish: + paths: + - .bazelrc + - .bazelversion + - .github/config.yml + - api/**/* + - bazel/**/* + - ci/**/* + - contrib/**/* + - envoy/**/* + - examples/**/* + - source/**/* + - tools/**/* + verify: + paths: + - .bazelrc + - .bazelversion + - .github/config.yml + - api/**/* + - bazel/**/* + - ci/**/* + - contrib/**/* + - envoy/**/* + - examples/**/* + - source/**/* + - tools/**/* + push: paths + +tables: + env: + collapse: true + title: Environment + table-title: Request variables + filter: | + .request + | del(.["build-image" as $prefix | keys[] | select(startswith($prefix))]) + | del(.["version" as $prefix | keys[] | select(startswith($prefix))]) + | .actor = "\"\(.actor.name)\" @\(.actor.name)" + build-image: + collapse: true + title: Build image + table-title: Container image/s (as used in this CI run) + filter: | + "https://hub.docker.com/r/envoyproxy/envoy-build-ubuntu/tags?page=1&name=" as $dockerLink + | .request["build-image"] + | del(.changed) + | with_entries( + .value as $v + | ($v | split(":") | .[1] | split("@") | .[0]) as $tag + | .value = "[\($v | split("@") | .[0])](\($dockerLink)\($tag))") + build-image-current: + collapse: true + title: Build image (current) + table-title: Current or previous container image + filter: | + "https://hub.docker.com/r/envoyproxy/envoy-build-ubuntu/tags?page=1&name=" as $dockerLink + | if .request["build-image"].changed then + .request["build-image-current"] + | with_entries( + .value as $v + | ($v | split(":") | .[1] | split("@") | .[0]) as $tag + | .value = "[\($v | split("@") | .[0])](\($dockerLink)\($tag))") + else {} end + version: + collapse: true + title: Version + table-title: Envoy version (as used in this CI run) + filter: | + .request.version + | del(.changed) + version-current: + collapse: true + title: Version (current) + table-title: Current or previous version + filter: | + if .request.version.changed then + .request["version-current"] + else + {} + end diff --git a/.github/workflows/_cache_docker.yml b/.github/workflows/_cache_docker.yml deleted file mode 100644 index f0d653cab0248..0000000000000 --- a/.github/workflows/_cache_docker.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Cache prime (docker) - -permissions: - contents: read - -on: - workflow_call: - inputs: - image_tag: - type: string - required: true - image_repo: - type: string - required: true - image_sha: - type: string - required: true - -concurrency: - group: cache_docker-${{ inputs.image_tag }} - cancel-in-progress: false - -## Docker cache -# -# This workflow will only prime the cache, and should be done separately first, prior -# to any jobs that require it. -# -# For a job that does, you can restore with something like: -# -# steps: -# - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.0.5 -# with: -# key: "${{ needs.env.outputs.build_image_ubuntu }}" -# - -jobs: - docker: - runs-on: ubuntu-22.04 - steps: - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.0.10 - name: Prime Docker cache (${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}) - with: - image_tag: "${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}" diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml deleted file mode 100644 index 8e857fab7c377..0000000000000 --- a/.github/workflows/_ci.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: Envoy CI - -on: - workflow_call: - secrets: - app_id: - app_key: - inputs: - target: - required: true - type: string - rbe: - type: boolean - default: true - managed: - type: boolean - default: true - - auth_bazel_rbe: - type: string - default: '' - - bazel_extra: - type: string - default: - bazel_local_cache: - type: string - default: - bazel_rbe_cache: - type: string - default: grpcs://remotebuildexecution.googleapis.com - bazel_rbe_instance: - type: string - default: projects/envoy-ci/instances/default_instance - bazel_rbe_jobs: - type: number - default: 75 - - cache_build_image: - type: string - - command_prefix: - type: string - default: ./ci/run_envoy_docker.sh - command_ci: - type: string - default: ./ci/do_ci.sh - - diskspace_hack: - type: boolean - default: false - - run_pre: - type: string - default: - run_pre_with: - type: string - default: - - run_post: - type: string - default: - run_post_with: - type: string - default: - - repo_fetch_depth: - type: number - default: 1 - repo_ref: - type: string - skip: - type: boolean - default: false - trusted: - type: boolean - default: false - - env: - type: string - -concurrency: - group: | - ${{ github.actor != 'trigger-release-envoy[bot]' - && github.event.inputs.head_ref - || github.run_id - }}-${{ github.workflow }}-${{ inputs.target }} - cancel-in-progress: true - -jobs: - do_ci: - if: ${{ ! inputs.skip }} - runs-on: ubuntu-22.04 - name: ${{ inputs.command_ci }} ${{ inputs.target }} - steps: - - if: ${{ inputs.cache_build_image }} - name: Restore Docker cache (${{ inputs.cache_build_image }}) - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.0.10 - with: - image_tag: ${{ inputs.cache_build_image }} - - - name: Check workflow context - id: context - run: | - if [[ "${{ inputs.trusted }}" != "false" && -n "${{ secrets.app_id }}" && -n "${{ secrets.app_key }}" ]]; then - echo "use_appauth=true" >> $GITHUB_OUTPUT - fi - - if: ${{ steps.context.outputs.use_appauth == 'true' }} - name: Fetch token for app auth - id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.18 - with: - app_id: ${{ secrets.app_id }} - key: ${{ secrets.app_key }} - - - uses: actions/checkout@v4 - name: Checkout Envoy repository - with: - fetch-depth: ${{ ! inputs.trusted && inputs.repo_fetch_depth || 0 }} - # WARNING: This allows untrusted code to run!!! - # If this is set, then anything before or after in the job should be regarded as - # compromised. - ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} - token: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} - - # If we are in a trusted CI run then the provided commit _must_ be either the latest for - # this branch, or an antecdent. - - run: | - if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD; then - echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 - exit 1 - fi - git checkout "${{ inputs.repo_ref }}" - if: ${{ inputs.trusted }} - name: Check provided ref - - - name: Add safe directory - run: git config --global --add safe.directory /__w/envoy/envoy - - - if: ${{ inputs.diskspace_hack }} - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.0.17 - - run: | - echo "disk space at beginning of build:" - df -h - name: "Check disk space at beginning" - - - if: ${{ inputs.run_pre }} - name: Run pre action ${{ inputs.run_pre && format('({0})', inputs.run_pre) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.0.10 - with: - uses: ${{ inputs.run_pre }} - with: ${{ inputs.run_pre_with }} - - - uses: ./.github/actions/do_ci - name: Do CI - with: - target: ${{ inputs.target }} - rbe: ${{ inputs.rbe }} - managed: ${{ inputs.managed }} - auth_bazel_rbe: ${{ inputs.auth_bazel_rbe }} - bazel_extra: ${{ inputs.bazel_extra }} - bazel_local_cache: ${{ inputs.bazel_local_cache }} - bazel_rbe_cache: ${{ inputs.bazel_rbe_cache }} - bazel_rbe_instance: ${{ inputs.bazel_rbe_instance }} - bazel_rbe_jobs: ${{ inputs.bazel_rbe_jobs }} - command_prefix: ${{ inputs.command_prefix }} - command_ci: ${{ inputs.command_ci }} - env: ${{ inputs.env }} - GITHUB_TOKEN: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} - - - if: ${{ inputs.run_post }} - name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.0.10 - with: - uses: ${{ inputs.run_post }} - with: ${{ inputs.run_post_with }} - - - run: | - echo "disk space at end of build:" - df -h - echo - du -ch "${{ runner.temp }}" | grep -E "[0-9]{2,}M|[0-9]G" - name: "Check disk space at end" diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml deleted file mode 100644 index dd0ca05d204da..0000000000000 --- a/.github/workflows/_env.yml +++ /dev/null @@ -1,178 +0,0 @@ -name: Environment - -permissions: - contents: read - -on: - workflow_call: - inputs: - # Authoritative configuration for build image/s - build_image_repo: - type: string - default: envoyproxy/envoy-build-ubuntu - build_image_sha: - type: string - default: 3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e - build_image_mobile_sha: - type: string - default: f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e - build_image_tag: - type: string - default: fdd65c6270a8507a18d5acd6cf19a18cb695e4fa - - check_mobile_run: - type: boolean - default: true - prime_build_image: - type: boolean - default: false - - repo_ref: - type: string - default: - repo_ref_sha: - type: string - default: - repo_ref_name: - type: string - default: - - outputs: - debug: - value: false - agent_ubuntu: - value: ubuntu-22.04 - build_image_ubuntu: - value: ${{ jobs.repo.outputs.build_image_ubuntu }} - build_image_ubuntu_mobile: - value: ${{ jobs.repo.outputs.build_image_ubuntu_mobile }} - mobile_android_build: - value: ${{ jobs.repo.outputs.mobile_android_build }} - mobile_android_build_all: - value: ${{ jobs.repo.outputs.mobile_android_build_all }} - mobile_android_tests: - value: ${{ jobs.repo.outputs.mobile_android_tests }} - mobile_asan: - value: ${{ jobs.repo.outputs.mobile_asan }} - mobile_cc_tests: - value: ${{ jobs.repo.outputs.mobile_cc_tests }} - mobile_compile_time_options: - value: ${{ jobs.repo.outputs.mobile_compile_time_options }} - mobile_coverage: - value: ${{ jobs.repo.outputs.mobile_coverage }} - mobile_formatting: - value: ${{ jobs.repo.outputs.mobile_formatting }} - mobile_ios_build: - value: ${{ jobs.repo.outputs.mobile_ios_build }} - mobile_ios_build_all: - value: ${{ jobs.repo.outputs.mobile_ios_build_all }} - mobile_ios_tests: - value: ${{ jobs.repo.outputs.mobile_ios_tests }} - mobile_release_validation: - value: ${{ jobs.repo.outputs.mobile_release_validation }} - mobile_tsan: - value: ${{ jobs.repo.outputs.mobile_tsan }} - - repo_ref: - value: ${{ jobs.repo.outputs.repo_ref }} - repo_ref_name: - value: ${{ jobs.repo.outputs.repo_ref_name }} - repo_ref_sha: - value: ${{ jobs.repo.outputs.repo_ref_sha }} - repo_ref_sha_short: - value: ${{ jobs.repo.outputs.repo_ref_sha_short }} - repo_ref_title: - value: ${{ jobs.repo.outputs.repo_ref_title }} - - trusted: - value: ${{ jobs.repo.outputs.trusted }} - - version_dev: - value: ${{ jobs.repo.outputs.version_dev }} - version_patch: - value: ${{ jobs.repo.outputs.version_patch }} - -concurrency: - group: | - ${{ github.actor != 'trigger-release-envoy[bot]' - && github.event.inputs.head_ref - || github.run_id - }}-${{ github.workflow }}-env - cancel-in-progress: true - -jobs: - repo: - if: github.repository == 'envoyproxy/envoy' - runs-on: ubuntu-22.04 - outputs: - build_image_ubuntu: ${{ steps.env.outputs.build_image_ubuntu }} - build_image_ubuntu_mobile: ${{ steps.env.outputs.build_image_ubuntu_mobile }} - mobile_android_build: ${{ steps.env.outputs.mobile_android_build }} - mobile_android_build_all: ${{ steps.env.outputs.mobile_android_build_all }} - mobile_android_tests: ${{ steps.env.outputs.mobile_android_tests }} - mobile_asan: ${{ steps.env.outputs.mobile_asan }} - mobile_cc_tests: ${{ steps.env.outputs.mobile_cc_tests }} - mobile_compile_time_options: ${{ steps.env.outputs.mobile_compile_time_options }} - mobile_coverage: ${{ steps.env.outputs.mobile_coverage }} - mobile_formatting: ${{ steps.env.outputs.mobile_formatting }} - mobile_ios_build: ${{ steps.env.outputs.mobile_ios_build }} - mobile_ios_build_all: ${{ steps.env.outputs.mobile_ios_build_all }} - mobile_ios_tests: ${{ steps.env.outputs.mobile_ios_tests }} - mobile_release_validation: ${{ steps.env.outputs.mobile_release_validation }} - mobile_tsan: ${{ steps.env.outputs.mobile_tsan }} - repo_ref: ${{ steps.env.outputs.repo_ref }} - repo_ref_name: ${{ steps.env.outputs.repo_ref_name }} - repo_ref_sha: ${{ steps.env.outputs.repo_ref_sha }} - repo_ref_sha_short: ${{ steps.env.outputs.repo_ref_sha_short }} - repo_ref_title: ${{ steps.env.outputs.repo_ref_title }} - trusted: ${{ steps.env.outputs.trusted }} - version_dev: ${{ steps.env.outputs.version_dev }} - version_patch: ${{ steps.env.outputs.version_patch }} - steps: - - uses: actions/checkout@v3 - name: Checkout Envoy repository - with: - fetch-depth: ${{ ! (inputs.check_mobile_run || ! startsWith(github.event_name, 'pull_request')) && 1 || 0 }} - # WARNING: This allows untrusted code to run!!! - # If this is set, then anything before or after in the job should be regarded as - # compromised. - ref: ${{ startsWith(github.event_name, 'pull_request') && inputs.repo_ref || '' }} - - - uses: ./.github/actions/env - name: Generate environment variables - id: env - with: - check_mobile_run: ${{ inputs.check_mobile_run }} - repo_ref: ${{ inputs.repo_ref }} - repo_ref_name: ${{ inputs.repo_ref_name }} - repo_ref_sha: ${{ inputs.repo_ref_sha }} - build_image_repo: ${{ inputs.build_image_repo }} - build_image_tag: ${{ inputs.build_image_tag }} - build_image_mobile_sha: ${{ inputs.build_image_mobile_sha }} - build_image_sha: ${{ inputs.build_image_sha }} - - - name: 'Print env' - run: | - echo "version_dev=${{ steps.env.outputs.version_dev }}" - echo "version_patch=${{ steps.env.outputs.version_patch }}" - echo "trusted=${{ steps.env.outputs.trusted }}" - echo "repo_ref=${{ steps.env.outputs.repo_ref }}" - echo "repo_ref_name=${{ steps.env.outputs.repo_ref_name }}" - echo "repo_ref_pr_number=${{ steps.env.outputs.repo_ref_pr_number }}" - echo "repo_ref_sha=${{ steps.env.outputs.repo_ref_sha }}" - echo "repo_ref_sha_short=${{ steps.env.outputs.repo_ref_sha_short }}" - echo "repo_ref_title=${{ steps.env.outputs.repo_ref_title }}" - echo "build_image_ubuntu=${{ steps.env.outputs.build_image_ubuntu }}" - echo "build_image_ubuntu_mobile=${{ steps.env.outputs.build_image_ubuntu_mobile }}" - echo - if [[ -n "${{ steps.env.outputs.repo_ref_pr_number }}" ]]; then - echo "PR: https://github.com/envoyproxy/envoy/pull/${{ steps.env.outputs.repo_ref_pr_number }}" - fi - - cache: - if: ${{ inputs.prime_build_image }} - uses: ./.github/workflows/_cache_docker.yml - with: - image_repo: ${{ inputs.build_image_repo }} - image_tag: ${{ inputs.build_image_tag }} - image_sha: ${{ inputs.build_image_sha }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml deleted file mode 100644 index ef3ba7f01e5d6..0000000000000 --- a/.github/workflows/_stage_publish.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Publish - -permissions: - contents: read - -# The matrices in this config can be combined once the calling workflow has shifted -# to a `pull_request`/`commit` pattern (ie not `workflow_dispatch`) -# -# For now pre/post submit is split between `publish_ci`/`publish`, the latter running -# only for "trusted" runs and having access to secrets/permissions - -on: - workflow_call: - inputs: - trusted: - type: boolean - default: false - build_image_ubuntu: - type: string - default: '' - version_dev: - type: string - default: '' - head_ref: - type: string - default: '' - repo_ref: - type: string - sha: - type: string - secrets: - ENVOY_CI_SYNC_APP_ID: - ENVOY_CI_SYNC_APP_KEY: - ENVOY_CI_PUBLISH_APP_ID: - ENVOY_CI_PUBLISH_APP_KEY: - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-publish - cancel-in-progress: true - -jobs: - publish_ci: - if: ${{ ! inputs.trusted }} - name: ${{ matrix.name || matrix.target }} - strategy: - fail-fast: false - matrix: - include: - - target: publish - name: github - run_pre: ./.github/actions/publish/release/setup - run_pre_with: | - ref: ${{ inputs.repo_ref }} - bucket: envoy-pr - env: | - export ENVOY_PUBLISH_DRY_RUN=1 - uses: ./.github/workflows/_ci.yml - with: - target: ${{ matrix.target }} - rbe: false - managed: true - cache_build_image: ${{ inputs.build_image_ubuntu }} - run_pre: ${{ matrix.run_pre }} - run_pre_with: ${{ matrix.run_pre_with }} - env: ${{ matrix.env }} - trusted: false - repo_ref: ${{ inputs.repo_ref }} - - publish: - if: ${{ inputs.trusted }} - name: ${{ matrix.name || matrix.target }} - permissions: - contents: read - packages: read - strategy: - fail-fast: false - matrix: - include: - - target: publish - name: github - run_pre: ./.github/actions/publish/release/setup - run_pre_with: | - ref: ${{ inputs.repo_ref }} - bucket: envoy-postsubmit - env: | - export ENVOY_COMMIT=${{ inputs.sha }} - if [[ '${{ inputs.version_dev }}' == 'dev' ]]; then - export ENVOY_PUBLISH_DRY_RUN=1 - fi - uses: ./.github/workflows/_ci.yml - with: - target: ${{ matrix.target }} - rbe: false - managed: true - cache_build_image: ${{ inputs.build_image_ubuntu }} - run_pre: ${{ matrix.run_pre }} - run_pre_with: ${{ matrix.run_pre_with }} - env: ${{ matrix.env }} - trusted: true - repo_ref: ${{ inputs.repo_ref }} - secrets: - app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} - app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - - publish_docs: - # For normal commits to Envoy main this will trigger an update in the website repo, - # which will update its envoy dep shas, and rebuild the website for the latest docs - # - # For commits that create a release, it instead triggers an update in the archive repo, - # which builds a static version of the docs for the release and commits it to the archive. - # In turn the archive repo triggers an update in the website so the new release docs are - # included in the published site - if: ${{ inputs.trusted }} - runs-on: ubuntu-22.04 - needs: - - publish - steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.0.18 - with: - app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} - key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" - ref: main - repository: ${{ inputs.version_dev == 'dev' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} - workflow: envoy-sync.yaml - inputs: | - commit_sha: ${{ inputs.version_dev == 'dev' && github.sha || '' }} diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml deleted file mode 100644 index a1a40d2b5fd47..0000000000000 --- a/.github/workflows/_stage_verify.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Verify - -permissions: - contents: read - -on: - workflow_call: - inputs: - trusted: - type: boolean - default: false - repo_ref: - type: string - given_ref: - type: string - -concurrency: - group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-verify - cancel-in-progress: true - -jobs: - verify: - name: ${{ matrix.name || matrix.target }} - strategy: - fail-fast: false - matrix: - include: - - target: verify_examples - name: examples - rbe: false - managed: true - cache_build_image: "" - command_prefix: "" - diskspace_hack: true - run_pre: ./.github/actions/verify/examples/setup - run_pre_with: | - bucket: envoy-${{ inputs.trusted && 'postsubmit' || 'pr' }} - ref: ${{ inputs.given_ref }} - env: | - export NO_BUILD_SETUP=1 - uses: ./.github/workflows/_ci.yml - with: - target: ${{ matrix.target }} - rbe: ${{ matrix.rbe }} - managed: ${{ matrix.managed }} - cache_build_image: ${{ matrix.cache_build_image }} - diskspace_hack: ${{ matrix.diskspace_hack }} - command_prefix: ${{ matrix.command_prefix }} - run_pre: ${{ matrix.run_pre }} - run_pre_with: ${{ matrix.run_pre_with }} - env: ${{ matrix.env }} - trusted: ${{ inputs.trusted }} - repo_ref: ${{ inputs.repo_ref }} diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml deleted file mode 100644 index b4e758778c2b5..0000000000000 --- a/.github/workflows/_workflow-start.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Workflow start -# This workflow is only required for externally triggered jobs that need to manually -# set the check status for a commit/PR - -permissions: - contents: read - -on: - workflow_call: - inputs: - workflow_name: - required: true - type: string - sha: - required: true - type: string - -jobs: - start: - runs-on: ubuntu-22.04 - permissions: - statuses: write - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/env - id: env - with: - check_mobile_run: false - - - if: ${{ steps.env.outputs.trusted != 'true' }} - name: Start status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.0.10 - with: - authToken: ${{ secrets.GITHUB_TOKEN }} - context: ${{ inputs.workflow_name }} - state: 'pending' - sha: ${{ inputs.sha }} - target_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - - if: ${{ steps.env.outputs.trusted != 'true' }} - name: Save the SHA - env: - STATE_SHA: ${{ inputs.sha }} - run: | - mkdir -p ./sha - echo $STATE_SHA > ./sha/state_sha - - if: ${{ steps.env.outputs.trusted != 'true' }} - uses: actions/upload-artifact@v3 - with: - name: state_sha - path: sha/ diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml deleted file mode 100644 index d1e4339f34ad2..0000000000000 --- a/.github/workflows/commands.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: commands - -on: - issue_comment: - types: [created] - -permissions: - contents: read - -jobs: - retest: - if: | - ${{ - github.event.issue.pull_request - && github.repository == 'envoyproxy/envoy' - && github.actor != 'repokitteh-read-only[bot]' - && github.actor != 'dependabot[bot]' - }} - name: Retest - runs-on: ubuntu-22.04 - permissions: - pull-requests: write - actions: write - steps: - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.0.10 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml deleted file mode 100644 index 3c99a8a7ac3cb..0000000000000 --- a/.github/workflows/envoy-publish.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Publish & verify - -permissions: - contents: read - -on: - # This runs untrusted code, do not expose secrets in the verify job - workflow_dispatch: - inputs: - ref: - description: "Git SHA ref to checkout" - sha: - description: "Git SHA of commit HEAD (ie last commit of PR)" - head_ref: - description: "Ref for grouping PRs" - -concurrency: - group: | - ${{ github.actor != 'trigger-release-envoy[bot]' - && github.event.inputs.head_ref - || github.run_id - }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - env: - if: | - ${{ - github.repository == 'envoyproxy/envoy' - && (!contains(github.actor, '[bot]') - || github.actor == 'trigger-workflow-envoy[bot]' - || github.actor == 'trigger-release-envoy[bot]') - }} - uses: ./.github/workflows/_env.yml - with: - check_mobile_run: false - prime_build_image: true - repo_ref: ${{ inputs.ref }} - repo_ref_sha: ${{ inputs.sha }} - repo_ref_name: ${{ inputs.head_ref }} - permissions: - contents: read - packages: read - - check: - if: ${{ github.event_name != 'pull_request' }} - uses: ./.github/workflows/_workflow-start.yml - permissions: - contents: read - statuses: write - with: - workflow_name: Verify/examples - sha: ${{ inputs.sha }} - - publish: - needs: - - env - - check - uses: ./.github/workflows/_stage_publish.yml - name: Publish ${{ needs.env.outputs.repo_ref_title }} - with: - build_image_ubuntu: ${{ needs.env.outputs.build_image_ubuntu }} - trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} - version_dev: ${{ needs.env.outputs.version_dev }} - repo_ref: ${{ inputs.ref }} - permissions: - contents: read - packages: read - secrets: - ENVOY_CI_SYNC_APP_ID: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} - ENVOY_CI_SYNC_APP_KEY: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - ENVOY_CI_PUBLISH_APP_ID: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} - ENVOY_CI_PUBLISH_APP_KEY: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - - verify: - uses: ./.github/workflows/_stage_verify.yml - name: Verify ${{ needs.env.outputs.repo_ref_title }} - needs: - - env - with: - trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} - given_ref: ${{ inputs.ref }} - repo_ref: ${{ inputs.ref }} diff --git a/.github/workflows/request.yml b/.github/workflows/request.yml new file mode 100644 index 0000000000000..a245052db14a4 --- /dev/null +++ b/.github/workflows/request.yml @@ -0,0 +1,39 @@ +# This file must live on every branch and pass necessary secrets and permissions +# to initiate the request +name: Request + +permissions: + contents: read + +on: + pull_request_target: + push: + branches: + - main + - release/v* + +concurrency: + group: | + ${{ github.head_ref + || github.run_id + }}-${{ github.workflow }}-request + cancel-in-progress: true + + +jobs: + request: + # For branches this can be pinned to a specific version if required + # NB: `uses` cannot be dynamic so it _must_ be hardcoded anywhere it is read + uses: envoyproxy/envoy/.github/workflows/_request.yml@main + if: ${{ vars.ENVOY_CI || github.repository == 'envoyproxy/envoy' }} + permissions: + actions: read + contents: read + # required for engflow/bazel caching (not yet used) + packages: read + # required to fetch merge commit + pull-requests: read + secrets: + # these are required to start checks + app-key: ${{ secrets.ENVOY_CI_APP_KEY }} + app-id: ${{ secrets.ENVOY_CI_APP_ID }} diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml deleted file mode 100644 index e81503bcca993..0000000000000 --- a/.github/workflows/workflow-complete.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Workflow complete -# This workflow is only required for externally triggered jobs that have manually -# set the check status for a commit/PR - -permissions: - contents: read - -on: - # Do not run untrusted code here - workflow_run: - workflows: - - Publish & verify - types: - - completed - -jobs: - complete: - if: ${{ github.actor == 'trigger-workflow-envoy[bot]' }} - runs-on: ubuntu-22.04 - permissions: - statuses: write - steps: - - name: 'Download artifact' - uses: actions/github-script@v6 - with: - script: | - let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { - return artifact.name == "state_sha" - })[0]; - let download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - let fs = require('fs'); - fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/state_sha.zip`, Buffer.from(download.data)); - - - run: | - set -e - unzip state_sha.zip - STATE_SHA="$(cat state_sha)" - echo "state_sha=$STATE_SHA" >> "$GITHUB_OUTPUT" - STATE="${{ github.event.workflow_run.conclusion }}" - if [[ ${STATE} != "success" ]]; then - STATE=failure - fi - echo "state=${STATE}" >> "$GITHUB_OUTPUT" - id: job - - name: Complete status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.0.10 - with: - authToken: ${{ secrets.GITHUB_TOKEN }} - context: Verify/examples - state: ${{ steps.job.outputs.state }} - sha: ${{ steps.job.outputs.state_sha }} - target_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }} diff --git a/ci/do_ci.sh b/ci/do_ci.sh index c77ddd36f7b0c..e7211b1babf91 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -163,7 +163,7 @@ function bazel_binary_build() { # The COMPILE_TYPE variable is redundant in this case and is only here for # readability. It is already set in the .bazelrc config for sizeopt. COMPILE_TYPE="opt" - CONFIG_ARGS="--config=sizeopt" + CONFIG_ARGS=("--config=sizeopt") elif [[ "${BINARY_TYPE}" == "fastbuild" ]]; then COMPILE_TYPE="fastbuild" fi @@ -181,7 +181,7 @@ function bazel_binary_build() { # This is a workaround for https://github.com/bazelbuild/bazel/issues/11834 [[ -n "${ENVOY_RBE}" ]] && rm -rf bazel-bin/"${ENVOY_BIN}"* - bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_TARGET}" ${CONFIG_ARGS} + bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_TARGET}" "${CONFIG_ARGS[@]}" collect_build_profile "${BINARY_TYPE}"_build # Copy the built envoy binary somewhere that we can access outside of the @@ -191,14 +191,14 @@ function bazel_binary_build() { if [[ "${COMPILE_TYPE}" == "dbg" || "${COMPILE_TYPE}" == "opt" ]]; then # Generate dwp file for debugging since we used split DWARF to reduce binary # size - bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_DEBUG_INFORMATION}" ${CONFIG_ARGS} + bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_DEBUG_INFORMATION}" "${CONFIG_ARGS[@]}" # Copy the debug information cp -f bazel-bin/"${ENVOY_BIN}".dwp "${FINAL_DELIVERY_DIR}"/envoy.dwp fi # Validation tools for the tools image. bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" \ - //test/tools/schema_validator:schema_validator_tool ${CONFIG_ARGS} + //test/tools/schema_validator:schema_validator_tool "${CONFIG_ARGS[@]}" # Build su-exec utility bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" external:su-exec @@ -278,9 +278,7 @@ case $CI_TARGET in ;& api.go) - if [[ -z "$NO_BUILD_SETUP" ]]; then - setup_clang_toolchain - fi + setup_clang_toolchain GO_IMPORT_BASE="github.com/envoyproxy/go-control-plane" GO_TARGETS=(@envoy_api//...) read -r -a GO_PROTOS <<< "$(bazel query "${BAZEL_GLOBAL_OPTIONS[@]}" "kind('go_proto_library', ${GO_TARGETS[*]})" | tr '\n' ' ')" @@ -787,6 +785,8 @@ case $CI_TARGET in publish) setup_clang_toolchain BUILD_SHA="$(git rev-parse HEAD)" + ENVOY_COMMIT="${ENVOY_COMMIT:-${BUILD_SHA}}" + ENVOY_REPO="${ENVOY_REPO:-envoyproxy/envoy}" VERSION_DEV="$(cut -d- -f2 < VERSION.txt)" PUBLISH_ARGS=( --publish-commitish="$BUILD_SHA" @@ -796,7 +796,8 @@ case $CI_TARGET in fi bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ @envoy_repo//:publish \ - -- "${PUBLISH_ARGS[@]}" + -- --repo="$ENVOY_REPO" \ + "${PUBLISH_ARGS[@]}" ;; release|release.server_only) @@ -933,7 +934,6 @@ case $CI_TARGET in WORKFLOW="envoy-publish.yml" # * Note on vars * # `ENVOY_REPO`: Should always be envoyproxy/envoy unless testing - # `ENVOY_BRANCH`: Target branch for PRs, source branch for others # `COMMIT`: This may be a merge commit in a PR # `ENVOY_COMMIT`: The actual last commit of branch/PR # `ENVOY_HEAD_REF`: must also be set in PRs to provide a unique key for job grouping, @@ -941,9 +941,10 @@ case $CI_TARGET in COMMIT="$(git rev-parse HEAD)" ENVOY_COMMIT="${ENVOY_COMMIT:-${COMMIT}}" ENVOY_REPO="${ENVOY_REPO:-envoyproxy/envoy}" + # Note: CI is always called in main, the CI request is matched from there echo "Trigger workflow (${WORKFLOW})" echo " Repo: ${ENVOY_REPO}" - echo " Branch: ${ENVOY_BRANCH}" + echo " Branch: main" echo " Ref: ${COMMIT}" echo " Inputs:" echo " sha: ${ENVOY_COMMIT}" @@ -956,7 +957,7 @@ case $CI_TARGET in -- --repo="$ENVOY_REPO" \ --trigger-app-id="$GITHUB_APP_ID" \ --trigger-installation-id="$GITHUB_INSTALL_ID" \ - --trigger-ref="$ENVOY_BRANCH" \ + --trigger-ref="main" \ --trigger-workflow="$WORKFLOW" \ --trigger-inputs="$INPUTS" ;; From e99a9ad2e5f18b3fd771d847a635ec8a95b2d380 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 24 Oct 2023 05:05:40 +0100 Subject: [PATCH 135/274] deps: Bump `com_github_grpc_grpc` -> 1.59.1 (#30370) Signed-off-by: Ryan Northey --- bazel/grpc.patch | 15 ++++++++++++++- bazel/repository_locations.bzl | 6 +++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/bazel/grpc.patch b/bazel/grpc.patch index c8872879824c6..4608049f1bf8e 100644 --- a/bazel/grpc.patch +++ b/bazel/grpc.patch @@ -23,4 +23,17 @@ index 1bb970e049..81265483e9 100644 + "-layering_check", ], ) - + +diff --git a/src/core/lib/channel/channel_args.h b/src/core/lib/channel/channel_args.h +index 38bb070213..b53086e680 100644 +--- a/src/core/lib/channel/channel_args.h ++++ b/src/core/lib/channel/channel_args.h +@@ -284,7 +284,7 @@ class ChannelArgs { + + class Value { + public: +- explicit Value(int n) : rep_(reinterpret_cast(n), &int_vtable_) {} ++ explicit Value(int n) : rep_(reinterpret_cast(static_cast(n)), &int_vtable_) {} + explicit Value(std::string s) + : rep_(RefCountedString::Make(s).release(), &string_vtable_) {} + explicit Value(Pointer p) : rep_(std::move(p)) {} diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 94457b963dc60..aec16ea6367ae 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -376,12 +376,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "gRPC", project_desc = "gRPC C core library", project_url = "https://grpc.io", - version = "1.56.2", - sha256 = "931f07db9d48cff6a6007c1033ba6d691fe655bea2765444bc1ad974dfc840aa", + version = "1.59.1", + sha256 = "916f88a34f06b56432611aaa8c55befee96d0a7b7d7457733b9deeacbc016f99", strip_prefix = "grpc-{version}", urls = ["https://github.com/grpc/grpc/archive/v{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2023-07-14", + release_date = "2023-10-06", cpe = "cpe:2.3:a:grpc:grpc:*", license = "Apache-2.0", license_url = "https://github.com/grpc/grpc/blob/v{version}/LICENSE", From 76e4d65836e4453290f183d4b014d6b34b7a15c7 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 11 Dec 2023 16:55:08 +0000 Subject: [PATCH 136/274] ci: Shift windows and macos jobs to github Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 12 --- .azure-pipelines/stage/macos.yml | 62 -------------- .azure-pipelines/stage/windows.yml | 125 ----------------------------- .azure-pipelines/stages.yml | 34 -------- .github/config.yml | 36 +++++++++ 5 files changed, 36 insertions(+), 233 deletions(-) delete mode 100644 .azure-pipelines/stage/macos.yml delete mode 100644 .azure-pipelines/stage/windows.yml diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 8b3a86af2bd09..4a09a485ef71f 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -68,10 +68,6 @@ stages: parameters: buildStageDeps: - env - macBuildStageDeps: - - env - windowsBuildStageDeps: - - env # Scheduled run anywhere - ${{ if eq(variables.pipelineScheduled, true) }}: @@ -87,10 +83,6 @@ stages: - env checkStageDeps: - env - macBuildStageDeps: - - env - windowsBuildStageDeps: - - env # Postsubmit main/release branches - ${{ if eq(variables.pipelinePostsubmit, true) }}: @@ -103,7 +95,3 @@ stages: - env checkStageDeps: - env - macBuildStageDeps: - - env - windowsBuildStageDeps: - - env diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml deleted file mode 100644 index fc990eafd737f..0000000000000 --- a/.azure-pipelines/stage/macos.yml +++ /dev/null @@ -1,62 +0,0 @@ - -parameters: - -# Auth -- name: authGCP - type: string - default: "" - -- name: runBuild - displayName: "Run build" - type: string - default: true - -jobs: -- job: test - displayName: Build and test - condition: | - and(not(canceled()), - eq(${{ parameters.runBuild }}, 'true')) - timeoutInMinutes: 180 - pool: - vmImage: "macos-11" - steps: - - script: ./ci/mac_ci_setup.sh - displayName: "Install dependencies" - - - bash: | - set -e - GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -t gcp_service_account.XXXXXX.json) - bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" - BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" - ./ci/mac_ci_steps.sh - displayName: "Run Mac CI" - env: - BAZEL_BUILD_EXTRA_OPTIONS: >- - --remote_download_toplevel - --flaky_test_attempts=2 - --remote_cache=grpcs://remotebuildexecution.googleapis.com - --remote_instance_name=projects/envoy-ci/instances/default_instance - ENVOY_RBE: 1 - - - task: PublishTestResults@2 - inputs: - testResultsFiles: "**/bazel-testlogs/**/test.xml" - testRunTitle: "macOS" - timeoutInMinutes: 10 - condition: not(canceled()) - -- job: tested - displayName: Complete - dependsOn: ["test"] - pool: - vmImage: $(agentUbuntu) - # This condition ensures that this (required) job passes if all of - # the preceeding jobs either pass or are skipped - # adapted from: - # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#job-to-job-dependencies-within-one-stage - condition: and(eq(variables['Build.Reason'], 'PullRequest'), in(dependencies.test.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')) - steps: - - checkout: none - - bash: | - echo "macos tested" diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml deleted file mode 100644 index fa2729b822545..0000000000000 --- a/.azure-pipelines/stage/windows.yml +++ /dev/null @@ -1,125 +0,0 @@ - -parameters: - -# Auth -- name: authGCP - type: string - default: "" - -- name: runBuild - displayName: "Run build" - type: string - default: true - -jobs: -- job: release - displayName: Build and test - condition: | - and(not(canceled()), - eq(${{ parameters.runBuild }}, 'true')) - timeoutInMinutes: 180 - pool: - vmImage: "windows-2019" - steps: - - task: Cache@2 - inputs: - key: '"windows.release" | $(cacheKeyBazel)' - path: $(Build.StagingDirectory)/repository_cache - continueOnError: true - - - bash: | - set -e - ENVOY_SHARED_TMP_DIR="C:\\Users\\VSSADM~1\\AppData\\Local\\Temp\\bazel-shared" - mkdir -p "$ENVOY_SHARED_TMP_DIR" - GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -p "${ENVOY_SHARED_TMP_DIR}" -t gcp_service_account.XXXXXX.json) - bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" - export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" - export ENVOY_SHARED_TMP_DIR - ci/run_envoy_docker.sh ci/windows_ci_steps.sh - displayName: "Run Windows msvc-cl CI" - env: - CI_TARGET: "windows" - ENVOY_DOCKER_BUILD_DIR: "$(Build.StagingDirectory)" - ENVOY_RBE: "true" - BAZEL_BUILD_EXTRA_OPTIONS: >- - --config=remote-ci - --config=rbe-google - --config=remote-msvc-cl - --jobs=$(RbeJobs) - --flaky_test_attempts=2 - - - task: PublishTestResults@2 - inputs: - testResultsFiles: "**/bazel-out/**/testlogs/**/test.xml" - testRunTitle: "windows" - searchFolder: $(Build.StagingDirectory)/tmp - timeoutInMinutes: 10 - condition: not(canceled()) - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: "$(Build.StagingDirectory)/envoy" - artifactName: windows.release - timeoutInMinutes: 10 - condition: not(canceled()) - -- job: docker - displayName: Build Docker image - condition: and(not(canceled()), succeeded(), ne(stageDependencies.env.repo.outputs['changed.mobileOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.docsOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.examplesOnly'], 'true')) - strategy: - matrix: - windows2019: - imageName: 'windows-2019' - windowsBuildType: "windows" - windowsImageBase: "mcr.microsoft.com/windows/servercore" - windowsImageTag: "ltsc2019" - windows2022: - imageName: 'windows-2022' - windowsBuildType: "windows-ltsc2022" - windowsImageBase: "mcr.microsoft.com/windows/nanoserver" - windowsImageTag: "ltsc2022" - dependsOn: ["release"] - timeoutInMinutes: 120 - pool: - vmImage: $(imageName) - steps: - - task: DownloadBuildArtifacts@0 - inputs: - buildType: current - artifactName: "windows.release" - itemPattern: "windows.release/envoy_binary.tar.gz" - downloadType: single - targetPath: $(Build.StagingDirectory) - - bash: | - set -e - # Convert to Unix-style path so tar doesn't think drive letter is a hostname - STAGING_DIR="/$(echo '$(Build.StagingDirectory)' | tr -d ':' | tr '\\' '/')" - mkdir -p windows/amd64 && tar zxf "${STAGING_DIR}/windows.release/envoy_binary.tar.gz" -C ./windows/amd64 - ci/docker_ci.sh - workingDirectory: $(Build.SourcesDirectory) - env: - CI_BRANCH: $(Build.SourceBranch) - CI_SHA1: $(Build.SourceVersion) - DOCKERHUB_USERNAME: $(DockerUsername) - DOCKERHUB_PASSWORD: $(DockerPassword) - WINDOWS_BUILD_TYPE: $(windowsBuildType) - WINDOWS_IMAGE_BASE: $(windowsImageBase) - WINDOWS_IMAGE_TAG: $(windowsImageTag) - -- job: released - displayName: Complete - dependsOn: ["release", "docker"] - pool: - vmImage: $(agentUbuntu) - # This condition ensures that this (required) job passes if all of - # the preceeding jobs either pass or are skipped - # adapted from: - # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#job-to-job-dependencies-within-one-stage - condition: | - and( - eq(variables['Build.Reason'], 'PullRequest'), - in(dependencies.release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), - in(dependencies.docker.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')) - steps: - - checkout: none - - bash: | - echo "windows released" diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index c87f3996e6c28..c957a14a4a9eb 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -8,18 +8,6 @@ parameters: default: - env - prechecks -- name: macBuildStageDeps - displayName: "macOS stage dependencies" - type: object - default: - - env - - prechecks -- name: windowsBuildStageDeps - displayName: "Windows stage dependencies" - type: object - default: - - env - - prechecks - name: checkStageDeps displayName: "Check stage dependencies" type: object @@ -158,25 +146,3 @@ stages: - template: stage/verify.yml parameters: authGCP: $(GcpServiceAccountKey) - -- stage: macos - displayName: macOS - dependsOn: ${{ parameters.macBuildStageDeps }} - variables: - RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] - jobs: - - template: stage/macos.yml - parameters: - authGCP: $(GcpServiceAccountKey) - runBuild: variables['RUN_BUILD'] - -- stage: windows - displayName: Windows - dependsOn: ${{ parameters.windowsBuildStageDeps }} - variables: - RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] - jobs: - - template: stage/windows.yml - parameters: - authGCP: $(GcpServiceAccountKey) - runBuild: variables['RUN_BUILD'] diff --git a/.github/config.yml b/.github/config.yml index 54f7de70d8f35..98a9bf66b8a53 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -23,6 +23,11 @@ checks: # # For example if macos is marked as `required: true` but then has a path # selection that means its doesnt run the check will be `skipped` and pass + macos: + name: Envoy/macOS + required: true + on-run: + - build-macos prechecks: name: Envoy/Prechecks on-run: @@ -75,8 +80,39 @@ checks: - publish - verify required: true + windows: + name: Envoy/Windows + required: true + on-run: + - build-windows run: + build-windows: + paths: + - .bazelrc + - .bazelversion + - .github/config.yml + - api/**/* + - bazel/**/* + - ci/**/* + - configs/**/* + - contrib/**/* + - envoy/**/* + - source/**/* + - test/**/* + build-macos: + paths: + - .bazelrc + - .bazelversion + - .github/config.yml + - api/**/* + - bazel/**/* + - ci/**/* + - configs/**/* + - contrib/**/* + - envoy/**/* + - source/**/* + - test/**/* precheck-deps: paths: - .bazelrc From 1ad59673136eb608eb24e220902eada49f4e3eaa Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 5 Dec 2023 16:04:38 +0800 Subject: [PATCH 137/274] CI: explicitly specify the service want to start for kafka and websocket example (#31175) Signed-off-by: He Jie Xu Signed-off-by: Ryan Northey --- examples/kafka/verify.sh | 4 ++++ examples/websocket/verify.sh | 2 ++ 2 files changed, 6 insertions(+) diff --git a/examples/kafka/verify.sh b/examples/kafka/verify.sh index b234f0bf044b4..efcf0f4afaddc 100755 --- a/examples/kafka/verify.sh +++ b/examples/kafka/verify.sh @@ -4,6 +4,10 @@ export NAME=kafka export PORT_PROXY="${KAFKA_PORT_PROXY:-11100}" export PORT_ADMIN="${KAFKA_PORT_ADMIN:-11101}" +# Explicitly specified the service want to start, since the `kafka-client` is expected to +# not start. +UPARGS="proxy kafka-server zookeeper" + # shellcheck source=examples/verify-common.sh . "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" diff --git a/examples/websocket/verify.sh b/examples/websocket/verify.sh index dd76fef1fcd7f..e241d19e82241 100755 --- a/examples/websocket/verify.sh +++ b/examples/websocket/verify.sh @@ -21,6 +21,8 @@ mkdir -p certs openssl req -batch -new -x509 -nodes -keyout certs/key.pem -out certs/cert.pem openssl pkcs12 -export -passout pass: -out certs/output.pkcs12 -inkey certs/key.pem -in certs/cert.pem +UPARGS="proxy-ws proxy-wss-wss proxy-wss-passthrough service-ws service-wss" + bring_up_example run_log "Interact with web socket ws -> ws" From 9e2eec78fa91b0c28ad97be72b8b912140b087a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:23:38 +0000 Subject: [PATCH 138/274] build(deps): bump distroless/base-nossl-debian12 from `bad3646` to `8a0cabc` in /ci (#31322) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `bad3646` to `8a0cabc`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 37857b0469fcd..967f11f4f3b2c 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:bad36468fcd4e6a96d961eab19ec794be3f86d97da4b75730673d63d8cad336d AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:8a0cabc3a404dfe80725874f4be24ba716c1115693f3a33bdc9565173e84bdfa AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From b92053eccf7cfe3635ba3f53167df4db62b153dc Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 13 Dec 2023 10:26:36 +0000 Subject: [PATCH 139/274] docker/build: Update Ubuntu base image Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 967f11f4f3b2c..d8e88f44fc667 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e3b2026b4fc2faba +ARG BUILD_TAG=20.04@sha256:8eab65df33a6de2844c9aefd19efe8ddb87b7df5e9185a4ab73af936225685bb ARG ENVOY_VRP_BASE_IMAGE=envoy-base From 1ed1fb5abcf652a8bc3f2bbfd34493a2fb18566a Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Tue, 12 Dec 2023 21:24:52 +0800 Subject: [PATCH 140/274] examples/grpc: fix go.mod (#31299) Signed-off-by: doujiang24 Signed-off-by: Ryan Northey --- examples/grpc-bridge/server/kv/go.mod | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/grpc-bridge/server/kv/go.mod b/examples/grpc-bridge/server/kv/go.mod index e69de29bb2d1d..957d61ca9e057 100644 --- a/examples/grpc-bridge/server/kv/go.mod +++ b/examples/grpc-bridge/server/kv/go.mod @@ -0,0 +1,3 @@ +module github.com/envoyproxy/envoy/examples/grpc-bridge/server/kv + +go 1.13 From 4c871d82ab58e98b1cb96569f10bf6c8c44f57b0 Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Wed, 13 Dec 2023 18:43:44 +0800 Subject: [PATCH 141/274] examples/grpc: remove useless go.mod (#31318) also introduce the empty.go file to import the protobuf package, which will be imported from the generated kv.pb.go file. Signed-off-by: doujiang24 Signed-off-by: Ryan Northey --- examples/grpc-bridge/server/go.mod | 6 ++---- examples/grpc-bridge/server/kv/empty.go | 8 ++++++++ examples/grpc-bridge/server/kv/go.mod | 3 --- examples/shared/golang/Dockerfile | 1 - 4 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 examples/grpc-bridge/server/kv/empty.go delete mode 100644 examples/grpc-bridge/server/kv/go.mod diff --git a/examples/grpc-bridge/server/go.mod b/examples/grpc-bridge/server/go.mod index bd7672d03387e..b4fedc2b0e21b 100644 --- a/examples/grpc-bridge/server/go.mod +++ b/examples/grpc-bridge/server/go.mod @@ -1,12 +1,10 @@ -module github.com/envoyproxy/envoy +module github.com/envoyproxy/envoy/examples/grpc-bridge/server go 1.13 require ( - github.com/envoyproxy/envoy/examples/grpc-bridge/server/kv v0.0.0-00010101000000-000000000000 + github.com/golang/protobuf v1.5.2 golang.org/x/net v0.8.0 google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect google.golang.org/grpc v1.53.0 ) - -replace github.com/envoyproxy/envoy/examples/grpc-bridge/server/kv => ./kv diff --git a/examples/grpc-bridge/server/kv/empty.go b/examples/grpc-bridge/server/kv/empty.go new file mode 100644 index 0000000000000..d0c265a0e2afe --- /dev/null +++ b/examples/grpc-bridge/server/kv/empty.go @@ -0,0 +1,8 @@ +// make the kv module is not empty, make go mod tidy happy. +// a kv.pb.go file will be generated by protoc, while running the example. +// also, introduce the empty.go file to import the protobuf package, +// which will be imported from the generated kv.pb.go file. + +package kv + +import _ "github.com/golang/protobuf/proto" diff --git a/examples/grpc-bridge/server/kv/go.mod b/examples/grpc-bridge/server/kv/go.mod deleted file mode 100644 index 957d61ca9e057..0000000000000 --- a/examples/grpc-bridge/server/kv/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/envoyproxy/envoy/examples/grpc-bridge/server/kv - -go 1.13 diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index 6284b723fcb98..1f6d679c848f6 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -44,7 +44,6 @@ WORKDIR /build # Resolve and build Go dependencies as Docker cache COPY go.mod /build/go.mod COPY go.sum /build/go.sum -COPY kv/go.mod /build/kv/go.mod ENV GO111MODULE=on RUN go mod download COPY service.go /build/main.go From 0290902d3b088ac5717c6c3b4bb554a3229bc274 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Thu, 19 Oct 2023 15:15:27 -0700 Subject: [PATCH 142/274] deps: Bump `com_github_curl` -> 8.4.0 (#30088) Signed-off-by: Sunil Narasimhamurthy Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- test/dependencies/curl_test.cc | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index aec16ea6367ae..7fdcf3f6aedd0 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1075,8 +1075,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "curl", project_desc = "Library for transferring data with URLs", project_url = "https://curl.haxx.se", - version = "8.2.1", - sha256 = "f98bdb06c0f52bdd19e63c4a77b5eb19b243bcbbd0f5b002b9f3cba7295a3a42", + version = "8.4.0", + sha256 = "816e41809c043ff285e8c0f06a75a1fa250211bbfb2dc0a037eeef39f1a9e427", strip_prefix = "curl-{version}", urls = ["https://github.com/curl/curl/releases/download/curl-{underscore_version}/curl-{version}.tar.gz"], use_category = ["dataplane_ext", "observability_ext"], @@ -1086,7 +1086,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.grpc_credentials.aws_iam", "envoy.tracers.opencensus", ], - release_date = "2023-07-26", + release_date = "2023-10-11", cpe = "cpe:2.3:a:haxx:libcurl:*", license = "curl", license_url = "https://github.com/curl/curl/blob/curl-{underscore_version}/COPYING", diff --git a/test/dependencies/curl_test.cc b/test/dependencies/curl_test.cc index 6218a3dea66d7..6c402f3d64829 100644 --- a/test/dependencies/curl_test.cc +++ b/test/dependencies/curl_test.cc @@ -15,7 +15,6 @@ TEST(CurlTest, BuiltWithExpectedFeatures) { EXPECT_EQ(0, info->features & CURL_VERSION_KERBEROS4); EXPECT_EQ(0, info->features & CURL_VERSION_SSL); EXPECT_NE(0, info->features & CURL_VERSION_LIBZ); - EXPECT_EQ(0, info->features & CURL_VERSION_NTLM); EXPECT_EQ(0, info->features & CURL_VERSION_GSSNEGOTIATE); EXPECT_NE(0, info->features & CURL_VERSION_ASYNCHDNS); EXPECT_EQ(0, info->features & CURL_VERSION_SPNEGO); From 5ca4acf7ac750090287c62746d7897b5138acea1 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 15 Dec 2023 09:36:12 +0000 Subject: [PATCH 143/274] tooling/deps: CVE updates Signed-off-by: Ryan Northey --- .github/dependabot.yml | 12 - examples/grpc-bridge/client/requirements.in | 1 + examples/grpc-bridge/client/requirements.txt | 20 +- .../shared/python/aiohttp/requirements.in | 2 +- .../shared/python/aiohttp/requirements.txt | 246 ++++--------- mobile/docs/requirements.txt | 202 ----------- tools/base/requirements.in | 9 +- tools/base/requirements.txt | 340 ++++++------------ 8 files changed, 210 insertions(+), 622 deletions(-) delete mode 100644 mobile/docs/requirements.txt diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe477923a6a6f..c1952f33f039b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,12 +9,6 @@ updates: # # Please ensure any new ones are added here, and any that are removed are removed here also. -- package-ecosystem: "pip" - directory: "/.github/actions/pr_notifier" - schedule: - interval: "daily" - time: "06:00" - - package-ecosystem: "pip" directory: "/examples/grpc-bridge/client" schedule: @@ -39,12 +33,6 @@ updates: interval: "daily" time: "06:00" -- package-ecosystem: "pip" - directory: "/mobile/docs" - schedule: - interval: "daily" - time: "06:00" - - package-ecosystem: "pip" directory: "/tools/base" schedule: diff --git a/examples/grpc-bridge/client/requirements.in b/examples/grpc-bridge/client/requirements.in index 96b06d428c7ef..9a455e6a7388d 100644 --- a/examples/grpc-bridge/client/requirements.in +++ b/examples/grpc-bridge/client/requirements.in @@ -2,3 +2,4 @@ requests>=2.22.0 grpcio grpcio-tools protobuf>=3.18.0 +urllib3>=2.0.7 diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index 8c7b31340c018..ede2c17ed3e2e 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile -# To update, run: +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: # # pip-compile --allow-unsafe --generate-hashes requirements.in # @@ -133,7 +133,15 @@ requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via -r requirements.in -urllib3==1.26.17 \ - --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ - --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b - # via requests +urllib3==2.1.0 \ + --hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \ + --hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54 + # via + # -r requirements.in + # requests + +# The following packages are considered to be unsafe in a requirements file: +setuptools==69.0.2 \ + --hash=sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2 \ + --hash=sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6 + # via grpcio-tools diff --git a/examples/shared/python/aiohttp/requirements.in b/examples/shared/python/aiohttp/requirements.in index df84c65abf502..c19919ae7787e 100644 --- a/examples/shared/python/aiohttp/requirements.in +++ b/examples/shared/python/aiohttp/requirements.in @@ -1,2 +1,2 @@ -aiohttp +aiohttp>=3.9.1 pyyaml diff --git a/examples/shared/python/aiohttp/requirements.txt b/examples/shared/python/aiohttp/requirements.txt index f7d632e86ae61..1a3bfa8672d42 100644 --- a/examples/shared/python/aiohttp/requirements.txt +++ b/examples/shared/python/aiohttp/requirements.txt @@ -4,184 +4,92 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -aiohttp==3.8.5 \ - --hash=sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67 \ - --hash=sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c \ - --hash=sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda \ - --hash=sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755 \ - --hash=sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d \ - --hash=sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5 \ - --hash=sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548 \ - --hash=sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690 \ - --hash=sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84 \ - --hash=sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4 \ - --hash=sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a \ - --hash=sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a \ - --hash=sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9 \ - --hash=sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef \ - --hash=sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b \ - --hash=sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a \ - --hash=sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d \ - --hash=sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945 \ - --hash=sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634 \ - --hash=sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7 \ - --hash=sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691 \ - --hash=sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802 \ - --hash=sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c \ - --hash=sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0 \ - --hash=sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8 \ - --hash=sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82 \ - --hash=sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a \ - --hash=sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975 \ - --hash=sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b \ - --hash=sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d \ - --hash=sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3 \ - --hash=sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7 \ - --hash=sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e \ - --hash=sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5 \ - --hash=sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649 \ - --hash=sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff \ - --hash=sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e \ - --hash=sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c \ - --hash=sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22 \ - --hash=sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df \ - --hash=sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e \ - --hash=sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780 \ - --hash=sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905 \ - --hash=sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51 \ - --hash=sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543 \ - --hash=sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6 \ - --hash=sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873 \ - --hash=sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f \ - --hash=sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35 \ - --hash=sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938 \ - --hash=sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b \ - --hash=sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d \ - --hash=sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8 \ - --hash=sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c \ - --hash=sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af \ - --hash=sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42 \ - --hash=sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3 \ - --hash=sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc \ - --hash=sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8 \ - --hash=sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410 \ - --hash=sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c \ - --hash=sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825 \ - --hash=sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9 \ - --hash=sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53 \ - --hash=sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a \ - --hash=sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc \ - --hash=sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8 \ - --hash=sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c \ - --hash=sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a \ - --hash=sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b \ - --hash=sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd \ - --hash=sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14 \ - --hash=sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2 \ - --hash=sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c \ - --hash=sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9 \ - --hash=sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692 \ - --hash=sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1 \ - --hash=sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa \ - --hash=sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a \ - --hash=sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de \ - --hash=sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91 \ - --hash=sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761 \ - --hash=sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd \ - --hash=sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced \ - --hash=sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28 \ - --hash=sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8 \ - --hash=sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824 +aiohttp==3.9.1 \ + --hash=sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f \ + --hash=sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c \ + --hash=sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af \ + --hash=sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4 \ + --hash=sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a \ + --hash=sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489 \ + --hash=sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213 \ + --hash=sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01 \ + --hash=sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5 \ + --hash=sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361 \ + --hash=sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26 \ + --hash=sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0 \ + --hash=sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4 \ + --hash=sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8 \ + --hash=sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1 \ + --hash=sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7 \ + --hash=sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6 \ + --hash=sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a \ + --hash=sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd \ + --hash=sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4 \ + --hash=sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499 \ + --hash=sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183 \ + --hash=sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544 \ + --hash=sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821 \ + --hash=sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501 \ + --hash=sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f \ + --hash=sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe \ + --hash=sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f \ + --hash=sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672 \ + --hash=sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5 \ + --hash=sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2 \ + --hash=sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57 \ + --hash=sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87 \ + --hash=sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0 \ + --hash=sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f \ + --hash=sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7 \ + --hash=sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed \ + --hash=sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70 \ + --hash=sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0 \ + --hash=sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f \ + --hash=sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d \ + --hash=sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f \ + --hash=sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d \ + --hash=sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431 \ + --hash=sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff \ + --hash=sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf \ + --hash=sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83 \ + --hash=sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690 \ + --hash=sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587 \ + --hash=sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e \ + --hash=sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb \ + --hash=sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3 \ + --hash=sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66 \ + --hash=sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014 \ + --hash=sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35 \ + --hash=sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f \ + --hash=sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0 \ + --hash=sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449 \ + --hash=sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23 \ + --hash=sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5 \ + --hash=sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd \ + --hash=sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4 \ + --hash=sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b \ + --hash=sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558 \ + --hash=sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd \ + --hash=sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766 \ + --hash=sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a \ + --hash=sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636 \ + --hash=sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d \ + --hash=sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590 \ + --hash=sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e \ + --hash=sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d \ + --hash=sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c \ + --hash=sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28 \ + --hash=sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065 \ + --hash=sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca # via -r requirements.in aiosignal==1.3.1 \ --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ --hash=sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17 # via aiohttp -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c - # via aiohttp attrs==22.2.0 \ --hash=sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836 \ --hash=sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99 # via aiohttp -charset-normalizer==3.1.0 \ - --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \ - --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \ - --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \ - --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \ - --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \ - --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \ - --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \ - --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \ - --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \ - --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \ - --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \ - --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \ - --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \ - --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \ - --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \ - --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \ - --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \ - --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \ - --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \ - --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \ - --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \ - --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \ - --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \ - --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \ - --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \ - --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \ - --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \ - --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \ - --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \ - --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \ - --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \ - --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \ - --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \ - --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \ - --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \ - --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \ - --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \ - --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \ - --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \ - --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \ - --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \ - --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \ - --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \ - --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \ - --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \ - --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \ - --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \ - --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \ - --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \ - --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \ - --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \ - --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \ - --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \ - --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \ - --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \ - --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \ - --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \ - --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \ - --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \ - --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \ - --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \ - --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \ - --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \ - --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \ - --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \ - --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \ - --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \ - --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \ - --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \ - --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \ - --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \ - --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \ - --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \ - --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ - --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab - # via aiohttp frozenlist==1.3.3 \ --hash=sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c \ --hash=sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f \ diff --git a/mobile/docs/requirements.txt b/mobile/docs/requirements.txt deleted file mode 100644 index 535a68bca954a..0000000000000 --- a/mobile/docs/requirements.txt +++ /dev/null @@ -1,202 +0,0 @@ -alabaster==0.7.13 \ - --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \ - --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2 -Babel==2.12.1 \ - --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \ - --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455 -certifi==2023.7.22 \ - --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ - --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 -charset-normalizer==3.1.0 \ - --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \ - --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \ - --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \ - --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \ - --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \ - --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \ - --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \ - --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \ - --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \ - --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \ - --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \ - --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \ - --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \ - --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \ - --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \ - --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \ - --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \ - --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \ - --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \ - --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \ - --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \ - --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \ - --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \ - --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \ - --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \ - --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \ - --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \ - --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \ - --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \ - --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \ - --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \ - --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \ - --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \ - --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \ - --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \ - --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \ - --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \ - --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \ - --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \ - --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \ - --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \ - --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \ - --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \ - --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \ - --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \ - --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \ - --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \ - --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \ - --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \ - --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \ - --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \ - --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \ - --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \ - --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \ - --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \ - --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \ - --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \ - --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \ - --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \ - --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \ - --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \ - --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \ - --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \ - --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \ - --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \ - --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \ - --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \ - --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \ - --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \ - --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \ - --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \ - --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \ - --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \ - --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ - --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab -docutils==0.19 \ - --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ - --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc -idna==3.4 \ - --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ - --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 -imagesize==1.4.1 \ - --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ - --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a -Jinja2==3.1.2 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 -MarkupSafe==2.1.2 \ - --hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \ - --hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \ - --hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \ - --hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \ - --hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \ - --hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \ - --hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \ - --hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \ - --hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \ - --hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \ - --hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \ - --hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \ - --hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \ - --hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \ - --hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \ - --hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \ - --hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \ - --hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \ - --hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \ - --hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \ - --hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \ - --hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \ - --hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \ - --hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \ - --hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \ - --hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \ - --hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \ - --hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \ - --hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \ - --hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \ - --hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \ - --hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \ - --hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \ - --hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \ - --hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \ - --hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \ - --hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \ - --hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \ - --hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \ - --hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \ - --hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \ - --hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \ - --hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \ - --hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \ - --hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \ - --hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \ - --hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \ - --hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \ - --hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \ - --hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58 -packaging==23.1 \ - --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ - --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f -Pygments==2.15.1 \ - --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ - --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1 -pyparsing==3.0.9 \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb -pytz==2023.3 \ - --hash=sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588 \ - --hash=sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb -requests==2.31.0 \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 -six==1.16.0 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 -snowballstemmer==2.2.0 \ - --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a \ - --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 -Sphinx==7.0.0 \ - --hash=sha256:283c44aa28922bb4223777b44ac0d59af50a279ac7690dfe945bb2b9575dc41b \ - --hash=sha256:3cfc1c6756ef1b132687b813ec6ea2214cb7a7e5d1dcb2772006cb895a0fa469 -sphinx-rtd-theme==1.2.0 \ - --hash=sha256:a0d8bd1a2ed52e0b338cbe19c4b2eef3c5e7a048769753dac6a9f059c7b641b8 \ - --hash=sha256:f823f7e71890abe0ac6aaa6013361ea2696fc8d3e1fa798f463e82bdb77eeff2 -sphinxcontrib-applehelp==1.0.4 \ - --hash=sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228 \ - --hash=sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e -sphinxcontrib-devhelp==1.0.2 \ - --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \ - --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4 -sphinxcontrib-htmlhelp==2.0.1 \ - --hash=sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff \ - --hash=sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903 -sphinxcontrib-httpdomain==1.8.1 \ - --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \ - --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b -sphinxcontrib-googleanalytics==0.4 \ - --hash=sha256:4b19c1f0fce5df6c7da5633201b64a9e5b0cb3210a14fdb4134942ceee8c5d12 \ - --hash=sha256:a6574983f9a58e5864ec10d34dc99914c4d647108b22c9249c8f0038b0cb18b3 -sphinxcontrib-jsmath==1.0.1 \ - --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ - --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 -sphinxcontrib-qthelp==1.0.3 \ - --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 \ - --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 -sphinxcontrib-serializinghtml==1.1.5 \ - --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \ - --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952 -urllib3==2.0.3 \ - --hash=sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1 \ - --hash=sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825 diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 32c3fcbd48362..64d90e7e9e29b 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -1,13 +1,13 @@ abstracts>=0.0.12 aio.api.bazel aio.api.github>=0.2.5 -aiohttp>=3.8.1 +aiohttp>=3.9.1 cffi>=1.15.0 clang-format==14.0.6 clang-tidy==14.0.6 colorama coloredlogs -cryptography>=41.0.1 +cryptography>=41.0.7 dependatool>=0.2.2 envoy.base.utils>=0.4.16 envoy.code.check>=0.5.8 @@ -21,8 +21,8 @@ envoy.gpg.sign>=0.2.0 flake8>=6 frozendict>=2.3.7 gitpython -google-cloud-storage -gsutil +google-auth[aiohttp]>=2.23.3 +gsutil>=5.26 jinja2 multidict>=6.0.2 orjson @@ -38,6 +38,7 @@ setuptools slackclient sphinx>=7 thrift +urllib3>=2.0.7 verboselogs yapf yarl>=1.7.2 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 23f3914623e85..b79ee3c957817 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -86,94 +86,83 @@ aiofiles==23.1.0 \ --hash=sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2 \ --hash=sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635 # via envoy-github-release -aiohttp==3.8.5 \ - --hash=sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67 \ - --hash=sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c \ - --hash=sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda \ - --hash=sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755 \ - --hash=sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d \ - --hash=sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5 \ - --hash=sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548 \ - --hash=sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690 \ - --hash=sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84 \ - --hash=sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4 \ - --hash=sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a \ - --hash=sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a \ - --hash=sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9 \ - --hash=sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef \ - --hash=sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b \ - --hash=sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a \ - --hash=sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d \ - --hash=sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945 \ - --hash=sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634 \ - --hash=sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7 \ - --hash=sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691 \ - --hash=sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802 \ - --hash=sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c \ - --hash=sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0 \ - --hash=sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8 \ - --hash=sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82 \ - --hash=sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a \ - --hash=sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975 \ - --hash=sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b \ - --hash=sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d \ - --hash=sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3 \ - --hash=sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7 \ - --hash=sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e \ - --hash=sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5 \ - --hash=sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649 \ - --hash=sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff \ - --hash=sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e \ - --hash=sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c \ - --hash=sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22 \ - --hash=sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df \ - --hash=sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e \ - --hash=sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780 \ - --hash=sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905 \ - --hash=sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51 \ - --hash=sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543 \ - --hash=sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6 \ - --hash=sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873 \ - --hash=sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f \ - --hash=sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35 \ - --hash=sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938 \ - --hash=sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b \ - --hash=sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d \ - --hash=sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8 \ - --hash=sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c \ - --hash=sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af \ - --hash=sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42 \ - --hash=sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3 \ - --hash=sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc \ - --hash=sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8 \ - --hash=sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410 \ - --hash=sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c \ - --hash=sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825 \ - --hash=sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9 \ - --hash=sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53 \ - --hash=sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a \ - --hash=sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc \ - --hash=sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8 \ - --hash=sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c \ - --hash=sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a \ - --hash=sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b \ - --hash=sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd \ - --hash=sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14 \ - --hash=sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2 \ - --hash=sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c \ - --hash=sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9 \ - --hash=sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692 \ - --hash=sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1 \ - --hash=sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa \ - --hash=sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a \ - --hash=sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de \ - --hash=sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91 \ - --hash=sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761 \ - --hash=sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd \ - --hash=sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced \ - --hash=sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28 \ - --hash=sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8 \ - --hash=sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824 +aiohttp==3.9.1 \ + --hash=sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f \ + --hash=sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c \ + --hash=sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af \ + --hash=sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4 \ + --hash=sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a \ + --hash=sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489 \ + --hash=sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213 \ + --hash=sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01 \ + --hash=sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5 \ + --hash=sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361 \ + --hash=sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26 \ + --hash=sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0 \ + --hash=sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4 \ + --hash=sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8 \ + --hash=sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1 \ + --hash=sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7 \ + --hash=sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6 \ + --hash=sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a \ + --hash=sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd \ + --hash=sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4 \ + --hash=sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499 \ + --hash=sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183 \ + --hash=sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544 \ + --hash=sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821 \ + --hash=sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501 \ + --hash=sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f \ + --hash=sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe \ + --hash=sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f \ + --hash=sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672 \ + --hash=sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5 \ + --hash=sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2 \ + --hash=sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57 \ + --hash=sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87 \ + --hash=sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0 \ + --hash=sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f \ + --hash=sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7 \ + --hash=sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed \ + --hash=sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70 \ + --hash=sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0 \ + --hash=sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f \ + --hash=sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d \ + --hash=sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f \ + --hash=sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d \ + --hash=sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431 \ + --hash=sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff \ + --hash=sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf \ + --hash=sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83 \ + --hash=sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690 \ + --hash=sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587 \ + --hash=sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e \ + --hash=sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb \ + --hash=sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3 \ + --hash=sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66 \ + --hash=sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014 \ + --hash=sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35 \ + --hash=sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f \ + --hash=sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0 \ + --hash=sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449 \ + --hash=sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23 \ + --hash=sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5 \ + --hash=sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd \ + --hash=sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4 \ + --hash=sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b \ + --hash=sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558 \ + --hash=sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd \ + --hash=sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766 \ + --hash=sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a \ + --hash=sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636 \ + --hash=sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d \ + --hash=sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590 \ + --hash=sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e \ + --hash=sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d \ + --hash=sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c \ + --hash=sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28 \ + --hash=sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065 \ + --hash=sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca # via # -r requirements.in # aio-api-github @@ -197,10 +186,6 @@ argcomplete==3.1.1 \ --hash=sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948 \ --hash=sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff # via gsutil -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c - # via aiohttp attrs==23.1.0 \ --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 @@ -366,9 +351,7 @@ charset-normalizer==3.2.0 \ --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \ --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \ --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa - # via - # aiohttp - # requests + # via requests clang-format==14.0.6 \ --hash=sha256:13f2d6d4a2af004a783c65f0921afa8f0384bffcdaf500b6c2cb542edeb0b4a5 \ --hash=sha256:810c649ab97d208cd418c897d50ab6e958eb8d96854527edd80d0dd21a75e914 \ @@ -405,30 +388,30 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==41.0.4 \ - --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ - --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ - --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ - --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ - --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ - --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ - --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ - --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ - --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ - --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ - --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ - --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ - --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ - --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ - --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ - --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ - --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ - --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ - --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ - --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ - --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ - --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ - --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f +cryptography==41.0.7 \ + --hash=sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960 \ + --hash=sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a \ + --hash=sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc \ + --hash=sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a \ + --hash=sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf \ + --hash=sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1 \ + --hash=sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39 \ + --hash=sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406 \ + --hash=sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a \ + --hash=sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a \ + --hash=sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c \ + --hash=sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be \ + --hash=sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15 \ + --hash=sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2 \ + --hash=sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d \ + --hash=sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157 \ + --hash=sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003 \ + --hash=sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248 \ + --hash=sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a \ + --hash=sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec \ + --hash=sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309 \ + --hash=sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7 \ + --hash=sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d # via # -r requirements.in # pyjwt @@ -653,118 +636,24 @@ gitpython==3.1.37 \ --hash=sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33 \ --hash=sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54 # via -r requirements.in -google-api-core==2.11.1 \ - --hash=sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a \ - --hash=sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a - # via - # google-cloud-core - # google-cloud-storage google-apitools==0.5.32 \ --hash=sha256:b78f74116558e0476e19501b5b4b2ac7c93261a69c5449c861ea95cbc853c688 \ --hash=sha256:c3763e52289f61e21c41d5531e20fbda9cc8484a088b8686fd460770db8bad13 # via gsutil -google-auth[aiohttp]==2.22.0 \ - --hash=sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce \ - --hash=sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873 +google-auth[aiohttp]==2.25.2 \ + --hash=sha256:42f707937feb4f5e5a39e6c4f343a17300a459aaf03141457ba505812841cc40 \ + --hash=sha256:473a8dfd0135f75bb79d878436e568f2695dce456764bf3a02b6f8c540b1d256 # via - # google-api-core - # google-cloud-core - # google-cloud-storage + # -r requirements.in # gsutil -google-cloud-core==2.3.3 \ - --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ - --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 - # via google-cloud-storage -google-cloud-storage==2.10.0 \ - --hash=sha256:934b31ead5f3994e5360f9ff5750982c5b6b11604dc072bc452c25965e076dc7 \ - --hash=sha256:9433cf28801671de1c80434238fb1e7e4a1ba3087470e90f70c928ea77c2b9d7 - # via -r requirements.in -google-crc32c==1.5.0 \ - --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ - --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ - --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ - --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ - --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ - --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ - --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ - --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ - --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ - --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ - --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ - --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ - --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ - --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ - --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ - --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ - --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ - --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ - --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ - --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ - --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ - --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ - --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ - --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ - --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ - --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ - --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ - --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ - --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ - --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ - --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ - --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ - --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ - --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ - --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ - --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ - --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ - --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ - --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ - --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ - --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ - --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ - --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ - --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ - --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ - --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ - --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ - --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ - --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ - --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ - --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ - --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ - --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ - --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ - --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ - --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ - --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ - --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ - --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ - --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ - --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ - --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ - --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ - --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ - --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ - --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ - --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ - --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 - # via google-resumable-media google-reauth==0.1.1 \ --hash=sha256:cb39074488d74c8853074dde47368bbf8f739d4a4338b89aab696c895b6d8368 \ --hash=sha256:f9f6852a55c2c5453d581cd01f3d1278e86147c03d008409800390a834235892 # via # gcs-oauth2-boto-plugin # gsutil -google-resumable-media==2.5.0 \ - --hash=sha256:218931e8e2b2a73a58eb354a288e03a0fd5fb1c4583261ac6e4c078666468c93 \ - --hash=sha256:da1bd943e2e114a56d85d6848497ebf9be6a14d3db23e9fc57581e7c3e8170ec - # via google-cloud-storage -googleapis-common-protos==1.59.1 \ - --hash=sha256:0cbedb6fb68f1c07e18eb4c48256320777707e7d0c55063ae56c15db3224a61e \ - --hash=sha256:b35d530fe825fb4227857bc47ad84c33c809ac96f312e13182bdeaa2abe1178a - # via google-api-core -gsutil==5.25 \ - --hash=sha256:7e4cb7fa9a332c401e4b7f5fef1da3e9ef21e3e4885de6d007b07a11b5d0524a +gsutil==5.27 \ + --hash=sha256:681a2d844acdf05fac989da6dd406944ae11cb27a4cf3c9edef74d2585ab5f05 # via -r requirements.in httplib2==0.20.4 \ --hash=sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585 \ @@ -1043,8 +932,6 @@ protobuf==4.21.12 \ # -r requirements.in # envoy-base-utils # envoy-docs-sphinx-runner - # google-api-core - # googleapis-common-protos pyasn1==0.5.0 \ --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde @@ -1174,9 +1061,7 @@ requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via - # google-api-core # google-auth - # google-cloud-storage # pygithub # sphinx retry-decorator==1.1.1 \ @@ -1197,7 +1082,6 @@ six==1.16.0 \ # via # gcs-oauth2-boto-plugin # google-apitools - # google-auth # gsutil # oauth2client # pyu2f @@ -1295,11 +1179,11 @@ uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e # via gidgethub -urllib3==1.26.17 \ - --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ - --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b +urllib3==2.1.0 \ + --hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \ + --hash=sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54 # via - # google-auth + # -r requirements.in # requests uvloop==0.17.0 \ --hash=sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d \ From 23bfb5057ead78db80886f298d57cbf46d827155 Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Tue, 12 Dec 2023 20:55:58 +0800 Subject: [PATCH 144/274] golang: fix replace path in go.mod (#31297) golang: fix path in replace. Signed-off-by: doujiang24 Signed-off-by: Ryan Northey --- .../source/go/pkg/cluster_specifier/capi_impl.go | 1 - .../router/cluster_specifier/test/test_data/simple/go.mod | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go b/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go index 71b3d441d0d25..52847c22aca3b 100644 --- a/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go +++ b/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go @@ -28,7 +28,6 @@ package cluster_specifier #include #include "api.h" - */ import "C" import ( diff --git a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod index b4e13bffc8ab6..9d02c08aeeaa4 100644 --- a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod +++ b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 - github.com/envoyproxy/envoy/contrib/golang v1.24.0 + github.com/envoyproxy/envoy v1.27.0 ) require ( @@ -18,4 +18,4 @@ require ( google.golang.org/protobuf v1.28.1 ) -replace github.com/envoyproxy/envoy/contrib/golang => ../../../../../ +replace github.com/envoyproxy/envoy => ../../../../../../../ From 9bf0fdd2da47e87fdea38cc83eff349af6d4622a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:10:40 +0000 Subject: [PATCH 145/274] build(deps): bump golang.org/x/net from 0.7.0 to 0.17.0 in /contrib/golang/router/cluster_specifier/test/test_data/simple Dependabot couldn't find the original pull request head commit, 31ecd83498872ed5ac58fdae6775a6e6daa99f1f. Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../router/cluster_specifier/test/test_data/simple/go.mod | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod index 9d02c08aeeaa4..408b877343beb 100644 --- a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod +++ b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod @@ -10,12 +10,12 @@ require ( require ( github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect github.com/golang/protobuf v1.5.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect google.golang.org/grpc v1.25.1 // indirect - google.golang.org/protobuf v1.28.1 + google.golang.org/protobuf v1.31.0 ) replace github.com/envoyproxy/envoy => ../../../../../../../ From 745c2a311a3a70c0b62a8d2ab8d99244edbeff9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:13:34 +0000 Subject: [PATCH 146/274] build(deps): bump google.golang.org/grpc Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.56.2 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.56.2...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/load-reporting-service/go.mod | 2 +- examples/load-reporting-service/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/load-reporting-service/go.mod b/examples/load-reporting-service/go.mod index 9f489316e2c07..3236aa638c0ce 100644 --- a/examples/load-reporting-service/go.mod +++ b/examples/load-reporting-service/go.mod @@ -5,5 +5,5 @@ go 1.13 require ( github.com/envoyproxy/go-control-plane v0.11.1 github.com/golang/protobuf v1.5.3 - google.golang.org/grpc v1.56.2 + google.golang.org/grpc v1.56.3 ) diff --git a/examples/load-reporting-service/go.sum b/examples/load-reporting-service/go.sum index 00e178a677513..8ae5c4311de2b 100644 --- a/examples/load-reporting-service/go.sum +++ b/examples/load-reporting-service/go.sum @@ -1473,8 +1473,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From e563329f2d0c304dd59f7583a4bd433caa9adfb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:16:31 +0000 Subject: [PATCH 147/274] build(deps): bump google.golang.org/grpc in /examples/grpc-bridge/server Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.53.0 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.53.0...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/grpc-bridge/server/go.mod | 7 +- examples/grpc-bridge/server/go.sum | 147 +++++++++++++++++++++++++---- 2 files changed, 131 insertions(+), 23 deletions(-) diff --git a/examples/grpc-bridge/server/go.mod b/examples/grpc-bridge/server/go.mod index b4fedc2b0e21b..bc803e2ce7526 100644 --- a/examples/grpc-bridge/server/go.mod +++ b/examples/grpc-bridge/server/go.mod @@ -3,8 +3,7 @@ module github.com/envoyproxy/envoy/examples/grpc-bridge/server go 1.13 require ( - github.com/golang/protobuf v1.5.2 - golang.org/x/net v0.8.0 - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.53.0 + github.com/golang/protobuf v1.5.3 + golang.org/x/net v0.9.0 + google.golang.org/grpc v1.56.3 ) diff --git a/examples/grpc-bridge/server/go.sum b/examples/grpc-bridge/server/go.sum index 77970dfea09bd..14b779866ea36 100644 --- a/examples/grpc-bridge/server/go.sum +++ b/examples/grpc-bridge/server/go.sum @@ -42,13 +42,18 @@ cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wx cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= @@ -57,25 +62,35 @@ cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= @@ -96,6 +111,7 @@ cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oe cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -105,12 +121,16 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= @@ -122,9 +142,12 @@ cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5v cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= @@ -133,6 +156,7 @@ cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uX cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= @@ -146,6 +170,8 @@ cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARy cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= 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= @@ -156,9 +182,12 @@ cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iW cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= @@ -166,6 +195,7 @@ cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= @@ -173,6 +203,7 @@ cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KF cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= @@ -182,6 +213,7 @@ cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxB cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= @@ -191,14 +223,17 @@ cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZW cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= @@ -206,6 +241,7 @@ cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= @@ -214,12 +250,14 @@ cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= @@ -227,15 +265,19 @@ cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aU cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= @@ -250,6 +292,7 @@ cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+o cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= @@ -265,19 +308,26 @@ cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQE cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= @@ -296,6 +346,7 @@ cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtq cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= @@ -312,22 +363,26 @@ cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJP cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= @@ -353,9 +408,11 @@ cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2om cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -363,8 +420,10 @@ cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjp cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= @@ -372,6 +431,7 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7d cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= @@ -388,6 +448,8 @@ cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0 cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= @@ -399,11 +461,13 @@ cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQk cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= @@ -414,35 +478,44 @@ cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3s cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -453,9 +526,11 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/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= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= @@ -470,12 +545,18 @@ cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV6 cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= @@ -487,11 +568,14 @@ cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiC cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= @@ -522,6 +606,7 @@ github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGW github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -547,6 +632,7 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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= @@ -562,9 +648,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -584,6 +672,7 @@ github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -612,8 +701,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -630,7 +720,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -674,6 +763,7 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= 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= @@ -702,6 +792,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -719,6 +810,7 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -741,6 +833,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -760,6 +853,7 @@ 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/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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -825,6 +919,7 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -871,18 +966,18 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= -golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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= @@ -910,6 +1005,8 @@ golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 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= @@ -993,23 +1090,25 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 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= @@ -1018,20 +1117,21 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1092,15 +1192,14 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= @@ -1165,6 +1264,8 @@ google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= 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= @@ -1257,8 +1358,6 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220708155623-50e5f4832e73 h1:sdZWfcGN37Dv0QWIhuasQGMzAQJOL2oqnvot4/kPgfQ= -google.golang.org/genproto v0.0.0-20220708155623-50e5f4832e73/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= @@ -1298,8 +1397,15 @@ google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= 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= @@ -1331,15 +1437,17 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 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= @@ -1354,10 +1462,11 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 8ddcd9219682d665adbb71f8d1ed259d07cc5cd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:17:11 +0000 Subject: [PATCH 148/274] build(deps): bump google.golang.org/grpc Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.56.2 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.56.2...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/ext_authz/auth/grpc-service/go.mod | 2 +- examples/ext_authz/auth/grpc-service/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/ext_authz/auth/grpc-service/go.mod b/examples/ext_authz/auth/grpc-service/go.mod index a12d0d8c17bbe..ec88b7c8d576d 100644 --- a/examples/ext_authz/auth/grpc-service/go.mod +++ b/examples/ext_authz/auth/grpc-service/go.mod @@ -6,5 +6,5 @@ require ( github.com/envoyproxy/go-control-plane v0.11.1 github.com/golang/protobuf v1.5.3 google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e - google.golang.org/grpc v1.56.2 + google.golang.org/grpc v1.56.3 ) diff --git a/examples/ext_authz/auth/grpc-service/go.sum b/examples/ext_authz/auth/grpc-service/go.sum index 00e178a677513..8ae5c4311de2b 100644 --- a/examples/ext_authz/auth/grpc-service/go.sum +++ b/examples/ext_authz/auth/grpc-service/go.sum @@ -1473,8 +1473,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From ffe8aaf239198340bc3f61ef1e1a331e08a9e1d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:19:05 +0000 Subject: [PATCH 149/274] build(deps): bump golang.org/x/net in /examples/grpc-bridge/server Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.8.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/grpc-bridge/server/go.mod | 2 +- examples/grpc-bridge/server/go.sum | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/grpc-bridge/server/go.mod b/examples/grpc-bridge/server/go.mod index bc803e2ce7526..893351a62f470 100644 --- a/examples/grpc-bridge/server/go.mod +++ b/examples/grpc-bridge/server/go.mod @@ -4,6 +4,6 @@ go 1.13 require ( github.com/golang/protobuf v1.5.3 - golang.org/x/net v0.9.0 + golang.org/x/net v0.17.0 google.golang.org/grpc v1.56.3 ) diff --git a/examples/grpc-bridge/server/go.sum b/examples/grpc-bridge/server/go.sum index 14b779866ea36..fed580182a516 100644 --- a/examples/grpc-bridge/server/go.sum +++ b/examples/grpc-bridge/server/go.sum @@ -863,6 +863,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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= @@ -976,8 +977,10 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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= @@ -1099,8 +1102,10 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1109,6 +1114,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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= @@ -1124,8 +1131,9 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 629758e73d396f1815513896cce9adb4cec6efff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:19:44 +0000 Subject: [PATCH 150/274] build(deps): bump envoy-dependency-check in /tools/base Bumps [envoy-dependency-check](https://github.com/envoyproxy/toolshed) from 0.1.10 to 0.1.11. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/0.1.10...0.1.11) --- updated-dependencies: - dependency-name: envoy-dependency-check dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index b79ee3c957817..bafefb5aeeac8 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -32,9 +32,9 @@ aio-api-github==0.2.5 \ # -r requirements.in # envoy-base-utils # envoy-dependency-check -aio-api-nist==0.0.3 \ - --hash=sha256:3465d25e4ffdec35d824960e6d68fbff070f823fde55a40fa4eb53a7fd7d18ca \ - --hash=sha256:5ecf9f32e19ad8804bba1358dde93d1008029335009541dadc69c3823241b382 +aio-api-nist==0.0.4 \ + --hash=sha256:1f2909d60ed4fdb3a3ffc37ad6012666f34078b71648394be91f5e67bbf8b6ca \ + --hash=sha256:c948ee597b9e7cda7982e17bc4aca509b8aa68510899b42e2d382c10fb0d6f89 # via envoy-dependency-check aio-core==0.10.0 \ --hash=sha256:57e2d8dd8ee8779b0ebc2e2447492c0db8d7ed782e9ad1bb2662593740751acb \ @@ -449,9 +449,9 @@ envoy-code-check==0.5.8 \ --hash=sha256:03f32588cc9ed98ab6703cbca6f81df1527db71c3a0f962be6a6084ded40d528 \ --hash=sha256:2b12c51098c78d393823cf055a54e9308c37321d769041f01a2f35b04074d6f3 # via -r requirements.in -envoy-dependency-check==0.1.10 \ - --hash=sha256:4a637e0ed7184791b495041f9baf44567a95cbb979e1e5f26f6a8c33f724cf9e \ - --hash=sha256:e6ae41249f298c865a357edcd8e4850354f222ea4f0dd629c737706b23670c75 +envoy-dependency-check==0.1.11 \ + --hash=sha256:1c4e9f238787bda6d1270452538b361b3f33be3866640373161b70ac9c98c740 \ + --hash=sha256:3318930cf8632b3e9d0bfbd724f148c8eeb2b3e20784d92f62e16c6c706ba511 # via -r requirements.in envoy-distribution-distrotest==0.0.10 \ --hash=sha256:83e912c48da22eb3e514fc1142247d33eb7ed0d59e94eca2ffbd178a26fbf808 \ @@ -973,6 +973,7 @@ pyjwt[crypto]==2.8.0 \ # via # gidgethub # pygithub + # pyjwt pynacl==1.5.0 \ --hash=sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858 \ --hash=sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d \ From efff40eeb11d9aaf99abff8671fd4e64b8a8980b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:22:16 +0000 Subject: [PATCH 151/274] build(deps): bump google.golang.org/grpc Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.25.1 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.25.1...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../cluster_specifier/test/test_data/simple/go.mod | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod index 408b877343beb..257fa217303e3 100644 --- a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod +++ b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod @@ -3,18 +3,16 @@ module example.com/routeconfig go 1.18 require ( - github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 + github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 github.com/envoyproxy/envoy v1.27.0 ) +require github.com/google/go-cmp v0.5.9 // indirect + require ( - github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect - github.com/golang/protobuf v1.5.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect - google.golang.org/grpc v1.25.1 // indirect + github.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 ) From 698c19445c69d191711cc5a7608724a005a9405f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:59:55 +0000 Subject: [PATCH 152/274] build(deps): bump distroless/base-nossl-debian12 from `8a0cabc` to `8c957f0` in /ci (#31389) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `8a0cabc` to `8c957f0`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index d8e88f44fc667..582b539ff333f 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:8a0cabc3a404dfe80725874f4be24ba716c1115693f3a33bdc9565173e84bdfa AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:8c957f0c06030921ee439d028b5778dd1cee9e095092833fe8e4ee795d3a2298 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 49d1aada470a19478793e70dc756f6c09812a419 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 15 Dec 2023 13:33:29 +0000 Subject: [PATCH 153/274] bazel/deps: Shift edenhill/kakfa dep -> confluentinc (#31393) Signed-off-by: Ryan Northey Signed-off-by: phlax --- bazel/repository_locations.bzl | 12 ++++++------ tools/dependency/BUILD | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7fdcf3f6aedd0..3e60cc8f58a44 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1274,17 +1274,17 @@ REPOSITORY_LOCATIONS_SPEC = dict( edenhill_librdkafka = dict( project_name = "Kafka (C/C++ client)", project_desc = "C/C++ client for Apache Kafka (open-source distributed event streaming platform)", - project_url = "https://github.com/edenhill/librdkafka", - version = "2.2.0", - sha256 = "af9a820cbecbc64115629471df7c7cecd40403b6c34bfdbb9223152677a47226", + project_url = "https://github.com/confluentinc/librdkafka", + version = "2.3.0", + sha256 = "2d49c35c77eeb3d42fa61c43757fcbb6a206daa560247154e60642bcdcc14d12", strip_prefix = "librdkafka-{version}", - urls = ["https://github.com/edenhill/librdkafka/archive/v{version}.tar.gz"], + urls = ["https://github.com/confluentinc/librdkafka/archive/v{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.network.kafka_mesh"], - release_date = "2023-07-12", + release_date = "2023-10-25", cpe = "N/A", license = "librdkafka", - license_url = "https://github.com/edenhill/librdkafka/blob/v{version}/LICENSE", + license_url = "https://github.com/confluentinc/librdkafka/blob/v{version}/LICENSE", ), kafka_server_binary = dict( project_name = "Kafka (server binary)", diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 7969cd51d6295..ecbdc0de24163 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -21,12 +21,24 @@ config_setting( flag_values = { ":preload_cve_data": "true", }, + +# Currently we are unable to check for the libdrdkafka dep +# this is a workaround to just exclude it from checks for now +# which is sub-optimal as it also excludes it from CVE scanning +# https://github.com/envoyproxy/envoy/issues/31394 +envoy_genjson( + name = "filtered-dependencies", + filter = """ + .[0] + | del(.edenhill_librdkafka) + """, + srcs = ["//bazel:all_repository_locations"], ) envoy_entry_point( name = "check", args = [ - "--repository_locations=$(location //bazel:all_repository_locations)", + "--repository_locations=$(location :filtered-dependencies)", "--cve_config=$(location :cve.yaml)", ] + select({ ":preloaded_cve_data": ["--cve_data=$(location :cve_data)"], @@ -34,7 +46,7 @@ envoy_entry_point( }), data = [ ":cve.yaml", - "//bazel:all_repository_locations", + ":filtered-dependencies", ] + select({ ":preloaded_cve_data": [":cve_data"], "//conditions:default": [], From d7b4053314ed80be88d10930e67063b151556577 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 15 Dec 2023 14:16:02 +0000 Subject: [PATCH 154/274] bazel/updater: Use workaround to filter deps + format fix (#31397) Signed-off-by: Ryan Northey Signed-off-by: phlax --- bazel/BUILD | 2 +- tools/dependency/BUILD | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bazel/BUILD b/bazel/BUILD index d520ed2e94fb9..d181b91dfb4c6 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -891,7 +891,7 @@ cc_library( updater( name = "update", data = ["//tools/dependency:check"], - dependencies = ":repository_locations", + dependencies = "//tools/dependency:filtered-dependencies", post_script = ":version_update_post.sh", pydict = True, tags = ["skip_on_windows"], diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index ecbdc0de24163..75311222d7880 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -2,7 +2,7 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load("@envoy_repo//:path.bzl", "PATH") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_genjson", "envoy_pytool_binary") load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 @@ -21,6 +21,7 @@ config_setting( flag_values = { ":preload_cve_data": "true", }, +) # Currently we are unable to check for the libdrdkafka dep # this is a workaround to just exclude it from checks for now @@ -28,11 +29,11 @@ config_setting( # https://github.com/envoyproxy/envoy/issues/31394 envoy_genjson( name = "filtered-dependencies", + srcs = ["//bazel:all_repository_locations"], filter = """ .[0] - | del(.edenhill_librdkafka) + | del(.confluentinc_librdkafka) """, - srcs = ["//bazel:all_repository_locations"], ) envoy_entry_point( @@ -48,7 +49,6 @@ envoy_entry_point( ":cve.yaml", ":filtered-dependencies", ] + select({ - ":preloaded_cve_data": [":cve_data"], "//conditions:default": [], }), pkg = "envoy.dependency.check", From fcad60f70958862013df6252ae812365cb50fc54 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 15 Dec 2023 20:12:53 +0000 Subject: [PATCH 155/274] deps/tooling: Fix for CVE scanner Signed-off-by: Ryan Northey --- tools/dependency/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 75311222d7880..4b596c7498473 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -49,6 +49,7 @@ envoy_entry_point( ":cve.yaml", ":filtered-dependencies", ] + select({ + ":preloaded_cve_data": [":cve_data"], "//conditions:default": [], }), pkg = "envoy.dependency.check", From 43d2e2db2f9cb8ce5f1327acb48e961c5b186e89 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 15 Dec 2023 14:21:18 +0000 Subject: [PATCH 156/274] deps/tooling: Add CI paths Signed-off-by: Ryan Northey --- .github/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/config.yml b/.github/config.yml index 98a9bf66b8a53..e490829182060 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -118,6 +118,9 @@ run: - .bazelrc - .bazelversion - .github/config.yml + - .github/dependabot.yml + - bazel/BUILD + - tools/dependency/* - "**/*.bzl" - "**/requirements.txt" publish: From 89aaab450baf66f704d1a75b5c9fba5e87b6d07d Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 15 Dec 2023 14:02:00 +0000 Subject: [PATCH 157/274] deps: Rename edenhill -> confluentinc Signed-off-by: Ryan Northey --- bazel/foreign_cc/BUILD | 2 +- bazel/repositories.bzl | 2 +- bazel/repository_locations.bzl | 2 +- .../kafka/filters/network/source/mesh/upstream_kafka_client.h | 2 +- .../filters/network/source/mesh/upstream_kafka_consumer_impl.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index 3d3b13d969448..765497ca3c738 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -56,7 +56,7 @@ configure_make( name = "librdkafka_build", configure_in_place = True, configure_options = ["--disable-ssl --disable-gssapi --disable-lz4-ext --disable-zstd --disable-curl && cp Makefile.config src/.. && cp config.h src/.."], - lib_source = "@edenhill_librdkafka//:all", + lib_source = "@confluentinc_librdkafka//:all", out_static_libs = [ "librdkafka.a", "librdkafka++.a", diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 71667227f73c0..59615dc6bf2c3 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -1334,7 +1334,7 @@ filegroup( # This archive provides Kafka C/CPP client used by mesh filter to communicate with upstream # Kafka clusters. external_http_archive( - name = "edenhill_librdkafka", + name = "confluentinc_librdkafka", build_file_content = BUILD_ALL_CONTENT, # (adam.kotwasinski) librdkafka bundles in cJSON, which is also bundled in by libvppinfra. # For now, let's just drop this dependency from Kafka, as it's used only for monitoring. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 3e60cc8f58a44..505530aa2d592 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1271,7 +1271,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "Apache-2.0", license_url = "https://github.com/apache/kafka/blob/{version}/LICENSE", ), - edenhill_librdkafka = dict( + confluentinc_librdkafka = dict( project_name = "Kafka (C/C++ client)", project_desc = "C/C++ client for Apache Kafka (open-source distributed event streaming platform)", project_url = "https://github.com/confluentinc/librdkafka", diff --git a/contrib/kafka/filters/network/source/mesh/upstream_kafka_client.h b/contrib/kafka/filters/network/source/mesh/upstream_kafka_client.h index f034f7da4f421..bb883e5c8b2ef 100644 --- a/contrib/kafka/filters/network/source/mesh/upstream_kafka_client.h +++ b/contrib/kafka/filters/network/source/mesh/upstream_kafka_client.h @@ -69,7 +69,7 @@ class KafkaProducer { // Theoretically we do not need to do this and leave it all to destructor, but then closing N // producers would require doing that in sequence, while we can optimize it somewhat (so we just // wait for the slowest one). - // See https://github.com/edenhill/librdkafka/issues/2972 + // See https://github.com/confluentinc/librdkafka/issues/2972 virtual void markFinished() PURE; }; diff --git a/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc b/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc index a6ba7a3e1d71e..0c0a6cd66463c 100644 --- a/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc +++ b/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc @@ -133,7 +133,7 @@ std::vector RichKafkaConsumer::receiveRecordBatch() { // XXX (adam.kotwasinski) There could be something more present in the consumer, // and we could drain it (at least a little) in the next commits. - // See: https://github.com/edenhill/librdkafka/discussions/3897 + // See: https://github.com/confluentinc/librdkafka/discussions/3897 return {inbound_record}; } else { // Nothing extraordinary (timeout because there is nothing upstream), From 596b716eddcbadbe510c5f32b9d9a8c9dfc99e5b Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 3 Jan 2024 08:35:17 +0000 Subject: [PATCH 158/274] docs/examples: Assorted cleanups (#31588) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .github/workflows/verify-requirements.in | 1 + .github/workflows/verify-requirements.txt | 74 +++++++++++++++++++++++ docs/root/_static/css/envoy.css | 5 ++ docs/root/start/sandboxes/setup.rst | 18 ++++++ examples/shared/python/Dockerfile | 6 +- examples/skywalking/verify.sh | 4 +- 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/verify-requirements.in create mode 100644 .github/workflows/verify-requirements.txt diff --git a/.github/workflows/verify-requirements.in b/.github/workflows/verify-requirements.in new file mode 100644 index 0000000000000..87de2e955af37 --- /dev/null +++ b/.github/workflows/verify-requirements.in @@ -0,0 +1 @@ +yq diff --git a/.github/workflows/verify-requirements.txt b/.github/workflows/verify-requirements.txt new file mode 100644 index 0000000000000..2c6e79d55e41c --- /dev/null +++ b/.github/workflows/verify-requirements.txt @@ -0,0 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes verify-requirements.in +# +argcomplete==3.2.1 \ + --hash=sha256:30891d87f3c1abe091f2142613c9d33cac84a5e15404489f033b20399b691fec \ + --hash=sha256:437f67fb9b058da5a090df505ef9be0297c4883993f3f56cb186ff087778cfb4 + # via yq +pyyaml==6.0.1 \ + --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ + --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ + --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ + --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ + --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ + --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \ + --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ + --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ + --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ + --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ + --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ + --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ + --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ + --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ + --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \ + --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ + --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ + --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ + --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ + --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ + --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ + --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ + --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ + --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ + --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ + --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \ + --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \ + --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \ + --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \ + --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \ + --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \ + --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \ + --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \ + --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ + --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ + --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ + --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ + --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ + --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ + --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ + --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \ + --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ + --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f + # via yq +tomlkit==0.12.3 \ + --hash=sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4 \ + --hash=sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba + # via yq +xmltodict==0.13.0 \ + --hash=sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56 \ + --hash=sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852 + # via yq +yq==3.2.3 \ + --hash=sha256:29c8fe1d36b4f64163f4d01314c6ae217539870f610216dee6025dfb5eafafb1 \ + --hash=sha256:b50c91894dad9894d1d36ea77d5722d5495cac9482d2351e55089360a90709ae + # via -r verify-requirements.in diff --git a/docs/root/_static/css/envoy.css b/docs/root/_static/css/envoy.css index c02a65b16df9c..ae805c099312f 100644 --- a/docs/root/_static/css/envoy.css +++ b/docs/root/_static/css/envoy.css @@ -46,6 +46,11 @@ table.docutils div.line-block { border: solid #eee 1px; } +/* restore margin bottom on aligned images */ +.rst-content img.align-center { + margin-bottom: 24px +} + /* suppress errs on pseudo-json code highlights */ .highlight-json .highlight .err { border: inherit; diff --git a/docs/root/start/sandboxes/setup.rst b/docs/root/start/sandboxes/setup.rst index 57c34201a09f4..19de3de6b0663 100644 --- a/docs/root/start/sandboxes/setup.rst +++ b/docs/root/start/sandboxes/setup.rst @@ -107,6 +107,15 @@ Many of the examples use the `curl `_ utility to make ``HTTP`` Instructions for installing `curl `_ on many platforms and operating systems can be `found on the curl website `_. +.. _start_sandboxes_setup_envsubst: + +envsubst +~~~~~~~~ + +Some of the examples require the ``envsubst`` command to interpolate environment variables in templates. + +The command is a part of the GNU ‘gettext’ package, and is available through most package managers. + .. _start_sandboxes_setup_jq: jq @@ -118,6 +127,15 @@ whether it be ``HTTP`` response data, logs or statistics. Instructions for installing `jq `_ on many platforms and operating systems can be `found on the jq website `_. +.. _start_sandboxes_setup_mkpasswd: + +mkpasswd +~~~~~~~~ + +Some of the examples require the ``mkpasswd`` command to generate ~random tokens. + +The command is a part of the ‘whois’ package, and is available through most package managers. + .. _start_sandboxes_setup_netcat: netcat diff --git a/examples/shared/python/Dockerfile b/examples/shared/python/Dockerfile index 998865b454059..73dee5e3f1147 100644 --- a/examples/shared/python/Dockerfile +++ b/examples/shared/python/Dockerfile @@ -15,7 +15,9 @@ CMD tail -f /dev/null FROM python-base as aiohttp-service -ENV DEBIAN_FRONTEND=noninteractive +ARG SERVICE_PORT=8080 +ENV DEBIAN_FRONTEND=noninteractive \ + SERVICE_PORT=$SERVICE_PORT ADD "$PYTHON_REQUIREMENTS_FILE" /tmp/requirements.txt RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ @@ -28,7 +30,7 @@ HEALTHCHECK \ --timeout=1s \ --start-period=1s \ --retries=3 \ - CMD nc -zv localhost 8080 + CMD nc -zv localhost "$SERVICE_PORT" ENTRYPOINT ["python3", "/code/service.py"] diff --git a/examples/skywalking/verify.sh b/examples/skywalking/verify.sh index 9eca5f37e7ae7..b115b7ca2d828 100755 --- a/examples/skywalking/verify.sh +++ b/examples/skywalking/verify.sh @@ -1,8 +1,8 @@ #!/bin/bash -e export NAME=skywalking -export PORT_PROXY="${SKYWALKING_PORT_PROXY:-12600}" -export PORT_UI="${SKYWALKING_PORT_UI:-12601}" +export PORT_PROXY="${SKYWALKING_PORT_PROXY:-11910}" +export PORT_UI="${SKYWALKING_PORT_UI:-11911}" # NB: This allows ES to run in a low-resource environment, # dont do this in a production environment. From be8b96c8bc43a35e749db04ae90864f0287dc1fe Mon Sep 17 00:00:00 2001 From: David Goffredo Date: Thu, 4 Jan 2024 02:28:38 -0500 Subject: [PATCH 159/274] datadog: fix names in Span::spawnChild (#31593) Signed-off-by: David Goffredo --- changelogs/current.yaml | 4 + source/extensions/tracers/datadog/span.cc | 7 +- test/extensions/tracers/datadog/BUILD | 1 + .../extensions/tracers/datadog/naming_test.cc | 223 ++++++++++++++++++ test/extensions/tracers/datadog/span_test.cc | 6 +- 5 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 test/extensions/tracers/datadog/naming_test.cc diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 7a71d5669308f..13f01ba8ce0a6 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -22,11 +22,15 @@ bug_fixes: - area: grpc change: | Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. - area: tracing change: | Fixed a bug that caused the Datadog tracing extension to drop traces that should be kept on account of an extracted sampling decision. + removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/tracers/datadog/span.cc b/source/extensions/tracers/datadog/span.cc index d027419152187..bb15fd9fd1a9b 100644 --- a/source/extensions/tracers/datadog/span.cc +++ b/source/extensions/tracers/datadog/span.cc @@ -90,8 +90,13 @@ Tracing::SpanPtr Span::spawnChild(const Tracing::Config&, const std::string& nam // The OpenTracing implementation ignored the `Tracing::Config` argument, // so we will as well. + // The `name` parameter to this function more closely matches Datadog's + // concept of "resource name." Datadog's "span name," or "operation name," + // instead describes the category of operation being performed, which here + // we hard-code. datadog::tracing::SpanConfig config; - config.name = name; + config.name = "envoy.proxy"; + config.resource = name; config.start = estimateTime(start_time); return std::make_unique(span_->create_child(config)); diff --git a/test/extensions/tracers/datadog/BUILD b/test/extensions/tracers/datadog/BUILD index d292aa9c0f393..4eba5b353436c 100644 --- a/test/extensions/tracers/datadog/BUILD +++ b/test/extensions/tracers/datadog/BUILD @@ -19,6 +19,7 @@ envoy_extension_cc_test( "dict_util_test.cc", "event_scheduler_test.cc", "logger_test.cc", + "naming_test.cc", "span_test.cc", "time_util_test.cc", "tracer_stats_test.cc", diff --git a/test/extensions/tracers/datadog/naming_test.cc b/test/extensions/tracers/datadog/naming_test.cc new file mode 100644 index 0000000000000..0d45cde32b1ac --- /dev/null +++ b/test/extensions/tracers/datadog/naming_test.cc @@ -0,0 +1,223 @@ +/** + * The tests in this file aren't specific to a class, but instead test all + * behavior related to spans' "operation name" (a.k.a. "span name"), + * "resource name," and "service name." + * + * Datadog's model of a span is different from Envoy's. Each Datadog span, + * in addition to having a "service name" and an "operation name," also has a + * "resource name." The operation name indicates the _kind_ of operation + * that is being performed by the service, whereas the resource name contains + * more specifics about what is being operated upon, or about what is doing the + * operating. Envoy has no notion of "resource name," and instead uses + * operation name and tags for this purpose. + * + * When Envoy's tracing interface indicates an operation name, the Datadog + * tracer translates it into a resource name instead. The actual Datadog + * operation name is always hard-coded to the value "envoy.proxy". + * + * Finally, each span's "service name" is derived either from the tracer's + * configuration or a hard-coded default, which is "envoy". + * + * The tests in this file verify all of this behavior for a variety of + * scenarios where spans are created or modified. + */ + +#include "source/extensions/tracers/datadog/config.h" +#include "source/extensions/tracers/datadog/span.h" +#include "source/extensions/tracers/datadog/tracer.h" + +#include "test/mocks/stream_info/mocks.h" +#include "test/mocks/thread_local/mocks.h" +#include "test/mocks/tracing/mocks.h" +#include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace Datadog { +namespace { + +template Config makeConfig(const std::string& yaml) { + Config result; + TestUtility::loadFromYaml(yaml, result); + return result; +} + +class DatadogTracerNamingTest : public testing::Test { +public: + DatadogTracerNamingTest(); + +protected: + /** + * Verify that a tracer configured using the specified \p config_yaml + * produces spans and child spans having the specified + * \p expected_service_name. + * @param config_yaml YAML representation of a + * \c type.googleapis.com/envoy.config.trace.v3.DatadogConfig + * @param expected_service_name service name to expect each span to have + */ + void serviceNameTest(const std::string& config_yaml, const std::string& expected_service_name); + + /** + * Assign through the specified \p result a pointer to the underlying + * \c datadog::tracing::Span to which the specified \p span refers. + * If \p span does not refer to a Datadog span, then this function triggers a + * fatal test assertion. + * An output parameter is used because the \c ASSERT_* macros require that + * the enclosing function have \c void return type. + * @param result pointer to the output value to overwrite + * @param span pointer to an Envoy span that refers to a Datadog span + */ + static void asDatadogSpan(const datadog::tracing::Span** result, const Tracing::SpanPtr& span); + + NiceMock cluster_manager_; + Stats::TestUtil::TestStore store_; + NiceMock thread_local_slot_allocator_; + Event::SimulatedTimeSystem time_; + NiceMock stream_info_; +}; + +DatadogTracerNamingTest::DatadogTracerNamingTest() { + cluster_manager_.initializeClusters({"fake_cluster"}, {}); + cluster_manager_.thread_local_cluster_.cluster_.info_->name_ = "fake_cluster"; + cluster_manager_.initializeThreadLocalClusters({"fake_cluster"}); +} + +void DatadogTracerNamingTest::asDatadogSpan(const datadog::tracing::Span** result, + const Tracing::SpanPtr& span) { + ASSERT_TRUE(span); + const auto as_dd_span_wrapper = dynamic_cast(span.get()); + ASSERT_NE(nullptr, as_dd_span_wrapper); + + const datadog::tracing::Optional& maybe_dd_span = + as_dd_span_wrapper->impl(); + ASSERT_TRUE(maybe_dd_span); + *result = &*maybe_dd_span; +} + +void DatadogTracerNamingTest::serviceNameTest(const std::string& config_yaml, + const std::string& expected_service_name) { + auto config_proto = makeConfig(config_yaml); + + Tracer tracer{config_proto.collector_cluster(), + config_proto.collector_hostname(), + DatadogTracerFactory::makeConfig(config_proto), + cluster_manager_, + *store_.rootScope(), + thread_local_slot_allocator_}; + + // Any values will do for the sake of this test. What we care about is the + // `expected_service_name`. + Tracing::Decision decision; + decision.reason = Tracing::Reason::Sampling; + decision.traced = true; + const std::string operation_name = "some.operation.name"; + Tracing::TestTraceContextImpl context{}; + + const Tracing::SpanPtr span = + tracer.startSpan(Tracing::MockConfig{}, context, stream_info_, operation_name, decision); + const datadog::tracing::Span* dd_span; + asDatadogSpan(&dd_span, span); + + EXPECT_EQ(expected_service_name, dd_span->service_name()); + + const auto child_start = time_.timeSystem().systemTime(); + const std::string child_operation_name = "some.other.operation.name"; + const Tracing::SpanPtr child = + span->spawnChild(Tracing::MockConfig{}, child_operation_name, child_start); + const datadog::tracing::Span* dd_child; + asDatadogSpan(&dd_child, child); + + EXPECT_EQ(expected_service_name, dd_child->service_name()); +} + +TEST_F(DatadogTracerNamingTest, ServiceNameConfigured) { + // If you specify a `service_name` in the tracer configuration, then spans + // created will have that service name. + serviceNameTest(R"EOF( + collector_cluster: fake_cluster + service_name: mr_bigglesworth + )EOF", + "mr_bigglesworth"); +} + +TEST_F(DatadogTracerNamingTest, ServiceNameDefault) { + // If you don't specify a `service_name` in the tracer configuration, then + // spans created will have the default service name, which is "envoy". + serviceNameTest(R"EOF( + collector_cluster: fake_cluster + )EOF", + "envoy"); +} + +TEST_F(DatadogTracerNamingTest, OperationNameAndResourceName) { + // Concerns: + // + // - The span returned by `Tracer::startSpan` has as its resource name the + // operation name passed to `Tracer::startSpan`, and has as its operation + // name "envoy.proxy". + // - The span returned by `Span::spawnChild` has as its resource name the + // operation name passed to `Tracer::spawnChild`, and has as its operation + // name "envoy.proxy". + // - `Span::setOperation` sets the resource name of the span, but does not + // change the operation name. + + auto config_proto = makeConfig(R"EOF( + collector_cluster: fake_cluster + )EOF"); + + Tracer tracer{config_proto.collector_cluster(), + config_proto.collector_hostname(), + DatadogTracerFactory::makeConfig(config_proto), + cluster_manager_, + *store_.rootScope(), + thread_local_slot_allocator_}; + + // Any values will do for the sake of this test. What we care about are the + // operation names and the resource names. + Tracing::Decision decision; + decision.reason = Tracing::Reason::Sampling; + decision.traced = true; + Tracing::TestTraceContextImpl context{}; + + const std::string operation_name = "some.operation.name"; + const Tracing::SpanPtr span = + tracer.startSpan(Tracing::MockConfig{}, context, stream_info_, operation_name, decision); + const datadog::tracing::Span* dd_span; + asDatadogSpan(&dd_span, span); + + EXPECT_EQ("envoy.proxy", dd_span->name()); + EXPECT_EQ(operation_name, dd_span->resource_name()); + + const std::string new_operation_name = "some.new.operation.name"; + span->setOperation(new_operation_name); + + EXPECT_EQ("envoy.proxy", dd_span->name()); + EXPECT_EQ(new_operation_name, dd_span->resource_name()); + + const auto child_start = time_.timeSystem().systemTime(); + const std::string child_operation_name = "some.child.operation.name"; + const Tracing::SpanPtr child = + span->spawnChild(Tracing::MockConfig{}, child_operation_name, child_start); + const datadog::tracing::Span* dd_child; + asDatadogSpan(&dd_child, child); + + EXPECT_EQ("envoy.proxy", dd_child->name()); + EXPECT_EQ(child_operation_name, dd_child->resource_name()); + + const std::string child_new_operation_name = "some.child.new.operation.name"; + child->setOperation(child_new_operation_name); + + EXPECT_EQ("envoy.proxy", dd_child->name()); + EXPECT_EQ(child_new_operation_name, dd_child->resource_name()); +} + +} // namespace +} // namespace Datadog +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/datadog/span_test.cc b/test/extensions/tracers/datadog/span_test.cc index aec618037cb55..bdae08c1437e1 100644 --- a/test/extensions/tracers/datadog/span_test.cc +++ b/test/extensions/tracers/datadog/span_test.cc @@ -207,7 +207,11 @@ TEST_F(DatadogTracerSpanTest, SpawnChild) { EXPECT_NE(nullptr, child_ptr); const datadog::tracing::SpanData& child = *child_ptr; EXPECT_EQ(estimateTime(child_start).wall, child.start.wall); - EXPECT_EQ("child", child.name); + // Setting the operation name actually sets the resource name, because + // Envoy's notion of operation name more closely matches Datadog's notion of + // resource name. The actual operation name is hard-coded as "envoy.proxy". + EXPECT_EQ("child", child.resource); + EXPECT_EQ("envoy.proxy", child.name); EXPECT_EQ(id_, child.trace_id); EXPECT_EQ(id_, child.span_id); EXPECT_EQ(id_, child.parent_id); From 15b30f4097b0faab8fce08ec482ca70001114c9f Mon Sep 17 00:00:00 2001 From: Xie Zhihao Date: Wed, 3 Jan 2024 16:43:31 +0800 Subject: [PATCH 160/274] deps: bump Boost to 1.84.0 (#31566) Signed-off-by: Xie Zhihao Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 505530aa2d592..3dbac5e7b850c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -649,16 +649,16 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Boost", project_desc = "Boost C++ source libraries", project_url = "https://www.boost.org/", - version = "1.78.0", - sha256 = "94ced8b72956591c4775ae2207a9763d3600b30d9d7446562c552f0a14a63be7", + version = "1.84.0", + sha256 = "a5800f405508f5df8114558ca9855d2640a2de8f0445f051fa1c7c3383045724", strip_prefix = "boost_{underscore_version}", - urls = ["https://boostorg.jfrog.io/artifactory/main/release/{version}/source/boost_{underscore_version}.tar.gz"], + urls = ["https://archives.boost.io/release/{version}/source/boost_{underscore_version}.tar.gz"], use_category = ["dataplane_ext"], extensions = [ "envoy.matching.input_matchers.hyperscan", "envoy.regex_engines.hyperscan", ], - release_date = "2021-12-08", + release_date = "2023-12-13", cpe = "cpe:2.3:a:boost:boost:*", license = "Boost", license_url = "https://github.com/boostorg/boost/blob/boost-{version}/LICENSE_1_0.txt", From da23aa430764aa6f11fb5f3cf16137f6219601f2 Mon Sep 17 00:00:00 2001 From: David Goffredo Date: Mon, 8 Jan 2024 10:27:45 -0500 Subject: [PATCH 161/274] datadog: fix span error property Signed-off-by: David Goffredo --- changelogs/current.yaml | 3 + source/extensions/tracers/datadog/BUILD | 1 + source/extensions/tracers/datadog/span.cc | 27 +++-- test/extensions/tracers/datadog/span_test.cc | 106 ++++++++++++++++++- 4 files changed, 127 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 13f01ba8ce0a6..6ee7ecb2820d8 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -22,6 +22,9 @@ bug_fixes: - area: grpc change: | Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug where Datadog spans tagged as errors would not have the appropriate error property set. - area: tracing change: | Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. diff --git a/source/extensions/tracers/datadog/BUILD b/source/extensions/tracers/datadog/BUILD index 43c0f6f2c0e78..94a3e66102918 100644 --- a/source/extensions/tracers/datadog/BUILD +++ b/source/extensions/tracers/datadog/BUILD @@ -43,6 +43,7 @@ envoy_cc_library( deps = [ "//source/common/config:utility_lib", "//source/common/http:async_client_utility_lib", + "//source/common/tracing:common_values_lib", "//source/common/tracing:null_span_lib", "//source/common/upstream:cluster_update_tracker_lib", "//source/common/version:version_lib", diff --git a/source/extensions/tracers/datadog/span.cc b/source/extensions/tracers/datadog/span.cc index bb15fd9fd1a9b..ca6654b83d29e 100644 --- a/source/extensions/tracers/datadog/span.cc +++ b/source/extensions/tracers/datadog/span.cc @@ -2,6 +2,7 @@ #include +#include "source/common/tracing/common_values.h" #include "source/common/tracing/null_span_impl.h" #include "source/extensions/tracers/datadog/time_util.h" @@ -51,14 +52,28 @@ void Span::setTag(absl::string_view name, absl::string_view value) { return; } - // The special "resource.name" tag is a holdover from when the Datadog tracer - // was OpenTracing-based, and so there was no way to set the Datadog resource - // name directly. - // In Envoy, it's still the case that there's no way to set the Datadog - // resource name directly; so, here if the tag name is "resource.name", we - // actually set the resource name instead of setting a tag. + const auto& Tags = Envoy::Tracing::Tags::get(); + if (name == "resource.name") { + // The special "resource.name" tag is a holdover from when the Datadog + // tracer was OpenTracing-based, and so there was no way to set the Datadog + // resource name directly. + // In Envoy, it's still the case that there's no way to set the Datadog + // resource name directly; so, here if the tag name is "resource.name", we + // actually set the resource name instead of setting a tag. span_->set_resource_name(value); + } else if (name == Tags.Error) { + // Envoy marks spans as containing errors by setting the "error" tag. + // Here we translate into the dd-trace-cpp equivalent. + if (value == Tags.True) { + span_->set_error(true); + } + } else if (name == Tags.ErrorReason) { + // Envoy conveys information about an error by setting the "error.reason" + // tag. + // Here we translate into the dd-trace-cpp equivalent. + span_->set_error_message(value); + span_->set_tag(name, value); } else { span_->set_tag(name, value); } diff --git a/test/extensions/tracers/datadog/span_test.cc b/test/extensions/tracers/datadog/span_test.cc index bdae08c1437e1..2b2f624817814 100644 --- a/test/extensions/tracers/datadog/span_test.cc +++ b/test/extensions/tracers/datadog/span_test.cc @@ -5,6 +5,7 @@ #include #include +#include "source/common/tracing/common_values.h" #include "source/common/tracing/null_span_impl.h" #include "source/extensions/tracers/datadog/span.h" #include "source/extensions/tracers/datadog/time_util.h" @@ -139,7 +140,6 @@ TEST_F(DatadogTracerSpanTest, SetTag) { span.setTag("foo", "bar"); span.setTag("boom", "bam"); span.setTag("foo", "new"); - span.setTag("resource.name", "vespene gas"); span.finishSpan(); ASSERT_EQ(1, collector_->chunks.size()); @@ -156,14 +156,112 @@ TEST_F(DatadogTracerSpanTest, SetTag) { found = data.tags.find("boom"); ASSERT_NE(data.tags.end(), found); EXPECT_EQ("bam", found->second); +} + +TEST_F(DatadogTracerSpanTest, SetTagResourceName) { + // The "resource.name" tag is special. It doesn't set a tag, but instead sets + // the span's resource name. + + Span span{std::move(span_)}; + span.setTag("resource.name", "vespene gas"); + span.finishSpan(); - // The "resource.name" tag is special. It doesn't set a tag, but instead the - // span's resource name. - found = data.tags.find("resource.name"); + ASSERT_EQ(1, collector_->chunks.size()); + const auto& chunk = collector_->chunks[0]; + ASSERT_EQ(1, chunk.size()); + const auto& data_ptr = chunk[0]; + ASSERT_NE(nullptr, data_ptr); + const datadog::tracing::SpanData& data = *data_ptr; + + const auto found = data.tags.find("resource.name"); ASSERT_EQ(data.tags.end(), found); EXPECT_EQ("vespene gas", data.resource); } +// The "error" and "error.reason" tags are special. +// +// - The "error" tag is only ever set to "true", and doing so indicates that +// an error occurred during the extent of the span. The corresponding notion +// for a Datadog span is to call `.set_error(true)`, and the result is that +// the underlying Datadog span's `error` property will be `1`. +// - The "error.reason" tag is set to some description of the kind of error +// that occurred. It's debatable whether this more closely corresponds to +// Datadog's `.set_error_message(...)` or to `.set_error_type(...)`, but this +// library chooses `.set_error_message(...)`, which has the result of setting +// the "error.message" tag. The "error.reason" tag is also set to the same +// value. +// - Note that calling `.set_error_message(...)` causes `.set_error(true)` to +// be called. However, it might be possible for Envoy to set the +// "error.reason" tag without also setting the "error" tag. This library +// chooses to treat all "error.reason" as if they imply a corresponding +// "error", i.e. setting "error.reason" without "error" still implies an +// error. + +TEST_F(DatadogTracerSpanTest, SetTagError) { + Span span{std::move(span_)}; + const auto& Tags = Envoy::Tracing::Tags::get(); + span.setTag(Tags.Error, Tags.True); + span.finishSpan(); + + ASSERT_EQ(1, collector_->chunks.size()); + const auto& chunk = collector_->chunks[0]; + ASSERT_EQ(1, chunk.size()); + const auto& data_ptr = chunk[0]; + ASSERT_NE(nullptr, data_ptr); + const datadog::tracing::SpanData& data = *data_ptr; + + ASSERT_TRUE(data.error); + ASSERT_EQ(0, data.tags.count(Tags.Error)); + ASSERT_EQ(0, data.tags.count("error.message")); + ASSERT_EQ(0, data.tags.count(Tags.ErrorReason)); +} + +TEST_F(DatadogTracerSpanTest, SetTagErrorBogus) { + Span span{std::move(span_)}; + const auto& Tags = Envoy::Tracing::Tags::get(); + // `Tags.True`, which is "true", is the only value accepted for the + // `Tags.Error` ("error") tag. All others are ignored. + span.setTag(Tags.Error, Tags.True); + span.setTag(Tags.Error, "false"); + span.setTag(Tags.Error, "supercalifragilisticexpialidocious"); + span.finishSpan(); + + ASSERT_EQ(1, collector_->chunks.size()); + const auto& chunk = collector_->chunks[0]; + ASSERT_EQ(1, chunk.size()); + const auto& data_ptr = chunk[0]; + ASSERT_NE(nullptr, data_ptr); + const datadog::tracing::SpanData& data = *data_ptr; + + ASSERT_TRUE(data.error); + ASSERT_EQ(0, data.tags.count(Tags.Error)); + ASSERT_EQ(0, data.tags.count("error.message")); + ASSERT_EQ(0, data.tags.count(Tags.ErrorReason)); +} + +TEST_F(DatadogTracerSpanTest, SetTagErrorReason) { + Span span{std::move(span_)}; + const auto& Tags = Envoy::Tracing::Tags::get(); + span.setTag(Tags.ErrorReason, "not enough minerals"); + span.finishSpan(); + + ASSERT_EQ(1, collector_->chunks.size()); + const auto& chunk = collector_->chunks[0]; + ASSERT_EQ(1, chunk.size()); + const auto& data_ptr = chunk[0]; + ASSERT_NE(nullptr, data_ptr); + const datadog::tracing::SpanData& data = *data_ptr; + + // In addition to setting the "error.message" and "error.reason" tags, we also + // have `.error == true`. But still there is no "error" tag. + ASSERT_TRUE(data.error); + ASSERT_EQ(0, data.tags.count(Tags.Error)); + ASSERT_EQ(1, data.tags.count("error.message")); + ASSERT_EQ("not enough minerals", data.tags.at("error.message")); + ASSERT_EQ(1, data.tags.count(Tags.ErrorReason)); + ASSERT_EQ("not enough minerals", data.tags.at(Tags.ErrorReason)); +} + TEST_F(DatadogTracerSpanTest, InjectContext) { Span span{std::move(span_)}; From 8c8f6b285dbef344932e7766feec246a0e5d8ff4 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 17 Jan 2024 16:31:36 +0000 Subject: [PATCH 162/274] debs/build: Bump Ubuntu base image -> f2034e7 Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 582b539ff333f..92b0b629066c1 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=20.04@sha256:8eab65df33a6de2844c9aefd19efe8ddb87b7df5e9185a4ab73af936225685bb +ARG BUILD_TAG=20.04@sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8 ARG ENVOY_VRP_BASE_IMAGE=envoy-base From 3663ae5cfbdf778710f0e50b30664d8609f281e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:43:27 +0000 Subject: [PATCH 163/274] build(deps): bump distroless/base-nossl-debian12 from `8c957f0` to `51ab103` in /ci (#32159) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `8c957f0` to `51ab103`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 92b0b629066c1..1979b03ab4588 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:8c957f0c06030921ee439d028b5778dd1cee9e095092833fe8e4ee795d3a2298 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:51ab103bb161fdf8fee4c6311a2d41f484effc409d4f4c58342ab68b2da7ccc2 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 2371cfc5cd117f53e335b298fbd54349ebd5d4e5 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Tue, 6 Feb 2024 09:55:58 +0000 Subject: [PATCH 164/274] deps/build: Bump Ubuntu base image -> bb1c41 Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 1979b03ab4588..7f6f0bd139113 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=20.04@sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8 +ARG BUILD_TAG=20.04@sha256:bb1c41682308d7040f74d103022816d41c50d7b0c89e9d706a74b4e548636e54 ARG ENVOY_VRP_BASE_IMAGE=envoy-base From 15ab92b2e5ab7ffacbb58bc6b811f325963cfcc0 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Tue, 6 Feb 2024 10:16:30 +0000 Subject: [PATCH 165/274] deps/tooling: Bump python (~vulnerable) dependencies Signed-off-by: Ryan Northey --- .../shared/python/aiohttp/requirements.in | 2 +- .../shared/python/aiohttp/requirements.txt | 154 ++++++------ tools/base/requirements.in | 8 +- tools/base/requirements.txt | 230 +++++++++--------- 4 files changed, 201 insertions(+), 193 deletions(-) diff --git a/examples/shared/python/aiohttp/requirements.in b/examples/shared/python/aiohttp/requirements.in index c19919ae7787e..909fe563b01ca 100644 --- a/examples/shared/python/aiohttp/requirements.in +++ b/examples/shared/python/aiohttp/requirements.in @@ -1,2 +1,2 @@ -aiohttp>=3.9.1 +aiohttp>=3.9.2 pyyaml diff --git a/examples/shared/python/aiohttp/requirements.txt b/examples/shared/python/aiohttp/requirements.txt index 1a3bfa8672d42..d34e0f83f6548 100644 --- a/examples/shared/python/aiohttp/requirements.txt +++ b/examples/shared/python/aiohttp/requirements.txt @@ -4,83 +4,83 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -aiohttp==3.9.1 \ - --hash=sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f \ - --hash=sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c \ - --hash=sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af \ - --hash=sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4 \ - --hash=sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a \ - --hash=sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489 \ - --hash=sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213 \ - --hash=sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01 \ - --hash=sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5 \ - --hash=sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361 \ - --hash=sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26 \ - --hash=sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0 \ - --hash=sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4 \ - --hash=sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8 \ - --hash=sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1 \ - --hash=sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7 \ - --hash=sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6 \ - --hash=sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a \ - --hash=sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd \ - --hash=sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4 \ - --hash=sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499 \ - --hash=sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183 \ - --hash=sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544 \ - --hash=sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821 \ - --hash=sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501 \ - --hash=sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f \ - --hash=sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe \ - --hash=sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f \ - --hash=sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672 \ - --hash=sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5 \ - --hash=sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2 \ - --hash=sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57 \ - --hash=sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87 \ - --hash=sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0 \ - --hash=sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f \ - --hash=sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7 \ - --hash=sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed \ - --hash=sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70 \ - --hash=sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0 \ - --hash=sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f \ - --hash=sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d \ - --hash=sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f \ - --hash=sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d \ - --hash=sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431 \ - --hash=sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff \ - --hash=sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf \ - --hash=sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83 \ - --hash=sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690 \ - --hash=sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587 \ - --hash=sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e \ - --hash=sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb \ - --hash=sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3 \ - --hash=sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66 \ - --hash=sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014 \ - --hash=sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35 \ - --hash=sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f \ - --hash=sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0 \ - --hash=sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449 \ - --hash=sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23 \ - --hash=sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5 \ - --hash=sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd \ - --hash=sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4 \ - --hash=sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b \ - --hash=sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558 \ - --hash=sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd \ - --hash=sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766 \ - --hash=sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a \ - --hash=sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636 \ - --hash=sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d \ - --hash=sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590 \ - --hash=sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e \ - --hash=sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d \ - --hash=sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c \ - --hash=sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28 \ - --hash=sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065 \ - --hash=sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca +aiohttp==3.9.3 \ + --hash=sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168 \ + --hash=sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb \ + --hash=sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5 \ + --hash=sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f \ + --hash=sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc \ + --hash=sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c \ + --hash=sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29 \ + --hash=sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4 \ + --hash=sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc \ + --hash=sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc \ + --hash=sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63 \ + --hash=sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e \ + --hash=sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d \ + --hash=sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a \ + --hash=sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60 \ + --hash=sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38 \ + --hash=sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b \ + --hash=sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2 \ + --hash=sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53 \ + --hash=sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5 \ + --hash=sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4 \ + --hash=sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96 \ + --hash=sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58 \ + --hash=sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa \ + --hash=sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321 \ + --hash=sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae \ + --hash=sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce \ + --hash=sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8 \ + --hash=sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194 \ + --hash=sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c \ + --hash=sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf \ + --hash=sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d \ + --hash=sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869 \ + --hash=sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b \ + --hash=sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52 \ + --hash=sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528 \ + --hash=sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5 \ + --hash=sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1 \ + --hash=sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4 \ + --hash=sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8 \ + --hash=sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d \ + --hash=sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7 \ + --hash=sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5 \ + --hash=sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54 \ + --hash=sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3 \ + --hash=sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5 \ + --hash=sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c \ + --hash=sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29 \ + --hash=sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3 \ + --hash=sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747 \ + --hash=sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672 \ + --hash=sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5 \ + --hash=sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11 \ + --hash=sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca \ + --hash=sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768 \ + --hash=sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6 \ + --hash=sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2 \ + --hash=sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533 \ + --hash=sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6 \ + --hash=sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266 \ + --hash=sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d \ + --hash=sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec \ + --hash=sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5 \ + --hash=sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1 \ + --hash=sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b \ + --hash=sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679 \ + --hash=sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283 \ + --hash=sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb \ + --hash=sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b \ + --hash=sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3 \ + --hash=sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051 \ + --hash=sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511 \ + --hash=sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e \ + --hash=sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d \ + --hash=sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542 \ + --hash=sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f # via -r requirements.in aiosignal==1.3.1 \ --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 64d90e7e9e29b..5984e86f5cc4e 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -1,13 +1,13 @@ abstracts>=0.0.12 aio.api.bazel aio.api.github>=0.2.5 -aiohttp>=3.9.1 +aiohttp>=3.9.2 cffi>=1.15.0 clang-format==14.0.6 clang-tidy==14.0.6 colorama coloredlogs -cryptography>=41.0.7 +cryptography>=42.0.0 dependatool>=0.2.2 envoy.base.utils>=0.4.16 envoy.code.check>=0.5.8 @@ -20,10 +20,10 @@ envoy.gpg.identity>=0.1.1 envoy.gpg.sign>=0.2.0 flake8>=6 frozendict>=2.3.7 -gitpython +gitpython>=3.1.41 google-auth[aiohttp]>=2.23.3 gsutil>=5.26 -jinja2 +jinja2>=3.1.3 multidict>=6.0.2 orjson pep8-naming diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index bafefb5aeeac8..8b0acd05a82d2 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -86,83 +86,83 @@ aiofiles==23.1.0 \ --hash=sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2 \ --hash=sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635 # via envoy-github-release -aiohttp==3.9.1 \ - --hash=sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f \ - --hash=sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c \ - --hash=sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af \ - --hash=sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4 \ - --hash=sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a \ - --hash=sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489 \ - --hash=sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213 \ - --hash=sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01 \ - --hash=sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5 \ - --hash=sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361 \ - --hash=sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26 \ - --hash=sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0 \ - --hash=sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4 \ - --hash=sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8 \ - --hash=sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1 \ - --hash=sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7 \ - --hash=sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6 \ - --hash=sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a \ - --hash=sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd \ - --hash=sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4 \ - --hash=sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499 \ - --hash=sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183 \ - --hash=sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544 \ - --hash=sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821 \ - --hash=sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501 \ - --hash=sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f \ - --hash=sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe \ - --hash=sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f \ - --hash=sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672 \ - --hash=sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5 \ - --hash=sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2 \ - --hash=sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57 \ - --hash=sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87 \ - --hash=sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0 \ - --hash=sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f \ - --hash=sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7 \ - --hash=sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed \ - --hash=sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70 \ - --hash=sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0 \ - --hash=sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f \ - --hash=sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d \ - --hash=sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f \ - --hash=sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d \ - --hash=sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431 \ - --hash=sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff \ - --hash=sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf \ - --hash=sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83 \ - --hash=sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690 \ - --hash=sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587 \ - --hash=sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e \ - --hash=sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb \ - --hash=sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3 \ - --hash=sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66 \ - --hash=sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014 \ - --hash=sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35 \ - --hash=sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f \ - --hash=sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0 \ - --hash=sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449 \ - --hash=sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23 \ - --hash=sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5 \ - --hash=sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd \ - --hash=sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4 \ - --hash=sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b \ - --hash=sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558 \ - --hash=sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd \ - --hash=sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766 \ - --hash=sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a \ - --hash=sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636 \ - --hash=sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d \ - --hash=sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590 \ - --hash=sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e \ - --hash=sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d \ - --hash=sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c \ - --hash=sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28 \ - --hash=sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065 \ - --hash=sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca +aiohttp==3.9.3 \ + --hash=sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168 \ + --hash=sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb \ + --hash=sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5 \ + --hash=sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f \ + --hash=sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc \ + --hash=sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c \ + --hash=sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29 \ + --hash=sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4 \ + --hash=sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc \ + --hash=sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc \ + --hash=sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63 \ + --hash=sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e \ + --hash=sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d \ + --hash=sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a \ + --hash=sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60 \ + --hash=sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38 \ + --hash=sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b \ + --hash=sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2 \ + --hash=sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53 \ + --hash=sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5 \ + --hash=sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4 \ + --hash=sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96 \ + --hash=sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58 \ + --hash=sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa \ + --hash=sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321 \ + --hash=sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae \ + --hash=sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce \ + --hash=sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8 \ + --hash=sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194 \ + --hash=sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c \ + --hash=sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf \ + --hash=sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d \ + --hash=sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869 \ + --hash=sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b \ + --hash=sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52 \ + --hash=sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528 \ + --hash=sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5 \ + --hash=sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1 \ + --hash=sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4 \ + --hash=sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8 \ + --hash=sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d \ + --hash=sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7 \ + --hash=sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5 \ + --hash=sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54 \ + --hash=sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3 \ + --hash=sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5 \ + --hash=sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c \ + --hash=sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29 \ + --hash=sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3 \ + --hash=sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747 \ + --hash=sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672 \ + --hash=sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5 \ + --hash=sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11 \ + --hash=sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca \ + --hash=sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768 \ + --hash=sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6 \ + --hash=sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2 \ + --hash=sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533 \ + --hash=sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6 \ + --hash=sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266 \ + --hash=sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d \ + --hash=sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec \ + --hash=sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5 \ + --hash=sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1 \ + --hash=sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b \ + --hash=sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679 \ + --hash=sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283 \ + --hash=sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb \ + --hash=sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b \ + --hash=sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3 \ + --hash=sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051 \ + --hash=sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511 \ + --hash=sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e \ + --hash=sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d \ + --hash=sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542 \ + --hash=sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f # via # -r requirements.in # aio-api-github @@ -388,30 +388,39 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==41.0.7 \ - --hash=sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960 \ - --hash=sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a \ - --hash=sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc \ - --hash=sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a \ - --hash=sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf \ - --hash=sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1 \ - --hash=sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39 \ - --hash=sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406 \ - --hash=sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a \ - --hash=sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a \ - --hash=sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c \ - --hash=sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be \ - --hash=sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15 \ - --hash=sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2 \ - --hash=sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d \ - --hash=sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157 \ - --hash=sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003 \ - --hash=sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248 \ - --hash=sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a \ - --hash=sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec \ - --hash=sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309 \ - --hash=sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7 \ - --hash=sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d +cryptography==42.0.2 \ + --hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \ + --hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \ + --hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \ + --hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \ + --hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \ + --hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \ + --hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \ + --hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \ + --hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \ + --hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \ + --hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \ + --hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \ + --hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \ + --hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \ + --hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \ + --hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \ + --hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \ + --hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \ + --hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \ + --hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \ + --hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \ + --hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \ + --hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \ + --hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \ + --hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \ + --hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \ + --hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \ + --hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \ + --hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \ + --hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \ + --hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \ + --hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f # via # -r requirements.in # pyjwt @@ -632,9 +641,9 @@ gitdb==4.0.10 \ --hash=sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a \ --hash=sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7 # via gitpython -gitpython==3.1.37 \ - --hash=sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33 \ - --hash=sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54 +gitpython==3.1.41 \ + --hash=sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c \ + --hash=sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048 # via -r requirements.in google-apitools==0.5.32 \ --hash=sha256:b78f74116558e0476e19501b5b4b2ac7c93261a69c5449c861ea95cbc853c688 \ @@ -681,9 +690,9 @@ importlib-metadata==6.8.0 \ --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 # via yapf -jinja2==3.1.2 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 +jinja2==3.1.3 \ + --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ + --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 # via # -r requirements.in # envoy-base-utils @@ -973,7 +982,6 @@ pyjwt[crypto]==2.8.0 \ # via # gidgethub # pygithub - # pyjwt pynacl==1.5.0 \ --hash=sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858 \ --hash=sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d \ @@ -986,9 +994,9 @@ pynacl==1.5.0 \ --hash=sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b \ --hash=sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543 # via pygithub -pyopenssl==23.2.0 \ - --hash=sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2 \ - --hash=sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac +pyopenssl==24.0.0 \ + --hash=sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf \ + --hash=sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3 # via # gcs-oauth2-boto-plugin # gsutil From 9ba5055a92869cd4f0c5f4eab51b2c8ae5a78241 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 20:56:55 +0000 Subject: [PATCH 166/274] deps: Bump `com_github_grpc_grpc` -> 1.59.3 (#31641) Signed-off-by: Ryan Northey Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- .bazelrc | 3 ++- bazel/repository_locations.bzl | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index 6b080508f38cf..7533ffc8a62f6 100644 --- a/.bazelrc +++ b/.bazelrc @@ -211,7 +211,8 @@ build:coverage --instrumentation_filter="^//source(?!/common/quic/platform)[/:], build:coverage --remote_download_minimal build:coverage --define=tcmalloc=gperftools build:coverage --define=no_debug_info=1 -build:coverage --linkopt=-Wl,-s +# `--no-relax` is required for coverage to not err with `relocation R_X86_64_REX_GOTPCRELX` +build:coverage --linkopt=-Wl,-s,--no-relax build:coverage --test_env=ENVOY_IP_TEST_VERSIONS=v4only build:test-coverage --test_arg="-l trace" diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 3dbac5e7b850c..3546ad1a74f2d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -376,12 +376,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "gRPC", project_desc = "gRPC C core library", project_url = "https://grpc.io", - version = "1.59.1", - sha256 = "916f88a34f06b56432611aaa8c55befee96d0a7b7d7457733b9deeacbc016f99", + version = "1.59.3", + sha256 = "ea281bb3489520ad4fb96ae84b45ed194a1f0b944d3e74f925f5e019d31ecd0f", strip_prefix = "grpc-{version}", urls = ["https://github.com/grpc/grpc/archive/v{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2023-10-06", + release_date = "2023-11-17", cpe = "cpe:2.3:a:grpc:grpc:*", license = "Apache-2.0", license_url = "https://github.com/grpc/grpc/blob/v{version}/LICENSE", From 22195f339a36a0f542edbb2ae709b76a2ecc0492 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Thu, 1 Feb 2024 14:48:06 -0800 Subject: [PATCH 167/274] coverage: lower coverage for source/common/io/ (#32150) Coverage CI is failing because coverage is too low in source/common/io because CI is not executing io_uring code. #32149 Signed-off-by: Ryan Hamilton Signed-off-by: Ryan Northey --- test/per_file_coverage.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 35e0e48d006ef..f00cb92391319 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -3,7 +3,7 @@ # directory:coverage_percent # for existing directories with low coverage. declare -a KNOWN_LOW_COVERAGE=( -"source/common:96.2" +"source/common:96.1" "source/common/api:84.5" "source/common/api/posix:81.8" "source/common/config:94.8" @@ -11,6 +11,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/event:95.1" # Emulated edge events guards don't report LCOV "source/common/filesystem/posix:96.2" # FileReadToEndNotReadable fails in some env; createPath can't test all failure branches. "source/common/http/http2:95.2" +"source/common/io:6.7" "source/common/json:93.7" "source/common/matcher:94.6" "source/common/network:94.4" # Flaky, `activateFileEvents`, `startSecureTransport` and `ioctl`, listener_socket do not always report LCOV From 29ad9261ed461a88135e735e54eb0c27fae692f3 Mon Sep 17 00:00:00 2001 From: Tony Allen Date: Wed, 7 Feb 2024 04:53:49 -0500 Subject: [PATCH 168/274] deps: Bump `com_github_grpc_grpc` from 1.59.3 -> 1.59.4 (#32235) Signed-off-by: Tony Allen Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 3546ad1a74f2d..0fff5cd49b197 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -376,12 +376,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "gRPC", project_desc = "gRPC C core library", project_url = "https://grpc.io", - version = "1.59.3", - sha256 = "ea281bb3489520ad4fb96ae84b45ed194a1f0b944d3e74f925f5e019d31ecd0f", + version = "1.59.4", + sha256 = "6edc67c2ad200c5b618c421f6e8c1b734a4aa3e741975e683491da03390ebf63", strip_prefix = "grpc-{version}", urls = ["https://github.com/grpc/grpc/archive/v{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2023-11-17", + release_date = "2024-02-05", cpe = "cpe:2.3:a:grpc:grpc:*", license = "Apache-2.0", license_url = "https://github.com/grpc/grpc/blob/v{version}/LICENSE", From 96d2db4898fca5aeb04b790cc515f251bbaebf6f Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 16 Jan 2024 15:45:24 +0000 Subject: [PATCH 169/274] release/ci: Fix artefact publishing (#31837) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .azure-pipelines/ci.yml | 9 ++++++++- .azure-pipelines/stage/linux.yml | 1 + .github/config.yml | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index 0103648c1ad22..0fa528e8c8d2c 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -3,6 +3,10 @@ parameters: displayName: "CI target" type: string default: release +- name: artifactName + displayName: "Artifact name" + type: string + default: "" - name: artifactSuffix displayName: "Suffix of artifact" type: string @@ -338,6 +342,9 @@ steps: - task: PublishBuildArtifacts@1 inputs: pathtoPublish: "$(Build.StagingDirectory)/envoy" - artifactName: ${{ parameters.ciTarget }} + ${{ if eq(parameters.artifactName, '') }}: + artifactName: ${{ parameters.ciTarget }} + ${{ if ne(parameters.artifactName, '') }}: + artifactName: ${{ parameters.artifactName }} timeoutInMinutes: 10 condition: eq(${{ parameters.publishEnvoy }}, 'true') diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index 01adafd38369f..04ce08fb03899 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -60,6 +60,7 @@ jobs: name: target - template: ../ci.yml parameters: + artifactName: release managedAgent: ${{ parameters.managedAgent }} ciTarget: $(target.value) cacheName: "release" diff --git a/.github/config.yml b/.github/config.yml index e490829182060..03f6340b27dd4 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -136,6 +136,7 @@ run: - examples/**/* - source/**/* - tools/**/* + - VERSION.txt verify: paths: - .bazelrc @@ -149,6 +150,7 @@ run: - examples/**/* - source/**/* - tools/**/* + - VERSION.txt push: paths tables: From aae5eb40a072ead82021c9c7fc125857b5bf6d4a Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 13 Dec 2023 21:45:45 +0000 Subject: [PATCH 170/274] verify/examples: Expect/allow failure for local_ratelimit (#31348) https://github.com/envoyproxy/envoy/issues/31347 Signed-off-by: Ryan Northey Signed-off-by: phlax --- ci/verify_examples.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/verify_examples.sh b/ci/verify_examples.sh index f710f497e0728..62b57d5ed2e9c 100755 --- a/ci/verify_examples.sh +++ b/ci/verify_examples.sh @@ -13,6 +13,8 @@ FLAKY_SANDBOXES=( double-proxy # https://github.com/envoyproxy/envoy/issues/28543 golang-network + # https://github.com/envoyproxy/envoy/issues/31347 + local_ratelimit # https://github.com/envoyproxy/envoy/issues/28541 wasm-cc # https://github.com/envoyproxy/envoy/issues/28546 From 8d5e0d9e9b2b10d0ef6579c3e212b3c78976e1d1 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 9 Feb 2024 09:33:27 +0000 Subject: [PATCH 171/274] ci/release: Ensure windows is built on release Signed-off-by: Ryan Northey --- .github/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/config.yml b/.github/config.yml index 03f6340b27dd4..50a28027053d7 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -100,6 +100,7 @@ run: - envoy/**/* - source/**/* - test/**/* + - VERSION.txt build-macos: paths: - .bazelrc From c6fe0ac50dee481834ff97d569d90876b020f45b Mon Sep 17 00:00:00 2001 From: Jacob Neil Taylor Date: Sat, 8 Jul 2023 21:35:42 +1000 Subject: [PATCH 172/274] Fix crash from AWS NLB healthchecks when proxy protocol is enabled Fix: [CVE-2024-23327](https://github.com/envoyproxy/envoy/security/advisories/GHSA-4h5x-x9vh-m29j) Signed-off-by: Jacob Neil Taylor Signed-off-by: Greg Greenway Signed-off-by: Ryan Northey --- changelogs/current.yaml | 4 ++ .../proxy_protocol/proxy_protocol_header.cc | 9 ++++ .../listener/proxy_protocol/proxy_protocol.cc | 33 ++++++++++----- .../proxy_protocol/proxy_protocol_test.cc | 28 +++++++++++++ .../proxy_protocol_integration_test.cc | 41 +++++++++++++++++++ test/per_file_coverage.sh | 1 + tools/spelling/spelling_dictionary.txt | 1 + 7 files changed, 107 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 6ee7ecb2820d8..910e7320f6e9e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -32,6 +32,10 @@ bug_fixes: change: | Fixed a bug that caused the Datadog tracing extension to drop traces that should be kept on account of an extracted sampling decision. +- area: proxy protocol + change: | + fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). removed_config_or_runtime: diff --git a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc index 0bbe17dd6d9ef..1c8537964f720 100644 --- a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc +++ b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc @@ -127,6 +127,15 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer } ASSERT(extension_length <= std::numeric_limits::max()); + if (proxy_proto_data.src_addr_ == nullptr || proxy_proto_data.src_addr_->ip() == nullptr) { + IS_ENVOY_BUG("Missing or incorrect source IP in proxy_proto_data_"); + return false; + } + if (proxy_proto_data.dst_addr_ == nullptr || proxy_proto_data.dst_addr_->ip() == nullptr) { + IS_ENVOY_BUG("Missing or incorrect dest IP in proxy_proto_data_"); + return false; + } + const auto& src = *proxy_proto_data.src_addr_->ip(); const auto& dst = *proxy_proto_data.dst_addr_->ip(); generateV2Header(src.addressAsString(), dst.addressAsString(), src.port(), dst.port(), diff --git a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc index ce4df5066bd93..fea08b3126295 100644 --- a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc @@ -144,24 +144,37 @@ ReadOrParseState Filter::parseBuffer(Network::ListenerFilterBuffer& buffer) { if (proxy_protocol_header_.has_value() && !cb_->filterState().hasData( Network::ProxyProtocolFilterState::key())) { - if (!proxy_protocol_header_.value().local_command_) { - auto buf = reinterpret_cast(buffer.rawSlice().mem_); + auto buf = reinterpret_cast(buffer.rawSlice().mem_); + if (proxy_protocol_header_.value().local_command_) { + ENVOY_LOG(trace, "Parsed proxy protocol header, cmd: LOCAL, length: {}, buffer: {}", + proxy_protocol_header_.value().wholeHeaderLength(), + Envoy::Hex::encode(buf, proxy_protocol_header_.value().wholeHeaderLength())); + + cb_->filterState().setData( + Network::ProxyProtocolFilterState::key(), + std::make_unique(Network::ProxyProtocolData{ + socket.connectionInfoProvider().remoteAddress(), + socket.connectionInfoProvider().localAddress(), parsed_tlvs_}), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + } else { ENVOY_LOG( trace, - "Parsed proxy protocol header, length: {}, buffer: {}, TLV length: {}, TLV buffer: {}", + "Parsed proxy protocol header, cmd: PROXY, length: {}, buffer: {}, TLV length: {}, TLV " + "buffer: {}", proxy_protocol_header_.value().wholeHeaderLength(), Envoy::Hex::encode(buf, proxy_protocol_header_.value().wholeHeaderLength()), proxy_protocol_header_.value().extensions_length_, Envoy::Hex::encode(buf + proxy_protocol_header_.value().headerLengthWithoutExtension(), proxy_protocol_header_.value().extensions_length_)); + cb_->filterState().setData( + Network::ProxyProtocolFilterState::key(), + std::make_unique(Network::ProxyProtocolData{ + proxy_protocol_header_.value().remote_address_, + proxy_protocol_header_.value().local_address_, parsed_tlvs_}), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); } - - cb_->filterState().setData( - Network::ProxyProtocolFilterState::key(), - std::make_unique(Network::ProxyProtocolData{ - proxy_protocol_header_.value().remote_address_, - proxy_protocol_header_.value().local_address_, parsed_tlvs_}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } if (proxy_protocol_header_.has_value() && !proxy_protocol_header_.value().local_command_) { diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc index 07eeaf2591abd..0fd93c8f8284e 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -627,6 +627,34 @@ TEST_P(ProxyProtocolTest, V2LocalConnectionExtension) { disconnect(); } +TEST_P(ProxyProtocolTest, V2LocalConnectionFilterState) { + // A well-formed local proxy protocol v2 header sampled from an AWS NLB healthcheck request, + // no address, 1 TLV is present. + constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, + 0x0a, 0x20, 0x00, 0x00, 0x07, 0x00, 0x00, 0x04, 0x0a, 0x0b, 0x0c, + 0x0d, 'm', 'o', 'r', 'e', 'd', 'a', 't', 'a'}; + envoy::extensions::filters::listener::proxy_protocol::v3::ProxyProtocol proto_config; + connect(true, &proto_config); + write(buffer, sizeof(buffer)); + expectData("moredata"); + + auto& filter_state = server_connection_->streamInfo().filterState(); + const auto& proxy_proto_data = filter_state + ->getDataReadOnly( + Network::ProxyProtocolFilterState::key()) + ->value(); + + if (server_connection_->connectionInfoProvider().remoteAddress()->ip()->version() == + Envoy::Network::Address::IpVersion::v6) { + EXPECT_EQ(proxy_proto_data.dst_addr_->ip()->addressAsString(), "::1"); + } else if (server_connection_->connectionInfoProvider().remoteAddress()->ip()->version() == + Envoy::Network::Address::IpVersion::v4) { + EXPECT_EQ(proxy_proto_data.dst_addr_->ip()->addressAsString(), "127.0.0.1"); + } + EXPECT_FALSE(server_connection_->connectionInfoProvider().localAddressRestored()); + disconnect(); +} + TEST_P(ProxyProtocolTest, V2ShortV4) { // An ipv4/tcp connection that has incorrect addr-len encoded constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc index 24c44e27a7339..72637a7acd02f 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc @@ -640,5 +640,46 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassAll) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } +TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2ProxyProtocolPassWithTypeLocal) { + setup(true, {}, {}); + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + + // A well-formed proxy protocol v2 header sampled from an AWS NLB healthcheck request, with + // command type 'LOCAL' (0 for the low 4 bits of the 13th octet). + constexpr uint8_t v2_protocol[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, + 0x55, 0x49, 0x54, 0x0a, 0x20, 0x00, 0x00, 0x00, + 'm', 'o', 'r', 'e', 'd', 'a', 't', 'a'}; + Buffer::OwnedImpl buffer(v2_protocol, sizeof(v2_protocol)); + ASSERT_TRUE(tcp_client->write(buffer.toString())); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); + std::string header_start; + // - signature + // - version and command type, address family and protocol, length of addresses + // - src address, dest address + if (GetParam() == Envoy::Network::Address::IpVersion::v4) { + const char data[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, + 0x21, 0x11, 0x00, 0x0c, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01}; + header_start = std::string(data, sizeof(data)); + } else { + const char data[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, + 0x21, 0x21, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; + header_start = std::string(data, sizeof(data)); + } + + constexpr absl::string_view more_data("moredata"); + const size_t offset = header_start.length() + (2 * sizeof(uint16_t)); // Skip over the ports + std::string observed_data; + ASSERT_TRUE(fake_upstream_connection_->waitForData(offset + more_data.length(), &observed_data)); + EXPECT_THAT(observed_data, testing::StartsWith(header_start)); + EXPECT_EQ(more_data, absl::string_view(&observed_data[offset], more_data.length())); + + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); +} + } // namespace } // namespace Envoy diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index f00cb92391319..c156aea3e8bce 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -29,6 +29,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/access_loggers/wasm:93.5" "source/extensions/clusters/common:91.5" # This can be increased again once `#24903` lands "source/extensions/common:93.0" #flaky: be careful adjusting +"source/extensions/common/proxy_protocol:93.8" # Adjusted for security patch "source/extensions/common/tap:94.2" "source/extensions/common/wasm:87.5" # flaky: be careful adjusting "source/extensions/common/wasm/ext:92.0" diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 83eb11a753d44..a91f8e605f7b9 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -966,6 +966,7 @@ netblocks netfilter netlink netmask +NLB NLMSG nonblocking noncopyable From 84ffb386f99c79ea7700017e314a8294735c8b2d Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 15 Nov 2023 10:10:56 -0500 Subject: [PATCH 173/274] Fix crash when idle and per try timeouts occurs within backoff interval Fix [CVE-2024-23322](https://github.com/envoyproxy/envoy/security/advisories/GHSA-6p83-mfmh-qv38) Signed-off-by: yavlasov Signed-off-by: Ryan Northey Signed-off-by: yanavlasov --- changelogs/current.yaml | 4 +- source/common/router/router.cc | 1 + source/common/router/upstream_request.cc | 9 +++ .../http_timeout_integration_test.cc | 77 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 910e7320f6e9e..5c57a1a696af9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -36,7 +36,9 @@ bug_fixes: change: | fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives a PROXY protocol header with address type LOCAL (typically used for health checks). - +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 82dea84326498..ab28804d09863 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1065,6 +1065,7 @@ void Filter::onResponseTimeout() { // Called when the per try timeout is hit but we didn't reset the request // (hedge_on_per_try_timeout enabled). void Filter::onSoftPerTryTimeout(UpstreamRequest& upstream_request) { + ASSERT(!upstream_request.retried()); // Track this as a timeout for outlier detection purposes even though we didn't // cancel the request yet and might get a 2xx later. updateOutlierDetection(Upstream::Outlier::Result::LocalOriginTimeout, upstream_request, diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 630465d000ac1..653c87d39db9c 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -503,11 +503,20 @@ void UpstreamRequest::setupPerTryTimeout() { void UpstreamRequest::onPerTryIdleTimeout() { ENVOY_STREAM_LOG(debug, "upstream per try idle timeout", *parent_.callbacks()); + if (per_try_timeout_) { + // Disable the per try idle timer, so it does not trigger further retries + per_try_timeout_->disableTimer(); + } stream_info_.setResponseFlag(StreamInfo::ResponseFlag::StreamIdleTimeout); parent_.onPerTryIdleTimeout(*this); } void UpstreamRequest::onPerTryTimeout() { + if (per_try_idle_timeout_) { + // Delete the per try idle timer, so it does not trigger further retries. + // The timer has to be deleted to prevent data flow from re-arming it. + per_try_idle_timeout_.reset(); + } // If we've sent anything downstream, ignore the per try timeout and let the response continue // up to the global timeout if (!parent_.downstreamResponseStarted()) { diff --git a/test/integration/http_timeout_integration_test.cc b/test/integration/http_timeout_integration_test.cc index 50c6726395c3e..12eeb44c0c80d 100644 --- a/test/integration/http_timeout_integration_test.cc +++ b/test/integration/http_timeout_integration_test.cc @@ -625,4 +625,81 @@ TEST_P(HttpTimeoutIntegrationTest, RequestHeaderTimeout) { EXPECT_THAT(response, AllOf(HasSubstr("408"), HasSubstr("header"))); } +// Validate that Envoy correctly handles per try and per try IDLE timeouts +// that are firing within the backoff interval. +TEST_P(HttpTimeoutIntegrationTest, OriginalRequestCompletesBeforeBackoffTimer) { + auto host = config_helper_.createVirtualHost("example.com", "/test_retry"); + host.set_include_is_timeout_retry_header(true); + config_helper_.addVirtualHost(host); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + auto* route_config = hcm.mutable_route_config(); + auto* virtual_host = route_config->mutable_virtual_hosts(1); + auto* route = virtual_host->mutable_routes(0)->mutable_route(); + auto* retry_policy = route->mutable_retry_policy(); + retry_policy->mutable_per_try_idle_timeout()->set_seconds(0); + // per try IDLE timeout is 400 ms + retry_policy->mutable_per_try_idle_timeout()->set_nanos(400 * 1000 * 1000); + }); + initialize(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + auto encoder_decoder = codec_client_->startRequest(Http::TestRequestHeaderMapImpl{ + {":method", "POST"}, + {":path", "/test_retry"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {"x-forwarded-for", "10.0.0.1"}, + {"x-envoy-retry-on", "5xx"}, + // Enable hedge_on_per_try_timeout so that original request is not reset + {"x-envoy-hedge-on-per-try-timeout", "true"}, + {"x-envoy-upstream-rq-timeout-ms", "500"}, + // Make per try timeout the same as the per try idle timeout + // NOTE: it can be a bit longer, within the back off interval + {"x-envoy-upstream-rq-per-try-timeout-ms", "400"}}); + auto response = std::move(encoder_decoder.second); + request_encoder_ = &encoder_decoder.first; + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + codec_client_->sendData(*request_encoder_, 0, true); + + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); + + // Trigger per try timeout (but not global timeout). This will actually trigger + // both IDLE and request timeouts in the same I/O operation. + timeSystem().advanceTimeWait(std::chrono::milliseconds(400)); + + // Trigger retry (there's a 25ms backoff before it's issued). + timeSystem().advanceTimeWait(std::chrono::milliseconds(26)); + + // Wait for a second request to be sent upstream + FakeStreamPtr upstream_request2; + + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request2)); + + ASSERT_TRUE(upstream_request2->waitForHeadersComplete()); + + // Expect the x-envoy-is-timeout-header to set to indicate to the upstream this is a retry + // initiated by a previous per try timeout. + EXPECT_EQ(upstream_request2->headers().getEnvoyIsTimeoutRetryValue(), "true"); + + ASSERT_TRUE(upstream_request2->waitForEndStream(*dispatcher_)); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + // Respond to the second request (it does not matter which request gets response). + upstream_request2->encodeHeaders(response_headers, true); + ASSERT_TRUE(response->waitForEndStream()); + + // The first request should be reset since we used the response from the second request. + ASSERT_TRUE(upstream_request_->waitForReset(std::chrono::seconds(15))); + + codec_client_->close(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + } // namespace Envoy From d41d61a841b5377e47147921da2dc20208d35d8f Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 15 Nov 2023 13:57:57 -0500 Subject: [PATCH 174/274] Cache RE object in uri template matcher. Fix [CVE-2024-23323](https://github.com/envoyproxy/envoy/security/advisories/GHSA-x278-4w4x-r7ch) Signed-off-by: yavlasov Signed-off-by: Ryan Northey Signed-off-by: yanavlasov --- changelogs/current.yaml | 3 +++ .../extensions/path/match/uri_template/uri_template_match.cc | 3 +-- .../extensions/path/match/uri_template/uri_template_match.h | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 5c57a1a696af9..9b7368d602900 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -39,6 +39,9 @@ bug_fixes: - area: http change: | Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/path/match/uri_template/uri_template_match.cc b/source/extensions/path/match/uri_template/uri_template_match.cc index 6313c291b4a5a..421f6a5f296b5 100644 --- a/source/extensions/path/match/uri_template/uri_template_match.cc +++ b/source/extensions/path/match/uri_template/uri_template_match.cc @@ -18,8 +18,7 @@ namespace UriTemplate { namespace Match { bool UriTemplateMatcher::match(absl::string_view path) const { - RE2 matching_pattern_regex = RE2(convertPathPatternSyntaxToRegex(path_template_).value()); - return RE2::FullMatch(Http::PathUtil::removeQueryAndFragment(path), matching_pattern_regex); + return RE2::FullMatch(Http::PathUtil::removeQueryAndFragment(path), matching_pattern_regex_); } absl::string_view UriTemplateMatcher::uriTemplate() const { return path_template_; } diff --git a/source/extensions/path/match/uri_template/uri_template_match.h b/source/extensions/path/match/uri_template/uri_template_match.h index 23ecd89a23605..fbbe9ce5e1658 100644 --- a/source/extensions/path/match/uri_template/uri_template_match.h +++ b/source/extensions/path/match/uri_template/uri_template_match.h @@ -29,7 +29,8 @@ class UriTemplateMatcher : public Router::PathMatcher { public: explicit UriTemplateMatcher( const envoy::extensions::path::match::uri_template::v3::UriTemplateMatchConfig& config) - : path_template_(config.path_template()) {} + : path_template_(config.path_template()), + matching_pattern_regex_(convertPathPatternSyntaxToRegex(path_template_).value()) {} // Router::PathMatcher bool match(absl::string_view path) const override; @@ -38,6 +39,7 @@ class UriTemplateMatcher : public Router::PathMatcher { private: const std::string path_template_; + const RE2 matching_pattern_regex_; }; } // namespace Match From b09657251b0b6c81f8af83425b995c19ae723614 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 15 Nov 2023 13:07:19 -0800 Subject: [PATCH 175/274] Fix crashes when using address type that isn't supported by the OS Fix [CVE-2024-23325](https://github.com/envoyproxy/envoy/security/advisories/GHSA-5m7c-mrwr-pm26) Signed-off-by: Greg Greenway Signed-off-by: Ryan Northey --- changelogs/current.yaml | 9 +++ source/common/network/BUILD | 1 + source/common/network/address_impl.cc | 24 +++++++- source/common/network/address_impl.h | 16 ++++++ .../listener/proxy_protocol/proxy_protocol.cc | 38 +++++++++---- .../tls/connection_info_impl_base.cc | 2 +- .../transport_sockets/tls/utility.cc | 12 +++- .../transport_sockets/tls/utility.h | 4 +- test/config/integration/certs/clientcert.cfg | 2 + test/config/integration/certs/clientcert.pem | 38 ++++++------- .../integration/certs/clientcert_hash.h | 4 +- test/config/integration/certs/clientkey.pem | 55 ++++++++++--------- .../proxy_protocol/proxy_protocol_test.cc | 43 +++++++++++++++ .../tls/integration/ssl_integration_test.cc | 41 ++++++++++++++ 14 files changed, 225 insertions(+), 64 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9b7368d602900..e706d75737db9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -36,6 +36,15 @@ bug_fixes: change: | fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. - area: http change: | Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. diff --git a/source/common/network/BUILD b/source/common/network/BUILD index dc73e67d73596..37a4a3d716b8b 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -20,6 +20,7 @@ envoy_cc_library( ":socket_interface_lib", "//envoy/network:address_interface", "//source/common/common:assert_lib", + "//source/common/common:cleanup_lib", "//source/common/common:safe_memcpy_lib", "//source/common/common:statusor_lib", "//source/common/common:thread_lib", diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index a00c5e121e2b1..bd05cbd7048f7 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -211,9 +211,19 @@ std::string Ipv4Instance::sockaddrToString(const sockaddr_in& addr) { return std::string(start, str + BufferSize - start); } +namespace { +bool force_ipv4_unsupported_for_test = false; +} + +Cleanup Ipv4Instance::forceProtocolUnsupportedForTest(bool new_val) { + bool old_val = force_ipv4_unsupported_for_test; + force_ipv4_unsupported_for_test = new_val; + return Cleanup([old_val]() { force_ipv4_unsupported_for_test = old_val; }); +} + absl::Status Ipv4Instance::validateProtocolSupported() { static const bool supported = SocketInterfaceSingleton::get().ipFamilySupported(AF_INET); - if (supported) { + if (supported && !force_ipv4_unsupported_for_test) { return absl::OkStatus(); } return absl::FailedPreconditionError("IPv4 addresses are not supported on this machine"); @@ -323,9 +333,19 @@ Ipv6Instance::Ipv6Instance(absl::Status& status, const sockaddr_in6& address, bo initHelper(address, v6only); } +namespace { +bool force_ipv6_unsupported_for_test = false; +} + +Cleanup Ipv6Instance::forceProtocolUnsupportedForTest(bool new_val) { + bool old_val = force_ipv6_unsupported_for_test; + force_ipv6_unsupported_for_test = new_val; + return Cleanup([old_val]() { force_ipv6_unsupported_for_test = old_val; }); +} + absl::Status Ipv6Instance::validateProtocolSupported() { static const bool supported = SocketInterfaceSingleton::get().ipFamilySupported(AF_INET6); - if (supported) { + if (supported && !force_ipv6_unsupported_for_test) { return absl::OkStatus(); } return absl::FailedPreconditionError("IPv6 addresses are not supported on this machine"); diff --git a/source/common/network/address_impl.h b/source/common/network/address_impl.h index f2d1d085db4f9..88782f5b150bc 100644 --- a/source/common/network/address_impl.h +++ b/source/common/network/address_impl.h @@ -11,12 +11,16 @@ #include "envoy/network/socket.h" #include "source/common/common/assert.h" +#include "source/common/common/cleanup.h" #include "source/common/common/statusor.h" namespace Envoy { namespace Network { namespace Address { +// Add an address-specific version for easier searching. +#define TRY_NEEDS_AUDIT_ADDRESS TRY_NEEDS_AUDIT + /** * Check whether we are a) on Android or an Apple platform and b) configured via runtime to always * use v6 sockets. @@ -144,6 +148,12 @@ class Ipv4Instance : public InstanceBase { // given address if not. static absl::Status validateProtocolSupported(); + /** + * For use in tests only. + * Force validateProtocolSupported() to return false for IPv4. + */ + static Envoy::Cleanup forceProtocolUnsupportedForTest(bool new_val); + private: /** * Construct from an existing unix IPv4 socket address (IP v4 address and port). @@ -226,6 +236,12 @@ class Ipv6Instance : public InstanceBase { // Validate that IPv6 is supported on this platform static absl::Status validateProtocolSupported(); + /** + * For use in tests only. + * Force validateProtocolSupported() to return false for IPv6. + */ + static Envoy::Cleanup forceProtocolUnsupportedForTest(bool new_val); + private: /** * Construct from an existing unix IPv6 socket address (IP v6 address and port). diff --git a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc index fea08b3126295..892ad0288b0bb 100644 --- a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc @@ -276,11 +276,21 @@ bool Filter::parseV2Header(const char* buf) { la4.sin_family = AF_INET; la4.sin_port = v4->dst_port; la4.sin_addr.s_addr = v4->dst_addr; - proxy_protocol_header_.emplace( - WireHeader{PROXY_PROTO_V2_HEADER_LEN, hdr_addr_len, PROXY_PROTO_V2_ADDR_LEN_INET, - hdr_addr_len - PROXY_PROTO_V2_ADDR_LEN_INET, Network::Address::IpVersion::v4, - std::make_shared(&ra4), - std::make_shared(&la4)}); + + TRY_NEEDS_AUDIT_ADDRESS { + // TODO(ggreenway): make this work without requiring operating system support for an + // address family. + proxy_protocol_header_.emplace(WireHeader{ + PROXY_PROTO_V2_HEADER_LEN, hdr_addr_len, PROXY_PROTO_V2_ADDR_LEN_INET, + hdr_addr_len - PROXY_PROTO_V2_ADDR_LEN_INET, Network::Address::IpVersion::v4, + std::make_shared(&ra4), + std::make_shared(&la4)}); + } + END_TRY CATCH(const EnvoyException& e, { + ENVOY_LOG(debug, "Proxy protocol failure: {}", e.what()); + return false; + }); + return true; } else if (((proto_family & 0xf0) >> 4) == PROXY_PROTO_V2_AF_INET6) { PACKED_STRUCT(struct pp_ipv6_addr { @@ -302,11 +312,19 @@ bool Filter::parseV2Header(const char* buf) { la6.sin6_port = v6->dst_port; safeMemcpy(&(la6.sin6_addr.s6_addr), &(v6->dst_addr)); - proxy_protocol_header_.emplace(WireHeader{ - PROXY_PROTO_V2_HEADER_LEN, hdr_addr_len, PROXY_PROTO_V2_ADDR_LEN_INET6, - hdr_addr_len - PROXY_PROTO_V2_ADDR_LEN_INET6, Network::Address::IpVersion::v6, - std::make_shared(ra6), - std::make_shared(la6)}); + TRY_NEEDS_AUDIT_ADDRESS { + proxy_protocol_header_.emplace(WireHeader{ + PROXY_PROTO_V2_HEADER_LEN, hdr_addr_len, PROXY_PROTO_V2_ADDR_LEN_INET6, + hdr_addr_len - PROXY_PROTO_V2_ADDR_LEN_INET6, Network::Address::IpVersion::v6, + std::make_shared(ra6), + std::make_shared(la6)}); + } + END_TRY CATCH(const EnvoyException& e, { + // TODO(ggreenway): make this work without requiring operating system support for an + // address family. + ENVOY_LOG(debug, "Proxy protocol failure: {}", e.what()); + return false; + }); return true; } } diff --git a/source/extensions/transport_sockets/tls/connection_info_impl_base.cc b/source/extensions/transport_sockets/tls/connection_info_impl_base.cc index c080829c86ce4..13d556bfc604b 100644 --- a/source/extensions/transport_sockets/tls/connection_info_impl_base.cc +++ b/source/extensions/transport_sockets/tls/connection_info_impl_base.cc @@ -185,7 +185,7 @@ absl::Span ConnectionInfoImplBase::ipSansPeerCertificate() co ASSERT(cached_ip_san_peer_certificate_.empty()); return cached_ip_san_peer_certificate_; } - cached_ip_san_peer_certificate_ = Utility::getSubjectAltNames(*cert, GEN_IPADD); + cached_ip_san_peer_certificate_ = Utility::getSubjectAltNames(*cert, GEN_IPADD, true); return cached_ip_san_peer_certificate_; } diff --git a/source/extensions/transport_sockets/tls/utility.cc b/source/extensions/transport_sockets/tls/utility.cc index 2219998916de5..14a7dcaf266db 100644 --- a/source/extensions/transport_sockets/tls/utility.cc +++ b/source/extensions/transport_sockets/tls/utility.cc @@ -167,7 +167,7 @@ std::string Utility::getSerialNumberFromCertificate(X509& cert) { return ""; } -std::vector Utility::getSubjectAltNames(X509& cert, int type) { +std::vector Utility::getSubjectAltNames(X509& cert, int type, bool skip_unsupported) { std::vector subject_alt_names; bssl::UniquePtr san_names( static_cast(X509_get_ext_d2i(&cert, NID_subject_alt_name, nullptr, nullptr))); @@ -176,7 +176,15 @@ std::vector Utility::getSubjectAltNames(X509& cert, int type) { } for (const GENERAL_NAME* san : san_names.get()) { if (san->type == type) { - subject_alt_names.push_back(generalNameAsString(san)); + if (skip_unsupported) { + // An IP SAN for an unsupported IP version will throw an exception. + // TODO(ggreenway): remove this when IP address construction no longer throws. + TRY_NEEDS_AUDIT_ADDRESS { subject_alt_names.push_back(generalNameAsString(san)); } + END_TRY CATCH(const EnvoyException& e, + { ENVOY_LOG_MISC(debug, "Error reading SAN, value skipped: {}", e.what()); }); + } else { + subject_alt_names.push_back(generalNameAsString(san)); + } } } return subject_alt_names; diff --git a/source/extensions/transport_sockets/tls/utility.h b/source/extensions/transport_sockets/tls/utility.h index fb9f6787c282e..da9be3441174b 100644 --- a/source/extensions/transport_sockets/tls/utility.h +++ b/source/extensions/transport_sockets/tls/utility.h @@ -52,9 +52,11 @@ std::string getSerialNumberFromCertificate(X509& cert); * Retrieves the subject alternate names of a certificate. * @param cert the certificate * @param type type of subject alternate name + * @param skip_unsupported If true and a name is for an unsupported (on this host) IP version, + * omit that name from the return value. If false, an exception will be thrown in this situation. * @return std::vector returns the list of subject alternate names. */ -std::vector getSubjectAltNames(X509& cert, int type); +std::vector getSubjectAltNames(X509& cert, int type, bool skip_unsupported = false); /** * Converts the Subject Alternate Name to string. diff --git a/test/config/integration/certs/clientcert.cfg b/test/config/integration/certs/clientcert.cfg index dc63b5c89ca6b..61f840dafa5c5 100644 --- a/test/config/integration/certs/clientcert.cfg +++ b/test/config/integration/certs/clientcert.cfg @@ -39,3 +39,5 @@ URI.1 = spiffe://lyft.com/frontend-team URI.2 = http://frontend.lyft.com DNS.1 = lyft.com DNS.2 = www.lyft.com +IP.1 = 1.2.3.4 +IP.2 = 0:1:2:3::4 diff --git a/test/config/integration/certs/clientcert.pem b/test/config/integration/certs/clientcert.pem index 57a7a59a70871..a476c9b8736f4 100644 --- a/test/config/integration/certs/clientcert.pem +++ b/test/config/integration/certs/clientcert.pem @@ -1,27 +1,27 @@ -----BEGIN CERTIFICATE----- -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 -ahwsslCKM3xGY4ax9Q== +Lmx5ZnQuY29thwQBAgMEhxAAAAABAAIAAwAAAAAAAAAEMB0GA1UdDgQWBBTl8J5P +CF97S4cY6TytejTb3sngmTAfBgNVHSMEGDAWgBQdDTmYdOz7TqwMpoOli3Dmj78y +gjANBgkqhkiG9w0BAQsFAAOCAQEAsMuSPKvSx/uDRIHWNQhUWSHfa4nfonyGBmnV +VvC7Xatq3kZ1MCedzxHbqOOdlO4cSVq+eOHlVzWJUsJSj1J8hcVh3vZp6GFoRZgU +F93g2dlgkmEEqEFB4qI71PwjC6amEV+xY21v/QPEouI1VumUnMnAV81G5uJDzPtn +gmNyM6hnvKGufpaovZFeXsB0ZUnYPz+4QdKwHTErsV8uUdeJUhFHg1NjCmrqQAmm +PG0G9JOi/dY/X5/LfGomAb7E+wuJFKHFP7gE6JvWi5M1Y1IlW1tCgN3dSCdCaUZm +JPKWR3x+gYOFHfKNpdG/zRwOrClgISmDzZiXXFSHCn95tFocXA== -----END CERTIFICATE----- diff --git a/test/config/integration/certs/clientcert_hash.h b/test/config/integration/certs/clientcert_hash.h index 8e89302b1a88c..44aeb4df168ad 100644 --- a/test/config/integration/certs/clientcert_hash.h +++ b/test/config/integration/certs/clientcert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_CLIENT_CERT_HASH[] = "4A:FD:3A:AE:4B:36:08:A6:CB:41:4F:20:8A:86:1F:3B:43:6F:2F:" - "12:49:82:8D:9F:F6:FA:53:4D:23:26:FB:43"; +constexpr char TEST_CLIENT_CERT_HASH[] = "F6:31:41:AA:8E:E3:D7:AC:AE:A8:AF:AD:C9:11:CD:0A:83:72:03:" + "6D:4B:B3:72:4F:6F:71:E1:ED:18:5B:92:AA"; diff --git a/test/config/integration/certs/clientkey.pem b/test/config/integration/certs/clientkey.pem index 20c2f5a325ca1..1d4391b72ce4d 100644 --- a/test/config/integration/certs/clientkey.pem +++ b/test/config/integration/certs/clientkey.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA7A5D3pTadhwD6VXl4WYerCRRUf9vnCf8VeAAsF9PapKfanMy -aqPdEd5uo3gju6JEY+aTt5UfGS1b/K0UN2T4DBsYQ4/y9aX3NHvem94pyEdVJ1I0 -nhGRemAMtlgMMOP8lDE3MPA8Pjd4hTD1IqP4zD2dNG/SGbf2GGGGMGa859t1aNA8 -yy5IOoQA8YTTo+mKtN3Bk1Ky4GN++jrN2cnFODN38J/Uog11rg/GSXD0z0K4pAx4 -vBV/AwM14ja4nuSfV2mw6FIMFTssSV5JYYYCVzJwPMu7L/n+spx0430wpQr7zasn -5wwToj4NipLU3P7Bj23x9qtcu4YrjWfQbiqvUwIDAQABAoIBAQDKY5ixODLuXSrF -Xo6QaLwXn7PReA67dlUVU8+DaNRwbXIdFNO/NuuOLIXzxkfs0j2M4d744fQd5BQg -Wk0hCYLa7kgpdTw8faWr7CB6x0pPm0lZQ1Q1yp5OrBd6J5ecO30NmfzWCsO8HFdK -6yTiJHBmvNUSZmVfA6kOUl95FD0XFB7J/4MPmE0UqEg5IhoWxpH9cEV8yll/bZZL -FHzA1cfwjtcHp5kHm/7IYlWqpShpdsquMmZt8vHFaoGvT5pms/AtAFjjDcW5dFpZ -GipS5D/3oPMXWbhrzMoIe8ERVBmlKPVoUmxUNIomgRSTP6/+mgcgojRRqcLW3l0W -KXi4i1dhAoGBAPmgVd+2K5vox09EbYaD1KAZpwuW61PQ6RI3wU5YCf84FoNAzK+e -KHSI+5Vo7JkwwiP7MSIJhiODG9VRnfou06NPEY4jllcMXp73PVGhOzsnHTqsbgps -Yg9tXsX5jQDx60yIOHf7halAz3vpEldQ6YecLscYV0Oz5i84gl/jee5JAoGBAPIV -Ofuf88WjErvuAxqIJWx85afr4B8YfChKHIVEx1eI6o89xuB16FjFzKAhHe9SJdfk -YVOwYD/9MRZ5+ZZAsRrKTHbD3VX20c0ECX9++/Sz2LGMewgvqiscpfGzDTsTJIVA -Ep31IAXmAUsmBAQAbuXxtbpKgU8Wi3mSp4nFzKC7AoGAUlpgGkbqSixYnMERdSBG -5G6yGnO2vVcdnWIBhwwqeCWT38df/8wowpFylo8gB0X7to0nX3hO5aZaZ1zexmvu -bGEohIEfFybAjKc6dpS/irtTvEiooQ2yqC5H5v52U0p8eyoxnvu+0+DK0rFI2L+b -255eHFbeazqNhSSadnIAhukCgYEAtfKAPv/sb0nupbLxQDq9rfl9fqVJMPXtMzbo -kr2r+b2dVgW/eSsFc9tOvbfGUP50FPzAre7tmIqLH3KTxXtf4VvU9pqlu5uj+iwj -m2Dsq/GUV3XXbsKsanTAwJWrxw/PLhuHIpN++w/xPvMWp49PyqHNzXN8Ft5B/CDe -rS0ubEsCgYA5kvmEDFYLF4TU7xg3QJ08+6+tJ+HZdvCBzE7RGJD8l5ZfpJuE6s9E -aW09E+65hSMaE85MpiM/s08quiMQeR6i7UWeN4GwoQznH1f9zUAuyP1J1Iv2knj7 -lZ+oGk0EgnjkiIxo3ppAoKGz2/9Oi09lsEbdIBHvC56DAu44uOP50A== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9K5Xk1JJlLO4P +zwPbJLlmaGukaCvzX8MHwlp4ioIl/skWXAcvf/+h5Tg94y3sw+rafrJodVMyLt6+ +vf69Hg4wPj7MUCgxR+vSye+Amhbcfa42qWN1TcLhN4zXJXCsw54zVR67zQ/mACJC +4UW18gaoN5+09QtsLVSLOczsu/Yrve71bl++OCA7CLllFj6RSJn4HRC3gAckavCD +d9945Y0396Tdxn4w3/WiizOo0xkYrTVT2Npjnui6ZFJ7R7ONsHlAQmdgd/XPKJV1 +nhKAiS9+RDbifyZcaWpj/jpSv3koaK7BRPlB/LWETHjZG/GRHRR6IWEBjvFieVQc +OHeO5aEbAgMBAAECggEARVEny2KDRFSq5RsPyCjUUOy5aNSNKlBwSDMU8K+cUizi +5XESZvrpopq6OZ850FTYBXlAiZtYQX7AOzemlQji3RWp8Db9C1XV2XcKbl7IOsJI +6Jm4Kp80Zk9zKdD70SqbGSc7LEjPZxGsfEJMx4donhJH0MisB1cy8BNdfm+/nDYK +NsezfOYAD4UkX1NcrdfwLsWimZHPifwxL+va5cV3FiWO3S861/aE0pLhh+AJFYGI +3lEZxr6Gh+uaARcV4YNZPogYbrc8wJWP/6uR8pDwjqS8aUTBfyo1wUDd6bTvlQDv ++nKBiVjmWPgY6TlZ/Okp+H28fO3zqoXExE6KJamxmQKBgQDzV5jIemNDUrxodc1i +AQIchTbchvGiSpLyS2PY1W0vyYpyc5mugvzgaHPVEtaQTtR+QHWrYEFksOBZeIqX +rQGTDk6jGWBzI7qa/itqr9jydZsMYgJ7eqGpiSsiD0ka65xO+Ho6FkZAV11+qPyE +QmPD3Izj/58pSod4PADFQSP18wKBgQDHAp9Sd9fGCn/RHRVyf1nao2ZeKCQUDyyj +g+uCIswhE8lT7C5K76FPxZrV6enpTkLjEnMdRrPl4fQ5xl0SPAR/gaZXhM1U4sjo +w0dWITeMHwR7HqwbpumNbMccZMGA7o1Ua/k7GPIyD2UE7hiJyJRNyfeN09/cC18p +EjHcSs4qOQKBgCwU0jh+8zxe4IKL1IjMZfWErEuGpn8fwz7hKVU+VGkzuUDCcDSM +xgJg6ZrPrs61eQjl5GsHJNF4uSt8Cp8vV/mrvdMN5cr1zfgF0xegg0xowY2cs5Zq +wJ5Vmtqwqi2WQNqNaJbdMhy1ttobAqNy41+3tE4ZIFv6hE/jjsAs7LbBAoGAXy64 +5uec0wKYiXqglGemoTS/tE78mn97eSWSUWa1PSjKhRIUPhEIlS/M030SPF0LDrH3 +TsxPJKcCeVOPljYQbK+k0H0a+/uP3gvwJZiziZgYO467AGq/j720Kbdi+XifLf6K +cKKIzDqitU3vfI7rp5zugu4QRp1FwU4LfPJmUrkCgYAzzBz3K7jg4JPdY7Od22SQ +F/eToChPu60B9uQciR6gGwR1ry2sVSlK0Y1DMFMBCFEqPfJFO4OwZP0NWbRd13X1 +9nUvpVoGxxJ51fIdMrPPm0G6f8HS79JhOGUp9tYsQ+LPCcnrrH2ZDGQYxZsSXqbe +5qc2rk4Sgt8Ua/oVrxghzA== +-----END PRIVATE KEY----- diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc index 0fd93c8f8284e..57ab7142cd56d 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -10,6 +10,7 @@ #include "source/common/api/os_sys_calls_impl.h" #include "source/common/buffer/buffer_impl.h" #include "source/common/event/dispatcher_impl.h" +#include "source/common/network/address_impl.h" #include "source/common/network/connection_balancer_impl.h" #include "source/common/network/listen_socket_impl.h" #include "source/common/network/proxy_protocol_filter_state.h" @@ -237,6 +238,20 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, ProxyProtocolTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); +TEST_P(ProxyProtocolTest, V1UnsupportedIPv4) { + connect(false); + Cleanup cleaner = Network::Address::Ipv4Instance::forceProtocolUnsupportedForTest(true); + write("PROXY TCP4 1.2.3.4 253.253.253.253 65535 1234\r\nmore data"); + expectProxyProtoError(); +} + +TEST_P(ProxyProtocolTest, V1UnsupportedIPv6) { + connect(false); + Cleanup cleaner = Network::Address::Ipv6Instance::forceProtocolUnsupportedForTest(true); + write("PROXY TCP6 1:2:3::4 5:6::7:8 65535 1234\r\nmore data"); + expectProxyProtoError(); +} + TEST_P(ProxyProtocolTest, V1Basic) { connect(); write("PROXY TCP4 1.2.3.4 253.253.253.253 65535 1234\r\nmore data"); @@ -391,6 +406,34 @@ TEST_P(ProxyProtocolTest, V2BasicV6) { disconnect(); } +TEST_P(ProxyProtocolTest, V2UnsupportedIPv4) { + // A well-formed ipv4/tcp message, no extensions + constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, + 0x54, 0x0a, 0x21, 0x11, 0x00, 0x0c, 0x01, 0x02, 0x03, 0x04, + 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x00, 0x02, 'm', 'o', + 'r', 'e', ' ', 'd', 'a', 't', 'a'}; + + connect(false); + Cleanup cleaner = Network::Address::Ipv4Instance::forceProtocolUnsupportedForTest(true); + write(buffer, sizeof(buffer)); + expectProxyProtoError(); +} + +TEST_P(ProxyProtocolTest, V2UnsupportedIPv6) { + // A well-formed ipv6/tcp message, no extensions + constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, + 0x0a, 0x21, 0x22, 0x00, 0x24, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x01, 0x01, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 'm', 'o', 'r', + 'e', ' ', 'd', 'a', 't', 'a'}; + + connect(false); + Cleanup cleaner = Network::Address::Ipv6Instance::forceProtocolUnsupportedForTest(true); + write(buffer, sizeof(buffer)); + expectProxyProtoError(); +} + TEST_P(ProxyProtocolTest, V2UnsupportedAF) { // A well-formed message with an unsupported address family constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index 11a6fa1d4277a..707c84dc0189d 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -461,6 +461,47 @@ TEST_P(SslIntegrationTest, RouterHeaderOnlyRequestAndResponseWithSni) { checkStats(); } +TEST_P(SslIntegrationTest, LogPeerIpSanUnsupportedIpVersion) { + useListenerAccessLog("%DOWNSTREAM_PEER_IP_SAN%"); + config_helper_.addFilter("name: sni-to-header-filter"); + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(ClientSslTransportOptions().setSni("host.com")); + }; + initialize(); + codec_client_ = makeHttpConnection( + makeSslClientConnection(ClientSslTransportOptions().setSni("www.host.com"))); + + // Disable IP version for the alternate type from the test. The client cert has both an ipv4 and + // an ipv6 SAN. This must happen after the client has loaded the cert to send as the client cert. + auto disabler = (version_ == Network::Address::IpVersion::v4) + ? Network::Address::Ipv6Instance::forceProtocolUnsupportedForTest + : Network::Address::Ipv4Instance::forceProtocolUnsupportedForTest; + Cleanup cleaner(disabler(true)); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/"}, {":scheme", "https"}, {":authority", "host.com"}}; + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + waitForNextUpstreamRequest(); + + EXPECT_EQ("www.host.com", upstream_request_->headers() + .get(Http::LowerCaseString("x-envoy-client-sni"))[0] + ->value() + .getStringView()); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, true); + RELEASE_ASSERT(response->waitForEndStream(), "unexpected timeout"); + codec_client_->close(); + + checkStats(); + auto result = waitForAccessLog(listener_access_log_name_); + if (version_ == Network::Address::IpVersion::v4) { + EXPECT_EQ(result, "1.2.3.4"); + } else { + EXPECT_EQ(result, "0:1:2:3::4"); + } +} + TEST_P(SslIntegrationTest, AsyncCertValidationSucceeds) { // Config client to use an async cert validator which defer the actual validation by 5ms. envoy::config::core::v3::TypedExtensionConfig* custom_validator_config = From 4795bf3e8e65e53561400f255f9fc783b4d27d14 Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Fri, 12 Jan 2024 13:43:40 +0000 Subject: [PATCH 176/274] Proxy protocol: sanitise non utf8 chars in TLVs Fix [CVE-2024-23324](https://github.com/envoyproxy/envoy/security/advisories/GHSA-gq3v-vvhj-96j6) Signed-off-by: Kateryna Nezdolii Signed-off-by: Ryan Northey --- changelogs/current.yaml | 4 ++ .../listener/proxy_protocol/proxy_protocol.cc | 5 +- .../proxy_protocol/proxy_protocol_test.cc | 64 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e706d75737db9..8cd88bff6042c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -40,6 +40,10 @@ bug_fixes: change: | Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. - area: tls change: | Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is diff --git a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc index 892ad0288b0bb..265eb570a0b36 100644 --- a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc @@ -24,6 +24,7 @@ #include "source/common/network/address_impl.h" #include "source/common/network/proxy_protocol_filter_state.h" #include "source/common/network/utility.h" +#include "source/common/protobuf/utility.h" #include "source/extensions/common/proxy_protocol/proxy_protocol_header.h" using envoy::config::core::v3::ProxyProtocolPassThroughTLVs; @@ -440,7 +441,9 @@ bool Filter::parseTlvs(const uint8_t* buf, size_t len) { auto key_value_pair = config_->isTlvTypeNeeded(tlv_type); if (nullptr != key_value_pair) { ProtobufWkt::Value metadata_value; - metadata_value.set_string_value(tlv_value.data(), tlv_value.size()); + // Sanitize any non utf8 characters. + auto sanitised_tlv_value = MessageUtil::sanitizeUtf8String(tlv_value); + metadata_value.set_string_value(sanitised_tlv_value.data(), sanitised_tlv_value.size()); std::string metadata_key = key_value_pair->metadata_namespace().empty() ? "envoy.filters.listener.proxy_protocol" diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc index 57ab7142cd56d..d545380325062 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -1583,6 +1583,70 @@ TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterest) { disconnect(); } +TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterestAndSanitiseNonUtf8) { + // A well-formed ipv4/tcp with a pair of TLV extensions is accepted. + constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, + 0x54, 0x0a, 0x21, 0x11, 0x00, 0x39, 0x01, 0x02, 0x03, 0x04, + 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x00, 0x02}; + // A TLV of type 0x00 with size of 4 (1 byte is value). + constexpr uint8_t tlv1[] = {0x00, 0x00, 0x01, 0xff}; + // A TLV of type 0x02 with size of 10 bytes (7 bytes are value). Second and last bytes in the + // value are non utf8 characters. + constexpr uint8_t tlv_type_authority[] = {0x02, 0x00, 0x07, 0x66, 0xfe, + 0x6f, 0x2e, 0x63, 0x6f, 0xc1}; + // A TLV of type 0x0f with size of 6 bytes (3 bytes are value). + constexpr uint8_t tlv3[] = {0x0f, 0x00, 0x03, 0xf0, 0x00, 0x0f}; + // A TLV of type 0xea with size of 25 bytes (22 bytes are value). 7th and 21st bytes are non utf8 + // characters. + constexpr uint8_t tlv_vpc_id[] = {0xea, 0x00, 0x16, 0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, + 0xc0, 0x35, 0x74, 0x65, 0x73, 0x74, 0x32, 0x66, 0x61, + 0x36, 0x63, 0x36, 0x33, 0x68, 0xf9, 0x37}; + constexpr uint8_t data[] = {'D', 'A', 'T', 'A'}; + + envoy::extensions::filters::listener::proxy_protocol::v3::ProxyProtocol proto_config; + auto rule_type_authority = proto_config.add_rules(); + rule_type_authority->set_tlv_type(0x02); + rule_type_authority->mutable_on_tlv_present()->set_key("PP2 type authority"); + + auto rule_vpc_id = proto_config.add_rules(); + rule_vpc_id->set_tlv_type(0xea); + rule_vpc_id->mutable_on_tlv_present()->set_key("PP2 vpc id"); + + connect(true, &proto_config); + write(buffer, sizeof(buffer)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + write(tlv1, sizeof(tlv1)); + write(tlv_type_authority, sizeof(tlv_type_authority)); + write(tlv3, sizeof(tlv3)); + write(tlv_vpc_id, sizeof(tlv_vpc_id)); + write(data, sizeof(data)); + expectData("DATA"); + + EXPECT_EQ(1, server_connection_->streamInfo().dynamicMetadata().filter_metadata_size()); + + auto metadata = server_connection_->streamInfo().dynamicMetadata().filter_metadata(); + EXPECT_EQ(1, metadata.size()); + EXPECT_EQ(1, metadata.count(ProxyProtocol)); + + auto fields = metadata.at(ProxyProtocol).fields(); + EXPECT_EQ(2, fields.size()); + EXPECT_EQ(1, fields.count("PP2 type authority")); + EXPECT_EQ(1, fields.count("PP2 vpc id")); + + const char replacement = 0x21; + auto value_type_authority = fields.at("PP2 type authority").string_value(); + // Non utf8 characters have been replaced with `0x21` (`!` character). + ASSERT_THAT(value_type_authority, + ElementsAre(0x66, replacement, 0x6f, 0x2e, 0x63, 0x6f, replacement)); + + auto value_vpc_id = fields.at("PP2 vpc id").string_value(); + ASSERT_THAT(value_vpc_id, + ElementsAre(0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, replacement, 0x35, 0x74, 0x65, 0x73, + 0x74, 0x32, 0x66, 0x61, 0x36, 0x63, 0x36, 0x33, 0x68, replacement, 0x37)); + disconnect(); +} + TEST_P(ProxyProtocolTest, V2WillNotOverwriteTLV) { // A well-formed ipv4/tcp with a pair of TLV extensions is accepted constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, From 0fd81ee7ffcd7cfc864094b24dc9b5c3ade89ff2 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 9 Feb 2024 16:49:18 +0000 Subject: [PATCH 177/274] repo: Release v1.27.3 **Summary of changes**: - Fix [CVE-2024-23324](https://github.com/envoyproxy/envoy/security/advisories/GHSA-gq3v-vvhj-96j6) - Fix [CVE-2024-23325](https://github.com/envoyproxy/envoy/security/advisories/GHSA-5m7c-mrwr-pm26) - Fix [CVE-2024-23322](https://github.com/envoyproxy/envoy/security/advisories/GHSA-6p83-mfmh-qv38) - Fix [CVE-2024-23323](https://github.com/envoyproxy/envoy/security/advisories/GHSA-x278-4w4x-r7ch) - Fix [CVE-2024-23327](https://github.com/envoyproxy/envoy/security/advisories/GHSA-4h5x-x9vh-m29j) - Assorted bug fixes **Docker images**: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.3 **Docs**: https://www.envoyproxy.io/docs/envoy/v1.27.3/ **Release notes**: https://www.envoyproxy.io/docs/envoy/v1.27.3/version_history/v1.27/v1.27.3 **Full changelog**: https://github.com/envoyproxy/envoy/compare/v1.27.2...v1.27.3 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.26.7.yaml | 28 ++++++++++++++++++++++++++++ changelogs/current.yaml | 14 +------------- docs/inventories/v1.26/objects.inv | Bin 153917 -> 153965 bytes docs/inventories/v1.27/objects.inv | Bin 159791 -> 159876 bytes docs/versions.yaml | 4 ++-- 6 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 changelogs/1.26.7.yaml diff --git a/VERSION.txt b/VERSION.txt index a759f59ddc72e..3bae5204be9e5 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.3-dev +1.27.3 diff --git a/changelogs/1.26.7.yaml b/changelogs/1.26.7.yaml new file mode 100644 index 0000000000000..48e27387880c7 --- /dev/null +++ b/changelogs/1.26.7.yaml @@ -0,0 +1,28 @@ +date: February 9, 2024 + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: proxy protocol + change: | + fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8cd88bff6042c..a67d0b6cabb28 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,17 +1,12 @@ -date: Pending - -behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +date: February 9, 2024 minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* - area: access_log change: | When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried to find the keys configured in filter_state_objects_to_log. bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: buffer change: | Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined @@ -55,10 +50,3 @@ bug_fixes: - area: url matching change: | Fixed excessive CPU utilization when using regex URL template matcher. - -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - -new_features: - -deprecated: diff --git a/docs/inventories/v1.26/objects.inv b/docs/inventories/v1.26/objects.inv index 477894d1f6bccd82887a88b242a0da1ebc5bf066..26b06951df94722c60592253d264b2378a4075bd 100644 GIT binary patch delta 8466 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 delta 8413 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 diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index 4ebd9397af40885f3f5a5dc2e390d930f9cbc5da..0b0acf5c7b4287c47fed7f8d5f931d7530922948 100644 GIT binary patch delta 14653 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 E2M^fPtN;K2 diff --git a/docs/versions.yaml b/docs/versions.yaml index c81255bc2d094..91b80a86fe857 100644 --- a/docs/versions.yaml +++ b/docs/versions.yaml @@ -19,5 +19,5 @@ "1.23": 1.23.12 "1.24": 1.24.12 "1.25": 1.25.11 -"1.26": 1.26.6 -"1.27": 1.27.1 +"1.26": 1.26.7 +"1.27": 1.27.2 From 37bca06f1fe319237067c6972fe7d6b93fd7ba5c Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 13 Feb 2024 14:06:19 +0000 Subject: [PATCH 178/274] repo: Dev 1.27.4 (#32330) Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/current.yaml | 61 +++++++++-------------------------------- 2 files changed, 14 insertions(+), 49 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 3bae5204be9e5..30eedfa5bbb6b 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.3 +1.27.4-dev diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a67d0b6cabb28..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,52 +1,17 @@ -date: February 9, 2024 +date: Pending + +behavior_changes: +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* minor_behavior_changes: -- area: access_log - change: | - When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried - to find the keys configured in filter_state_objects_to_log. +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: buffer - change: | - Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined - behavior due to the unintended release of the buffer memory. -- area: http - change: | - Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. -- area: grpc - change: | - Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. -- area: tracing - change: | - Fixed a bug where Datadog spans tagged as errors would not have the appropriate error property set. -- area: tracing - change: | - Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. -- area: tracing - change: | - Fixed a bug that caused the Datadog tracing extension to drop traces that - should be kept on account of an extracted sampling decision. -- area: proxy protocol - change: | - fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives - a PROXY protocol header with address type LOCAL (typically used for health checks). -- area: proxy_protocol - change: | - Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is - received in a proxy protocol header. Connections will instead be dropped/reset. -- area: proxy_protocol - change: | - Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing - ext_authz checks when ``failure_mode_allow`` is set to ``true``. -- area: tls - change: | - Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is - received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter - ``%DOWNSTREAM_PEER_IP_SAN%``. -- area: http - change: | - Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. -- area: url matching - change: | - Fixed excessive CPU utilization when using regex URL template matcher. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: + +deprecated: From cdc5852f6e5c54ad2ab4c1a8ada744d85296cfb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:01:59 +0000 Subject: [PATCH 179/274] build(deps): bump distroless/base-nossl-debian12 from `51ab103` to `49edf70` in /ci (#32348) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `51ab103` to `49edf70`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 7f6f0bd139113..cf7e0d7dc0b3a 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:51ab103bb161fdf8fee4c6311a2d41f484effc409d4f4c58342ab68b2da7ccc2 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:49edf7003af1015b0841f34a04197e8b1c5f1d0c91e897c97749c78ee38b8ec2 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 08639893233945e0f2c82cc3680f8e2cc89fe001 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:43:52 +0000 Subject: [PATCH 180/274] build(deps): bump distroless/base-nossl-debian12 from `49edf70` to `0e777c6` in /ci (#32576) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `49edf70` to `0e777c6`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index cf7e0d7dc0b3a..9e4e156bc75ea 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:49edf7003af1015b0841f34a04197e8b1c5f1d0c91e897c97749c78ee38b8ec2 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0e777c69ba810353b9f3f2033280bbe7d029d81fa55760f6eec817ef595aa19c AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 8ca7973b9b1f2f639e1ba87aeeb6aa23c6885769 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:26:18 +0000 Subject: [PATCH 181/274] build(deps): bump distroless/base-nossl-debian12 from `0e777c6` to `28dc895` in /ci (#32613) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `0e777c6` to `28dc895`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 9e4e156bc75ea..b9a043670d909 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0e777c69ba810353b9f3f2033280bbe7d029d81fa55760f6eec817ef595aa19c AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:28dc8956c04a92ffc192d06c5da69fa747c675ee44043ba18128e747c2f539f5 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 659649e0019a455e2157605271096c231a20b7ea Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 23 Feb 2024 09:10:36 +0000 Subject: [PATCH 182/274] ci/macos: Use Engflow for bazel cache (#32520) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .bazelrc | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.bazelrc b/.bazelrc index 7533ffc8a62f6..a14ceec398de9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -492,16 +492,18 @@ build:rbe-engflow --remote_timeout=3600s build:rbe-engflow --bes_timeout=3600s build:rbe-engflow --bes_upload_mode=fully_async -build:rbe-envoy-engflow --google_default_credentials=false -build:rbe-envoy-engflow --remote_cache=grpcs://morganite.cluster.engflow.com +build:cache-envoy-engflow --google_default_credentials=false +build:cache-envoy-engflow --remote_cache=grpcs://morganite.cluster.engflow.com +build:cache-envoy-engflow --remote_timeout=3600s +build:cache-envoy-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:cache-envoy-engflow --grpc_keepalive_time=30s +build:bes-envoy-engflow --bes_backend=grpcs://morganite.cluster.engflow.com/ +build:bes-envoy-engflow --bes_results_url=https://morganite.cluster.engflow.com/invocation/ +build:bes-envoy-engflow --bes_timeout=3600s +build:bes-envoy-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --config=cache-envoy-engflow +build:rbe-envoy-engflow --config=bes-envoy-engflow build:rbe-envoy-engflow --remote_executor=grpcs://morganite.cluster.engflow.com -build:rbe-envoy-engflow --bes_backend=grpcs://morganite.cluster.engflow.com/ -build:rbe-envoy-engflow --bes_results_url=https://morganite.cluster.engflow.com/invocation/ -build:rbe-envoy-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh -build:rbe-envoy-engflow --grpc_keepalive_time=30s -build:rbe-envoy-engflow --remote_timeout=3600s -build:rbe-envoy-engflow --bes_timeout=3600s -build:rbe-envoy-engflow --bes_upload_mode=fully_async build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e ############################################################################# From 3db916fa9a36e749a48f55296ee1826e01a708c5 Mon Sep 17 00:00:00 2001 From: Kuat Date: Fri, 23 Feb 2024 01:11:34 -0800 Subject: [PATCH 183/274] google_grpc: add a runtime flag to disable TLSv1.3 (#32532) * google_grpc: add a runtime flag to disable TLSv1.3 (#32315) Change-Id: Id88723a81d4b1586bf12be6f4dc7a81ae7b0d9c4 Commit Message: Adds a temporary runtime flag to disable TLSv1.3 by gRPC SDK until a proper xDS extension can be added. Additional Description: Risk Level: low, default false Testing: regression Change-Id: I34daae55ede7c8093b0dac1fa6ff5a5dc8df677d Signed-off-by: Kuat Yessenov --- changelogs/current.yaml | 5 +++ source/common/grpc/BUILD | 1 + source/common/grpc/google_grpc_creds_impl.cc | 38 +++++++++++++++---- source/common/runtime/runtime_features.cc | 4 ++ test/common/grpc/BUILD | 1 + .../grpc/grpc_client_integration_test.cc | 24 ++++++++++++ .../grpc_client_integration_test_harness.h | 16 +++++++- 7 files changed, 81 insertions(+), 8 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..2b8dc99df2e09 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -13,5 +13,10 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: google_grpc + change: | + Added an off-by-default runtime flag + ``envoy.reloadable_features.google_grpc_disable_tls_13`` to disable TLSv1.3 + usage by gRPC SDK for ``google_grpc`` services. deprecated: diff --git a/source/common/grpc/BUILD b/source/common/grpc/BUILD index 96d1ed06353d1..d662c6863e4bb 100644 --- a/source/common/grpc/BUILD +++ b/source/common/grpc/BUILD @@ -210,6 +210,7 @@ envoy_cc_library( "//envoy/grpc:google_grpc_creds_interface", "//envoy/registry", "//source/common/config:datasource_lib", + "//source/common/runtime:runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], alwayslink = LEGACY_ALWAYSLINK, diff --git a/source/common/grpc/google_grpc_creds_impl.cc b/source/common/grpc/google_grpc_creds_impl.cc index ae49d3257a7f6..5aa2ea91fd8aa 100644 --- a/source/common/grpc/google_grpc_creds_impl.cc +++ b/source/common/grpc/google_grpc_creds_impl.cc @@ -4,6 +4,9 @@ #include "envoy/grpc/google_grpc_creds.h" #include "source/common/config/datasource.h" +#include "source/common/runtime/runtime_features.h" + +#include "grpcpp/security/tls_certificate_provider.h" namespace Envoy { namespace Grpc { @@ -15,12 +18,29 @@ std::shared_ptr CredsUtility::getChannelCredentials( case envoy::config::core::v3::GrpcService::GoogleGrpc::ChannelCredentials:: CredentialSpecifierCase::kSslCredentials: { const auto& ssl_credentials = google_grpc.channel_credentials().ssl_credentials(); - const grpc::SslCredentialsOptions ssl_credentials_options = { - Config::DataSource::read(ssl_credentials.root_certs(), true, api), - Config::DataSource::read(ssl_credentials.private_key(), true, api), - Config::DataSource::read(ssl_credentials.cert_chain(), true, api), - }; - return grpc::SslCredentials(ssl_credentials_options); + const auto root_certs = Config::DataSource::read(ssl_credentials.root_certs(), true, api); + const auto private_key = Config::DataSource::read(ssl_credentials.private_key(), true, api); + const auto cert_chain = Config::DataSource::read(ssl_credentials.cert_chain(), true, api); + grpc::experimental::TlsChannelCredentialsOptions options; + if (!private_key.empty() || !cert_chain.empty()) { + options.set_certificate_provider( + std::make_shared( + root_certs, + std::vector{{private_key, cert_chain}})); + } else if (!root_certs.empty()) { + options.set_certificate_provider( + std::make_shared(root_certs)); + } + if (!root_certs.empty()) { + options.watch_root_certs(); + } + if (!private_key.empty() || !cert_chain.empty()) { + options.watch_identity_key_cert_pairs(); + } + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.google_grpc_disable_tls_13")) { + options.set_max_tls_version(grpc_tls_version::TLS1_2); + } + return grpc::experimental::TlsCredentials(options); } case envoy::config::core::v3::GrpcService::GoogleGrpc::ChannelCredentials:: CredentialSpecifierCase::kLocalCredentials: { @@ -43,7 +63,11 @@ std::shared_ptr CredsUtility::defaultSslChannelCredent if (creds != nullptr) { return creds; } - return grpc::SslCredentials({}); + grpc::experimental::TlsChannelCredentialsOptions options; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.google_grpc_disable_tls_13")) { + options.set_max_tls_version(grpc_tls_version::TLS1_2); + } + return grpc::experimental::TlsCredentials(options); } std::vector> diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 60d594c964319..607adfb66f05c 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -122,6 +122,10 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); // TODO(danzh) false deprecate it once QUICHE has its own enable/disable flag. FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); +// A flag to set the maximum TLS version for google_grpc client to TLS1.2, when needed for +// compliance restrictions. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_google_grpc_disable_tls_13); + // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index 6cf74e7f7d7e7..0c8cb33806840 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -176,6 +176,7 @@ envoy_cc_test( ":grpc_client_integration_test_harness_lib", "//source/common/grpc:async_client_lib", "//source/extensions/grpc_credentials/example:config", + "//test/test_common:test_runtime_lib", ] + envoy_select_google_grpc(["//source/common/grpc:google_async_client_lib"]), ) diff --git a/test/common/grpc/grpc_client_integration_test.cc b/test/common/grpc/grpc_client_integration_test.cc index 3fd32c96d8fc7..099d6fa5704c3 100644 --- a/test/common/grpc/grpc_client_integration_test.cc +++ b/test/common/grpc/grpc_client_integration_test.cc @@ -5,6 +5,7 @@ #endif +#include "test/test_common/test_runtime.h" #include "test/common/grpc/grpc_client_integration_test_harness.h" using testing::Eq; @@ -409,6 +410,29 @@ TEST_P(GrpcSslClientIntegrationTest, BasicSslRequestWithClientCert) { dispatcher_helper_.runDispatcher(); } +// Validate TLS version mismatch between the client and the server. +TEST_P(GrpcSslClientIntegrationTest, BasicSslRequestHandshakeFailure) { + SKIP_IF_GRPC_CLIENT(ClientType::EnvoyGrpc); + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.google_grpc_disable_tls_13", "true"}}); + use_server_tls_13_ = true; + initialize(); + auto request = createRequest(empty_metadata_, false); + EXPECT_CALL(*request->child_span_, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("13"))); + EXPECT_CALL(*request->child_span_, + setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(*request, onFailure(Status::Internal, "", _)).WillOnce(InvokeWithoutArgs([this]() { + dispatcher_helper_.dispatcher_.exit(); + })); + EXPECT_CALL(*request->child_span_, finishSpan()); + FakeRawConnectionPtr fake_connection; + ASSERT_TRUE(fake_upstream_->waitForRawConnection(fake_connection)); + if (fake_connection->connected()) { + ASSERT_TRUE(fake_connection->waitForDisconnect()); + } + dispatcher_helper_.dispatcher_.run(Event::Dispatcher::RunType::Block); +} + #ifdef ENVOY_GOOGLE_GRPC // AccessToken credential validation tests. class GrpcAccessTokenClientIntegrationTest : public GrpcSslClientIntegrationTest { diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index a30067cb190ad..93990a8982abf 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -365,7 +365,8 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { virtual void expectExtraHeaders(FakeStream&) {} - HelloworldRequestPtr createRequest(const TestMetadata& initial_metadata) { + HelloworldRequestPtr createRequest(const TestMetadata& initial_metadata, + bool expect_upstream_request = true) { auto request = std::make_unique(dispatcher_helper_); EXPECT_CALL(*request, onCreateInitialMetadata(_)) .WillOnce(Invoke([&initial_metadata](Http::HeaderMap& headers) { @@ -392,6 +393,10 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { active_span, Http::AsyncClient::RequestOptions()); EXPECT_NE(request->grpc_request_, nullptr); + if (!expect_upstream_request) { + return request; + } + if (!fake_connection_) { AssertionResult result = fake_upstream_->waitForHttpConnection(*dispatcher_, fake_connection_); @@ -526,6 +531,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { tls_cert->mutable_private_key()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); } + auto cfg = std::make_unique( tls_context, factory_context_); @@ -557,6 +563,13 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { validation_context->mutable_trusted_ca()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); } + if (use_server_tls_13_) { + auto* tls_params = common_tls_context->mutable_tls_params(); + tls_params->set_tls_minimum_protocol_version( + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3); + tls_params->set_tls_maximum_protocol_version( + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3); + } auto cfg = std::make_unique( tls_context, factory_context_); @@ -568,6 +581,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { } bool use_client_cert_{}; + bool use_server_tls_13_{false}; testing::NiceMock factory_context_; }; From 3164ebde91924089f6db4e52dd8993d3edb842b3 Mon Sep 17 00:00:00 2001 From: Sebastian Schepens Date: Fri, 1 Mar 2024 17:45:20 -0300 Subject: [PATCH 184/274] populate histogram summary sample sum Signed-off-by: Sebastian Schepens --- .../stat_sinks/metrics_service/grpc_metrics_service_impl.cc | 1 + .../metrics_service/grpc_metrics_service_impl_test.cc | 2 ++ 2 files changed, 3 insertions(+) diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc index 2b74663df3978..f1f593a83aa63 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc @@ -135,6 +135,7 @@ void MetricsFlusher::flushSummary(io::prometheus::client::MetricFamily& metrics_ quantile->set_value(hist_stats.computedQuantiles()[i]); } summary->set_sample_count(hist_stats.sampleCount()); + summary->set_sample_sum(hist_stats.sampleSum()); } io::prometheus::client::Metric* diff --git a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc index 6e7dc7425ce0b..59ceb061fbd41 100644 --- a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc +++ b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc @@ -344,6 +344,7 @@ TEST_F(MetricsServiceSinkTest, HistogramEmitModeBoth) { const auto& metric1 = (*metrics)[0].metric(0); EXPECT_TRUE(metric1.has_summary()); + EXPECT_TRUE(metric1.summary().has_sample_sum()); const auto& metric2 = (*metrics)[1].metric(0); EXPECT_TRUE(metric2.has_histogram()); })); @@ -364,6 +365,7 @@ TEST_F(MetricsServiceSinkTest, HistogramEmitModeSummary) { const auto& metric1 = (*metrics)[0].metric(0); EXPECT_TRUE(metric1.has_summary()); + EXPECT_TRUE(metric1.summary().has_sample_sum()); })); sink.flush(snapshot_); } From dfd30809b1714823824f9a012e5921d84caf7ff6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:24:01 +0000 Subject: [PATCH 185/274] build(deps): bump distroless/base-nossl-debian12 from `28dc895` to `0e777c6` in /ci (#32652) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `28dc895` to `0e777c6`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index b9a043670d909..9e4e156bc75ea 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:28dc8956c04a92ffc192d06c5da69fa747c675ee44043ba18128e747c2f539f5 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0e777c69ba810353b9f3f2033280bbe7d029d81fa55760f6eec817ef595aa19c AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 55f034049b22a4cfd85f74bb410e08b2aa0c3372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 09:53:42 +0000 Subject: [PATCH 186/274] build(deps): bump distroless/base-nossl-debian12 from `0e777c6` to `099c134` in /ci (#32843) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `0e777c6` to `099c134`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 9e4e156bc75ea..9c5c824d38c37 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0e777c69ba810353b9f3f2033280bbe7d029d81fa55760f6eec817ef595aa19c AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:099c13463fdd2f52d31af8b61f5a991ed8e97bdac529f10b22c4f4ebf0c21c0d AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From dbcd32446dee106526e1cc5f7fc7b547e75ba0f6 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 22:59:48 +0000 Subject: [PATCH 187/274] deps: Bump `com_github_nghttp2_nghttp2` -> 1.58.0 (#31361) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 0fff5cd49b197..e16080fb96d5a 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -456,12 +456,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Nghttp2", project_desc = "Implementation of HTTP/2 and its header compression algorithm HPACK in C", project_url = "https://nghttp2.org", - version = "1.57.0", - sha256 = "1e3258453784d3b7e6cc48d0be087b168f8360b5d588c66bfeda05d07ad39ffd", + version = "1.58.0", + sha256 = "9ebdfbfbca164ef72bdf5fd2a94a4e6dfb54ec39d2ef249aeb750a91ae361dfb", strip_prefix = "nghttp2-{version}", urls = ["https://github.com/nghttp2/nghttp2/releases/download/v{version}/nghttp2-{version}.tar.gz"], use_category = ["controlplane", "dataplane_core"], - release_date = "2023-10-10", + release_date = "2023-10-27", cpe = "cpe:2.3:a:nghttp2:nghttp2:*", license = "MIT", license_url = "https://github.com/nghttp2/nghttp2/blob/v{version}/LICENSE", From f389da327416e36ee7020a914209e087d625599e Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:44:06 +0000 Subject: [PATCH 188/274] deps: Bump `com_github_nghttp2_nghttp2` -> 1.59.0 (#31953) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index e16080fb96d5a..4554b7af10cb3 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -456,12 +456,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Nghttp2", project_desc = "Implementation of HTTP/2 and its header compression algorithm HPACK in C", project_url = "https://nghttp2.org", - version = "1.58.0", - sha256 = "9ebdfbfbca164ef72bdf5fd2a94a4e6dfb54ec39d2ef249aeb750a91ae361dfb", + version = "1.59.0", + sha256 = "90fd27685120404544e96a60ed40398a3457102840c38e7215dc6dec8684470f", strip_prefix = "nghttp2-{version}", urls = ["https://github.com/nghttp2/nghttp2/releases/download/v{version}/nghttp2-{version}.tar.gz"], use_category = ["controlplane", "dataplane_core"], - release_date = "2023-10-27", + release_date = "2024-01-21", cpe = "cpe:2.3:a:nghttp2:nghttp2:*", license = "MIT", license_url = "https://github.com/nghttp2/nghttp2/blob/v{version}/LICENSE", From 776c3d75b396e0af81a7729f49622af6a8381269 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 14 Mar 2024 20:41:52 +0000 Subject: [PATCH 189/274] ci: Disable windows (#32904) Signed-off-by: Ryan Northey --- .github/config.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/config.yml b/.github/config.yml index 50a28027053d7..a9dde4d03896d 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -80,27 +80,8 @@ checks: - publish - verify required: true - windows: - name: Envoy/Windows - required: true - on-run: - - build-windows run: - build-windows: - paths: - - .bazelrc - - .bazelversion - - .github/config.yml - - api/**/* - - bazel/**/* - - ci/**/* - - configs/**/* - - contrib/**/* - - envoy/**/* - - source/**/* - - test/**/* - - VERSION.txt build-macos: paths: - .bazelrc From f7d7ae74f17474eadd15c20b3e8f6d3037795c2c Mon Sep 17 00:00:00 2001 From: Sean Killeen Date: Thu, 4 Apr 2024 04:36:55 -0400 Subject: [PATCH 190/274] H/2: discard Host header when :authority is present (#30005) Discard the Host header if the :authority header was received to bring Envoy into compliance with https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1 This behavioral change can be reverted by setting runtime flag envoy.reloadable_features.http2_discard_host_header to false. --------- Signed-off-by: Yan Avlasov Signed-off-by: Sean Killeen --- changelogs/current.yaml | 5 ++ source/common/http/http2/codec_impl.cc | 12 +++ source/common/runtime/runtime_features.cc | 1 + test/common/http/http2/http2_frame.h | 6 +- .../multiplexed_integration_test.cc | 77 +++++++++++++++++++ 5 files changed, 98 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2b8dc99df2e09..8002c795d9778 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -2,6 +2,11 @@ date: Pending behavior_changes: # *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +- area: http2 + change: | + Discard the ``Host`` header if the ``:authority`` header was received to bring Envoy into compliance with + https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1 This behavioral change can be reverted by setting runtime flag + ``envoy.reloadable_features.http2_discard_host_header`` to false. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index ad7c29b423dc4..fb4f1f9215fdd 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -2110,6 +2110,18 @@ int ServerConnectionImpl::onHeader(const nghttp2_frame* frame, HeaderString&& na // For a server connection, we should never get push promise frames. ASSERT(frame->hd.type == NGHTTP2_HEADERS); ASSERT(frame->headers.cat == NGHTTP2_HCAT_REQUEST || frame->headers.cat == NGHTTP2_HCAT_HEADERS); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http2_discard_host_header")) { + StreamImpl* stream = getStreamUnchecked(frame->hd.stream_id); + if (stream && name == static_cast(Http::Headers::get().HostLegacy)) { + // Check if there is already the :authority header + const auto result = stream->headers().get(Http::Headers::get().Host); + if (!result.empty()) { + // Discard the host header value + return 0; + } + // Otherwise use host value as :authority + } + } return saveHeader(frame, std::move(name), std::move(value)); } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 607adfb66f05c..c1d28ac747d46 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -47,6 +47,7 @@ RUNTIME_GUARD(envoy_reloadable_features_format_ports_as_numbers); RUNTIME_GUARD(envoy_reloadable_features_handle_uppercase_scheme); RUNTIME_GUARD(envoy_reloadable_features_http1_allow_codec_error_response_after_1xx_headers); RUNTIME_GUARD(envoy_reloadable_features_http2_decode_metadata_with_quiche); +RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http_allow_partial_urls_in_referer); RUNTIME_GUARD(envoy_reloadable_features_http_ext_auth_failure_mode_allow_header_add); diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index 6349f5d94d845..0789fc5bf11ac 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -260,6 +260,9 @@ class Http2Frame { ASSERT(size() >= HeaderSize); setPayloadSize(size() - HeaderSize); } + // Headers are directly encoded + void appendStaticHeader(StaticHeaderIndex index); + void appendHeaderWithoutIndexing(StaticHeaderIndex index, absl::string_view value); private: void buildHeader(Type type, uint32_t payload_size = 0, uint8_t flags = 0, uint32_t stream_id = 0); @@ -277,9 +280,6 @@ class Http2Frame { std::copy(data.begin(), data.end(), data_.begin() + 9); } - // Headers are directly encoded - void appendStaticHeader(StaticHeaderIndex index); - void appendHeaderWithoutIndexing(StaticHeaderIndex index, absl::string_view value); void appendEmptyHeader(); DataContainer data_; diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index f2e7df6b2b98f..c2c9dc5e5486f 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2129,7 +2129,84 @@ TEST_P(Http2FrameIntegrationTest, AccessLogOfWireBytesIfResponseSizeGreaterThanW // Cleanup. tcp_client_->close(); } +TEST_P(Http2FrameIntegrationTest, HostDifferentFromAuthority) { + beginSession(); + + uint32_t request_idx = 0; + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx), + "one.example.com", "/path", {{"host", "two.example.com"}}); + sendFrame(request); + + waitForNextUpstreamRequest(); + EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com"); + upstream_request_->encodeHeaders(default_response_headers_, true); + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()); + tcp_client_->close(); +} + +TEST_P(Http2FrameIntegrationTest, HostSameAsAuthority) { + beginSession(); + + uint32_t request_idx = 0; + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx), + "one.example.com", "/path", {{"host", "one.example.com"}}); + sendFrame(request); + + waitForNextUpstreamRequest(); + EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com"); + upstream_request_->encodeHeaders(default_response_headers_, true); + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()); + tcp_client_->close(); +} + +TEST_P(Http2FrameIntegrationTest, HostConcatenatedWithAuthorityWithOverride) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.http2_discard_host_header", "false"); + beginSession(); + + uint32_t request_idx = 0; + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx), + "one.example.com", "/path", {{"host", "two.example.com"}}); + sendFrame(request); + + waitForNextUpstreamRequest(); + EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com,two.example.com"); + upstream_request_->encodeHeaders(default_response_headers_, true); + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()); + tcp_client_->close(); +} + +// All HTTP/2 static headers must be before non-static headers. +// Verify that codecs validate this. +TEST_P(Http2FrameIntegrationTest, HostBeforeAuthorityIsRejected) { +#ifdef ENVOY_ENABLE_UHV + // TODO(yanavlasov): fix this check for oghttp2 in UHV mode. + return; +#endif + beginSession(); + Http2Frame request = Http2Frame::makeEmptyHeadersFrame(Http2Frame::makeClientStreamId(0), + Http2Frame::HeadersFlags::EndHeaders); + request.appendStaticHeader(Http2Frame::StaticHeaderIndex::MethodPost); + request.appendStaticHeader(Http2Frame::StaticHeaderIndex::SchemeHttps); + request.appendHeaderWithoutIndexing(Http2Frame::StaticHeaderIndex::Path, "/path"); + // Add the `host` header before `:authority` + request.appendHeaderWithoutIndexing({"host", "two.example.com"}); + request.appendHeaderWithoutIndexing(Http2Frame::StaticHeaderIndex::Authority, "one.example.com"); + request.adjustPayloadSize(); + + sendFrame(request); + + // By default codec treats stream errors as protocol errors and closes the connection. + tcp_client_->waitForDisconnect(); + tcp_client_->close(); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_cx_protocol_error")->value()); +} TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequests) { const int kRequestsSentPerIOCycle = 20; autonomous_upstream_ = true; From c473913397454b4d0626a90f0fe2b0e9614a0d87 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 3 Apr 2024 17:49:28 +0100 Subject: [PATCH 191/274] docker/release: Update Ubuntu images (80ef4a44) Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 9c5c824d38c37..1752d328fe0fd 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=20.04@sha256:bb1c41682308d7040f74d103022816d41c50d7b0c89e9d706a74b4e548636e54 +ARG BUILD_TAG=20.04@sha256:80ef4a44043dec4490506e6cc4289eeda2d106a70148b74b5ae91ee670e9c35d ARG ENVOY_VRP_BASE_IMAGE=envoy-base From 8754c0ffe87c36906103815ce8d5434fc8b9bcd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Apr 2024 09:16:32 +0100 Subject: [PATCH 192/274] build(deps): bump distroless/base-nossl-debian12 from `099c134` to `0cf184c` in /ci (#33282) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `099c134` to `0cf184c`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 1752d328fe0fd..396e1fb1d7703 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:099c13463fdd2f52d31af8b61f5a991ed8e97bdac529f10b22c4f4ebf0c21c0d AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0cf184cfdb9ac2878822b15b8917fae5d42fba26da654cd75ab3ed34add0737f AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 95d92b4a15ddbf588b949999a75fdca289a96550 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Thu, 4 Apr 2024 12:08:41 +0100 Subject: [PATCH 193/274] deps/CVE: Fix (nghttp2) CVE-2024-30255 Signed-off-by: Ryan Northey --- bazel/foreign_cc/nghttp2.patch | 168 +++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/bazel/foreign_cc/nghttp2.patch b/bazel/foreign_cc/nghttp2.patch index d1cbab6356e5b..511e2a2e4b29b 100644 --- a/bazel/foreign_cc/nghttp2.patch +++ b/bazel/foreign_cc/nghttp2.patch @@ -14,3 +14,171 @@ diff -u -r a/CMakeLists.txt b/CMakeLists.txt endif() # AC_TYPE_UINT8_T # AC_TYPE_UINT16_T +diff --git a/doc/Makefile.am b/doc/Makefile.am +index 7d7f31c6..ce50d89e 100644 +--- a/doc/Makefile.am ++++ b/doc/Makefile.am +@@ -74,6 +74,7 @@ APIDOCS= \ + nghttp2_option_set_peer_max_concurrent_streams.rst \ + nghttp2_option_set_server_fallback_rfc7540_priorities.rst \ + nghttp2_option_set_user_recv_extension_type.rst \ ++ nghttp2_option_set_max_continuations.rst \ + nghttp2_option_set_max_outbound_ack.rst \ + nghttp2_option_set_max_settings.rst \ + nghttp2_option_set_stream_reset_rate_limit.rst \ +diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h +index 7910db23..a54efbfd 100644 +--- a/lib/includes/nghttp2/nghttp2.h ++++ b/lib/includes/nghttp2/nghttp2.h +@@ -440,7 +440,12 @@ typedef enum { + * exhaustion on server side to send these frames forever and does + * not read network. + */ +- NGHTTP2_ERR_FLOODED = -904 ++ NGHTTP2_ERR_FLOODED = -904, ++ /** ++ * When a local endpoint receives too many CONTINUATION frames ++ * following a HEADER frame. ++ */ ++ NGHTTP2_ERR_TOO_MANY_CONTINUATIONS = -905, + } nghttp2_error; + + /** +@@ -2773,6 +2778,17 @@ NGHTTP2_EXTERN void + nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option, + uint64_t burst, uint64_t rate); + ++/** ++ * @function ++ * ++ * This function sets the maximum number of CONTINUATION frames ++ * following an incoming HEADER frame. If more than those frames are ++ * received, the remote endpoint is considered to be misbehaving and ++ * session will be closed. The default value is 8. ++ */ ++NGHTTP2_EXTERN void nghttp2_option_set_max_continuations(nghttp2_option *option, ++ size_t val); ++ + /** + * @function + * +diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c +index 93dd4754..b3563d98 100644 +--- a/lib/nghttp2_helper.c ++++ b/lib/nghttp2_helper.c +@@ -336,6 +336,8 @@ const char *nghttp2_strerror(int error_code) { + "closed"; + case NGHTTP2_ERR_TOO_MANY_SETTINGS: + return "SETTINGS frame contained more than the maximum allowed entries"; ++ case NGHTTP2_ERR_TOO_MANY_CONTINUATIONS: ++ return "Too many CONTINUATION frames following a HEADER frame"; + default: + return "Unknown error code"; + } +diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c +index 43d4e952..53144b9b 100644 +--- a/lib/nghttp2_option.c ++++ b/lib/nghttp2_option.c +@@ -150,3 +150,8 @@ void nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option, + option->stream_reset_burst = burst; + option->stream_reset_rate = rate; + } ++ ++void nghttp2_option_set_max_continuations(nghttp2_option *option, size_t val) { ++ option->opt_set_mask |= NGHTTP2_OPT_MAX_CONTINUATIONS; ++ option->max_continuations = val; ++} +diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h +index 2259e184..c89cb97f 100644 +--- a/lib/nghttp2_option.h ++++ b/lib/nghttp2_option.h +@@ -71,6 +71,7 @@ typedef enum { + NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13, + NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14, + NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15, ++ NGHTTP2_OPT_MAX_CONTINUATIONS = 1 << 16, + } nghttp2_option_flag; + + /** +@@ -98,6 +99,10 @@ struct nghttp2_option { + * NGHTTP2_OPT_MAX_SETTINGS + */ + size_t max_settings; ++ /** ++ * NGHTTP2_OPT_MAX_CONTINUATIONS ++ */ ++ size_t max_continuations; + /** + * Bitwise OR of nghttp2_option_flag to determine that which fields + * are specified. +diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c +index ce21caf9..18949528 100644 +--- a/lib/nghttp2_session.c ++++ b/lib/nghttp2_session.c +@@ -496,6 +496,7 @@ static int session_new(nghttp2_session **session_ptr, + (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN; + (*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; + (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_SETTINGS; ++ (*session_ptr)->max_continuations = NGHTTP2_DEFAULT_MAX_CONTINUATIONS; + + if (option) { + if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && +@@ -584,6 +585,10 @@ static int session_new(nghttp2_session **session_ptr, + option->stream_reset_burst, + option->stream_reset_rate); + } ++ ++ if (option->opt_set_mask & NGHTTP2_OPT_MAX_CONTINUATIONS) { ++ (*session_ptr)->max_continuations = option->max_continuations; ++ } + } + + rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater, +@@ -6778,6 +6783,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, + } + } + session_inbound_frame_reset(session); ++ ++ session->num_continuations = 0; + } + break; + } +@@ -6899,6 +6906,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, + } + #endif /* DEBUGBUILD */ + ++ if (++session->num_continuations > session->max_continuations) { ++ return NGHTTP2_ERR_TOO_MANY_CONTINUATIONS; ++ } ++ + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + +diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h +index b119329a..ef8f7b27 100644 +--- a/lib/nghttp2_session.h ++++ b/lib/nghttp2_session.h +@@ -110,6 +110,10 @@ typedef struct { + #define NGHTTP2_DEFAULT_STREAM_RESET_BURST 1000 + #define NGHTTP2_DEFAULT_STREAM_RESET_RATE 33 + ++/* The default max number of CONTINUATION frames following an incoming ++ HEADER frame. */ ++#define NGHTTP2_DEFAULT_MAX_CONTINUATIONS 8 ++ + /* Internal state when receiving incoming frame */ + typedef enum { + /* Receiving frame header */ +@@ -290,6 +294,12 @@ struct nghttp2_session { + size_t max_send_header_block_length; + /* The maximum number of settings accepted per SETTINGS frame. */ + size_t max_settings; ++ /* The maximum number of CONTINUATION frames following an incoming ++ HEADER frame. */ ++ size_t max_continuations; ++ /* The number of CONTINUATION frames following an incoming HEADER ++ frame. This variable is reset when END_HEADERS flag is seen. */ ++ size_t num_continuations; + /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */ + uint32_t next_stream_id; + /* The last stream ID this session initiated. For client session, From 8a6177740b518055b0cf16719b76c4c1794b6757 Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Wed, 3 Apr 2024 19:24:35 +0000 Subject: [PATCH 194/274] Update Envoy to use the patch Signed-off-by: Yan Avlasov Signed-off-by: Ryan Northey --- source/common/http/http2/codec_impl.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index fb4f1f9215fdd..ab4d5d3b91841 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -1812,6 +1812,11 @@ ConnectionImpl::Http2Options::Http2Options( // on this mitigation, set back to the old 10K number to avoid any changes in the HTTP/2 codec // behavior. nghttp2_option_set_max_outbound_ack(options_, 10000); + + // nghttp2 REQUIRES setting max number of CONTINUATION frames. + // 1024 is chosen to accommodate Envoy's 8Mb max limit of max_request_headers_kb + // in both headers and trailers + nghttp2_option_set_max_continuations(options_, 1024); } ConnectionImpl::Http2Options::~Http2Options() { nghttp2_option_del(options_); } @@ -1826,6 +1831,11 @@ ConnectionImpl::ClientHttp2Options::ClientHttp2Options( // TODO(PiotrSikora): remove this once multiple upstream connections or queuing are implemented. nghttp2_option_set_peer_max_concurrent_streams( options_, ::Envoy::Http2::Utility::OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS); + + // nghttp2 REQUIRES setting max number of CONTINUATION frames. + // 1024 is chosen to accommodate Envoy's 8Mb max limit of max_request_headers_kb + // in both headers and trailers + nghttp2_option_set_max_continuations(options_, 1024); } void ConnectionImpl::dumpState(std::ostream& os, int indent_level) const { From dacf7beddfdf3c808413aa028d6862a474ed4fa7 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Thu, 4 Apr 2024 11:53:31 +0100 Subject: [PATCH 195/274] changelogs: Add changelog for CVE-2024-30255 https://github.com/envoyproxy/envoy/security/advisories/GHSA-j654-3ccm-vfmm Signed-off-by: Ryan Northey --- changelogs/current.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8002c795d9778..a154c73f0916e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -13,6 +13,9 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: http2 + change: | + Update nghttp2 to resolve CVE-2024-30255 (https://github.com/envoyproxy/envoy/security/advisories/GHSA-j654-3ccm-vfmm). removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` From 9134d6a65e5c2c714d503807eb31a8490471fc5f Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Thu, 4 Apr 2024 12:00:12 +0000 Subject: [PATCH 196/274] repo: Release v1.27.4 **Summary of changes**: - Patch nghttp2 to resolve [CVE-2024-30255](https://github.com/envoyproxy/envoy/security/advisories/GHSA-j654-3ccm-vfmm) - Assorted fixes **Docker images**: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.4 **Docs**: https://www.envoyproxy.io/docs/envoy/v1.27.4/ **Release notes**: https://www.envoyproxy.io/docs/envoy/v1.27.4/version_history/v1.27/v1.27.4 **Full changelog**: https://github.com/envoyproxy/envoy/compare/v1.27.3...v1.27.4 Signed-off-by: Ryan Northey Signed-off-by: Yan Avlasov --- VERSION.txt | 2 +- changelogs/1.26.8.yaml | 13 ++++++++ changelogs/1.27.3.yaml | 52 +++++++++++++++++++++++++++++ changelogs/current.yaml | 12 +------ docs/inventories/v1.26/objects.inv | Bin 153965 -> 153979 bytes docs/inventories/v1.27/objects.inv | Bin 159876 -> 159932 bytes docs/versions.yaml | 4 +-- 7 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 changelogs/1.26.8.yaml create mode 100644 changelogs/1.27.3.yaml diff --git a/VERSION.txt b/VERSION.txt index 30eedfa5bbb6b..d6201580edb4f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.4-dev +1.27.4 diff --git a/changelogs/1.26.8.yaml b/changelogs/1.26.8.yaml new file mode 100644 index 0000000000000..a59f0acb0ad0c --- /dev/null +++ b/changelogs/1.26.8.yaml @@ -0,0 +1,13 @@ +date: April 4, 2024 + +bug_fixes: +- area: http2 + change: | + Update nghttp2 to resolve CVE-2024-30255 (https://github.com/envoyproxy/envoy/security/advisories/GHSA-j654-3ccm-vfmm). + +new_features: +- area: google_grpc + change: | + Added an off-by-default runtime flag + ``envoy.reloadable_features.google_grpc_disable_tls_13`` to disable TLSv1.3 + usage by gRPC SDK for ``google_grpc`` services. diff --git a/changelogs/1.27.3.yaml b/changelogs/1.27.3.yaml new file mode 100644 index 0000000000000..a67d0b6cabb28 --- /dev/null +++ b/changelogs/1.27.3.yaml @@ -0,0 +1,52 @@ +date: February 9, 2024 + +minor_behavior_changes: +- area: access_log + change: | + When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried + to find the keys configured in filter_state_objects_to_log. + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: grpc + change: | + Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug where Datadog spans tagged as errors would not have the appropriate error property set. +- area: tracing + change: | + Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. +- area: tracing + change: | + Fixed a bug that caused the Datadog tracing extension to drop traces that + should be kept on account of an extracted sampling decision. +- area: proxy protocol + change: | + fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a154c73f0916e..73d73f7b7a331 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,30 +1,20 @@ -date: Pending +date: April 4, 2024 behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - area: http2 change: | Discard the ``Host`` header if the ``:authority`` header was received to bring Envoy into compliance with https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1 This behavioral change can be reverted by setting runtime flag ``envoy.reloadable_features.http2_discard_host_header`` to false. -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* - bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: http2 change: | Update nghttp2 to resolve CVE-2024-30255 (https://github.com/envoyproxy/envoy/security/advisories/GHSA-j654-3ccm-vfmm). -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - new_features: - area: google_grpc change: | Added an off-by-default runtime flag ``envoy.reloadable_features.google_grpc_disable_tls_13`` to disable TLSv1.3 usage by gRPC SDK for ``google_grpc`` services. - -deprecated: diff --git a/docs/inventories/v1.26/objects.inv b/docs/inventories/v1.26/objects.inv index 26b06951df94722c60592253d264b2378a4075bd..e8c220301cff02a790bcb7e6cbc12c9d371cbe41 100644 GIT binary patch delta 18883 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 Cts)Ho delta 18862 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 B*#?+6g3KlzO>{813%9cx&= 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 delta 19228 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 diff --git a/docs/versions.yaml b/docs/versions.yaml index 91b80a86fe857..4be885fd2e54f 100644 --- a/docs/versions.yaml +++ b/docs/versions.yaml @@ -19,5 +19,5 @@ "1.23": 1.23.12 "1.24": 1.24.12 "1.25": 1.25.11 -"1.26": 1.26.7 -"1.27": 1.27.2 +"1.26": 1.26.8 +"1.27": 1.27.3 From aa8070ed5f8002b71c01860dfc10f589309c2113 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 8 Apr 2024 10:37:19 +0100 Subject: [PATCH 197/274] repo: Dev v1.27.5 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.4.yaml | 20 ++++++++++++++++++++ changelogs/current.yaml | 25 +++++++++++-------------- docs/inventories/v1.27/objects.inv | Bin 159932 -> 159977 bytes docs/versions.yaml | 2 +- 5 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 changelogs/1.27.4.yaml diff --git a/VERSION.txt b/VERSION.txt index d6201580edb4f..3057fc1429d61 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.4 +1.27.5-dev diff --git a/changelogs/1.27.4.yaml b/changelogs/1.27.4.yaml new file mode 100644 index 0000000000000..73d73f7b7a331 --- /dev/null +++ b/changelogs/1.27.4.yaml @@ -0,0 +1,20 @@ +date: April 4, 2024 + +behavior_changes: +- area: http2 + change: | + Discard the ``Host`` header if the ``:authority`` header was received to bring Envoy into compliance with + https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1 This behavioral change can be reverted by setting runtime flag + ``envoy.reloadable_features.http2_discard_host_header`` to false. + +bug_fixes: +- area: http2 + change: | + Update nghttp2 to resolve CVE-2024-30255 (https://github.com/envoyproxy/envoy/security/advisories/GHSA-j654-3ccm-vfmm). + +new_features: +- area: google_grpc + change: | + Added an off-by-default runtime flag + ``envoy.reloadable_features.google_grpc_disable_tls_13`` to disable TLSv1.3 + usage by gRPC SDK for ``google_grpc`` services. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 73d73f7b7a331..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,20 +1,17 @@ -date: April 4, 2024 +date: Pending behavior_changes: -- area: http2 - change: | - Discard the ``Host`` header if the ``:authority`` header was received to bring Envoy into compliance with - https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1 This behavioral change can be reverted by setting runtime flag - ``envoy.reloadable_features.http2_discard_host_header`` to false. +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +minor_behavior_changes: +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: http2 - change: | - Update nghttp2 to resolve CVE-2024-30255 (https://github.com/envoyproxy/envoy/security/advisories/GHSA-j654-3ccm-vfmm). +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` new_features: -- area: google_grpc - change: | - Added an off-by-default runtime flag - ``envoy.reloadable_features.google_grpc_disable_tls_13`` to disable TLSv1.3 - usage by gRPC SDK for ``google_grpc`` services. + +deprecated: diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index 4eee783c0fd4c10b7ce5d67cf5f70f67cbc2a93a..d2997321e46284e7e5a0e5123eaae1d7d1f4e0c0 100644 GIT binary patch delta 3656 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 Date: Mon, 8 Apr 2024 13:23:26 +0100 Subject: [PATCH 198/274] ci/tests: Re-issue test certs (#33389) Signed-off-by: Ryan Northey --- test/config/integration/certs/cacert.pem | 34 ++--- test/config/integration/certs/cacert_info.h | 10 +- test/config/integration/certs/cakey.pem | 50 +++---- .../integration/certs/client2_chain.pem | 140 +++++++++--------- test/config/integration/certs/client2cert.pem | 36 ++--- .../integration/certs/client2cert_hash.h | 4 +- test/config/integration/certs/client2key.pem | 50 +++---- .../integration/certs/client_ecdsacert.pem | 30 ++-- .../integration/certs/client_ecdsacert_hash.h | 4 +- .../integration/certs/client_ecdsakey.pem | 6 +- test/config/integration/certs/clientcert.pem | 34 ++--- .../integration/certs/clientcert_hash.h | 4 +- test/config/integration/certs/clientkey.pem | 55 ++++--- .../config/integration/certs/expired_cert.pem | 32 ++-- .../integration/certs/expired_cert_hash.h | 4 +- test/config/integration/certs/expired_key.pem | 50 +++---- .../certs/intermediate_ca_2cert.pem | 34 ++--- .../certs/intermediate_ca_2cert_info.h | 10 +- .../certs/intermediate_ca_2key.pem | 50 +++---- .../certs/intermediate_ca_cert_chain.pem | 104 ++++++------- .../integration/certs/intermediate_cacert.pem | 36 ++--- .../certs/intermediate_cacert_info.h | 10 +- .../integration/certs/intermediate_cakey.pem | 50 +++---- .../intermediate_partial_ca_cert_chain.pem | 70 ++++----- test/config/integration/certs/server2cert.pem | 34 ++--- .../integration/certs/server2cert_hash.h | 4 +- .../integration/certs/server2cert_info.h | 12 +- test/config/integration/certs/server2key.pem | 50 +++---- .../certs/server_ecdsa_ocsp_resp.der | Bin 1612 -> 1612 bytes .../integration/certs/server_ecdsacert.pem | 24 +-- .../integration/certs/server_ecdsacert_hash.h | 4 +- .../integration/certs/server_ecdsakey.pem | 6 +- .../integration/certs/server_ocsp_resp.der | Bin 1612 -> 1612 bytes test/config/integration/certs/servercert.pem | 34 ++--- .../integration/certs/servercert_hash.h | 4 +- .../integration/certs/servercert_info.h | 12 +- test/config/integration/certs/serverkey.pem | 50 +++---- .../integration/certs/upstreamcacert.pem | 36 ++--- .../integration/certs/upstreamcacert_info.h | 10 +- .../integration/certs/upstreamcakey.pem | 50 +++---- .../config/integration/certs/upstreamcert.pem | 36 ++--- .../integration/certs/upstreamcert_hash.h | 4 +- test/config/integration/certs/upstreamkey.pem | 50 +++---- .../certs/upstreamlocalhostcert.pem | 36 ++--- .../certs/upstreamlocalhostcert_hash.h | 4 +- .../certs/upstreamlocalhostkey.pem | 50 +++---- 46 files changed, 712 insertions(+), 705 deletions(-) diff --git a/test/config/integration/certs/cacert.pem b/test/config/integration/certs/cacert.pem index 47c66c6b07b60..051947023da5b 100644 --- a/test/config/integration/certs/cacert.pem +++ b/test/config/integration/certs/cacert.pem @@ -1,23 +1,23 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/test/config/integration/certs/cacert_info.h b/test/config/integration/certs/cacert_info.h index 66a9281c8942d..a3c9aa7e07489 100644 --- a/test/config/integration/certs/cacert_info.h +++ b/test/config/integration/certs/cacert_info.h @@ -1,6 +1,8 @@ // NOLINT(namespace-envoy) constexpr char TEST_CA_CERT_256_HASH[] = - "c3e7f584b399f0f11e65268a24b2ab536ba6a9f7d722150d0e10afa70a470ad4"; -constexpr char TEST_CA_CERT_1_HASH[] = "5df2ca889db6287a61ebc9464d6f474fa50d37aa"; -constexpr char TEST_CA_CERT_SPKI[] = "VYTUpj60bicmU8PDzK7BzeGY5Zx2x+0bs0V5tFpRk+Y="; -constexpr char TEST_CA_CERT_SERIAL[] = "190c1c9f7cff909627e6a766d2747edf03724801"; + "1c7f2d43bdc25371d076256fb7fa0eabcdee0669bf7b2436a10e81d773e8084c"; +constexpr char TEST_CA_CERT_1_HASH[] = "296e3734cdeb171a37897084e1ff5a682ce6e990"; +constexpr char TEST_CA_CERT_SPKI[] = "hKrwfF6o/hsLRqDuwbFxjlpu45B4dgHbc9Ac8DyKFCU="; +constexpr char TEST_CA_CERT_SERIAL[] = "252b689fe0842d51f9f2506ea99615b826f440df"; +constexpr char TEST_CA_CERT_NOT_BEFORE[] = "Apr 8 10:42:53 2024 GMT"; +constexpr char TEST_CA_CERT_NOT_AFTER[] = "Apr 8 10:42:53 2026 GMT"; diff --git a/test/config/integration/certs/cakey.pem b/test/config/integration/certs/cakey.pem index b204c0cdc2902..bf133bef1aa9b 100644 --- a/test/config/integration/certs/cakey.pem +++ b/test/config/integration/certs/cakey.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/client2_chain.pem b/test/config/integration/certs/client2_chain.pem index 8ff72541a58b2..81c6c28100030 100644 --- a/test/config/integration/certs/client2_chain.pem +++ b/test/config/integration/certs/client2_chain.pem @@ -1,98 +1,98 @@ -----BEGIN CERTIFICATE----- -MIIEgTCCA2mgAwIBAgIUey0cXhM8zYlPeGMD2uvRTIIj55gwDQYJKoZIhvcNAQEL +MIIEgTCCA2mgAwIBAgIUByRChCb97f8uyPWy6jRX+nEZ/y4wDQYJKoZIhvcNAQEL BQAwgYUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH DA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVu -Z2luZWVyaW5nMR8wHQYDVQQDDBZUZXN0IEludGVybWVkaWF0ZSBDQSAyMB4XDTIy -MDgxMDA1MzIxM1oXDTI0MDgwOTA1MzIxM1owgaoxCzAJBgNVBAYTAlVTMRMwEQYD +Z2luZWVyaW5nMR8wHQYDVQQDDBZUZXN0IEludGVybWVkaWF0ZSBDQSAyMB4XDTI0 +MDQwODEwNDI1M1oXDTI2MDQwODEwNDI1M1owgaoxCzAJBgNVBAYTAlVTMRMwEQYD VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQK DARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVuZ2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0 IEZyb250ZW5kIFRlYW0gMjElMCMGCSqGSIb3DQEJARYWZnJvbnRlbmQtdGVhbUBs -eWZ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANFyOAesZNT0 -t14namQJHdu6Nw2RK6YeSYTTNG9S1cK6O4krd3qp9Onq/1/79XMGo7sgGG8SANnp -g07I2CSu70f0Kv7gUeg1eVJg21GIADed6LY2vhDvRHb4GGQ173ERZTGMqXJPA8he -6rG88tMk4VM6zjHqLyHC6sXoCYjh96K2ZVR5co4dxeeNrp5P+zj/Cu92X+pQF3Xz -KlOtxrhBQfPOqbmjM5ArUXWzJfi6x5PV5PHhihsOqycAqgu0TqemISDmIyhlGtOB -holf8/TBsLD42prGVomLYe4IHRdZg7mY6UCDJ0tjJa9X93y3RmY0zfqIOLoG0HqO -ocMqlsJts7UCAwEAAaOBwTCBvjAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DAd +eWZ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMF33D8RgRPU +XLD+17brWe6pX3Kq7rIAD4lnpy/NqxUqcSdnMSPgtm9B7dTq97weOy6aLPoSpEJ4 +KsAoPsJTYXXdLkh6pkBzVu5ywzlH9AFrm3f3IC0cq80QviSZtNi32JSlQzr2e3YW +3vy9SylWmaJYbCetvsSt1ltUxCHpqKxpbAXwbMIe77nzDkIB/AlLJd4lWArkjB3Z +UmCOcbVSfzcEXAiy0pBQAi4Jd8oezBBjSj3WaSqCJeMcw4EtlJWN2Fo2wTmls6gE +8YJ+3ijj/0GYuBz9sW8iuPo1sXs2p1A0OMwGmUKGtMzb/A32oaW8iZ+65YijL3w2 +888MAP442zsCAwEAAaOBwTCBvjAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DAd BgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwQgYDVR0RBDswOYYfc3BpZmZl Oi8vbHlmdC5jb20vZnJvbnRlbmQtdGVhbYIIbHlmdC5jb22CDHd3dy5seWZ0LmNv -bTAdBgNVHQ4EFgQUR5hJ+QnnPifNgmWWD9qx34I7/34wHwYDVR0jBBgwFoAUrI68 -Zd9dZkAAnYUfreaEe8yZVOgwDQYJKoZIhvcNAQELBQADggEBADAY6U3N6cBJF3PE -By92hpCWCpWBH46+93hrzHihJ5j0+NKKOfuCzpOzm06/3tv1bSgS3dkl3K/22R8T -E4cc2dRrSo4U/zWcCJJ2GB+BgEJBf9GyRc309EkV5yR/M89ZHxHcvJ+0xjo1C8e7 -94EQcoi19S2DZxK+ksdZCjx494GihtMDmLS7LuqIMQLbSgxUbGD9kO9EHpzFAfx/ -oaoVeC907ZNeaRkoJBbAJyWZWkFHReKkHZZ1KN9Cw34efTQEeFlcepVmHrBOSmk8 -Wctsc8tK5d2utnKmAsVWrloPFas159s1nfZNT1MhBnB0wQF9TTkvUzgJcgldcm/8 -QDJOZHk= +bTAdBgNVHQ4EFgQU+AKZHGHAWa6i1805V1qukMFRFSkwHwYDVR0jBBgwFoAURBe1 +0i/EdPXydr5FKFmURcbIJKcwDQYJKoZIhvcNAQELBQADggEBAH8UqQVPx2uzsamT +NZoR7jBJhu6u0jcIStSrI94IWEZRcA4OLi7ek1ueagFMKaPUerwYwUZO087FXcq1 +MIqMoNsedEXY1PW0RlLiHuOEIwyjIi2ZO1Gew0zcx+g/LmqLzjnp/j40R8MACso0 +R5TeIDdnegV6iG8/5qVAwsHnNBAJkClMNqphLQghO8xLeN7ZbIssDDJdqhva7WZL +gNOAzJ0UsohtM+/OYn3iKtc0RNfZHY6vx5csstSmUr44v94MQtTWV1oEJoUrMFG5 +UdjwHFoEIWHfZX6xA5FVmuSRxjAGtyeI+izbXfeE64ip1UlYwuyKTPVMuSz6tuu7 +eglYKjc= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIUBxMfbayC92pCbOlOL7oIgKfvkMUwDQYJKoZIhvcNAQEL +MIID/jCCAuagAwIBAgIUOa+6oqSVm0oN+c6P2ho4+G90MVAwDQYJKoZIhvcNAQEL BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH DA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVu -Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yMjA4 -MTAwNTMyMTNaFw0yNDA4MDkwNTMyMTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE +Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yNDA0 +MDgxMDQyNTNaFw0yNjA0MDgxMDQyNTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwE THlmdDEZMBcGA1UECwwQTHlmdCBFbmdpbmVlcmluZzEfMB0GA1UEAwwWVGVzdCBJ bnRlcm1lZGlhdGUgQ0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AKQ8j159/Q3m4CmgHfWONzgGbXa4AGK/T/3VKW4jGkumHE3uqvD0/JDviR3WljC3 -wVOrUuvNLc+8jAx3Kn4+d5bsjpTAqNOGUZ7km4fQiYDM/MgakVIWA6J7FFEX8dxF -JluDpWovNTGNZjPp5m+6SXOE+/awzpCBZvutDf7nmXu153BccALaB1uNy16/KdLr -cdLCoUJb9XvIb/g+kZlEA+sNYupIyEqOvn0NmMYEzGe9Ai4eUjQroCuB7o6dhuGS -BIKmddbz0I8hLvevb3hwmGUDZfhT1idMwNl3RrdsvJz29AA8ZGymPbYY0LOsbcIv -myE87cE06c72XKpiPD89qlUCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFKyOvGXfXWZAAJ2FH63mhHvMmVToMB8G -A1UdIwQYMBaAFDrbZAitYeYzFz7HjYVVFtaVmFQ2MA0GCSqGSIb3DQEBCwUAA4IB -AQC0s5vtw829/FSadL7D8PyYnxvLVFmkVXp+6PbvN7swKdbM5xPOYifjlhNrO+XQ -TK4vwHRdat8AuvzVlWcoZGa5ICYdAuob2967wlR9d4VS7lPlxUOPs9/toDWLKurX -2gYSucTJ1eR52pH8HWrnqTROZvXUqGNS3/bjiW2XDLWItUp0w605RXH3Po48m6/1 -JQ1g3bcios5bWlczH6yu5yQIKFwm6DRFmHBC+U55oAxKIrfu1/m4Omzdtjuku/MJ -UdwnBJHAu1hWwDJlld0yd+9Hp6fNdBeuGvo+qXZycJt6Gd7m0S0Ud5xDF0EeB5xt -tJjohk16NAouNKE5o6RHyNwh +AM5wicE+h4ywPbnFh1pGrO481ATe/6cxsg4JdqJqz9hFQ9S7qRpHWsmChRAgZK44 +Abeac05k0d2tbJxpWhYVZQkqERR6/m9FirkoLUF4yM8fspmLI3apoJtd/hr1p5su +R5MS9B3QcTMK6Dkd4wb8nmUQJoy7DiokODib6QVCoX0aih3eyKenVFELMvvHn2ap +xBKAYezZJJ2fRyMGEJPns51bfC5f19DRTZ5IQej+x2Lo3+Uihx+nZkJify5IeiOI +fUPzMMYInJqqxDgQztDmsMhrUZq5zJ31a2s7uTofDGETlUdJI598SmBQ1T5gYpbv +BvRuvB6+moDcrTjl8z15UJMCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEQXtdIvxHT18na+RShZlEXGyCSnMB8G +A1UdIwQYMBaAFOUbznHqq/YQTRDeZqs/373E3uU0MA0GCSqGSIb3DQEBCwUAA4IB +AQA135uojq+aMVIw2mRT75b8Hob0jEhuy/QY4wvY6oMKUP0CUAcUgJG0Y79RY14c +n9/rf2+ffOZErTAYB9KY9uLsPtYMQCfN/uBXKZHOaydfsgoJpnI0UgqGaAN0vQWg +iNyPCnhiYky5q434CEfyzxY6Ey1w4PEtIkvdNOR8FlynMIlQ73T3o8exJUTfuPvO +Fnajcf7er+jsxrKz6c/vAZVLMdwZi1DLTAP3XO0E9uOgBerok4vlTe40+int1+SH +RQiBz1y51JqxbjPoruEDJ9knhjJYblhr/9NLAgRFyRc64MTnrdSCT9wKxlhEeEp4 +RPcq7wHBOXpV4viXPsKrmPQj -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIID7jCCAtagAwIBAgIUAIJQvRnP5hHj7QTAFNZV2aFISmMwDQYJKoZIhvcNAQEL +MIID7jCCAtagAwIBAgIUQRkh3sY/JN5+tu5NX3Tbyx0Y8l4wDQYJKoZIhvcNAQEL BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n -aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjIwODEwMDUzMjEyWhcNMjQw -ODA5MDUzMjEyWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNDA4MTA0MjUzWhcNMjYw +NDA4MTA0MjUzWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsM EEx5ZnQgRW5naW5lZXJpbmcxHTAbBgNVBAMMFFRlc3QgSW50ZXJtZWRpYXRlIENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBSQC0OT++P5tOZMbJhd -DQ+5OCnhPd7PjnS12VBAiFjNFAhRvvNQ9tDp9Mu/p9kiOB/kh/3JLD05/bJPScm5 -qOS354XlEH3Wdhvsr5bH15xjtBj0k0u6iN0EhQPbdEvevxBSZFHdMr1QHwJwNF8G -S/9fE4NyZRAf6eezplH9z73eLk3tAa5FdOOMEUP3M8dwht1A4CO2RkG2f+y6u8Kn -VPadoX1wtJcixOycE64Svel47KpzRfsZDw4rXS/7EB0rLWde93ZAhEXDiDy7jA6u -rGgct262pHpJoZ77ZQ8fRk+LXk5Ry10+iY6NDJCYsUpCMRojCuTfniNKCGRVksQQ -twIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd -BgNVHQ4EFgQUOttkCK1h5jMXPseNhVUW1pWYVDYwHwYDVR0jBBgwFoAUHQ05mHTs -+06sDKaDpYtw5o+/MoIwDQYJKoZIhvcNAQELBQADggEBAGbcaAjYu0tudykPwNEN -AN3ygImUP6m2V+qS5wak1I5/dC2ZaMV9TzDv2B+WpTguznOZ6FMu/IKX009ZLnnw -o9weMSSh92MV2znJctC/FX7bBJ41mf07FdMt8uFOXX/maWZns/3BXtaUFgiW+8tl -n9WSXfI1DL7wHHT8uTMK9U+WPcV+ZiCRaWSbSgRJAiLuVc01BDQEijMhj+l22GST -J5OV+JlKB+Eol4vBIAbLR07yHseRMWRj2fJed9N/ZvYSj6jQ/xBGe2BUixjlfcR/ -ToQG7eebuzf1rqP9FFOutRnjYuzkghZ4vDjr5A+O11Gp4yYc7Wr12R6ToVvDDDs0 -JGM= +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2K8Udj7/LtZDAd1u/m92 +BgrJG2UQD9D/4IAKq7HJNYK517bhBON4vNCBPCLnUXqAzTrJP0QPfBG+6mg2mKcP +df9ng5p9oZRYL+E7/AeOnVphizlImpdllrSJX8Ms9eToRfy/15L8ayldAbhZ1ALD +DxznsKszTiHRXgCMYY590HXMhwB6Y8g0XnloiMoUJLoKxN4bf6vvr7NBiHRAllmZ +Avk6Kph0W4FRuZW5pJmXTJIH1pEkc64eqeSKZhxzLRFmLoMzpUrUgvbKbAHvgicj +iDTw6jpijCtaSUjRoBZnglm38MLrD0KZ4svbvxHaNO+6Ppn1DYOuEvLAi3qL4dHv +6QIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU5RvOceqr9hBNEN5mqz/fvcTe5TQwHwYDVR0jBBgwFoAUGf5zRCDq +psPJ8ZFM2TWPO4q5I4AwDQYJKoZIhvcNAQELBQADggEBAEwskvStLy4jT9IIcd8R +xtsigfNW8BnklqK4gizxN+xlWKT1r1VyK06SJP76Fe/sk4alMiUXpxN7wG1JZ9EM +OaQrtpU6PMQ2AFJVTUfvoA2UN/9UwkXZHh/LhQ5AqGVOM/6ZRUmVzyjNKo7HkD6A +fSLpHgS3WxBOogfyowGdT5Ok3P6sTpHZuPWe36cCq/YlgeWqH3eEhcdvfqeO8H7F +qwiQqtDEvnQyaMqbz6iEr0suq7c9bsAqcbWI9KzrHP/EqGNpBMly10OHTXbk7bI9 +6A56AiZC2YVWM8PoMLYPGWZbSQ2+2BAMh7SUGMoXmBWxHfbpWFv7TpExgQjmIkRD +6TM= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/test/config/integration/certs/client2cert.pem b/test/config/integration/certs/client2cert.pem index 48f4810f62841..c6c9a04609b4a 100644 --- a/test/config/integration/certs/client2cert.pem +++ b/test/config/integration/certs/client2cert.pem @@ -1,27 +1,27 @@ -----BEGIN CERTIFICATE----- -MIIEgTCCA2mgAwIBAgIUey0cXhM8zYlPeGMD2uvRTIIj55gwDQYJKoZIhvcNAQEL +MIIEgTCCA2mgAwIBAgIUByRChCb97f8uyPWy6jRX+nEZ/y4wDQYJKoZIhvcNAQEL BQAwgYUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH DA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVu -Z2luZWVyaW5nMR8wHQYDVQQDDBZUZXN0IEludGVybWVkaWF0ZSBDQSAyMB4XDTIy -MDgxMDA1MzIxM1oXDTI0MDgwOTA1MzIxM1owgaoxCzAJBgNVBAYTAlVTMRMwEQYD +Z2luZWVyaW5nMR8wHQYDVQQDDBZUZXN0IEludGVybWVkaWF0ZSBDQSAyMB4XDTI0 +MDQwODEwNDI1M1oXDTI2MDQwODEwNDI1M1owgaoxCzAJBgNVBAYTAlVTMRMwEQYD VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQK DARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVuZ2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0 IEZyb250ZW5kIFRlYW0gMjElMCMGCSqGSIb3DQEJARYWZnJvbnRlbmQtdGVhbUBs -eWZ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANFyOAesZNT0 -t14namQJHdu6Nw2RK6YeSYTTNG9S1cK6O4krd3qp9Onq/1/79XMGo7sgGG8SANnp -g07I2CSu70f0Kv7gUeg1eVJg21GIADed6LY2vhDvRHb4GGQ173ERZTGMqXJPA8he -6rG88tMk4VM6zjHqLyHC6sXoCYjh96K2ZVR5co4dxeeNrp5P+zj/Cu92X+pQF3Xz -KlOtxrhBQfPOqbmjM5ArUXWzJfi6x5PV5PHhihsOqycAqgu0TqemISDmIyhlGtOB -holf8/TBsLD42prGVomLYe4IHRdZg7mY6UCDJ0tjJa9X93y3RmY0zfqIOLoG0HqO -ocMqlsJts7UCAwEAAaOBwTCBvjAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DAd +eWZ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMF33D8RgRPU +XLD+17brWe6pX3Kq7rIAD4lnpy/NqxUqcSdnMSPgtm9B7dTq97weOy6aLPoSpEJ4 +KsAoPsJTYXXdLkh6pkBzVu5ywzlH9AFrm3f3IC0cq80QviSZtNi32JSlQzr2e3YW +3vy9SylWmaJYbCetvsSt1ltUxCHpqKxpbAXwbMIe77nzDkIB/AlLJd4lWArkjB3Z +UmCOcbVSfzcEXAiy0pBQAi4Jd8oezBBjSj3WaSqCJeMcw4EtlJWN2Fo2wTmls6gE +8YJ+3ijj/0GYuBz9sW8iuPo1sXs2p1A0OMwGmUKGtMzb/A32oaW8iZ+65YijL3w2 +888MAP442zsCAwEAAaOBwTCBvjAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DAd BgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwQgYDVR0RBDswOYYfc3BpZmZl Oi8vbHlmdC5jb20vZnJvbnRlbmQtdGVhbYIIbHlmdC5jb22CDHd3dy5seWZ0LmNv -bTAdBgNVHQ4EFgQUR5hJ+QnnPifNgmWWD9qx34I7/34wHwYDVR0jBBgwFoAUrI68 -Zd9dZkAAnYUfreaEe8yZVOgwDQYJKoZIhvcNAQELBQADggEBADAY6U3N6cBJF3PE -By92hpCWCpWBH46+93hrzHihJ5j0+NKKOfuCzpOzm06/3tv1bSgS3dkl3K/22R8T -E4cc2dRrSo4U/zWcCJJ2GB+BgEJBf9GyRc309EkV5yR/M89ZHxHcvJ+0xjo1C8e7 -94EQcoi19S2DZxK+ksdZCjx494GihtMDmLS7LuqIMQLbSgxUbGD9kO9EHpzFAfx/ -oaoVeC907ZNeaRkoJBbAJyWZWkFHReKkHZZ1KN9Cw34efTQEeFlcepVmHrBOSmk8 -Wctsc8tK5d2utnKmAsVWrloPFas159s1nfZNT1MhBnB0wQF9TTkvUzgJcgldcm/8 -QDJOZHk= +bTAdBgNVHQ4EFgQU+AKZHGHAWa6i1805V1qukMFRFSkwHwYDVR0jBBgwFoAURBe1 +0i/EdPXydr5FKFmURcbIJKcwDQYJKoZIhvcNAQELBQADggEBAH8UqQVPx2uzsamT +NZoR7jBJhu6u0jcIStSrI94IWEZRcA4OLi7ek1ueagFMKaPUerwYwUZO087FXcq1 +MIqMoNsedEXY1PW0RlLiHuOEIwyjIi2ZO1Gew0zcx+g/LmqLzjnp/j40R8MACso0 +R5TeIDdnegV6iG8/5qVAwsHnNBAJkClMNqphLQghO8xLeN7ZbIssDDJdqhva7WZL +gNOAzJ0UsohtM+/OYn3iKtc0RNfZHY6vx5csstSmUr44v94MQtTWV1oEJoUrMFG5 +UdjwHFoEIWHfZX6xA5FVmuSRxjAGtyeI+izbXfeE64ip1UlYwuyKTPVMuSz6tuu7 +eglYKjc= -----END CERTIFICATE----- diff --git a/test/config/integration/certs/client2cert_hash.h b/test/config/integration/certs/client2cert_hash.h index 3c7c736298510..70cbe50eaa01c 100644 --- a/test/config/integration/certs/client2cert_hash.h +++ b/test/config/integration/certs/client2cert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_CLIENT2_CERT_HASH[] = "FB:43:B3:07:22:BB:B6:79:C5:64:E0:7D:CA:22:3E:01:86:3F:" - "99:D2:0C:8B:D2:96:67:E3:6E:B1:D8:2B:0B:26"; +constexpr char TEST_CLIENT2_CERT_HASH[] = "29:01:4E:E3:B0:05:7D:03:A1:04:48:67:93:13:72:23:AD:2B:" + "F2:20:0B:E3:92:9A:93:BB:72:C2:BB:AF:8C:B9"; diff --git a/test/config/integration/certs/client2key.pem b/test/config/integration/certs/client2key.pem index 5746fa0c0975b..a81d8f6f0ac2a 100644 --- a/test/config/integration/certs/client2key.pem +++ b/test/config/integration/certs/client2key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/client_ecdsacert.pem b/test/config/integration/certs/client_ecdsacert.pem index 75f14a3972a8f..fa6a8333334b4 100644 --- a/test/config/integration/certs/client_ecdsacert.pem +++ b/test/config/integration/certs/client_ecdsacert.pem @@ -1,23 +1,23 @@ -----BEGIN CERTIFICATE----- -MIIDvjCCAqagAwIBAgIUT9Wze0Fvw/pMvqAmPJjlD7HNjZEwDQYJKoZIhvcNAQEL +MIID1jCCAr6gAwIBAgIUQRkh3sY/JN5+tu5NX3Tbyx0Y8mMwDQYJKoZIhvcNAQEL BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n -aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjIwNDA3MTY0NjM1WhcNMjQw -NDA2MTY0NjM1WjCBqDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNDA4MTA0MjUzWhcNMjYw +NDA4MTA0MjUzWjCBqDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsM EEx5ZnQgRW5naW5lZXJpbmcxGzAZBgNVBAMMElRlc3QgRnJvbnRlbmQgVGVhbTEl MCMGCSqGSIb3DQEJARYWZnJvbnRlbmQtdGVhbUBseWZ0LmNvbTBZMBMGByqGSM49 -AgEGCCqGSM49AwEHA0IABL6CuUXn9awlELiqXIsllF7TbpC9q8FIvP0ldxhe26by -/VNcfFtrnDJLmLiLrdUPL17vRpvHAywN4piffCnGHZajgdswgdgwDAYDVR0TAQH/ +AgEGCCqGSM49AwEHA0IABOvelrlHVmDCSeVr902umIoLnaeXtrosgkLCHXyUFfcc +jo9VbCGy4W48H/s1IBG2T5b/Divm8Pzz3Av63xqbrLSjgfMwgfAwDAYDVR0TAQH/ BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB -MFwGA1UdEQRVMFOGH3NwaWZmZTovL2x5ZnQuY29tL2Zyb250ZW5kLXRlYW2GGGh0 -dHA6Ly9mcm9udGVuZC5seWZ0LmNvbYIIbHlmdC5jb22CDHd3dy5seWZ0LmNvbTAd -BgNVHQ4EFgQUYC7EHuPp4iyNqKbzS/5BoIWR18UwHwYDVR0jBBgwFoAUHQ05mHTs -+06sDKaDpYtw5o+/MoIwDQYJKoZIhvcNAQELBQADggEBAJK3wbWO5fNtRDgl5LQW -r6y4f86ltQ5kwZ9zYwC/aMUsM5nqbG91rfmzM3gMFCi+GM0lhRY4Cl5ZUdcwaO+A -MrCIeDLr7corCaXXj+DFT72XgeNbuNmrZCFJMzi6aoA+1XUeh16JBcGFJSANr5Es -0XPQvVb2bYij17HmLaOJJ4+2nHKaVvLC2DcaG8OwO/0YIKoEfmg9phAAtXenWwTF -BO3eJ3ZtciVKrmF4VPza5NLuWWkGi0FH0iVVEeQaF1N4QDZ3rvhGtq6MNgHnq16s -SSqksCee6MoxmGeLK96REn8uaVvVu5/70qi4nHj8bDjdMm/6JEwf2qzgcIBNGTlM -D2c= +MHQGA1UdEQRtMGuGH3NwaWZmZTovL2x5ZnQuY29tL2Zyb250ZW5kLXRlYW2GGGh0 +dHA6Ly9mcm9udGVuZC5seWZ0LmNvbYIIbHlmdC5jb22CDHd3dy5seWZ0LmNvbYcE +AQIDBIcQAAAAAQACAAMAAAAAAAAABDAdBgNVHQ4EFgQURjDmKNs9AT3Hgw3XkyYU +rKKZeC8wHwYDVR0jBBgwFoAUGf5zRCDqpsPJ8ZFM2TWPO4q5I4AwDQYJKoZIhvcN +AQELBQADggEBAAH62cuawD5wTX3kvK4xvvUPVYfsImmGV028+qYD48pMlmptr6/L +2HeiaIKq9qmijez+Qm5gko2FDtuMrRPL8Kf25KVxl2tV6bsIb/GkIlLyjqoUCpZa +qBo9vqSEqu5DImZUGAnPhinq8UbCYMt9+Y1QtpW8XjLz/pihtK0So0UOI5uOhNPM +MsPPJQO/G3G+cY0pYXUD6cDogCiULrsKTvtNQyXGnK4KlUG1ZTSHAoQKkIkh1cPv +QgyUZXb5/jbYpT9m29t7Bodqi04OUDZ0g/tUwaQ2/2/cwG7NUEcKqlFWit5G7c33 +/Iv0mQoMqRYePHCRt2EiOj+FfO2gFRc18YQ= -----END CERTIFICATE----- diff --git a/test/config/integration/certs/client_ecdsacert_hash.h b/test/config/integration/certs/client_ecdsacert_hash.h index 33f3ffda48e2b..7fabca236c72f 100644 --- a/test/config/integration/certs/client_ecdsacert_hash.h +++ b/test/config/integration/certs/client_ecdsacert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_CLIENT_ECDSA_CERT_HASH[] = "99:AD:96:C7:72:15:C6:70:F8:D2:A5:5F:72:82:36:C6:9B:" - "E9:49:AA:8E:ED:90:A0:8D:F4:65:E3:3C:CF:6F:8C"; +constexpr char TEST_CLIENT_ECDSA_CERT_HASH[] = "D3:61:4E:7E:2F:35:CF:64:C1:39:C2:9E:27:9B:D4:09:E4:" + "4C:A3:F5:4C:EA:F3:BB:28:FF:58:1D:CD:7B:4B:27"; diff --git a/test/config/integration/certs/client_ecdsakey.pem b/test/config/integration/certs/client_ecdsakey.pem index e648c287d1749..0bef7823995dd 100644 --- a/test/config/integration/certs/client_ecdsakey.pem +++ b/test/config/integration/certs/client_ecdsakey.pem @@ -2,7 +2,7 @@ BggqhkjOPQMBBw== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- -MHcCAQEEINWmBoJfwX+mkEjkMMm6BkLH73TtuMwptVDbsWycoKYRoAoGCCqGSM49 -AwEHoUQDQgAEvoK5Ref1rCUQuKpciyWUXtNukL2rwUi8/SV3GF7bpvL9U1x8W2uc -MkuYuIut1Q8vXu9Gm8cDLA3imJ98KcYdlg== +MHcCAQEEIF+j6aMMQt4Dj8sebcJJAhLXhFpex3TRAjmBtltLpWf+oAoGCCqGSM49 +AwEHoUQDQgAE696WuUdWYMJJ5Wv3Ta6Yigudp5e2uiyCQsIdfJQV9xyOj1VsIbLh +bjwf+zUgEbZPlv8OK+bw/PPcC/rfGpustA== -----END EC PRIVATE KEY----- diff --git a/test/config/integration/certs/clientcert.pem b/test/config/integration/certs/clientcert.pem index a476c9b8736f4..f61d52ded9ace 100644 --- a/test/config/integration/certs/clientcert.pem +++ b/test/config/integration/certs/clientcert.pem @@ -1,27 +1,27 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/test/config/integration/certs/clientcert_hash.h b/test/config/integration/certs/clientcert_hash.h index 44aeb4df168ad..ac619d9ed6bde 100644 --- a/test/config/integration/certs/clientcert_hash.h +++ b/test/config/integration/certs/clientcert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_CLIENT_CERT_HASH[] = "F6:31:41:AA:8E:E3:D7:AC:AE:A8:AF:AD:C9:11:CD:0A:83:72:03:" - "6D:4B:B3:72:4F:6F:71:E1:ED:18:5B:92:AA"; +constexpr char TEST_CLIENT_CERT_HASH[] = "73:46:B3:83:6C:FC:41:38:53:51:19:1B:5E:61:63:F1:A6:97:04:" + "CF:DF:0A:03:63:4E:D2:01:91:28:E6:FD:C4"; diff --git a/test/config/integration/certs/clientkey.pem b/test/config/integration/certs/clientkey.pem index 1d4391b72ce4d..0ec6248e930fb 100644 --- a/test/config/integration/certs/clientkey.pem +++ b/test/config/integration/certs/clientkey.pem @@ -1,28 +1,27 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9K5Xk1JJlLO4P -zwPbJLlmaGukaCvzX8MHwlp4ioIl/skWXAcvf/+h5Tg94y3sw+rafrJodVMyLt6+ -vf69Hg4wPj7MUCgxR+vSye+Amhbcfa42qWN1TcLhN4zXJXCsw54zVR67zQ/mACJC -4UW18gaoN5+09QtsLVSLOczsu/Yrve71bl++OCA7CLllFj6RSJn4HRC3gAckavCD -d9945Y0396Tdxn4w3/WiizOo0xkYrTVT2Npjnui6ZFJ7R7ONsHlAQmdgd/XPKJV1 -nhKAiS9+RDbifyZcaWpj/jpSv3koaK7BRPlB/LWETHjZG/GRHRR6IWEBjvFieVQc -OHeO5aEbAgMBAAECggEARVEny2KDRFSq5RsPyCjUUOy5aNSNKlBwSDMU8K+cUizi -5XESZvrpopq6OZ850FTYBXlAiZtYQX7AOzemlQji3RWp8Db9C1XV2XcKbl7IOsJI -6Jm4Kp80Zk9zKdD70SqbGSc7LEjPZxGsfEJMx4donhJH0MisB1cy8BNdfm+/nDYK -NsezfOYAD4UkX1NcrdfwLsWimZHPifwxL+va5cV3FiWO3S861/aE0pLhh+AJFYGI -3lEZxr6Gh+uaARcV4YNZPogYbrc8wJWP/6uR8pDwjqS8aUTBfyo1wUDd6bTvlQDv -+nKBiVjmWPgY6TlZ/Okp+H28fO3zqoXExE6KJamxmQKBgQDzV5jIemNDUrxodc1i -AQIchTbchvGiSpLyS2PY1W0vyYpyc5mugvzgaHPVEtaQTtR+QHWrYEFksOBZeIqX -rQGTDk6jGWBzI7qa/itqr9jydZsMYgJ7eqGpiSsiD0ka65xO+Ho6FkZAV11+qPyE -QmPD3Izj/58pSod4PADFQSP18wKBgQDHAp9Sd9fGCn/RHRVyf1nao2ZeKCQUDyyj -g+uCIswhE8lT7C5K76FPxZrV6enpTkLjEnMdRrPl4fQ5xl0SPAR/gaZXhM1U4sjo -w0dWITeMHwR7HqwbpumNbMccZMGA7o1Ua/k7GPIyD2UE7hiJyJRNyfeN09/cC18p -EjHcSs4qOQKBgCwU0jh+8zxe4IKL1IjMZfWErEuGpn8fwz7hKVU+VGkzuUDCcDSM -xgJg6ZrPrs61eQjl5GsHJNF4uSt8Cp8vV/mrvdMN5cr1zfgF0xegg0xowY2cs5Zq -wJ5Vmtqwqi2WQNqNaJbdMhy1ttobAqNy41+3tE4ZIFv6hE/jjsAs7LbBAoGAXy64 -5uec0wKYiXqglGemoTS/tE78mn97eSWSUWa1PSjKhRIUPhEIlS/M030SPF0LDrH3 -TsxPJKcCeVOPljYQbK+k0H0a+/uP3gvwJZiziZgYO467AGq/j720Kbdi+XifLf6K -cKKIzDqitU3vfI7rp5zugu4QRp1FwU4LfPJmUrkCgYAzzBz3K7jg4JPdY7Od22SQ -F/eToChPu60B9uQciR6gGwR1ry2sVSlK0Y1DMFMBCFEqPfJFO4OwZP0NWbRd13X1 -9nUvpVoGxxJ51fIdMrPPm0G6f8HS79JhOGUp9tYsQ+LPCcnrrH2ZDGQYxZsSXqbe -5qc2rk4Sgt8Ua/oVrxghzA== ------END PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAp8SeFs+41uQ+57odZBLpMkrjybL8kSH7e9sdWOJowp70RHFd +bX3F8VccEJoHh1GtVPGsqVLdeK9CRNyTU/32zZiQK8NhSez0HiiZSeOP+Z1UvQ4+ +B27mZ8CaAoHkGoPamxK3XXsXMhP0CddDzSlKlAeYOsLllIRiSpsUA5TlBOnsADk0 +kwHtkpuCN6iF6EvAb3Yby2TgBGAHUz/rXmxV3Pe+Vs6xrhUsMpJRuCcZE2JChUXN +JQdCFvpyX7+EO1barmcEaIdbfN5gh0IzvXxdQ+U6c6Pjlj7YTR1dUze3EtcnSG5b +KWK2IK0rSMGnLuRIjtRtxrDUuFa5ivA7pLxt0QIDAQABAoIBAAsWQEhLo/B3lvyj +XQvfABA1yMXskSpaL49o1JCqLbgm3wei4kS5Ghqv4gfq1fKIYEcLzZhHBKHUGzIH +zaiwUN4XZfeuasCC60MO9cpfQoWBczcnOMwu9QUIQmVGBjgGXK0A2KNR3h+OGAOK +eU2Wm/oaYmIHvTpKYWCCzRjLfzMK0wm0EE5uiXVKoFchN5goD6cUgKO9RqG7t36t +5Lx5tNRUV4Y5tilTCkuZY6NgiwiNLgqEVpAUWkFMLbuTLuCcSLTdv/Vsyc8QPYzk +0xkLx+rpQOyOOxOxTLX9km6xCuyTf/+Mi3sBu2YhO+cNgA4hfQdrFJ1exJpS/rpF +9NoGkAECgYEA1VdmKrA7A10a1I0zvDojjpoMW2R2UgYJD53L0RucEeMCzF5iTqZU +BAGdcoLPeIqpeK8yu5e0EuqeF1+ftaAbH6qbeRDDDyGSnEt7zA9NphPn6atMGZ7i +yG5WV1dXOHodKNJPk3wnh/XNax2joacmD2KBYLCgSEfDNnFRPoGSxtECgYEAyVBk +RKt14h8JTOtTHLS9CrH/bnaKo5FSlIA7ke81JWpA2XdBvx6eIMPNIwUZTVzKmq90 +PRcr6pGWoolhNrWG9Qz9NLz51kQfVu74eHmVNlk8v72xtw5Upkjrhw/T3jKuBSNG +2p40eiFhFCUfnHTqEpSlF5S2Soct9dB2jRF29wECgYBbyt0UpPrfOaIfrhmCdWlz +I+kJMbKPHDWt2HEnqb9/GrPLhxi5Smt/xcoeyRXHgbHlUUx81osOUJBw8h3foeHJ +wDhg4MtsqT9RrFC5viPkv0/bhG6b33coUgEoV1YZ4MDHHaZLiPHhV+tomTochbSk +jfeoyJZOQ0JFHICealXi4QKBgAqHrfb5kPz26qGfXYWO3qSEkvvQL4hGva8O1g7Y +GEuQtzuoxUOd8l98XmqKhbxKMj7NFqNrZtyqKNrvnKoj7J88wrHTgK34ejst3MlQ +csGZAVh2L6mHkl3wSoGSDl78/6U9JyOgStZokI9hMboZU3UNWWVWXgKf94N4F/th +AnwBAoGBAJ3Xl2QyOFyHw9bKlDEeC6H4kW3DPRdOGJVyUApSfQY/EMLwpAok5Jqx +ACfynlYnITligL9nq2ltUz6cqVsNlGU8uRDg+GMOwHXrA074tNWIVS69B/ClsGry +T9i8WELtyma5ZKtuxQPWp8zqbtY3MMzOBPzJPSC4BmDmuYmRJouf +-----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/expired_cert.pem b/test/config/integration/certs/expired_cert.pem index 204588830aa9c..b7bb0c233ef6c 100644 --- a/test/config/integration/certs/expired_cert.pem +++ b/test/config/integration/certs/expired_cert.pem @@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/test/config/integration/certs/expired_cert_hash.h b/test/config/integration/certs/expired_cert_hash.h index dce7913703e54..06e5cf2d28c1b 100644 --- a/test/config/integration/certs/expired_cert_hash.h +++ b/test/config/integration/certs/expired_cert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_EXPIRED__CERT_HASH[] = "FC:F7:07:14:C3:0D:B4:BE:0B:BF:23:9B:C2:09:DA:CD:54:66:" - "32:65:07:50:35:E8:D0:14:ED:D6:B1:96:A1:3C"; +constexpr char TEST_EXPIRED__CERT_HASH[] = "B9:15:AC:D8:29:92:ED:B0:C4:D6:70:88:0F:04:F0:AD:63:4C:" + "DA:13:AA:37:B3:6B:35:CB:1E:84:2B:0D:32:D5"; diff --git a/test/config/integration/certs/expired_key.pem b/test/config/integration/certs/expired_key.pem index fdaa467c6c4ce..856e2c9720a18 100644 --- a/test/config/integration/certs/expired_key.pem +++ b/test/config/integration/certs/expired_key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/intermediate_ca_2cert.pem b/test/config/integration/certs/intermediate_ca_2cert.pem index 974bf506ddd3f..7359166a31788 100644 --- a/test/config/integration/certs/intermediate_ca_2cert.pem +++ b/test/config/integration/certs/intermediate_ca_2cert.pem @@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIUBxMfbayC92pCbOlOL7oIgKfvkMUwDQYJKoZIhvcNAQEL +MIID/jCCAuagAwIBAgIUOa+6oqSVm0oN+c6P2ho4+G90MVAwDQYJKoZIhvcNAQEL BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH DA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVu -Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yMjA4 -MTAwNTMyMTNaFw0yNDA4MDkwNTMyMTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE +Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yNDA0 +MDgxMDQyNTNaFw0yNjA0MDgxMDQyNTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwE THlmdDEZMBcGA1UECwwQTHlmdCBFbmdpbmVlcmluZzEfMB0GA1UEAwwWVGVzdCBJ bnRlcm1lZGlhdGUgQ0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AKQ8j159/Q3m4CmgHfWONzgGbXa4AGK/T/3VKW4jGkumHE3uqvD0/JDviR3WljC3 -wVOrUuvNLc+8jAx3Kn4+d5bsjpTAqNOGUZ7km4fQiYDM/MgakVIWA6J7FFEX8dxF -JluDpWovNTGNZjPp5m+6SXOE+/awzpCBZvutDf7nmXu153BccALaB1uNy16/KdLr -cdLCoUJb9XvIb/g+kZlEA+sNYupIyEqOvn0NmMYEzGe9Ai4eUjQroCuB7o6dhuGS -BIKmddbz0I8hLvevb3hwmGUDZfhT1idMwNl3RrdsvJz29AA8ZGymPbYY0LOsbcIv -myE87cE06c72XKpiPD89qlUCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFKyOvGXfXWZAAJ2FH63mhHvMmVToMB8G -A1UdIwQYMBaAFDrbZAitYeYzFz7HjYVVFtaVmFQ2MA0GCSqGSIb3DQEBCwUAA4IB -AQC0s5vtw829/FSadL7D8PyYnxvLVFmkVXp+6PbvN7swKdbM5xPOYifjlhNrO+XQ -TK4vwHRdat8AuvzVlWcoZGa5ICYdAuob2967wlR9d4VS7lPlxUOPs9/toDWLKurX -2gYSucTJ1eR52pH8HWrnqTROZvXUqGNS3/bjiW2XDLWItUp0w605RXH3Po48m6/1 -JQ1g3bcios5bWlczH6yu5yQIKFwm6DRFmHBC+U55oAxKIrfu1/m4Omzdtjuku/MJ -UdwnBJHAu1hWwDJlld0yd+9Hp6fNdBeuGvo+qXZycJt6Gd7m0S0Ud5xDF0EeB5xt -tJjohk16NAouNKE5o6RHyNwh +AM5wicE+h4ywPbnFh1pGrO481ATe/6cxsg4JdqJqz9hFQ9S7qRpHWsmChRAgZK44 +Abeac05k0d2tbJxpWhYVZQkqERR6/m9FirkoLUF4yM8fspmLI3apoJtd/hr1p5su +R5MS9B3QcTMK6Dkd4wb8nmUQJoy7DiokODib6QVCoX0aih3eyKenVFELMvvHn2ap +xBKAYezZJJ2fRyMGEJPns51bfC5f19DRTZ5IQej+x2Lo3+Uihx+nZkJify5IeiOI +fUPzMMYInJqqxDgQztDmsMhrUZq5zJ31a2s7uTofDGETlUdJI598SmBQ1T5gYpbv +BvRuvB6+moDcrTjl8z15UJMCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEQXtdIvxHT18na+RShZlEXGyCSnMB8G +A1UdIwQYMBaAFOUbznHqq/YQTRDeZqs/373E3uU0MA0GCSqGSIb3DQEBCwUAA4IB +AQA135uojq+aMVIw2mRT75b8Hob0jEhuy/QY4wvY6oMKUP0CUAcUgJG0Y79RY14c +n9/rf2+ffOZErTAYB9KY9uLsPtYMQCfN/uBXKZHOaydfsgoJpnI0UgqGaAN0vQWg +iNyPCnhiYky5q434CEfyzxY6Ey1w4PEtIkvdNOR8FlynMIlQ73T3o8exJUTfuPvO +Fnajcf7er+jsxrKz6c/vAZVLMdwZi1DLTAP3XO0E9uOgBerok4vlTe40+int1+SH +RQiBz1y51JqxbjPoruEDJ9knhjJYblhr/9NLAgRFyRc64MTnrdSCT9wKxlhEeEp4 +RPcq7wHBOXpV4viXPsKrmPQj -----END CERTIFICATE----- diff --git a/test/config/integration/certs/intermediate_ca_2cert_info.h b/test/config/integration/certs/intermediate_ca_2cert_info.h index 0b0279ff1c76b..488866fcf26c6 100644 --- a/test/config/integration/certs/intermediate_ca_2cert_info.h +++ b/test/config/integration/certs/intermediate_ca_2cert_info.h @@ -1,6 +1,8 @@ // NOLINT(namespace-envoy) constexpr char TEST_INTERMEDIATE_CA_2_CERT_256_HASH[] = - "92ddb0721eb7e97d3da5657f476709992fb6e76019c4909de66c889f79117963"; -constexpr char TEST_INTERMEDIATE_CA_2_CERT_1_HASH[] = "48c8dc6d8468f9a898a29557b7d8cad744886917"; -constexpr char TEST_INTERMEDIATE_CA_2_CERT_SPKI[] = "oGIt8PwV+L4J6YrZv6OvkzYiNAL109XEl1ZlU4VURVo="; -constexpr char TEST_INTERMEDIATE_CA_2_CERT_SERIAL[] = "07131f6dac82f76a426ce94e2fba0880a7ef90c5"; + "9ec107589465a379758a617b6dfee94d2b726a885bebc6db8e34d03d07aee5ec"; +constexpr char TEST_INTERMEDIATE_CA_2_CERT_1_HASH[] = "043c42adf1c71b64f61291704390e6da665c0054"; +constexpr char TEST_INTERMEDIATE_CA_2_CERT_SPKI[] = "mQWSar6E4Byme4h9nJk+8FIgTF5ZGCS1GeQla3++fm0="; +constexpr char TEST_INTERMEDIATE_CA_2_CERT_SERIAL[] = "39afbaa2a4959b4a0df9ce8fda1a38f86f743150"; +constexpr char TEST_INTERMEDIATE_CA_2_CERT_NOT_BEFORE[] = "Apr 8 10:42:53 2024 GMT"; +constexpr char TEST_INTERMEDIATE_CA_2_CERT_NOT_AFTER[] = "Apr 8 10:42:53 2026 GMT"; diff --git a/test/config/integration/certs/intermediate_ca_2key.pem b/test/config/integration/certs/intermediate_ca_2key.pem index 97abba505a806..54ee4538d46ca 100644 --- a/test/config/integration/certs/intermediate_ca_2key.pem +++ b/test/config/integration/certs/intermediate_ca_2key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/intermediate_ca_cert_chain.pem b/test/config/integration/certs/intermediate_ca_cert_chain.pem index 31abf9f5ce124..f8dbed605ed6b 100644 --- a/test/config/integration/certs/intermediate_ca_cert_chain.pem +++ b/test/config/integration/certs/intermediate_ca_cert_chain.pem @@ -1,71 +1,71 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIID7jCCAtagAwIBAgIUAIJQvRnP5hHj7QTAFNZV2aFISmMwDQYJKoZIhvcNAQEL +MIID7jCCAtagAwIBAgIUQRkh3sY/JN5+tu5NX3Tbyx0Y8l4wDQYJKoZIhvcNAQEL BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n -aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjIwODEwMDUzMjEyWhcNMjQw -ODA5MDUzMjEyWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNDA4MTA0MjUzWhcNMjYw +NDA4MTA0MjUzWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsM EEx5ZnQgRW5naW5lZXJpbmcxHTAbBgNVBAMMFFRlc3QgSW50ZXJtZWRpYXRlIENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBSQC0OT++P5tOZMbJhd -DQ+5OCnhPd7PjnS12VBAiFjNFAhRvvNQ9tDp9Mu/p9kiOB/kh/3JLD05/bJPScm5 -qOS354XlEH3Wdhvsr5bH15xjtBj0k0u6iN0EhQPbdEvevxBSZFHdMr1QHwJwNF8G -S/9fE4NyZRAf6eezplH9z73eLk3tAa5FdOOMEUP3M8dwht1A4CO2RkG2f+y6u8Kn -VPadoX1wtJcixOycE64Svel47KpzRfsZDw4rXS/7EB0rLWde93ZAhEXDiDy7jA6u -rGgct262pHpJoZ77ZQ8fRk+LXk5Ry10+iY6NDJCYsUpCMRojCuTfniNKCGRVksQQ -twIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd -BgNVHQ4EFgQUOttkCK1h5jMXPseNhVUW1pWYVDYwHwYDVR0jBBgwFoAUHQ05mHTs -+06sDKaDpYtw5o+/MoIwDQYJKoZIhvcNAQELBQADggEBAGbcaAjYu0tudykPwNEN -AN3ygImUP6m2V+qS5wak1I5/dC2ZaMV9TzDv2B+WpTguznOZ6FMu/IKX009ZLnnw -o9weMSSh92MV2znJctC/FX7bBJ41mf07FdMt8uFOXX/maWZns/3BXtaUFgiW+8tl -n9WSXfI1DL7wHHT8uTMK9U+WPcV+ZiCRaWSbSgRJAiLuVc01BDQEijMhj+l22GST -J5OV+JlKB+Eol4vBIAbLR07yHseRMWRj2fJed9N/ZvYSj6jQ/xBGe2BUixjlfcR/ -ToQG7eebuzf1rqP9FFOutRnjYuzkghZ4vDjr5A+O11Gp4yYc7Wr12R6ToVvDDDs0 -JGM= +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2K8Udj7/LtZDAd1u/m92 +BgrJG2UQD9D/4IAKq7HJNYK517bhBON4vNCBPCLnUXqAzTrJP0QPfBG+6mg2mKcP +df9ng5p9oZRYL+E7/AeOnVphizlImpdllrSJX8Ms9eToRfy/15L8ayldAbhZ1ALD +DxznsKszTiHRXgCMYY590HXMhwB6Y8g0XnloiMoUJLoKxN4bf6vvr7NBiHRAllmZ +Avk6Kph0W4FRuZW5pJmXTJIH1pEkc64eqeSKZhxzLRFmLoMzpUrUgvbKbAHvgicj +iDTw6jpijCtaSUjRoBZnglm38MLrD0KZ4svbvxHaNO+6Ppn1DYOuEvLAi3qL4dHv +6QIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU5RvOceqr9hBNEN5mqz/fvcTe5TQwHwYDVR0jBBgwFoAUGf5zRCDq +psPJ8ZFM2TWPO4q5I4AwDQYJKoZIhvcNAQELBQADggEBAEwskvStLy4jT9IIcd8R +xtsigfNW8BnklqK4gizxN+xlWKT1r1VyK06SJP76Fe/sk4alMiUXpxN7wG1JZ9EM +OaQrtpU6PMQ2AFJVTUfvoA2UN/9UwkXZHh/LhQ5AqGVOM/6ZRUmVzyjNKo7HkD6A +fSLpHgS3WxBOogfyowGdT5Ok3P6sTpHZuPWe36cCq/YlgeWqH3eEhcdvfqeO8H7F +qwiQqtDEvnQyaMqbz6iEr0suq7c9bsAqcbWI9KzrHP/EqGNpBMly10OHTXbk7bI9 +6A56AiZC2YVWM8PoMLYPGWZbSQ2+2BAMh7SUGMoXmBWxHfbpWFv7TpExgQjmIkRD +6TM= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIUBxMfbayC92pCbOlOL7oIgKfvkMUwDQYJKoZIhvcNAQEL +MIID/jCCAuagAwIBAgIUOa+6oqSVm0oN+c6P2ho4+G90MVAwDQYJKoZIhvcNAQEL BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH DA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVu -Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yMjA4 -MTAwNTMyMTNaFw0yNDA4MDkwNTMyMTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE +Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yNDA0 +MDgxMDQyNTNaFw0yNjA0MDgxMDQyNTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwE THlmdDEZMBcGA1UECwwQTHlmdCBFbmdpbmVlcmluZzEfMB0GA1UEAwwWVGVzdCBJ bnRlcm1lZGlhdGUgQ0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AKQ8j159/Q3m4CmgHfWONzgGbXa4AGK/T/3VKW4jGkumHE3uqvD0/JDviR3WljC3 -wVOrUuvNLc+8jAx3Kn4+d5bsjpTAqNOGUZ7km4fQiYDM/MgakVIWA6J7FFEX8dxF -JluDpWovNTGNZjPp5m+6SXOE+/awzpCBZvutDf7nmXu153BccALaB1uNy16/KdLr -cdLCoUJb9XvIb/g+kZlEA+sNYupIyEqOvn0NmMYEzGe9Ai4eUjQroCuB7o6dhuGS -BIKmddbz0I8hLvevb3hwmGUDZfhT1idMwNl3RrdsvJz29AA8ZGymPbYY0LOsbcIv -myE87cE06c72XKpiPD89qlUCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFKyOvGXfXWZAAJ2FH63mhHvMmVToMB8G -A1UdIwQYMBaAFDrbZAitYeYzFz7HjYVVFtaVmFQ2MA0GCSqGSIb3DQEBCwUAA4IB -AQC0s5vtw829/FSadL7D8PyYnxvLVFmkVXp+6PbvN7swKdbM5xPOYifjlhNrO+XQ -TK4vwHRdat8AuvzVlWcoZGa5ICYdAuob2967wlR9d4VS7lPlxUOPs9/toDWLKurX -2gYSucTJ1eR52pH8HWrnqTROZvXUqGNS3/bjiW2XDLWItUp0w605RXH3Po48m6/1 -JQ1g3bcios5bWlczH6yu5yQIKFwm6DRFmHBC+U55oAxKIrfu1/m4Omzdtjuku/MJ -UdwnBJHAu1hWwDJlld0yd+9Hp6fNdBeuGvo+qXZycJt6Gd7m0S0Ud5xDF0EeB5xt -tJjohk16NAouNKE5o6RHyNwh +AM5wicE+h4ywPbnFh1pGrO481ATe/6cxsg4JdqJqz9hFQ9S7qRpHWsmChRAgZK44 +Abeac05k0d2tbJxpWhYVZQkqERR6/m9FirkoLUF4yM8fspmLI3apoJtd/hr1p5su +R5MS9B3QcTMK6Dkd4wb8nmUQJoy7DiokODib6QVCoX0aih3eyKenVFELMvvHn2ap +xBKAYezZJJ2fRyMGEJPns51bfC5f19DRTZ5IQej+x2Lo3+Uihx+nZkJify5IeiOI +fUPzMMYInJqqxDgQztDmsMhrUZq5zJ31a2s7uTofDGETlUdJI598SmBQ1T5gYpbv +BvRuvB6+moDcrTjl8z15UJMCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEQXtdIvxHT18na+RShZlEXGyCSnMB8G +A1UdIwQYMBaAFOUbznHqq/YQTRDeZqs/373E3uU0MA0GCSqGSIb3DQEBCwUAA4IB +AQA135uojq+aMVIw2mRT75b8Hob0jEhuy/QY4wvY6oMKUP0CUAcUgJG0Y79RY14c +n9/rf2+ffOZErTAYB9KY9uLsPtYMQCfN/uBXKZHOaydfsgoJpnI0UgqGaAN0vQWg +iNyPCnhiYky5q434CEfyzxY6Ey1w4PEtIkvdNOR8FlynMIlQ73T3o8exJUTfuPvO +Fnajcf7er+jsxrKz6c/vAZVLMdwZi1DLTAP3XO0E9uOgBerok4vlTe40+int1+SH +RQiBz1y51JqxbjPoruEDJ9knhjJYblhr/9NLAgRFyRc64MTnrdSCT9wKxlhEeEp4 +RPcq7wHBOXpV4viXPsKrmPQj -----END CERTIFICATE----- diff --git a/test/config/integration/certs/intermediate_cacert.pem b/test/config/integration/certs/intermediate_cacert.pem index d38a63aa4fe02..f7d15415e1ac6 100644 --- a/test/config/integration/certs/intermediate_cacert.pem +++ b/test/config/integration/certs/intermediate_cacert.pem @@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID7jCCAtagAwIBAgIUAIJQvRnP5hHj7QTAFNZV2aFISmMwDQYJKoZIhvcNAQEL +MIID7jCCAtagAwIBAgIUQRkh3sY/JN5+tu5NX3Tbyx0Y8l4wDQYJKoZIhvcNAQEL BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n -aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjIwODEwMDUzMjEyWhcNMjQw -ODA5MDUzMjEyWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNDA4MTA0MjUzWhcNMjYw +NDA4MTA0MjUzWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsM EEx5ZnQgRW5naW5lZXJpbmcxHTAbBgNVBAMMFFRlc3QgSW50ZXJtZWRpYXRlIENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBSQC0OT++P5tOZMbJhd -DQ+5OCnhPd7PjnS12VBAiFjNFAhRvvNQ9tDp9Mu/p9kiOB/kh/3JLD05/bJPScm5 -qOS354XlEH3Wdhvsr5bH15xjtBj0k0u6iN0EhQPbdEvevxBSZFHdMr1QHwJwNF8G -S/9fE4NyZRAf6eezplH9z73eLk3tAa5FdOOMEUP3M8dwht1A4CO2RkG2f+y6u8Kn -VPadoX1wtJcixOycE64Svel47KpzRfsZDw4rXS/7EB0rLWde93ZAhEXDiDy7jA6u -rGgct262pHpJoZ77ZQ8fRk+LXk5Ry10+iY6NDJCYsUpCMRojCuTfniNKCGRVksQQ -twIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd -BgNVHQ4EFgQUOttkCK1h5jMXPseNhVUW1pWYVDYwHwYDVR0jBBgwFoAUHQ05mHTs -+06sDKaDpYtw5o+/MoIwDQYJKoZIhvcNAQELBQADggEBAGbcaAjYu0tudykPwNEN -AN3ygImUP6m2V+qS5wak1I5/dC2ZaMV9TzDv2B+WpTguznOZ6FMu/IKX009ZLnnw -o9weMSSh92MV2znJctC/FX7bBJ41mf07FdMt8uFOXX/maWZns/3BXtaUFgiW+8tl -n9WSXfI1DL7wHHT8uTMK9U+WPcV+ZiCRaWSbSgRJAiLuVc01BDQEijMhj+l22GST -J5OV+JlKB+Eol4vBIAbLR07yHseRMWRj2fJed9N/ZvYSj6jQ/xBGe2BUixjlfcR/ -ToQG7eebuzf1rqP9FFOutRnjYuzkghZ4vDjr5A+O11Gp4yYc7Wr12R6ToVvDDDs0 -JGM= +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2K8Udj7/LtZDAd1u/m92 +BgrJG2UQD9D/4IAKq7HJNYK517bhBON4vNCBPCLnUXqAzTrJP0QPfBG+6mg2mKcP +df9ng5p9oZRYL+E7/AeOnVphizlImpdllrSJX8Ms9eToRfy/15L8ayldAbhZ1ALD +DxznsKszTiHRXgCMYY590HXMhwB6Y8g0XnloiMoUJLoKxN4bf6vvr7NBiHRAllmZ +Avk6Kph0W4FRuZW5pJmXTJIH1pEkc64eqeSKZhxzLRFmLoMzpUrUgvbKbAHvgicj +iDTw6jpijCtaSUjRoBZnglm38MLrD0KZ4svbvxHaNO+6Ppn1DYOuEvLAi3qL4dHv +6QIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU5RvOceqr9hBNEN5mqz/fvcTe5TQwHwYDVR0jBBgwFoAUGf5zRCDq +psPJ8ZFM2TWPO4q5I4AwDQYJKoZIhvcNAQELBQADggEBAEwskvStLy4jT9IIcd8R +xtsigfNW8BnklqK4gizxN+xlWKT1r1VyK06SJP76Fe/sk4alMiUXpxN7wG1JZ9EM +OaQrtpU6PMQ2AFJVTUfvoA2UN/9UwkXZHh/LhQ5AqGVOM/6ZRUmVzyjNKo7HkD6A +fSLpHgS3WxBOogfyowGdT5Ok3P6sTpHZuPWe36cCq/YlgeWqH3eEhcdvfqeO8H7F +qwiQqtDEvnQyaMqbz6iEr0suq7c9bsAqcbWI9KzrHP/EqGNpBMly10OHTXbk7bI9 +6A56AiZC2YVWM8PoMLYPGWZbSQ2+2BAMh7SUGMoXmBWxHfbpWFv7TpExgQjmIkRD +6TM= -----END CERTIFICATE----- diff --git a/test/config/integration/certs/intermediate_cacert_info.h b/test/config/integration/certs/intermediate_cacert_info.h index 428dc073e739a..9233186d52235 100644 --- a/test/config/integration/certs/intermediate_cacert_info.h +++ b/test/config/integration/certs/intermediate_cacert_info.h @@ -1,6 +1,8 @@ // NOLINT(namespace-envoy) constexpr char TEST_INTERMEDIATE_CA_CERT_256_HASH[] = - "e0e1aba1c94462e7d2436e16bbba1c4135b0bf62d8d8f3ad63d682e1ab86a1bf"; -constexpr char TEST_INTERMEDIATE_CA_CERT_1_HASH[] = "643e191d93925bcbf8ae2c01c9b4bf0db68fd6ed"; -constexpr char TEST_INTERMEDIATE_CA_CERT_SPKI[] = "/Q7A4RyGvDZqf9dkXj4DhEbPM4eYYn9Jr3hqu9FlJHQ="; -constexpr char TEST_INTERMEDIATE_CA_CERT_SERIAL[] = "8250bd19cfe611e3ed04c014d655d9a1484a63"; + "eda5f36c714c5b0a76894ab997937187946120b9512ba53ec5526e0bf4ccf1ce"; +constexpr char TEST_INTERMEDIATE_CA_CERT_1_HASH[] = "b752550318881fd0ee374f139deac4b2f08c7b10"; +constexpr char TEST_INTERMEDIATE_CA_CERT_SPKI[] = "uENfhBrnMYWDUWvI+I3/4a8OElOU0/f4Tucn51CVwn4="; +constexpr char TEST_INTERMEDIATE_CA_CERT_SERIAL[] = "411921dec63f24de7eb6ee4d5f74dbcb1d18f25e"; +constexpr char TEST_INTERMEDIATE_CA_CERT_NOT_BEFORE[] = "Apr 8 10:42:53 2024 GMT"; +constexpr char TEST_INTERMEDIATE_CA_CERT_NOT_AFTER[] = "Apr 8 10:42:53 2026 GMT"; diff --git a/test/config/integration/certs/intermediate_cakey.pem b/test/config/integration/certs/intermediate_cakey.pem index 2e682b312239c..f61f364e4a32c 100644 --- a/test/config/integration/certs/intermediate_cakey.pem +++ b/test/config/integration/certs/intermediate_cakey.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpgIBAAKCAQEAvBSQC0OT++P5tOZMbJhdDQ+5OCnhPd7PjnS12VBAiFjNFAhR -vvNQ9tDp9Mu/p9kiOB/kh/3JLD05/bJPScm5qOS354XlEH3Wdhvsr5bH15xjtBj0 -k0u6iN0EhQPbdEvevxBSZFHdMr1QHwJwNF8GS/9fE4NyZRAf6eezplH9z73eLk3t -Aa5FdOOMEUP3M8dwht1A4CO2RkG2f+y6u8KnVPadoX1wtJcixOycE64Svel47Kpz -RfsZDw4rXS/7EB0rLWde93ZAhEXDiDy7jA6urGgct262pHpJoZ77ZQ8fRk+LXk5R -y10+iY6NDJCYsUpCMRojCuTfniNKCGRVksQQtwIDAQABAoIBAQCLqo1o/+71n86/ -ykE4Z1C6MVHu5hKwmjPxarPbw5+YYLExzhe+P/uAvZTuxxr0ruadXPmdDxYD9xeI -UJBWkCmBxQ7YK/L1cyz+GjCCF+shLq5PgDSm6RaFKfOAP2QJEYpAwgMdIjmrwy+r -R2lBSstnRiU5XWRmrjm0ve4HdV2QlP0HIAf825pceMLjbHFIOyNsSn1M26Q5E10W -pmP8et9xX/ps7wAqRr25uf89D1/58jcNlIiTL9uHNiqhD9Vc4W3/bVUg1es7PQQz -rY2GeF+Phiw8MrqmJEKWpPokt6nKHMY3qh/yAN+kU+qLiOjNM5QzXt9kp2ILjg41 -8JOsUgABAoGBAObfGRL7GyFsaWpWsVuVQFTJe2q0GNtopI5tdWKqil862UiRoOiX -tdXdhjQox+TfFe3ZS8E7X8aXE4yPiHHIsY786zLTOk1C+5EK/RpFveTIY9RCcOhn -PUqk/hcwjOkMYDkcSoRv7hpAGXrzDKH8+AckeApDPY2Rzp8dhx/vNZC3AoGBANCN -JpR9vgrHP94SFeT68VMs14FJJyIw0+jUBk8FNMoMtDp/i2m9HjFimKQxwFY7BCnk -0wD3rvG/kTIKnOcvrpbglz/MVIEw80JK6MxUYHFkmspMe4avU/q8r/Pdz3ddcZmS -6QY8RgDXp4W2SgqbyRQPjAatONoRVoPlovRVZYABAoGBAI7a7UD5b8g2tOO/0O7C -Y5QTN/LGjbr1/RzETiZHMGfMmjZ1JgPGHBXBhRExSr8r6v3Jvz5J5vGS+d230s3V -SUkY5tAEM8cq807EZeijzuSJunvUwNiNSTeu8CqZOBfcOI4eG13nhIVptFqHmgGB -7lc9EaoIAUZgOmHqe3ofIh3xAoGBAMjUBCDebZFnikNJWzgjxxx3hBeF4F92cbo1 -4tVZiBpB3ZSvmgr7CQY5khAbsMHZFwtYxTCbUyaNeT6dJbcjHZdBM6VGGOJHxxlB -laieYDkRzlWdDR8H23ELHs1R/iVOIMToyektRaQuB64lemt41UuyJP+Q87xbEdr1 -2dQ8IAABAoGBANz/5JuCi84CXnuAC770IcSGQz78YePpiYYwD5rcnixCEKnb8U2z -QdBplZRpgikIGVLbFhkdpWaz4V/ISbTVuqi8wBWLNfLYk2Rbc4gK1RMPZGzNTHbL -+Da9tIHJPeiuhcd5LE09tzROmhUbpa+LIfcODdDr+biPs82ZzoRsYT/F +MIIEpQIBAAKCAQEA2K8Udj7/LtZDAd1u/m92BgrJG2UQD9D/4IAKq7HJNYK517bh +BON4vNCBPCLnUXqAzTrJP0QPfBG+6mg2mKcPdf9ng5p9oZRYL+E7/AeOnVphizlI +mpdllrSJX8Ms9eToRfy/15L8ayldAbhZ1ALDDxznsKszTiHRXgCMYY590HXMhwB6 +Y8g0XnloiMoUJLoKxN4bf6vvr7NBiHRAllmZAvk6Kph0W4FRuZW5pJmXTJIH1pEk +c64eqeSKZhxzLRFmLoMzpUrUgvbKbAHvgicjiDTw6jpijCtaSUjRoBZnglm38MLr +D0KZ4svbvxHaNO+6Ppn1DYOuEvLAi3qL4dHv6QIDAQABAoIBAQCB+FJfst3BrEc5 +AScoepbifOmwAuildAlnEaZU4ij8bdMnLsy97+tl5fL+rTjuyHoln9NCnYJkOwi2 +9Zd57Qr7Dh169NnSZ3aWEZW3UJkcjS/hIJUKFTGcOeIEjL+VJp6kDDzOA55gcMkW +1QscfiOeFwpqD+aQQPyiU9XgVmXk+QkPxfyqeFF/GHWkOLEW+zvIB0gnp2noiWbZ +WPkCahJiq0eOSUMaO9dHCB/hb8Ri+mOZsH66q03BuDSM2wle1KtpNI7rYyk3Esk4 +8xEA3bIwH2cN5lPsk0M3x+cgf79MwFBzjvFkP6TM8zRN93f1SEdFrRU4zqtcsLi4 +D64cvURhAoGBAOud2USkb/ZoZoozvCBJMufWwyrJnI7/QVWJoxec+JAg+2oYpkOv +r3OS7sA9cX3r6kSfT3uDj2gnx/BU4s+2Rmui8zGsSmSIPlCRSps0E+X1oAOundLR +sXWMXnVJMfNnMiDqdb7tCMxzEoDXQc0FVoaYXF2EAu1gS0D7xgB+xgt1AoGBAOtt +7tLBxh5QqR3ItiG03lg+sevOy/b+i6L2DXZEPOLR4dI/gERrfBp9Fn+jW+ZAOAUl +cCfAbGOqJlsFXMO9c5TgF8avSBG34MJnTXcOJT8tAUV0GoYzlbqEp5PIaOccV6f7 +y2NqajICVGsxF0wGbQgOW4eifCdmz6GTotfCyCglAoGBAKqdrjQ/ovfa183h8quq +ddteMAuu3NjLzCLFvT0fJPsxdv14BgXjlXhqgMric2mMySKR/jthpBn3HJhSz787 +rbjJ0bM5oNE1u3IaE91Zk8Mk349rBcwgnpGRaOEjdLFeG/Wr8kGkYYezGEYGGhb9 +c4+ZiuEI9BmybiwLZXD8XCr1AoGBAL5Ao4vbnd/nrRLtnQmWIUxMtflqr3dVcsBp +eK2HLXDazVPbjys7dRnu1hylheGHKf42p/Dn1m+gE8jgh3uDAQFhKrWb9nCjGNXh +Jz+7xavNEcbvsXOcAjatdsK9ZNDHHasZlt4rI9BwA4pwLHR6iOfGun7cdtpcghP/ +u8wCkz+lAoGAZJ0OKwo7Ehta+q4i4gyHQNxx/Kw52KEwfyAyufRu9cMwBHwpv+f+ +2s6jJ544haG2mB3VlOooxtAPx3p70KEUGoibJ7F1RdxTuDootHO+JAWOV1XNetz/ +M91+Jj/YM22Nv29FrU/3c4mat04vH4ceaSt5W2AZLszSLxRtNRIaVcw= -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/intermediate_partial_ca_cert_chain.pem b/test/config/integration/certs/intermediate_partial_ca_cert_chain.pem index ee761ab0d0ef8..3bd60c6fcc6e8 100644 --- a/test/config/integration/certs/intermediate_partial_ca_cert_chain.pem +++ b/test/config/integration/certs/intermediate_partial_ca_cert_chain.pem @@ -1,48 +1,48 @@ -----BEGIN CERTIFICATE----- -MIID7jCCAtagAwIBAgIUAIJQvRnP5hHj7QTAFNZV2aFISmMwDQYJKoZIhvcNAQEL +MIID7jCCAtagAwIBAgIUQRkh3sY/JN5+tu5NX3Tbyx0Y8l4wDQYJKoZIhvcNAQEL BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n -aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjIwODEwMDUzMjEyWhcNMjQw -ODA5MDUzMjEyWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNDA4MTA0MjUzWhcNMjYw +NDA4MTA0MjUzWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsM EEx5ZnQgRW5naW5lZXJpbmcxHTAbBgNVBAMMFFRlc3QgSW50ZXJtZWRpYXRlIENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBSQC0OT++P5tOZMbJhd -DQ+5OCnhPd7PjnS12VBAiFjNFAhRvvNQ9tDp9Mu/p9kiOB/kh/3JLD05/bJPScm5 -qOS354XlEH3Wdhvsr5bH15xjtBj0k0u6iN0EhQPbdEvevxBSZFHdMr1QHwJwNF8G -S/9fE4NyZRAf6eezplH9z73eLk3tAa5FdOOMEUP3M8dwht1A4CO2RkG2f+y6u8Kn -VPadoX1wtJcixOycE64Svel47KpzRfsZDw4rXS/7EB0rLWde93ZAhEXDiDy7jA6u -rGgct262pHpJoZ77ZQ8fRk+LXk5Ry10+iY6NDJCYsUpCMRojCuTfniNKCGRVksQQ -twIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd -BgNVHQ4EFgQUOttkCK1h5jMXPseNhVUW1pWYVDYwHwYDVR0jBBgwFoAUHQ05mHTs -+06sDKaDpYtw5o+/MoIwDQYJKoZIhvcNAQELBQADggEBAGbcaAjYu0tudykPwNEN -AN3ygImUP6m2V+qS5wak1I5/dC2ZaMV9TzDv2B+WpTguznOZ6FMu/IKX009ZLnnw -o9weMSSh92MV2znJctC/FX7bBJ41mf07FdMt8uFOXX/maWZns/3BXtaUFgiW+8tl -n9WSXfI1DL7wHHT8uTMK9U+WPcV+ZiCRaWSbSgRJAiLuVc01BDQEijMhj+l22GST -J5OV+JlKB+Eol4vBIAbLR07yHseRMWRj2fJed9N/ZvYSj6jQ/xBGe2BUixjlfcR/ -ToQG7eebuzf1rqP9FFOutRnjYuzkghZ4vDjr5A+O11Gp4yYc7Wr12R6ToVvDDDs0 -JGM= +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2K8Udj7/LtZDAd1u/m92 +BgrJG2UQD9D/4IAKq7HJNYK517bhBON4vNCBPCLnUXqAzTrJP0QPfBG+6mg2mKcP +df9ng5p9oZRYL+E7/AeOnVphizlImpdllrSJX8Ms9eToRfy/15L8ayldAbhZ1ALD +DxznsKszTiHRXgCMYY590HXMhwB6Y8g0XnloiMoUJLoKxN4bf6vvr7NBiHRAllmZ +Avk6Kph0W4FRuZW5pJmXTJIH1pEkc64eqeSKZhxzLRFmLoMzpUrUgvbKbAHvgicj +iDTw6jpijCtaSUjRoBZnglm38MLrD0KZ4svbvxHaNO+6Ppn1DYOuEvLAi3qL4dHv +6QIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU5RvOceqr9hBNEN5mqz/fvcTe5TQwHwYDVR0jBBgwFoAUGf5zRCDq +psPJ8ZFM2TWPO4q5I4AwDQYJKoZIhvcNAQELBQADggEBAEwskvStLy4jT9IIcd8R +xtsigfNW8BnklqK4gizxN+xlWKT1r1VyK06SJP76Fe/sk4alMiUXpxN7wG1JZ9EM +OaQrtpU6PMQ2AFJVTUfvoA2UN/9UwkXZHh/LhQ5AqGVOM/6ZRUmVzyjNKo7HkD6A +fSLpHgS3WxBOogfyowGdT5Ok3P6sTpHZuPWe36cCq/YlgeWqH3eEhcdvfqeO8H7F +qwiQqtDEvnQyaMqbz6iEr0suq7c9bsAqcbWI9KzrHP/EqGNpBMly10OHTXbk7bI9 +6A56AiZC2YVWM8PoMLYPGWZbSQ2+2BAMh7SUGMoXmBWxHfbpWFv7TpExgQjmIkRD +6TM= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIUBxMfbayC92pCbOlOL7oIgKfvkMUwDQYJKoZIhvcNAQEL +MIID/jCCAuagAwIBAgIUOa+6oqSVm0oN+c6P2ho4+G90MVAwDQYJKoZIhvcNAQEL BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH DA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARMeWZ0MRkwFwYDVQQLDBBMeWZ0IEVu -Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yMjA4 -MTAwNTMyMTNaFw0yNDA4MDkwNTMyMTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE +Z2luZWVyaW5nMR0wGwYDVQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yNDA0 +MDgxMDQyNTNaFw0yNjA0MDgxMDQyNTNaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UE CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwE THlmdDEZMBcGA1UECwwQTHlmdCBFbmdpbmVlcmluZzEfMB0GA1UEAwwWVGVzdCBJ bnRlcm1lZGlhdGUgQ0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AKQ8j159/Q3m4CmgHfWONzgGbXa4AGK/T/3VKW4jGkumHE3uqvD0/JDviR3WljC3 -wVOrUuvNLc+8jAx3Kn4+d5bsjpTAqNOGUZ7km4fQiYDM/MgakVIWA6J7FFEX8dxF -JluDpWovNTGNZjPp5m+6SXOE+/awzpCBZvutDf7nmXu153BccALaB1uNy16/KdLr -cdLCoUJb9XvIb/g+kZlEA+sNYupIyEqOvn0NmMYEzGe9Ai4eUjQroCuB7o6dhuGS -BIKmddbz0I8hLvevb3hwmGUDZfhT1idMwNl3RrdsvJz29AA8ZGymPbYY0LOsbcIv -myE87cE06c72XKpiPD89qlUCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFKyOvGXfXWZAAJ2FH63mhHvMmVToMB8G -A1UdIwQYMBaAFDrbZAitYeYzFz7HjYVVFtaVmFQ2MA0GCSqGSIb3DQEBCwUAA4IB -AQC0s5vtw829/FSadL7D8PyYnxvLVFmkVXp+6PbvN7swKdbM5xPOYifjlhNrO+XQ -TK4vwHRdat8AuvzVlWcoZGa5ICYdAuob2967wlR9d4VS7lPlxUOPs9/toDWLKurX -2gYSucTJ1eR52pH8HWrnqTROZvXUqGNS3/bjiW2XDLWItUp0w605RXH3Po48m6/1 -JQ1g3bcios5bWlczH6yu5yQIKFwm6DRFmHBC+U55oAxKIrfu1/m4Omzdtjuku/MJ -UdwnBJHAu1hWwDJlld0yd+9Hp6fNdBeuGvo+qXZycJt6Gd7m0S0Ud5xDF0EeB5xt -tJjohk16NAouNKE5o6RHyNwh +AM5wicE+h4ywPbnFh1pGrO481ATe/6cxsg4JdqJqz9hFQ9S7qRpHWsmChRAgZK44 +Abeac05k0d2tbJxpWhYVZQkqERR6/m9FirkoLUF4yM8fspmLI3apoJtd/hr1p5su +R5MS9B3QcTMK6Dkd4wb8nmUQJoy7DiokODib6QVCoX0aih3eyKenVFELMvvHn2ap +xBKAYezZJJ2fRyMGEJPns51bfC5f19DRTZ5IQej+x2Lo3+Uihx+nZkJify5IeiOI +fUPzMMYInJqqxDgQztDmsMhrUZq5zJ31a2s7uTofDGETlUdJI598SmBQ1T5gYpbv +BvRuvB6+moDcrTjl8z15UJMCAwEAAaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEQXtdIvxHT18na+RShZlEXGyCSnMB8G +A1UdIwQYMBaAFOUbznHqq/YQTRDeZqs/373E3uU0MA0GCSqGSIb3DQEBCwUAA4IB +AQA135uojq+aMVIw2mRT75b8Hob0jEhuy/QY4wvY6oMKUP0CUAcUgJG0Y79RY14c +n9/rf2+ffOZErTAYB9KY9uLsPtYMQCfN/uBXKZHOaydfsgoJpnI0UgqGaAN0vQWg +iNyPCnhiYky5q434CEfyzxY6Ey1w4PEtIkvdNOR8FlynMIlQ73T3o8exJUTfuPvO +Fnajcf7er+jsxrKz6c/vAZVLMdwZi1DLTAP3XO0E9uOgBerok4vlTe40+int1+SH +RQiBz1y51JqxbjPoruEDJ9knhjJYblhr/9NLAgRFyRc64MTnrdSCT9wKxlhEeEp4 +RPcq7wHBOXpV4viXPsKrmPQj -----END CERTIFICATE----- diff --git a/test/config/integration/certs/server2cert.pem b/test/config/integration/certs/server2cert.pem index a156253acd8be..57eb658fd4615 100644 --- a/test/config/integration/certs/server2cert.pem +++ b/test/config/integration/certs/server2cert.pem @@ -1,27 +1,27 @@ -----BEGIN CERTIFICATE----- -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 -nam4zqTUbaBaasA= +ZnQyLmNvbTAdBgNVHQ4EFgQUyHqzQslIW7opjTTorEMu9Wjr8CkwHwYDVR0jBBgw +FoAUGf5zRCDqpsPJ8ZFM2TWPO4q5I4AwDQYJKoZIhvcNAQELBQADggEBABkkK9WA +Um75hZwDpcE1vKzybrNmSpQSm6w+rAJLUVK3mOTGXQfXbiYW75qNm8pWHS6niGe4 +CQxH+D6jmSrZUEt77Rs8mRSouNjOe3eb9Ka1NIaTj5m6BjzYk7piMK67hQH1e5nm +2M5B4uVQiVfPgCSrT1QaVbkekCZgR9BJn6aW/s9YFVZ/TqccuKNEgGuAbjht3pAM +7/2lZNEVnD1Hw8U1MedBEBaQdLAMkTtduCrCIRRci3Nw3ZoXlCYSuWnF7ZtVSYT7 +1G/MIXluE5TNMajFthrhkJ9zTvxB5XS1U8F6HG2YTuL0Vy50+MlZ+tfW0i3HOttN +yIY0gIU90b1qFy4= -----END CERTIFICATE----- diff --git a/test/config/integration/certs/server2cert_hash.h b/test/config/integration/certs/server2cert_hash.h index b7a424b2dd126..8e20c2ac8019f 100644 --- a/test/config/integration/certs/server2cert_hash.h +++ b/test/config/integration/certs/server2cert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_SERVER2_CERT_HASH[] = "0E:A1:1B:78:09:0B:D1:D6:F4:4A:8E:D2:48:7F:C3:B8:06:A7:" - "4C:8C:E4:7A:60:3D:3F:15:B0:99:03:11:2B:20"; +constexpr char TEST_SERVER2_CERT_HASH[] = "49:C0:A5:D2:51:C5:AD:3E:DF:C1:44:DC:55:68:F7:D1:5E:BC:" + "2E:19:A8:12:D5:F4:2C:C0:DB:2C:89:A7:3E:04"; diff --git a/test/config/integration/certs/server2cert_info.h b/test/config/integration/certs/server2cert_info.h index 8863a409cfadd..30622b7063f6d 100644 --- a/test/config/integration/certs/server2cert_info.h +++ b/test/config/integration/certs/server2cert_info.h @@ -1,8 +1,8 @@ // NOLINT(namespace-envoy) constexpr char TEST_SERVER2_CERT_256_HASH[] = - "0ea11b78090bd1d6f44a8ed2487fc3b806a74c8ce47a603d3f15b09903112b20"; -constexpr char TEST_SERVER2_CERT_1_HASH[] = "78912aeddf93afb00f2348dc13d1edabc2e71b2c"; -constexpr char TEST_SERVER2_CERT_SPKI[] = "J/kyBd/otG9+t94S1SbU3jj4lMyjNLvUQHZK2T/8Lbs="; -constexpr char TEST_SERVER2_CERT_SERIAL[] = "3f4a6fa7a8b8f1ad60783e78cfb314692399888e"; -constexpr char TEST_SERVER2_CERT_NOT_BEFORE[] = "Jan 12 03:35:18 2023 GMT"; -constexpr char TEST_SERVER2_CERT_NOT_AFTER[] = "Jan 11 03:35:18 2025 GMT"; + "49c0a5d251c5ad3edfc144dc5568f7d15ebc2e19a812d5f42cc0db2c89a73e04"; +constexpr char TEST_SERVER2_CERT_1_HASH[] = "23b26f75260c817c8bf7b57e58900003afdcc5c7"; +constexpr char TEST_SERVER2_CERT_SPKI[] = "SiHddsIo3p8NBQ8gaofOxHGpClhVo0QRRZI6Gsbt04k="; +constexpr char TEST_SERVER2_CERT_SERIAL[] = "411921dec63f24de7eb6ee4d5f74dbcb1d18f260"; +constexpr char TEST_SERVER2_CERT_NOT_BEFORE[] = "Apr 8 10:42:53 2024 GMT"; +constexpr char TEST_SERVER2_CERT_NOT_AFTER[] = "Apr 8 10:42:53 2026 GMT"; diff --git a/test/config/integration/certs/server2key.pem b/test/config/integration/certs/server2key.pem index 049cabe543512..ac6c2be2b87f0 100644 --- a/test/config/integration/certs/server2key.pem +++ b/test/config/integration/certs/server2key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/server_ecdsa_ocsp_resp.der b/test/config/integration/certs/server_ecdsa_ocsp_resp.der index 45cf00d3cc6af7471c66ab3cf46618b6b17c881a..1fa146ca56520c3a533f36e16e0c4e80610d43ac 100644 GIT binary patch delta 1096 zcmX@ZbB1TaQf?Ci69WrF0}~@tLvdbyC}R`cKGDSi9RnfnDmO5RI7%ws zJ7%wPuWs8r-}sW-r)4ERB{nfg@EaKzA?c4=AP5#RLl_ygP}xAyK%R|Ln~jl`mEC}m ziA9J-;7jAROVcE0YozuDY{)w1s%^l_#;Mij(e|CUo{^E8m4Ug5k&!`D+#tnITt`9u z+~zfroz1gvrq)jHitGRPe}kN4gF@gNC-M7BQeKJZwZ%&vnccZ~>Pq45TkNksJF3Rk zoYt}1B9nWUNBr`i9*0%Zol=doVlK|oeE8&Dy4Ul{^6d-O=(a&jjIdz3Z1pd^^6lt7);-2r4yEd;hI>JfsefR_0tx_M3 zJYFJ|YEr?gfB9I*#4n{~T`^Vy)*SVJd8RJfFPVDi$lKL*Uh`{vxwdUPzxDOKNn*## z&77N*(}fnu#jIkK_19gotT}qA`IG}+`6C~`RDbY&lhl?4oY&tmn}@YCXk~17{h{S) zwq-#R^HYN+=7&Ih*Pw~%=mKUYMkXc^Rqc%V4_b5se|`$cTRBa1huRm1`;+^adZdw~ zLY&tKIT|L39PQuwO*(ML=Cg%$^Z|j$AE)LfOuhXyim6Kc$6sZ4@9q_cJwLvDn5jYZo#g#ok-O)1WEocrml{nd%7~m97JTP^^|eoPCvAwh zuRsmU(~nd(_Cr;A#iy8Qk9{NZRhD0hsO{4!Ej*T0&Dx`Q-D}F04H3fLH$A@u7-jEU<9IdY z>u=L*0sZftl$W_K+pp2z_Id}y<(G5kG6#LxyT*Oqgw~ZC-&*aFcx$vSfByd`-&>yZ UzdZ3^dGFF7_8CgCY5}I+0D0=@QUCw| delta 1096 zcmX@ZbB1TaQf?yy69aQYGZQmo(}^3j>ScK?XOz78?YD+!S@Y8Ff@l5vjhdK5{I71V zcFaHg%V*yLHJcev`8S^J?Qdd`;5RZbLed|#KoBfs0yNSLVr0}pWdlV6c{WaMHbz!f zb^}Hx79kdaoa%;fkxWk=6LYt#&35*F2E1&XT5TR}-+Aj98M#>*n41_G84hj|4`1Ie zCMcqNMDH^;+sW1+UMIKnfb!Qzh8XUX0`P7b1 zM|-)xo=@gcR~PbDoT(2H`}j}S>qE}OMKI zoZ$^l-LJ2Hv+tJgeP10Beoo8fexs|i1N*wvbrXz4nw-p*x8y(9x;^Lm3Um7fsn;fV z9GEfjk>{rcP0UXXnwTE~@m+%^rlSj(nHZUvL?n4+=GXk6Firj0^0Z4Ob@!QzJQyeU zG4)6zqQWFfoYx2u4JJ{O=Q34JUdOCZe^$l#)zFb?{}*^l(u4->QvKR@3(E@dXZ*+qB=0{;0v`y2alfT z+FGQ$qwM>|O8K*!R{yjWUFi08S(aSwG%1p|d+J>Di;XolwN5G5S zb(V7tm*0IWv?liSsq*X@VZLt;^TqJYet0_}K*YfL3n!C)^yG6ardX3XFqtxk$||!+ z7>G59V9N)HWQs_oTN7@w_vjdYEDUpDuV|U8th46zjF5s^TaJ21o_T$G)vB|8_93SI zhpz8^aaosZ?(YRr(vQ{T7S&I7dVOY9^IWI)Ke07ZoR$Ki#}q#|s&OdqXw+RhtJSRW zk^Y<8wOKP)>HL}R#(8#gEHVT{ON?Ja! zZkX9_Uz2>bl`njjg4JV>ZSN=eT3z8hQ@r#&bD|&XqH`xCazEaiwW>kfWr{LvdbyC}R`cKGDSi9RnfnDmO5RI7%ws zJ7%wPuWs8r-}sW-r)4ER#WyiX@EaKzA?c4=AP5#RLl_ygP}xAyK%R|Ln~jl`mEC}m ziA9J-Ky1sZ6~QmLqk1$=0*tjz&NSd<ThB}&wVt;GcvTvN3xlQof%)*>`2g;T< zw`#6fy!@;Gap5oZoA&6WH=Yj5e|A#Dk1ygOfBB*7cJ?AG{|oOB(wi_LHSS&dqlSQ7 z=GNm64Ck`Sx=fSotGe0cX~q0%b+y!aQKgd9)Bc%x!j`K`n2x@8kd5+X38;G;aB9YD z+i2U33pQ`A-JWc{*_zf&vNPJQI^EpLPw{ppL#!{)wkfO-!x~B zC>^6mvBleM&;FObWjC!vYwLWLuA4f^k=p9#s&8<6DPsDIug-YKEKd{^CU|0QKz+d@8@Zo@7Z9h=V<*3kz9B7dBkn=tkE(G59V9N)H zWQs_o^Z4%DmGAR@%EYkc*I}jx(RY&fb4Bi++mU5lEnI3ep(rDAW?1l@`_ ztu6k5UfT~<=@p-1raktJ$X8i@DWbMdr?l``RyAvn;&rbnTQ)=pd*Af@5@3|QZ;j*C zl&`-{uLbnKcT!&Fx@^Bjf7|OF43}Tdoy#2bW$zmIc@tV!ZhULCN8+u~y8QY7pL}n5 W&j0engXO(TgV<*%#i|9EdIJDx7whu? delta 1096 zcmX@ZbB1TaQf?yy69aQYGZQmo(}^3j>ScK?XOz78?YD+!S@Y8Ff@l5vjhdK5{I71V zcFaHg%V*yLHJcev`8S^J?Q3F?;5RZbLed|#KoBfs0yNSLVr0}pWdlV6c{WaMHbz!f zb^}Hx79kb^58s}Tc2AGh*(g`fj4R!nV!+GBsnzDu_MNw$k&&B~fw_s1kzrPlhD*xb zq<7+1m1Umi)Yl!{$;=+wsJ187UZy>*e0`3YaMCB8q1O4O z#~(H2ewk(C`AFy4!g?Ez)V!wmHho)57BlSL=HGsHqSKoaQF|?il=6*Qzl>rtWS%9S zDplRK?cwgGRsVKOxVCI<jhb>kZL=`X09$*ZIxLUi0|z&u5WOxtq6iM5P@wDSuZpCE8$}d9mHQ@U=Z@ zSKI#23R%#^{M4X{`5_SBHE3cwx`3I9k%>t}l1FBK&HoA0)SoR+yHrwlpSj3`adID1 zk2E4GOrpejjS$gb5;b`)Q|07!%nJ2qRg7QWj_8-*&{*^Pq5Bv0hLtX-4_V1JZ0}B3 z>!{oKWA2XK)7AIfa6R2$_pT;Kvo{B3P3Av5ryza)Yo*7} zYiwt?uTPY|=W*+J@d?kM`L!FR&gb$cdEd9dO zbbarO%eq{1e=mrVeyk?9sD853>ocpG=Q_3jiLH_1v=j(Eruey0jYD}yqwd;St!9mn z^xxdB&6>GN=g)jM&a;~%=Qz)>a=C2nd|s}t@625P9j@QI&zX;-&AI6a82hojW0s`|;kaRSn`UQyh}Lf8GcU z@Kz45p7Nr}t^7o>=AnwZjBPvh3mz9K$F959^Uf!%$B$j+=Tn{L9NE}CY8C2od2C)S a_hWy~V{ZQ6QIoPq|MiXHuCHnhfr0=AD(rj! diff --git a/test/config/integration/certs/servercert.pem b/test/config/integration/certs/servercert.pem index 5316da1a47f27..134d1fe5cb6bb 100644 --- a/test/config/integration/certs/servercert.pem +++ b/test/config/integration/certs/servercert.pem @@ -1,27 +1,27 @@ -----BEGIN CERTIFICATE----- -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 -xYYobPcy1znA +dC5jb20wHQYDVR0OBBYEFF+sq41Nw9S3XL1yJQ51PLCa+mwUMB8GA1UdIwQYMBaA +FBn+c0Qg6qbDyfGRTNk1jzuKuSOAMA0GCSqGSIb3DQEBCwUAA4IBAQB56Z7/YUQ6 +SkazZPO2Eodg/4eKHQPEluzbz13/wkTBMTewgLgXRCVow8025yt0NaQ9AQzrKELc +WBD+BuoTA8koD52zXmG9kjpIzajIqv2urWIaH1vUzfM26uJgiQKXX3eo24fbGRQi +W452PvGPYoGAtucrEg15MrGlfqLMPkNIJ3ufIWRh+ycriWb8kHe+TgB6XQQGhHdJ +D0+MXSOkPoNM7I8hU2PNl29krHTl3npYK0zG4AOF6tbOuu6bta94kV8PQ4YBfojF +o8vYmMboYDfZnnh+92WT4Ra/BSIm/NXilo3mXOu+cuRP6Kl3kpJPT0zZIjI5DBLn +QmJKb8oDcA7+ -----END CERTIFICATE----- diff --git a/test/config/integration/certs/servercert_hash.h b/test/config/integration/certs/servercert_hash.h index 35c14e7d542af..b8234d905b8b0 100644 --- a/test/config/integration/certs/servercert_hash.h +++ b/test/config/integration/certs/servercert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_SERVER_CERT_HASH[] = "DC:E2:2B:65:90:43:9A:36:1C:8E:6D:CA:42:8A:8C:37:C7:A1:77:" - "00:5B:C1:3E:33:8A:B9:2D:04:2C:B1:3F:0A"; +constexpr char TEST_SERVER_CERT_HASH[] = "EF:9E:F0:7E:DF:44:22:1A:91:3C:1E:06:41:D1:D8:21:3E:C6:7C:" + "5F:C8:B0:CF:74:66:97:02:80:9C:EE:54:1D"; diff --git a/test/config/integration/certs/servercert_info.h b/test/config/integration/certs/servercert_info.h index 4dc5d591d371c..7e45a7e10a653 100644 --- a/test/config/integration/certs/servercert_info.h +++ b/test/config/integration/certs/servercert_info.h @@ -1,8 +1,8 @@ // NOLINT(namespace-envoy) constexpr char TEST_SERVER_CERT_256_HASH[] = - "dce22b6590439a361c8e6dca428a8c37c7a177005bc13e338ab92d042cb13f0a"; -constexpr char TEST_SERVER_CERT_1_HASH[] = "c777412ff69717898a3ffc61358094b6d431055a"; -constexpr char TEST_SERVER_CERT_SPKI[] = "E4cAEJmJCuF+bG3vK9LvNUaZ3Z8g+kcRKvQoJwplWAY="; -constexpr char TEST_SERVER_CERT_SERIAL[] = "4fd5b37b416fc3fa4cbea0263c98e50fb1cd8d8e"; -constexpr char TEST_SERVER_CERT_NOT_BEFORE[] = "Apr 7 16:46:35 2022 GMT"; -constexpr char TEST_SERVER_CERT_NOT_AFTER[] = "Apr 6 16:46:35 2024 GMT"; + "ef9ef07edf44221a913c1e0641d1d8213ec67c5fc8b0cf74669702809cee541d"; +constexpr char TEST_SERVER_CERT_1_HASH[] = "e36f584edbba2580996a75d045ba90c4c24d9f21"; +constexpr char TEST_SERVER_CERT_SPKI[] = "sPwxSjtGIzil8skjh9JlikzuhVhObk3YgtYk/gjj6o0="; +constexpr char TEST_SERVER_CERT_SERIAL[] = "411921dec63f24de7eb6ee4d5f74dbcb1d18f25f"; +constexpr char TEST_SERVER_CERT_NOT_BEFORE[] = "Apr 8 10:42:53 2024 GMT"; +constexpr char TEST_SERVER_CERT_NOT_AFTER[] = "Apr 8 10:42:53 2026 GMT"; diff --git a/test/config/integration/certs/serverkey.pem b/test/config/integration/certs/serverkey.pem index f71631600a3e8..14c5375f6907f 100644 --- a/test/config/integration/certs/serverkey.pem +++ b/test/config/integration/certs/serverkey.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/upstreamcacert.pem b/test/config/integration/certs/upstreamcacert.pem index 1f18cee72a911..fbdc094080b72 100644 --- a/test/config/integration/certs/upstreamcacert.pem +++ b/test/config/integration/certs/upstreamcacert.pem @@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID7zCCAtegAwIBAgIUAM3GAjabuMnzR08aU9j8mRwnOGQwDQYJKoZIhvcNAQEL +MIID7zCCAtegAwIBAgIUfXpfjZAzA9sFKKe0k9M1rCGG9rwwDQYJKoZIhvcNAQEL BQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n -aW5lZXJpbmcxGTAXBgNVBAMMEFRlc3QgVXBzdHJlYW0gQ0EwHhcNMjIwNDA3MTY0 -NjM2WhcNMjQwNDA2MTY0NjM2WjB/MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs +aW5lZXJpbmcxGTAXBgNVBAMMEFRlc3QgVXBzdHJlYW0gQ0EwHhcNMjQwNDA4MTA0 +MjUzWhcNMjYwNDA4MTA0MjUzWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs aWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZ MBcGA1UECwwQTHlmdCBFbmdpbmVlcmluZzEZMBcGA1UEAwwQVGVzdCBVcHN0cmVh -bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMSzKRJ0BRNcbgDJ -vDKGiC+dDTjWCELZmmhuXxGXn4nb9zkPrENul7D64Y/mPEFrAnzvkdbCStRRppqv -lih9aPBJGnLt/BFnE+1gwSVWHcIuGiscn43FfJQk1x9WzOFuNYRa8qFqiSy2yuBl -DLsE3GAJwlA3R+H42RroKSgc9QIu0YWOEuFxxwbZ4YludeVn4eZ2UIJc+9IalqQd -/USNWpDbF15rzTIdHQDkDWiJ7i0P1nQYOg9Ox8Fz4DHvFsZ8pec5ayt90fxQCDBZ -ltqg/XQN6gJTo6Sjt/+hlN8HYa6nPaTomky5p25nW83+1+VY6PXlWxJY5mNtnw2g -IzH+WQ8CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFHHiOkwR36EVUcLG8EXuMUbnJlgVMB8GA1UdIwQYMBaAFHHiOkwR -36EVUcLG8EXuMUbnJlgVMA0GCSqGSIb3DQEBCwUAA4IBAQAFPwnsXdW9k2c0bnhU -Q2L5mC9sMINg5+jlF1vaQC0bedAjkA7b+sNyTyiFFFRZtww+/bRLBDZA71psLp5Y -UxRIyq0xdoeYx+uauFYnVdHIyuyepXAc2nQaqniVejgD12GMkOrQJfRU0g9PCpwN -Su9VKJuIsXikGaiCFMMFMEqPrJ89TRXurIQFw2br6fAck0XkAIhRk636SocEinI2 -6KH27rApltg6hY9vP4sSrz+fY46o95v+2P3ef0y9ZG0h+4JkqmcjM3+Od1BehAZQ -4TC+xARjTmS2jqErZwAdw4ogElvO1w/0mMm7xZZJgqOf6rcdTyeJH0wMZAD1n0Bd -BxbX +bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANQ5VS5O8LtJdNY7 +L9sqH6vVhr9wyHsb7bvBSmg9JAxTU8vSFG/Uj4zoJDBYtEivU9F7leeqcqVLU9MA +2vvYt/LS7/j2HOU0AfilbIGRJiho24AMlrkgXQSweVD+Y46hH42xythcZhwYS6JQ +Mpe0jkSk8SDUZTCCFeosbt8yTxOILgNsFUgUJ1pkUFyQQDSW+cYfruXgg/U/BdP2 +bme/E6Wf41KhZIZJTGzbxmgRrmF29ktOSwLyJcKpMCVNFforIBOKnF7ANKirnAS4 +FuBx6Q4peQ6/qwmXcucBD4X+YBoTi6+CZejW9LHcZX4gFjWKFlny4QJKxz2eFS+1 +eudq86kCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFHrsizu33Ld1ed/tUUow717Z5RCDMB8GA1UdIwQYMBaAFHrsizu3 +3Ld1ed/tUUow717Z5RCDMA0GCSqGSIb3DQEBCwUAA4IBAQCPlwh6v03l09vHYA8k +FX0YVZDUKOcz8wtoRHwkTjetGRaDF2xEu2NGr/RHFS5EyJ9kuwgc1nOGS8lfqDk9 +Cznok/2qsN+ctp571ufhK+EZf5FI9etQJP1f0YleXrP3KR3ztQ5zLGXCv6E0oqXi +6ct4FZJwq5RdP4LYJUWCfCAf5z8Yr6nLUlXTW2Kwwi3+3isqc97jdRMkL37Y3CyR +EgAHSbw26XozFmY+K7ptspwb8zPaWKMUDNSGJVnfCqo8ABWJbDcdRa/AZA4KXScP +H/A2sZtKx8b3mOIu/uX5NQCO+e0Tvm6qqCSGr+Ykcn7HI6Rr43d19He/zn82oHZF +qhaf -----END CERTIFICATE----- diff --git a/test/config/integration/certs/upstreamcacert_info.h b/test/config/integration/certs/upstreamcacert_info.h index a63b99f7b5701..ff3d5efe79824 100644 --- a/test/config/integration/certs/upstreamcacert_info.h +++ b/test/config/integration/certs/upstreamcacert_info.h @@ -1,6 +1,8 @@ // NOLINT(namespace-envoy) constexpr char TEST_UPSTREAMCA_CERT_256_HASH[] = - "e68c68216b8cf85a36de259752425ddc372e031e360156914a39e909e27e1861"; -constexpr char TEST_UPSTREAMCA_CERT_1_HASH[] = "5d889c63937cdf6a3872f6a035aad7c8eacf2afd"; -constexpr char TEST_UPSTREAMCA_CERT_SPKI[] = "uEREslFrQdgEjTk6o9Bacq3xgndYt6rjgeXb2iINBsg="; -constexpr char TEST_UPSTREAMCA_CERT_SERIAL[] = "cdc602369bb8c9f3474f1a53d8fc991c273864"; + "0d80567cd519dbcca26d61050caeba7d3a2b05a8546ee438f95ca141d087daa0"; +constexpr char TEST_UPSTREAMCA_CERT_1_HASH[] = "3982160d342b1c7bf42b1649b982d5e22399360a"; +constexpr char TEST_UPSTREAMCA_CERT_SPKI[] = "+ZA+VSmiFPKoBUS9dTaBlDdEX8WgqXiTlSKWZ9cNEcI="; +constexpr char TEST_UPSTREAMCA_CERT_SERIAL[] = "7d7a5f8d903303db0528a7b493d335ac2186f6bc"; +constexpr char TEST_UPSTREAMCA_CERT_NOT_BEFORE[] = "Apr 8 10:42:53 2024 GMT"; +constexpr char TEST_UPSTREAMCA_CERT_NOT_AFTER[] = "Apr 8 10:42:53 2026 GMT"; diff --git a/test/config/integration/certs/upstreamcakey.pem b/test/config/integration/certs/upstreamcakey.pem index bed4d27c2255c..71632a113a1b3 100644 --- a/test/config/integration/certs/upstreamcakey.pem +++ b/test/config/integration/certs/upstreamcakey.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/upstreamcert.pem b/test/config/integration/certs/upstreamcert.pem index 1c2e53d0d44e9..5b2c7c734955c 100644 --- a/test/config/integration/certs/upstreamcert.pem +++ b/test/config/integration/certs/upstreamcert.pem @@ -1,25 +1,25 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/test/config/integration/certs/upstreamcert_hash.h b/test/config/integration/certs/upstreamcert_hash.h index b4b0766ab905e..a1f4bda50d8e5 100644 --- a/test/config/integration/certs/upstreamcert_hash.h +++ b/test/config/integration/certs/upstreamcert_hash.h @@ -1,3 +1,3 @@ // NOLINT(namespace-envoy) -constexpr char TEST_UPSTREAM_CERT_HASH[] = "05:F6:81:1E:68:37:4D:3B:0E:40:66:AB:E1:48:69:A4:FE:AF:" - "1C:A8:2D:4C:BF:DA:94:0D:86:5E:04:7B:5A:21"; +constexpr char TEST_UPSTREAM_CERT_HASH[] = "00:0B:AA:77:B6:76:9F:0C:38:EE:71:2C:62:24:30:C0:73:B7:" + "80:95:D4:6A:E0:B5:15:E4:A7:B5:5A:63:B1:38"; diff --git a/test/config/integration/certs/upstreamkey.pem b/test/config/integration/certs/upstreamkey.pem index 8e6ac3570415d..46a338e75e5d5 100644 --- a/test/config/integration/certs/upstreamkey.pem +++ b/test/config/integration/certs/upstreamkey.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/test/config/integration/certs/upstreamlocalhostcert.pem b/test/config/integration/certs/upstreamlocalhostcert.pem index 00f8bfedcfc08..a29a4b111ea49 100644 --- a/test/config/integration/certs/upstreamlocalhostcert.pem +++ b/test/config/integration/certs/upstreamlocalhostcert.pem @@ -1,25 +1,25 @@ -----BEGIN CERTIFICATE----- -MIIEPTCCAyWgAwIBAgIUFeQNMCYWuuMCb2Dmr7DaaMHVEU8wDQYJKoZIhvcNAQEL +MIIEPTCCAyWgAwIBAgIULqIPc9/DvZhPYmsi6pdT7OjwESswDQYJKoZIhvcNAQEL BQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n -aW5lZXJpbmcxGTAXBgNVBAMMEFRlc3QgVXBzdHJlYW0gQ0EwHhcNMjIwNDA3MTY0 -NjM2WhcNMjQwNDA2MTY0NjM2WjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh +aW5lZXJpbmcxGTAXBgNVBAMMEFRlc3QgVXBzdHJlYW0gQ0EwHhcNMjQwNDA4MTA0 +MjUzWhcNMjYwNDA4MTA0MjUzWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh bGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQx GTAXBgNVBAsMEEx5ZnQgRW5naW5lZXJpbmcxHTAbBgNVBAMMFFRlc3QgVXBzdHJl -YW0gU2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy6ls3XDO -brAMh4hUJqEaglnageKoOrJF464bh2ZQ+ZPiHRvVkkzsRyQz+SVh15ip7Qm9vpiq -w1nPLylwr0ZOcvmVi5BsozOPa0dlctmaFkH3yEkYmRYIG9b/GppH8ftGJygMie2l -cLmJToXlqvytoxMaFweDlvYCbEBMm8yLhA9l+ceQPhiY/+FW2McjZGKARkFp67Qx -dVKHj9qADZrUCA4ZhuJpR4YlKiD+30gf/rLUK2OnPTWY2z/5MpzfC7C3g19KGhCu -32H6UkAt1JG41aiFIWGWooXc+C2TmPnLRS+1AjJ2/VH7k+np4FdZHfi46g3Tqz6r -BktaTTkVoz/I7QIDAQABo4GrMIGoMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgXg +YW0gU2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt50gO1oc +7br+X9CMn2uIfXUmFEKku7AYD8H7ryAC/BH2SSJyK37WhQ+ZqlSx01iCXVv2eBIM +VD3IemDdijUWS6A2QuE177fmJ2YmvtHlBYHiPl3P0PSwpXKh/b6bVv6rdkDmNt1K +8t0b3kU4ufMIYpP43F6Et6aB0kZg26EfrZHBUXN3NZP5WLDPMSyBwfRk2H+wovHT +hZFjmwafNhcUaOGGk41uiYA6ML8oKzt4w2c769Vwnu3Nu0ezCXcdyDOueHzTu5fe +zX94eaiHhH8RGW6R/kHFkbUCVHjkFKRy2PuHkjLe08FY/QwaOcJkgCtzAXaoOsGa +DfQTw76y2tynkwIDAQABo4GrMIGoMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgXg MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAsBgNVHREEJTAjgglsb2Nh -bGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwHQYDVR0OBBYEFEVKvYm5gK4C -OUtyLPtuWKcwt/jnMB8GA1UdIwQYMBaAFHHiOkwR36EVUcLG8EXuMUbnJlgVMA0G -CSqGSIb3DQEBCwUAA4IBAQB8cnS58z16fSCp63VLh4e6LBHpzFa4V83oSVg9IuLA -h6bS/PoY6PlID7gz2ZxMfll6akF2imB3VcyNRSjaUS5Wguc/q+uyLFZOMG3017xr -+rdZcOKhfeyd9Ji/wHTrNqMQZI9z4Piurjal4dUNwnJRumgqJrqnJBfQkoDrokug -W5Hcg+MlmjsXiVYonQpsolDrXzNnVASGgZXvlU3kAV7DQPhQwEh9h6hFmSHeDdAh -yfYFg+JKNVzkpp2k+Zdx/AqOZdusvTjLij8XsohqpZ7vNx47CNhoEH6qHJnWqed2 -f3IC8COVrzQeXDyCyUnhJieQMnycvPwuHDZeaTcuvbI6 +bGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwHQYDVR0OBBYEFHVGLO/DOSh1 +GQe2TJvKNgPtYNTlMB8GA1UdIwQYMBaAFHrsizu33Ld1ed/tUUow717Z5RCDMA0G +CSqGSIb3DQEBCwUAA4IBAQBHbye/RFa7hHuRMgaZE2bFXiA6iml8F+JAqCS5fK/1 +fDn4acvaukfiz+azJuvB+4l+L3b9U0wzgqttWDTAZtg+xj+YCm4bQOq7balxqCP/ +R17eSNnu1tk333Luvut2fjK0PLM8/Nao4hZDwIyIZblB3BYIb8aAKYQXSfPdP3Ha +yQt4eLEyEyW5/+5VpP+HdM/boAQSHCtCZ4rU4bMyUPBGTgxwQFOcX9DUnKXG1sOm +eng+r0pC4pvS9cvij61MBM7Cqf1pzCcLCrEfpafdnUb94lEEOJMVLEScQ0yHiG++ +U78dPD6osbknmHF+u5sFUpRH5HK64tkinQ9s8dvT8eme -----END CERTIFICATE----- diff --git a/test/config/integration/certs/upstreamlocalhostcert_hash.h b/test/config/integration/certs/upstreamlocalhostcert_hash.h index 150e58a8d93e2..63f39e12affa3 100644 --- a/test/config/integration/certs/upstreamlocalhostcert_hash.h +++ b/test/config/integration/certs/upstreamlocalhostcert_hash.h @@ -1,4 +1,4 @@ // NOLINT(namespace-envoy) constexpr char TEST_UPSTREAMLOCALHOST_CERT_HASH[] = - "55:9D:BF:B2:76:73:B2:5C:40:12:C2:E1:D0:BF:51:F0:62:4D:9A:2C:B9:9E:05:FE:E4:C7:80:F1:02:BF:7D:" - "60"; + "FB:EB:F5:84:E2:C8:C9:85:97:E8:61:41:CE:80:72:41:32:4F:D7:58:C3:E1:74:0B:6C:50:18:46:1E:10:B9:" + "E5"; diff --git a/test/config/integration/certs/upstreamlocalhostkey.pem b/test/config/integration/certs/upstreamlocalhostkey.pem index 0064f75068a02..ab79854f794e1 100644 --- a/test/config/integration/certs/upstreamlocalhostkey.pem +++ b/test/config/integration/certs/upstreamlocalhostkey.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- From 9227800f69137c08dd8b8ba367bdc60dcc77a8c3 Mon Sep 17 00:00:00 2001 From: Christoph Pakulski Date: Wed, 10 Apr 2024 08:19:45 -0400 Subject: [PATCH 199/274] backport: DFP: move CM callbacks to thread local objects (#33303) Signed-off-by: Christoph Pakulski --- .../dynamic_forward_proxy/proxy_filter.cc | 17 ++++------ .../http/dynamic_forward_proxy/proxy_filter.h | 33 +++++++++++-------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc index 3ab49ca0bf263..a8e0ef864b549 100644 --- a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc +++ b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc @@ -98,11 +98,6 @@ LoadClusterEntryHandlePtr ProxyFilterConfig::addDynamicCluster( cluster_name, callbacks); } -Upstream::ClusterUpdateCallbacksHandlePtr -ProxyFilterConfig::addThreadLocalClusterUpdateCallbacks() { - return cluster_manager_.addThreadLocalClusterUpdateCallbacks(*this); -} - ProxyFilterConfig::ThreadLocalClusterInfo::~ThreadLocalClusterInfo() { for (const auto& it : pending_clusters_) { for (auto cluster : it.second) { @@ -111,24 +106,24 @@ ProxyFilterConfig::ThreadLocalClusterInfo::~ThreadLocalClusterInfo() { } } -void ProxyFilterConfig::onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) { +void ProxyFilterConfig::ThreadLocalClusterInfo::onClusterAddOrUpdate( + Upstream::ThreadLocalCluster& cluster) { const std::string& cluster_name = cluster.info()->name(); ENVOY_LOG(debug, "thread local cluster {} added or updated", cluster_name); - ThreadLocalClusterInfo& tls_cluster_info = *tls_slot_; - auto it = tls_cluster_info.pending_clusters_.find(cluster_name); - if (it != tls_cluster_info.pending_clusters_.end()) { + auto it = pending_clusters_.find(cluster_name); + if (it != pending_clusters_.end()) { for (auto* cluster : it->second) { auto& callbacks = cluster->callbacks_; cluster->cancel(); callbacks.onLoadClusterComplete(); } - tls_cluster_info.pending_clusters_.erase(it); + pending_clusters_.erase(it); } else { ENVOY_LOG(debug, "but not pending request waiting on {}", cluster_name); } } -void ProxyFilterConfig::onClusterRemoval(const std::string&) { +void ProxyFilterConfig::ThreadLocalClusterInfo::onClusterRemoval(const std::string&) { // do nothing, should have no pending clusters. } diff --git a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h index db9a417a2ad99..7164cdbe07a8e 100644 --- a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h +++ b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h @@ -34,8 +34,7 @@ class LoadClusterEntryCallbacks { virtual void onLoadClusterComplete() PURE; }; -class ProxyFilterConfig : public Upstream::ClusterUpdateCallbacks, - Logger::Loggable { +class ProxyFilterConfig : Logger::Loggable { public: ProxyFilterConfig( const envoy::extensions::filters::http::dynamic_forward_proxy::v3::FilterConfig& proto_config, @@ -55,12 +54,6 @@ class ProxyFilterConfig : public Upstream::ClusterUpdateCallbacks, addDynamicCluster(Extensions::Common::DynamicForwardProxy::DfpClusterSharedPtr cluster, const std::string& cluster_name, const std::string& host, const int port, LoadClusterEntryCallbacks& callback); - // run in each worker thread. - Upstream::ClusterUpdateCallbacksHandlePtr addThreadLocalClusterUpdateCallbacks(); - - // Upstream::ClusterUpdateCallbacks - void onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) override; - void onClusterRemoval(const std::string&) override; private: struct LoadClusterEntryHandleImpl @@ -75,14 +68,28 @@ class ProxyFilterConfig : public Upstream::ClusterUpdateCallbacks, LoadClusterEntryCallbacks& callbacks_; }; - // Per-thread cluster info including pending callbacks. - struct ThreadLocalClusterInfo : public ThreadLocal::ThreadLocalObject { - ThreadLocalClusterInfo(ProxyFilterConfig& parent) : parent_{parent} { - handle_ = parent.addThreadLocalClusterUpdateCallbacks(); + // Per-thread cluster info including pending clusters. + // The lifetime of ThreadLocalClusterInfo, which is allocated on each working thread + // may exceed lifetime of the parent object (ProxyFilterConfig), which is allocated + // and deleted on the main thread. + // Currently ThreadLocalClusterInfo does not hold any references to the parent object + // and therefore does not need to check if the parent object is still valid. + // IMPORTANT: If a reference to the parent object is added here, the validity of + // that object must be checked before using it. It is best achieved via + // combination of shared and weak pointers. + struct ThreadLocalClusterInfo : public ThreadLocal::ThreadLocalObject, + public Envoy::Upstream::ClusterUpdateCallbacks, + Logger::Loggable { + ThreadLocalClusterInfo(ProxyFilterConfig& parent) { + // run in each worker thread. + handle_ = parent.cluster_manager_.addThreadLocalClusterUpdateCallbacks(*this); } ~ThreadLocalClusterInfo() override; + + void onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) override; + void onClusterRemoval(const std::string& name) override; + absl::flat_hash_map> pending_clusters_; - ProxyFilterConfig& parent_; Upstream::ClusterUpdateCallbacksHandlePtr handle_; }; From 577a0caa98713f6078075f9e099f2363be079fe1 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 18 Apr 2024 11:57:13 +0100 Subject: [PATCH 200/274] docker/release: Bump Ubuntu image `71b82b8` (#33656) Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 396e1fb1d7703..636c1733f15aa 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=20.04@sha256:80ef4a44043dec4490506e6cc4289eeda2d106a70148b74b5ae91ee670e9c35d +ARG BUILD_TAG=20.04@sha256:71b82b8e734f5cd0b3533a16f40ca1271f28d87343972bb4cd6bd6c38f1bd38e ARG ENVOY_VRP_BASE_IMAGE=envoy-base From 81fb51958769eaedc6fc44106669f4fa7d9fff5a Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 17 Apr 2024 17:33:22 -0700 Subject: [PATCH 201/274] tls: fix RELEASE_ASSERT when using `auto_sni` (#33637) * tls: fix RELEASE_ASSERT when using `auto_sni` If the `:authority` was longer than 255 characters, Envoy would RELEASE_ASSERT when creating an upstream TLS connection when `auto_sni` (https://www.envoyproxy.io/docs/envoy/v1.30.0/api-v3/config/core/v3/protocol.proto.html#config-core-v3-upstreamhttpprotocoloptions) was used. Signed-off-by: Greg Greenway Signed-off-by: Ryan Northey --- changelogs/current.yaml | 5 ++ .../transport_sockets/tls/context_impl.cc | 17 ++++- .../transport_sockets/tls/context_impl.h | 5 +- .../transport_sockets/tls/ssl_socket.cc | 74 +++++++++++++++---- .../transport_sockets/tls/ssl_socket.h | 11 ++- .../proxy_filter_integration_test.cc | 18 +++++ 6 files changed, 108 insertions(+), 22 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..8cdc4928cd362 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,11 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: tls + change: | + Fix a RELEASE_ASSERT when using :ref:`auto_sni ` + if the downstream request ``:authority`` was longer than 255 characters. + removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index f3132d2ffb9bd..e3c7ddb378127 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -452,7 +452,7 @@ std::vector ContextImpl::parseAlpnProtocols(const std::string& alpn_pro return out; } -bssl::UniquePtr +absl::StatusOr> ContextImpl::newSsl(const Network::TransportSocketOptionsConstSharedPtr& options) { // We use the first certificate for a new SSL object, later in the // SSL_CTX_set_select_certificate_cb() callback following ClientHello, we replace with the @@ -680,16 +680,25 @@ bool ContextImpl::parseAndSetAlpn(const std::vector& alpn, SSL& ssl return false; } -bssl::UniquePtr +absl::StatusOr> ClientContextImpl::newSsl(const Network::TransportSocketOptionsConstSharedPtr& options) { - bssl::UniquePtr ssl_con(ContextImpl::newSsl(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()); - RELEASE_ASSERT(rc, Utility::getLastCryptoError().value_or("")); + 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()) { diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 8654d44e706e6..f0b89a55e1224 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -66,7 +66,8 @@ struct TlsContext { class ContextImpl : public virtual Envoy::Ssl::Context, protected Logger::Loggable { public: - virtual bssl::UniquePtr newSsl(const Network::TransportSocketOptionsConstSharedPtr& options); + virtual absl::StatusOr> + newSsl(const Network::TransportSocketOptionsConstSharedPtr& options); /** * Logs successful TLS handshake and updates stats. @@ -163,7 +164,7 @@ class ClientContextImpl : public ContextImpl, public Envoy::Ssl::ClientContext { ClientContextImpl(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, TimeSource& time_source); - bssl::UniquePtr + absl::StatusOr> newSsl(const Network::TransportSocketOptionsConstSharedPtr& options) override; private: diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index 3a982d5a23a75..6a23adb03d9f6 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -26,13 +26,11 @@ namespace { constexpr absl::string_view NotReadyReason{"TLS error: Secret is not supplied by SDS"}; -// This SslSocket will be used when SSL secret is not fetched from SDS server. -class NotReadySslSocket : public Network::TransportSocket { +class InvalidSslSocket : public Network::TransportSocket { public: // Network::TransportSocket void setTransportSocketCallbacks(Network::TransportSocketCallbacks&) override {} std::string protocol() const override { return EMPTY_STRING; } - absl::string_view failureReason() const override { return NotReadyReason; } bool canFlushClose() override { return true; } void closeSocket(Network::ConnectionEvent) override {} Network::IoResult doRead(Buffer::Instance&) override { return {PostIoAction::Close, 0, false}; } @@ -45,21 +43,62 @@ class NotReadySslSocket : public Network::TransportSocket { 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 -SslSocket::SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, - const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, - Ssl::HandshakerFactoryCb handshaker_factory_cb) +absl::StatusOr> +SslSocket::create(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, + const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, + Ssl::HandshakerFactoryCb handshaker_factory_cb) { + std::unique_ptr socket(new SslSocket(ctx, transport_socket_options)); + auto status = socket->initialize(state, handshaker_factory_cb); + if (status.ok()) { + return socket; + } else { + return status; + } +} + +SslSocket::SslSocket(Envoy::Ssl::ContextSharedPtr ctx, + const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options) : transport_socket_options_(transport_socket_options), - ctx_(std::dynamic_pointer_cast(ctx)), - info_(std::dynamic_pointer_cast(handshaker_factory_cb( - ctx_->newSsl(transport_socket_options_), ctx_->sslExtendedSocketInfoIndex(), this))) { + ctx_(std::dynamic_pointer_cast(ctx)) {} + +absl::Status SslSocket::initialize(InitialState state, + Ssl::HandshakerFactoryCb handshaker_factory_cb) { + auto status_or_ssl = ctx_->newSsl(transport_socket_options_); + if (!status_or_ssl.ok()) { + return status_or_ssl.status(); + } + + info_ = std::dynamic_pointer_cast(handshaker_factory_cb( + std::move(status_or_ssl.value()), ctx_->sslExtendedSocketInfoIndex(), this)); + if (state == InitialState::Client) { SSL_set_connect_state(rawSsl()); } else { ASSERT(state == InitialState::Server); SSL_set_accept_state(rawSsl()); } + + return absl::OkStatus(); } void SslSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) { @@ -394,8 +433,13 @@ Network::TransportSocketPtr ClientSslSocketFactory::createTransportSocket( ssl_ctx = ssl_ctx_; } if (ssl_ctx) { - return std::make_unique(std::move(ssl_ctx), InitialState::Client, - transport_socket_options, config_->createHandshaker()); + 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(); @@ -443,8 +487,12 @@ Network::TransportSocketPtr ServerSslSocketFactory::createDownstreamTransportSoc ssl_ctx = ssl_ctx_; } if (ssl_ctx) { - return std::make_unique(std::move(ssl_ctx), InitialState::Server, nullptr, - config_->createHandshaker()); + 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(); diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index ea9213ffe6d17..f0c679b23e702 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -48,9 +48,10 @@ class SslSocket : public Network::TransportSocket, public Ssl::HandshakeCallbacks, protected Logger::Loggable { public: - SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, - const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, - Ssl::HandshakerFactoryCb handshaker_factory_cb); + static absl::StatusOr> + create(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, + const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, + Ssl::HandshakerFactoryCb handshaker_factory_cb); // Network::TransportSocket void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override; @@ -79,6 +80,10 @@ class SslSocket : public Network::TransportSocket, SSL* rawSsl() const { return info_->ssl(); } private: + SslSocket(Envoy::Ssl::ContextSharedPtr ctx, + const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options); + absl::Status initialize(InitialState state, Ssl::HandshakerFactoryCb handshaker_factory_cb); + struct ReadResult { uint64_t bytes_read_{0}; absl::optional error_; diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc index d632e083e978b..fdaf0f7206393 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -580,6 +580,24 @@ TEST_P(ProxyFilterIntegrationTest, UpstreamTlsWithIpHost) { checkSimpleRequestSuccess(0, 0, response.get()); } +TEST_P(ProxyFilterIntegrationTest, UpstreamTlsWithTooLongSni) { + upstream_tls_ = true; + initializeWithArgs(1024, 1024, "x-host"); + std::string too_long_sni(300, 'a'); + ASSERT_EQ(too_long_sni.size(), 300); // Validate that the expected constructor was run. + codec_client_ = makeHttpConnection(lookupPort("http")); + const Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "localhost"}, + {"x-host", too_long_sni}}; + + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("503", response->headers().getStatusValue()); + // TODO(ggreenway): validate (in access logs probably) that failure reason is set appropriately. +} + // Verify that auto-SAN verification fails with an incorrect certificate. TEST_P(ProxyFilterIntegrationTest, UpstreamTlsInvalidSAN) { upstream_tls_ = true; From be4f1cfd31c79fc05651efa2f88429b3c03d1d9e Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Thu, 18 Apr 2024 13:30:24 +0000 Subject: [PATCH 202/274] repo: Release v1.27.5 **Summary of changes**: * Fix for potential TLS/SNI (`auto_sni`) crash [CVE pending](https://github.com/envoyproxy/envoy/security/advisories/GHSA-3mh5-6q8v-25wj). **Docker images**: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.5 **Docs**: https://www.envoyproxy.io/docs/envoy/v1.27.5/ **Release notes**: https://www.envoyproxy.io/docs/envoy/v1.27.5/version_history/v1.27/v1.27.5 **Full changelog**: https://github.com/envoyproxy/envoy/compare/v1.27.4...v1.27.5 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/current.yaml | 17 +---------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 3057fc1429d61..bd9c63786b1f6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.5-dev +1.27.5 diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8cdc4928cd362..ec9a51b0eb0a8 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,22 +1,7 @@ -date: Pending - -behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* +date: April 18, 2024 bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: tls change: | Fix a RELEASE_ASSERT when using :ref:`auto_sni ` if the downstream request ``:authority`` was longer than 255 characters. - - -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - -new_features: - -deprecated: From 328cf2de8cac66567237551ec524950c363ab5fc Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Thu, 18 Apr 2024 19:31:21 +0100 Subject: [PATCH 203/274] repo: Dev v1.27.6 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.5.yaml | 7 +++++++ changelogs/current.yaml | 20 +++++++++++++++----- docs/inventories/v1.27/objects.inv | Bin 159977 -> 160000 bytes docs/versions.yaml | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 changelogs/1.27.5.yaml diff --git a/VERSION.txt b/VERSION.txt index bd9c63786b1f6..7b16a461240e2 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.5 +1.27.6-dev diff --git a/changelogs/1.27.5.yaml b/changelogs/1.27.5.yaml new file mode 100644 index 0000000000000..ec9a51b0eb0a8 --- /dev/null +++ b/changelogs/1.27.5.yaml @@ -0,0 +1,7 @@ +date: April 18, 2024 + +bug_fixes: +- area: tls + change: | + Fix a RELEASE_ASSERT when using :ref:`auto_sni ` + if the downstream request ``:authority`` was longer than 255 characters. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index ec9a51b0eb0a8..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,7 +1,17 @@ -date: April 18, 2024 +date: Pending + +behavior_changes: +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +minor_behavior_changes: +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: tls - change: | - Fix a RELEASE_ASSERT when using :ref:`auto_sni ` - if the downstream request ``:authority`` was longer than 255 characters. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: + +deprecated: diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index d2997321e46284e7e5a0e5123eaae1d7d1f4e0c0..01563bfb768291679df12aaaeea1be817960262f 100644 GIT binary patch delta 14810 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 De?no0 delta 14787 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 diff --git a/docs/versions.yaml b/docs/versions.yaml index 62539f81ea45a..de539389428ea 100644 --- a/docs/versions.yaml +++ b/docs/versions.yaml @@ -20,4 +20,4 @@ "1.24": 1.24.12 "1.25": 1.25.11 "1.26": 1.26.8 -"1.27": 1.27.4 +"1.27": 1.27.5 From 30cc793afdf63fec2c2b0b8a461ac993910a982d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:04:53 +0100 Subject: [PATCH 204/274] build(deps): bump distroless/base-nossl-debian12 from `0cf184c` to `312c829` in /ci (#33860) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `0cf184c` to `312c829`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 636c1733f15aa..d435f1a926786 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0cf184cfdb9ac2878822b15b8917fae5d42fba26da654cd75ab3ed34add0737f AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:312c829b02cb4270e64e72973dfc15164e3fbbd6c1d685da55b6f0de2a99a2b2 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From cdd258e3fd061cd619b4b9108e437296fb2a76d3 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 26 Apr 2024 19:47:55 +0100 Subject: [PATCH 205/274] arm/tests: Temporarily disable failing `io_uring` test (#33822) Signed-off-by: Ryan Northey Signed-off-by: phlax --- test/common/io/BUILD | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/common/io/BUILD b/test/common/io/BUILD index d1231c0cb1399..e32376f5b6bdb 100644 --- a/test/common/io/BUILD +++ b/test/common/io/BUILD @@ -10,15 +10,22 @@ envoy_package() envoy_cc_test( name = "io_uring_impl_test", - srcs = ["io_uring_impl_test.cc"], + srcs = select({ + "//bazel:linux_x86_64": ["io_uring_impl_test.cc"], + "//conditions:default": [], + }), tags = [ "nocompdb", "skip_on_windows", ], deps = [ - "//source/common/io:io_uring_impl_lib", - "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", - ], + ] + select({ + "//bazel:linux": [ + "//source/common/io:io_uring_impl_lib", + "//test/mocks/server:server_mocks", + ], + "//conditions:default": [], + }), ) From dbe56775e9dc95e72f6c69c01c18f236eba7b7e7 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Tue, 30 Apr 2024 19:52:45 +0800 Subject: [PATCH 206/274] iouring: fix the IoUringImpl tests for latest kernel (#33833) Signed-off-by: He Jie Xu Signed-off-by: Alex Xu --- test/common/io/BUILD | 15 ++++-------- test/common/io/io_uring_impl_test.cc | 34 ++++++++++++++++++---------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/test/common/io/BUILD b/test/common/io/BUILD index e32376f5b6bdb..d1231c0cb1399 100644 --- a/test/common/io/BUILD +++ b/test/common/io/BUILD @@ -10,22 +10,15 @@ envoy_package() envoy_cc_test( name = "io_uring_impl_test", - srcs = select({ - "//bazel:linux_x86_64": ["io_uring_impl_test.cc"], - "//conditions:default": [], - }), + srcs = ["io_uring_impl_test.cc"], tags = [ "nocompdb", "skip_on_windows", ], deps = [ + "//source/common/io:io_uring_impl_lib", + "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", - ] + select({ - "//bazel:linux": [ - "//source/common/io:io_uring_impl_lib", - "//test/mocks/server:server_mocks", - ], - "//conditions:default": [], - }), + ], ) diff --git a/test/common/io/io_uring_impl_test.cc b/test/common/io/io_uring_impl_test.cc index 92fba458a7d42..a93a84c0a376d 100644 --- a/test/common/io/io_uring_impl_test.cc +++ b/test/common/io/io_uring_impl_test.cc @@ -1,3 +1,5 @@ +#include + #include "source/common/io/io_uring_impl.h" #include "test/mocks/server/mocks.h" @@ -10,6 +12,8 @@ namespace Envoy { namespace Io { namespace { +using WaitConditionFunc = std::function; + class IoUringImplTest : public ::testing::Test { public: IoUringImplTest() : api_(Api::createApiForTest()) { @@ -38,6 +42,18 @@ class IoUringImplTest : public ::testing::Test { } } + void waitForCondition(Event::Dispatcher& dispatcher, WaitConditionFunc condition_func, + std::chrono::milliseconds wait_timeout = TestUtility::DefaultTimeout) { + Event::TestTimeSystem::RealTimeBound bound(wait_timeout); + while (!condition_func()) { + if (!bound.withinBound()) { + RELEASE_ASSERT(0, "Timed out waiting for the condition."); + break; + } + dispatcher.run(Event::Dispatcher::RunType::NonBlock); + } + } + Api::ApiPtr api_; testing::NiceMock context_; std::unique_ptr factory_{}; @@ -101,8 +117,7 @@ TEST_P(IoUringImplParamTest, InvalidParams) { res = uring.submit(); EXPECT_EQ(res, IoUringResult::Ok); - dispatcher->run(Event::Dispatcher::RunType::NonBlock); - EXPECT_EQ(completions_nr, 2); + waitForCondition(*dispatcher, [&completions_nr]() { return completions_nr == 2; }); } TEST_F(IoUringImplTest, Instantiate) { @@ -155,10 +170,8 @@ TEST_F(IoUringImplTest, PrepareReadvAllDataFitsOneChunk) { EXPECT_STREQ(static_cast(iov.iov_base), ""); uring.submit(); - dispatcher->run(Event::Dispatcher::RunType::Block); - // Check that the completion callback has been actually called. - EXPECT_EQ(completions_nr, 1); + waitForCondition(*dispatcher, [&completions_nr]() { return completions_nr == 1; }); // The file's content is in the read buffer now. EXPECT_STREQ(static_cast(iov.iov_base), "test text"); } @@ -214,6 +227,7 @@ TEST_F(IoUringImplTest, PrepareReadvQueueOverflow) { res = uring.submit(); EXPECT_EQ(res, IoUringResult::Ok); + waitForCondition(*dispatcher, [&completions_nr]() { return completions_nr == 2; }); // Even though we haven't been notified about ops completion the buffers // are filled already. EXPECT_EQ(static_cast(iov1.iov_base)[0], 'a'); @@ -221,11 +235,9 @@ TEST_F(IoUringImplTest, PrepareReadvQueueOverflow) { EXPECT_EQ(static_cast(iov2.iov_base)[0], 'c'); EXPECT_EQ(static_cast(iov2.iov_base)[1], 'd'); - dispatcher->run(Event::Dispatcher::RunType::NonBlock); - // Only 2 completions are expected because the completion queue can contain // no more than 2 entries. - EXPECT_EQ(completions_nr, 2); + waitForCondition(*dispatcher, [&completions_nr]() { return completions_nr == 2; }); // Check a new event gets handled in the next dispatcher run. res = uring.prepareReadv(fd, &iov3, 1, 4, reinterpret_cast(3)); @@ -233,12 +245,10 @@ TEST_F(IoUringImplTest, PrepareReadvQueueOverflow) { res = uring.submit(); EXPECT_EQ(res, IoUringResult::Ok); + waitForCondition(*dispatcher, [&completions_nr]() { return completions_nr == 3; }); + EXPECT_EQ(static_cast(iov3.iov_base)[0], 'e'); EXPECT_EQ(static_cast(iov3.iov_base)[1], 'f'); - - dispatcher->run(Event::Dispatcher::RunType::NonBlock); - // Check the completion callback was called actually. - EXPECT_EQ(completions_nr, 3); } } // namespace From 8395c80a18235e7b1b0cb89f433ad9fc00340005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 09:51:31 +0100 Subject: [PATCH 207/274] build(deps): bump distroless/base-nossl-debian12 from `312c829` to `8a09e57` in /ci (#33956) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `312c829` to `8a09e57`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index d435f1a926786..3d61873a08414 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:312c829b02cb4270e64e72973dfc15164e3fbbd6c1d685da55b6f0de2a99a2b2 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:8a09e5752fb3ab9c9534fcc627eb1f451cd9bcfe66a6b149df62dcb84fb841a6 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 33f0bdf3411c21ed67249a3cb3749ba6067fd190 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 15 May 2024 11:39:17 +0100 Subject: [PATCH 208/274] docker/release: Bump Ubuntu base image -> 874aca5 Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 3d61873a08414..befae3be07553 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=20.04@sha256:71b82b8e734f5cd0b3533a16f40ca1271f28d87343972bb4cd6bd6c38f1bd38e +ARG BUILD_TAG=20.04@sha256:874aca52f79ae5f8258faff03e10ce99ae836f6e7d2df6ecd3da5c1cad3a912b ARG ENVOY_VRP_BASE_IMAGE=envoy-base From bfeecfacf803c9eb9548025eca34746f837151fe Mon Sep 17 00:00:00 2001 From: botengyao Date: Thu, 15 Feb 2024 08:13:17 -0500 Subject: [PATCH 209/274] route: fix a timing issue and remove assertion. (#32336) Remove debug assertion and add more checks for assessing upstream_requests related to timing. Signed-off-by: Boteng Yao --- changelogs/current.yaml | 4 ++ source/common/router/router.cc | 22 ++++++-- source/common/runtime/runtime_features.cc | 1 + test/common/router/router_test.cc | 63 +++++++++++++++++++++++ 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..943e660501b18 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,10 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: router + change: | + Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is + controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/router/router.cc b/source/common/router/router.cc index ab28804d09863..4c45840086b6c 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -831,10 +831,6 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea } } - // If we aren't buffering and there is no active request, an abort should have occurred - // already. - ASSERT(buffering || !upstream_requests_.empty()); - for (auto* shadow_stream : shadow_streams_) { if (end_stream) { shadow_stream->removeDestructorCallback(); @@ -860,7 +856,23 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea // this stack for whether `data` is the same buffer as already buffered data. callbacks_->addDecodedData(data, true); } else { - upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request")) { + upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + } else { + if (!upstream_requests_.empty()) { + upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + } else { + // not buffering any data for retry, shadow, and internal redirect, and there will be + // no more upstream request, abort the request and clean up. + cleanup(); + callbacks_->sendLocalReply( + Http::Code::ServiceUnavailable, + "upstream is closed prematurely during decoding data from downstream", modify_headers_, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().EarlyUpstreamReset); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + } } if (end_stream) { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index c1d28ac747d46..a911a4e1c5fd0 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -72,6 +72,7 @@ RUNTIME_GUARD(envoy_reloadable_features_quic_defer_logging_to_ack_listener); RUNTIME_GUARD(envoy_reloadable_features_reject_require_client_certificate_with_quic); RUNTIME_GUARD(envoy_reloadable_features_sanitize_original_path); RUNTIME_GUARD(envoy_reloadable_features_send_header_raw_value); +RUNTIME_GUARD(envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request); RUNTIME_GUARD(envoy_reloadable_features_service_sanitize_non_utf8_strings); RUNTIME_GUARD(envoy_reloadable_features_skip_dns_lookup_for_proxied_requests); RUNTIME_GUARD(envoy_reloadable_features_stateful_session_encode_ttl_in_cookie); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index bada575c09e52..87308f0126f5b 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -2674,6 +2674,69 @@ TEST_F(RouterTest, RetryRequestDuringBodyDataBetweenAttemptsNotEndStream) { EXPECT_TRUE(verifyHostUpstreamStats(1, 1)); } +// Test when the upstream request gets reset while the client is sending the body +// with more data arriving but not buffering any data. +TEST_F(RouterTest, UpstreamResetDuringBodyDataTransferNotBufferingNotEndStream) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request", "true"}}); + + Buffer::OwnedImpl decoding_buffer; + EXPECT_CALL(callbacks_, decodingBuffer()).WillRepeatedly(Return(&decoding_buffer)); + EXPECT_CALL(callbacks_, addDecodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) { decoding_buffer.move(data); })); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder1, &response_decoder, Http::Protocol::Http10); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-internal", "true"}, {"myheader", "present"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, false); + const std::string body1("body1"); + Buffer::OwnedImpl buf1(body1); + + // Send data while the upstream request is reset, should not have any failure. + encoder1.stream_.resetStream(Http::StreamResetReason::RemoteReset); + router_->decodeData(buf1, false); + + EXPECT_EQ(callbacks_.details(), "upstream_reset_before_response_started"); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + +// Test the original branch when local_reply_when_no_buffer_and_upstream_request runtime is false. +TEST_F(RouterTest, NormalPathUpstreamResetDuringBodyDataTransferNotBuffering) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request", + "false"}}); + + Buffer::OwnedImpl decoding_buffer; + EXPECT_CALL(callbacks_, decodingBuffer()).WillRepeatedly(Return(&decoding_buffer)); + EXPECT_CALL(callbacks_, addDecodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) { decoding_buffer.move(data); })); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder1, &response_decoder, Http::Protocol::Http10); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-internal", "true"}, {"myheader", "present"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, false); + + const std::string body1("body1"); + Buffer::OwnedImpl buf1(body1); + router_->decodeData(buf1, true); + EXPECT_EQ(1U, + callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); + + Http::ResponseHeaderMapPtr response_headers( + new Http::TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder->decodeHeaders(std::move(response_headers), true); + + EXPECT_TRUE(verifyHostUpstreamStats(1, 0)); +} + // Test retrying a request, when the first attempt fails while the client // is sending the body, with the rest of the request arriving in between upstream // request attempts. From c8643f550e6371141c6623c68ba5b46ffe14f45a Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 21 May 2024 15:05:40 +0100 Subject: [PATCH 210/274] ci/tooling/examples: Update vulnerable deps (#34271) Signed-off-by: Ryan Northey --- .../filters/http/test/test_data/basic/go.mod | 2 +- .../filters/http/test/test_data/dummy/go.mod | 2 +- .../filters/http/test/test_data/echo/go.mod | 7 +- .../http/test/test_data/passthrough/go.mod | 2 +- .../http/test/test_data/routeconfig/go.mod | 7 +- .../filters/network/test/test_data/go.mod | 2 +- .../test/test_data/simple/go.mod | 3 +- examples/golang-http/simple/go.mod | 2 +- examples/golang-http/simple/go.sum | 4 +- examples/golang-network/simple/go.mod | 2 +- examples/golang-network/simple/go.sum | 4 +- examples/grpc-bridge/client/requirements.in | 2 + examples/grpc-bridge/client/requirements.txt | 16 +- examples/grpc-bridge/server/go.mod | 2 +- examples/grpc-bridge/server/go.sum | 20 +- .../shared/python/aiohttp/requirements.in | 3 +- .../shared/python/aiohttp/requirements.txt | 164 ++++----- go.mod | 2 +- go.sum | 4 +- tools/base/requirements.in | 10 +- tools/base/requirements.txt | 334 +++++++++--------- 21 files changed, 303 insertions(+), 291 deletions(-) diff --git a/contrib/golang/filters/http/test/test_data/basic/go.mod b/contrib/golang/filters/http/test/test_data/basic/go.mod index 709dbc31fbce4..2a652f058099a 100644 --- a/contrib/golang/filters/http/test/test_data/basic/go.mod +++ b/contrib/golang/filters/http/test/test_data/basic/go.mod @@ -4,6 +4,6 @@ go 1.18 require github.com/envoyproxy/envoy v1.24.0 -require google.golang.org/protobuf v1.30.0 // indirect +require google.golang.org/protobuf v1.33.0 // indirect replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/dummy/go.mod b/contrib/golang/filters/http/test/test_data/dummy/go.mod index 2e9286b62ba91..9e0b2bb864103 100644 --- a/contrib/golang/filters/http/test/test_data/dummy/go.mod +++ b/contrib/golang/filters/http/test/test_data/dummy/go.mod @@ -4,6 +4,6 @@ go 1.18 require github.com/envoyproxy/envoy v1.24.0 -require google.golang.org/protobuf v1.30.0 // indirect +require google.golang.org/protobuf v1.33.0 // indirect replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/echo/go.mod b/contrib/golang/filters/http/test/test_data/echo/go.mod index 8614081f88c5b..2bc12bff34ef9 100644 --- a/contrib/golang/filters/http/test/test_data/echo/go.mod +++ b/contrib/golang/filters/http/test/test_data/echo/go.mod @@ -10,10 +10,9 @@ require ( require github.com/google/go-cmp v0.5.9 // indirect require ( - github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/protobuf v1.31.0 + github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + google.golang.org/protobuf v1.33.0 ) replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/passthrough/go.mod b/contrib/golang/filters/http/test/test_data/passthrough/go.mod index 3a42612f666c0..ac9f86a83f761 100644 --- a/contrib/golang/filters/http/test/test_data/passthrough/go.mod +++ b/contrib/golang/filters/http/test/test_data/passthrough/go.mod @@ -4,6 +4,6 @@ go 1.18 require github.com/envoyproxy/envoy v1.24.0 -require google.golang.org/protobuf v1.30.0 // indirect +require google.golang.org/protobuf v1.33.0 // indirect replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/routeconfig/go.mod b/contrib/golang/filters/http/test/test_data/routeconfig/go.mod index 2df147ea5ed16..5522e19383203 100644 --- a/contrib/golang/filters/http/test/test_data/routeconfig/go.mod +++ b/contrib/golang/filters/http/test/test_data/routeconfig/go.mod @@ -10,10 +10,9 @@ require ( require github.com/google/go-cmp v0.5.9 // indirect require ( - github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/protobuf v1.31.0 + github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + google.golang.org/protobuf v1.33.0 ) replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/network/test/test_data/go.mod b/contrib/golang/filters/network/test/test_data/go.mod index 8c20e9bd14f6f..e1a332e4375cf 100644 --- a/contrib/golang/filters/network/test/test_data/go.mod +++ b/contrib/golang/filters/network/test/test_data/go.mod @@ -4,6 +4,6 @@ go 1.18 require github.com/envoyproxy/envoy v1.24.0 -require google.golang.org/protobuf v1.30.0 // indirect +require google.golang.org/protobuf v1.33.0 // indirect replace github.com/envoyproxy/envoy => ../../../../../../ diff --git a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod index 257fa217303e3..de5c0b40abc0b 100644 --- a/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod +++ b/contrib/golang/router/cluster_specifier/test/test_data/simple/go.mod @@ -12,8 +12,7 @@ require github.com/google/go-cmp v0.5.9 // indirect require ( github.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect github.com/golang/protobuf v1.5.3 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/protobuf v1.31.0 + google.golang.org/protobuf v1.33.0 ) replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/examples/golang-http/simple/go.mod b/examples/golang-http/simple/go.mod index 2e96bd4b9d109..2b66642c61ac9 100644 --- a/examples/golang-http/simple/go.mod +++ b/examples/golang-http/simple/go.mod @@ -7,7 +7,7 @@ go 1.18 require ( github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 github.com/envoyproxy/envoy v1.24.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/protobuf v1.33.0 ) require ( diff --git a/examples/golang-http/simple/go.sum b/examples/golang-http/simple/go.sum index 67cbb674021e2..029b8a72e33d7 100644 --- a/examples/golang-http/simple/go.sum +++ b/examples/golang-http/simple/go.sum @@ -52,7 +52,7 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/examples/golang-network/simple/go.mod b/examples/golang-network/simple/go.mod index 19bb4281e3156..7630bceefeb01 100644 --- a/examples/golang-network/simple/go.mod +++ b/examples/golang-network/simple/go.mod @@ -7,7 +7,7 @@ go 1.18 require ( github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 github.com/envoyproxy/envoy v1.24.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/protobuf v1.33.0 ) require ( diff --git a/examples/golang-network/simple/go.sum b/examples/golang-network/simple/go.sum index 31c4080a6846b..fc5a07fdebac8 100644 --- a/examples/golang-network/simple/go.sum +++ b/examples/golang-network/simple/go.sum @@ -52,7 +52,7 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/examples/grpc-bridge/client/requirements.in b/examples/grpc-bridge/client/requirements.in index 9a455e6a7388d..2d844028ba97a 100644 --- a/examples/grpc-bridge/client/requirements.in +++ b/examples/grpc-bridge/client/requirements.in @@ -1,5 +1,7 @@ requests>=2.22.0 grpcio grpcio-tools +idna>=3.7 protobuf>=3.18.0 +requests>=2.32.0 urllib3>=2.0.7 diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index ede2c17ed3e2e..e1a0f5bd55b5a 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -108,10 +108,12 @@ grpcio-tools==1.56.2 \ --hash=sha256:ff16dd0b086e75f574dbc122e018a44dbd1c6dae3f3621ea99e8e5a6b2706e12 \ --hash=sha256:ffae7df3318266614f7aa440acb2098c064b6b5ae061fc22125092386349e526 # via -r requirements.in -idna==3.2 \ - --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \ - --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 - # via requests +idna==3.7 \ + --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ + --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 + # via + # -r requirements.in + # requests protobuf==4.23.4 \ --hash=sha256:0a5759f5696895de8cc913f084e27fd4125e8fb0914bb729a17816a33819f474 \ --hash=sha256:351cc90f7d10839c480aeb9b870a211e322bf05f6ab3f55fcb2f51331f80a7d2 \ @@ -129,9 +131,9 @@ protobuf==4.23.4 \ # via # -r requirements.in # grpcio-tools -requests==2.31.0 \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 +requests==2.32.1 \ + --hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \ + --hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685 # via -r requirements.in urllib3==2.1.0 \ --hash=sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3 \ diff --git a/examples/grpc-bridge/server/go.mod b/examples/grpc-bridge/server/go.mod index 893351a62f470..f8ae2a37b9ed3 100644 --- a/examples/grpc-bridge/server/go.mod +++ b/examples/grpc-bridge/server/go.mod @@ -4,6 +4,6 @@ go 1.13 require ( github.com/golang/protobuf v1.5.3 - golang.org/x/net v0.17.0 + golang.org/x/net v0.23.0 google.golang.org/grpc v1.56.3 ) diff --git a/examples/grpc-bridge/server/go.sum b/examples/grpc-bridge/server/go.sum index fed580182a516..f89fa36c09988 100644 --- a/examples/grpc-bridge/server/go.sum +++ b/examples/grpc-bridge/server/go.sum @@ -863,7 +863,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +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= @@ -979,8 +980,9 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.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= @@ -1104,8 +1106,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1115,7 +1118,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +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= @@ -1132,8 +1136,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/examples/shared/python/aiohttp/requirements.in b/examples/shared/python/aiohttp/requirements.in index 909fe563b01ca..3571efcf467a6 100644 --- a/examples/shared/python/aiohttp/requirements.in +++ b/examples/shared/python/aiohttp/requirements.in @@ -1,2 +1,3 @@ -aiohttp>=3.9.2 +aiohttp>=3.9.4 +idna>=3.7 pyyaml diff --git a/examples/shared/python/aiohttp/requirements.txt b/examples/shared/python/aiohttp/requirements.txt index d34e0f83f6548..aecfecd2608b5 100644 --- a/examples/shared/python/aiohttp/requirements.txt +++ b/examples/shared/python/aiohttp/requirements.txt @@ -4,83 +4,83 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -aiohttp==3.9.3 \ - --hash=sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168 \ - --hash=sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb \ - --hash=sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5 \ - --hash=sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f \ - --hash=sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc \ - --hash=sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c \ - --hash=sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29 \ - --hash=sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4 \ - --hash=sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc \ - --hash=sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc \ - --hash=sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63 \ - --hash=sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e \ - --hash=sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d \ - --hash=sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a \ - --hash=sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60 \ - --hash=sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38 \ - --hash=sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b \ - --hash=sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2 \ - --hash=sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53 \ - --hash=sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5 \ - --hash=sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4 \ - --hash=sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96 \ - --hash=sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58 \ - --hash=sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa \ - --hash=sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321 \ - --hash=sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae \ - --hash=sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce \ - --hash=sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8 \ - --hash=sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194 \ - --hash=sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c \ - --hash=sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf \ - --hash=sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d \ - --hash=sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869 \ - --hash=sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b \ - --hash=sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52 \ - --hash=sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528 \ - --hash=sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5 \ - --hash=sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1 \ - --hash=sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4 \ - --hash=sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8 \ - --hash=sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d \ - --hash=sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7 \ - --hash=sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5 \ - --hash=sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54 \ - --hash=sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3 \ - --hash=sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5 \ - --hash=sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c \ - --hash=sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29 \ - --hash=sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3 \ - --hash=sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747 \ - --hash=sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672 \ - --hash=sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5 \ - --hash=sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11 \ - --hash=sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca \ - --hash=sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768 \ - --hash=sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6 \ - --hash=sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2 \ - --hash=sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533 \ - --hash=sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6 \ - --hash=sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266 \ - --hash=sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d \ - --hash=sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec \ - --hash=sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5 \ - --hash=sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1 \ - --hash=sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b \ - --hash=sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679 \ - --hash=sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283 \ - --hash=sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb \ - --hash=sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b \ - --hash=sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3 \ - --hash=sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051 \ - --hash=sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511 \ - --hash=sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e \ - --hash=sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d \ - --hash=sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542 \ - --hash=sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f +aiohttp==3.9.5 \ + --hash=sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8 \ + --hash=sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c \ + --hash=sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475 \ + --hash=sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed \ + --hash=sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf \ + --hash=sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372 \ + --hash=sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81 \ + --hash=sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f \ + --hash=sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1 \ + --hash=sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd \ + --hash=sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a \ + --hash=sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb \ + --hash=sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46 \ + --hash=sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de \ + --hash=sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78 \ + --hash=sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c \ + --hash=sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771 \ + --hash=sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb \ + --hash=sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430 \ + --hash=sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233 \ + --hash=sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156 \ + --hash=sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9 \ + --hash=sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59 \ + --hash=sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888 \ + --hash=sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c \ + --hash=sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c \ + --hash=sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da \ + --hash=sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424 \ + --hash=sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2 \ + --hash=sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb \ + --hash=sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8 \ + --hash=sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a \ + --hash=sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10 \ + --hash=sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0 \ + --hash=sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09 \ + --hash=sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031 \ + --hash=sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4 \ + --hash=sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3 \ + --hash=sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa \ + --hash=sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a \ + --hash=sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe \ + --hash=sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a \ + --hash=sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2 \ + --hash=sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1 \ + --hash=sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323 \ + --hash=sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b \ + --hash=sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b \ + --hash=sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106 \ + --hash=sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac \ + --hash=sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6 \ + --hash=sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832 \ + --hash=sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75 \ + --hash=sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6 \ + --hash=sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d \ + --hash=sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72 \ + --hash=sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db \ + --hash=sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a \ + --hash=sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da \ + --hash=sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678 \ + --hash=sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b \ + --hash=sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24 \ + --hash=sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed \ + --hash=sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f \ + --hash=sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e \ + --hash=sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58 \ + --hash=sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a \ + --hash=sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342 \ + --hash=sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558 \ + --hash=sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2 \ + --hash=sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551 \ + --hash=sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595 \ + --hash=sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee \ + --hash=sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11 \ + --hash=sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d \ + --hash=sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7 \ + --hash=sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f # via -r requirements.in aiosignal==1.3.1 \ --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ @@ -168,10 +168,12 @@ frozenlist==1.3.3 \ # via # aiohttp # aiosignal -idna==3.4 \ - --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ - --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 - # via yarl +idna==3.7 \ + --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ + --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 + # via + # -r requirements.in + # yarl multidict==6.0.4 \ --hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \ --hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \ diff --git a/go.mod b/go.mod index 61b62bbe8bfec..5a9ad90398549 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/envoyproxy/envoy go 1.18 -require google.golang.org/protobuf v1.31.0 +require google.golang.org/protobuf v1.33.0 diff --git a/go.sum b/go.sum index 9ea5597b83466..3575a38aca0fa 100644 --- a/go.sum +++ b/go.sum @@ -4,5 +4,5 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 5984e86f5cc4e..d49b97423f6f7 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -1,13 +1,13 @@ abstracts>=0.0.12 aio.api.bazel aio.api.github>=0.2.5 -aiohttp>=3.9.2 +aiohttp>=3.9.4 cffi>=1.15.0 clang-format==14.0.6 clang-tidy==14.0.6 colorama coloredlogs -cryptography>=42.0.0 +cryptography>=42.0.4 dependatool>=0.2.2 envoy.base.utils>=0.4.16 envoy.code.check>=0.5.8 @@ -23,9 +23,10 @@ frozendict>=2.3.7 gitpython>=3.1.41 google-auth[aiohttp]>=2.23.3 gsutil>=5.26 -jinja2>=3.1.3 +idna>=3.7 +jinja2>=3.1.4 multidict>=6.0.2 -orjson +orjson>=3.10.3 pep8-naming ply # Upgrading beyond 4.21.x doesnt currently work. 4.23+ might work when the following is resolved @@ -34,6 +35,7 @@ protobuf<4.22.0 pygithub pyreadline pyyaml +requests>=2.32.0 setuptools slackclient sphinx>=7 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 8b0acd05a82d2..f887532776211 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -86,83 +86,83 @@ aiofiles==23.1.0 \ --hash=sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2 \ --hash=sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635 # via envoy-github-release -aiohttp==3.9.3 \ - --hash=sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168 \ - --hash=sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb \ - --hash=sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5 \ - --hash=sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f \ - --hash=sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc \ - --hash=sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c \ - --hash=sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29 \ - --hash=sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4 \ - --hash=sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc \ - --hash=sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc \ - --hash=sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63 \ - --hash=sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e \ - --hash=sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d \ - --hash=sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a \ - --hash=sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60 \ - --hash=sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38 \ - --hash=sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b \ - --hash=sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2 \ - --hash=sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53 \ - --hash=sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5 \ - --hash=sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4 \ - --hash=sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96 \ - --hash=sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58 \ - --hash=sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa \ - --hash=sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321 \ - --hash=sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae \ - --hash=sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce \ - --hash=sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8 \ - --hash=sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194 \ - --hash=sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c \ - --hash=sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf \ - --hash=sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d \ - --hash=sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869 \ - --hash=sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b \ - --hash=sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52 \ - --hash=sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528 \ - --hash=sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5 \ - --hash=sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1 \ - --hash=sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4 \ - --hash=sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8 \ - --hash=sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d \ - --hash=sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7 \ - --hash=sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5 \ - --hash=sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54 \ - --hash=sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3 \ - --hash=sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5 \ - --hash=sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c \ - --hash=sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29 \ - --hash=sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3 \ - --hash=sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747 \ - --hash=sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672 \ - --hash=sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5 \ - --hash=sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11 \ - --hash=sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca \ - --hash=sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768 \ - --hash=sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6 \ - --hash=sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2 \ - --hash=sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533 \ - --hash=sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6 \ - --hash=sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266 \ - --hash=sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d \ - --hash=sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec \ - --hash=sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5 \ - --hash=sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1 \ - --hash=sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b \ - --hash=sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679 \ - --hash=sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283 \ - --hash=sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb \ - --hash=sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b \ - --hash=sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3 \ - --hash=sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051 \ - --hash=sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511 \ - --hash=sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e \ - --hash=sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d \ - --hash=sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542 \ - --hash=sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f +aiohttp==3.9.5 \ + --hash=sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8 \ + --hash=sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c \ + --hash=sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475 \ + --hash=sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed \ + --hash=sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf \ + --hash=sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372 \ + --hash=sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81 \ + --hash=sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f \ + --hash=sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1 \ + --hash=sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd \ + --hash=sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a \ + --hash=sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb \ + --hash=sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46 \ + --hash=sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de \ + --hash=sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78 \ + --hash=sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c \ + --hash=sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771 \ + --hash=sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb \ + --hash=sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430 \ + --hash=sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233 \ + --hash=sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156 \ + --hash=sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9 \ + --hash=sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59 \ + --hash=sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888 \ + --hash=sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c \ + --hash=sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c \ + --hash=sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da \ + --hash=sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424 \ + --hash=sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2 \ + --hash=sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb \ + --hash=sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8 \ + --hash=sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a \ + --hash=sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10 \ + --hash=sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0 \ + --hash=sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09 \ + --hash=sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031 \ + --hash=sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4 \ + --hash=sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3 \ + --hash=sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa \ + --hash=sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a \ + --hash=sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe \ + --hash=sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a \ + --hash=sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2 \ + --hash=sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1 \ + --hash=sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323 \ + --hash=sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b \ + --hash=sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b \ + --hash=sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106 \ + --hash=sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac \ + --hash=sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6 \ + --hash=sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832 \ + --hash=sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75 \ + --hash=sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6 \ + --hash=sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d \ + --hash=sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72 \ + --hash=sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db \ + --hash=sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a \ + --hash=sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da \ + --hash=sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678 \ + --hash=sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b \ + --hash=sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24 \ + --hash=sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed \ + --hash=sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f \ + --hash=sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e \ + --hash=sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58 \ + --hash=sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a \ + --hash=sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342 \ + --hash=sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558 \ + --hash=sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2 \ + --hash=sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551 \ + --hash=sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595 \ + --hash=sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee \ + --hash=sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11 \ + --hash=sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d \ + --hash=sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7 \ + --hash=sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f # via # -r requirements.in # aio-api-github @@ -388,39 +388,39 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==42.0.2 \ - --hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \ - --hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \ - --hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \ - --hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \ - --hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \ - --hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \ - --hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \ - --hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \ - --hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \ - --hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \ - --hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \ - --hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \ - --hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \ - --hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \ - --hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \ - --hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \ - --hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \ - --hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \ - --hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \ - --hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \ - --hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \ - --hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \ - --hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \ - --hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \ - --hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \ - --hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \ - --hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \ - --hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \ - --hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \ - --hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \ - --hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \ - --hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f +cryptography==42.0.7 \ + --hash=sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55 \ + --hash=sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785 \ + --hash=sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b \ + --hash=sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886 \ + --hash=sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82 \ + --hash=sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1 \ + --hash=sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda \ + --hash=sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f \ + --hash=sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68 \ + --hash=sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60 \ + --hash=sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7 \ + --hash=sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd \ + --hash=sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582 \ + --hash=sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc \ + --hash=sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858 \ + --hash=sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b \ + --hash=sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2 \ + --hash=sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678 \ + --hash=sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13 \ + --hash=sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4 \ + --hash=sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8 \ + --hash=sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604 \ + --hash=sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477 \ + --hash=sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e \ + --hash=sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a \ + --hash=sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9 \ + --hash=sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14 \ + --hash=sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda \ + --hash=sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da \ + --hash=sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562 \ + --hash=sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2 \ + --hash=sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9 # via # -r requirements.in # pyjwt @@ -676,10 +676,11 @@ humanfriendly==10.0 \ --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc # via coloredlogs -idna==3.4 \ - --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ - --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 +idna==3.7 \ + --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ + --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 # via + # -r requirements.in # requests # yarl imagesize==1.4.1 \ @@ -690,9 +691,9 @@ importlib-metadata==6.8.0 \ --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 # via yapf -jinja2==3.1.3 \ - --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ - --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 +jinja2==3.1.4 \ + --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ + --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via # -r requirements.in # envoy-base-utils @@ -843,53 +844,53 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.2 \ - --hash=sha256:00c983896c2e01c94c0ef72fd7373b2aa06d0c0eed0342c4884559f812a6835b \ - --hash=sha256:02ef014f9a605e84b675060785e37ec9c0d2347a04f1307a9d6840ab8ecd6f55 \ - --hash=sha256:0325fe2d69512187761f7368c8cda1959bcb75fc56b8e7a884e9569112320e57 \ - --hash=sha256:03fb36f187a0c19ff38f6289418863df8b9b7880cdbe279e920bef3a09d8dab1 \ - --hash=sha256:0b9a26f1d1427a9101a1e8910f2e2df1f44d3d18ad5480ba031b15d5c1cb282e \ - --hash=sha256:1272688ea1865f711b01ba479dea2d53e037ea00892fd04196b5875f7021d9d3 \ - --hash=sha256:16fdf5a82df80c544c3c91516ab3882cd1ac4f1f84eefeafa642e05cef5f6699 \ - --hash=sha256:1882a70bb69595b9ec5aac0040a819e94d2833fe54901e2b32f5e734bc259a8b \ - --hash=sha256:1a6cdfcf9c7dd4026b2b01fdff56986251dc0cc1e980c690c79eec3ae07b36e7 \ - --hash=sha256:1aaa46d7d4ae55335f635eadc9be0bd9bcf742e6757209fc6dc697e390010adc \ - --hash=sha256:205925b179550a4ee39b8418dd4c94ad6b777d165d7d22614771c771d44f57bd \ - --hash=sha256:20925d07a97c49c6305bff1635318d9fc1804aa4ccacb5fb0deb8a910e57d97a \ - --hash=sha256:24257c8f641979bf25ecd3e27251b5cc194cdd3a6e96004aac8446f5e63d9664 \ - --hash=sha256:275b5a18fd9ed60b2720543d3ddac170051c43d680e47d04ff5203d2c6d8ebf1 \ - --hash=sha256:2e52c67ed6bb368083aa2078ea3ccbd9721920b93d4b06c43eb4e20c4c860046 \ - --hash=sha256:2ee743e8890b16c87a2f89733f983370672272b61ee77429c0a5899b2c98c1a7 \ - --hash=sha256:3164fc20a585ec30a9aff33ad5de3b20ce85702b2b2a456852c413e3f0d7ab09 \ - --hash=sha256:3245d230370f571c945f69aab823c279a868dc877352817e22e551de155cb06c \ - --hash=sha256:368e9cc91ecb7ac21f2aa475e1901204110cf3e714e98649c2502227d248f947 \ - --hash=sha256:4a39c2529d75373b7167bf84c814ef9b8f3737a339c225ed6c0df40736df8748 \ - --hash=sha256:58e9e70f0dcd6a802c35887f306b555ff7a214840aad7de24901fc8bd9cf5dde \ - --hash=sha256:5a60a1cfcfe310547a1946506dd4f1ed0a7d5bd5b02c8697d9d5dcd8d2e9245e \ - --hash=sha256:6320b28e7bdb58c3a3a5efffe04b9edad3318d82409e84670a9b24e8035a249d \ - --hash=sha256:6a5ca55b0d8f25f18b471e34abaee4b175924b6cd62f59992945b25963443141 \ - --hash=sha256:7323e4ca8322b1ecb87562f1ec2491831c086d9faa9a6c6503f489dadbed37d7 \ - --hash=sha256:7a6ccadf788531595ed4728aa746bc271955448d2460ff0ef8e21eb3f2a281ba \ - --hash=sha256:7d74ae0e101d17c22ef67b741ba356ab896fc0fa64b301c2bf2bb0a4d874b190 \ - --hash=sha256:806704cd58708acc66a064a9a58e3be25cf1c3f9f159e8757bd3f515bfabdfa1 \ - --hash=sha256:8170157288714678ffd64f5de33039e1164a73fd8b6be40a8a273f80093f5c4f \ - --hash=sha256:84ebd6fdf138eb0eb4280045442331ee71c0aab5e16397ba6645f32f911bfb37 \ - --hash=sha256:869b961df5fcedf6c79f4096119b35679b63272362e9b745e668f0391a892d39 \ - --hash=sha256:877872db2c0f41fbe21f852ff642ca842a43bc34895b70f71c9d575df31fffb4 \ - --hash=sha256:8cd4385c59bbc1433cad4a80aca65d2d9039646a9c57f8084897549b55913b17 \ - --hash=sha256:93864dec3e3dd058a2dbe488d11ac0345214a6a12697f53a63e34de7d28d4257 \ - --hash=sha256:992af54265ada1c1579500d6594ed73fe333e726de70d64919cf37f93defdd06 \ - --hash=sha256:a40958f7af7c6d992ee67b2da4098dca8b770fc3b4b3834d540477788bfa76d3 \ - --hash=sha256:a74036aab1a80c361039290cdbc51aa7adc7ea13f56e5ef94e9be536abd227bd \ - --hash=sha256:b7b065942d362aad4818ff599d2f104c35a565c2cbcbab8c09ec49edba91da75 \ - --hash=sha256:b9aea6dcb99fcbc9f6d1dd84fca92322fda261da7fb014514bb4689c7c2097a8 \ - --hash=sha256:c290c4f81e8fd0c1683638802c11610b2f722b540f8e5e858b6914b495cf90c8 \ - --hash=sha256:d7de3dbbe74109ae598692113cec327fd30c5a30ebca819b21dfa4052f7b08ef \ - --hash=sha256:e3e2f087161947dafe8319ea2cfcb9cea4bb9d2172ecc60ac3c9738f72ef2909 \ - --hash=sha256:e46e9c5b404bb9e41d5555762fd410d5466b7eb1ec170ad1b1609cbebe71df21 \ - --hash=sha256:eebfed53bec5674e981ebe8ed2cf00b3f7bcda62d634733ff779c264307ea505 \ - --hash=sha256:f8bc2c40d9bb26efefb10949d261a47ca196772c308babc538dd9f4b73e8d386 \ - --hash=sha256:fc05e060d452145ab3c0b5420769e7356050ea311fc03cb9d79c481982917cca +orjson==3.10.3 \ + --hash=sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2 \ + --hash=sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b \ + --hash=sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5 \ + --hash=sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b \ + --hash=sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0 \ + --hash=sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0 \ + --hash=sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7 \ + --hash=sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42 \ + --hash=sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5 \ + --hash=sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa \ + --hash=sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818 \ + --hash=sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25 \ + --hash=sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08 \ + --hash=sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7 \ + --hash=sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b \ + --hash=sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754 \ + --hash=sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0 \ + --hash=sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912 \ + --hash=sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b \ + --hash=sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3 \ + --hash=sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700 \ + --hash=sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78 \ + --hash=sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290 \ + --hash=sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134 \ + --hash=sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294 \ + --hash=sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0 \ + --hash=sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5 \ + --hash=sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d \ + --hash=sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d \ + --hash=sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16 \ + --hash=sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195 \ + --hash=sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da \ + --hash=sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8 \ + --hash=sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098 \ + --hash=sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8 \ + --hash=sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063 \ + --hash=sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534 \ + --hash=sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d \ + --hash=sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109 \ + --hash=sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d \ + --hash=sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3 \ + --hash=sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2 \ + --hash=sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf \ + --hash=sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069 \ + --hash=sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7 \ + --hash=sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176 # via # -r requirements.in # envoy-base-utils @@ -1066,10 +1067,11 @@ pyyaml==6.0.1 \ # aio-core # envoy-base-utils # yamllint -requests==2.31.0 \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 +requests==2.32.1 \ + --hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \ + --hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685 # via + # -r requirements.in # google-auth # pygithub # sphinx From e7dbdb97f016668ebb6c71cc98d10c959eab0d0f Mon Sep 17 00:00:00 2001 From: wbpcode Date: Wed, 24 Jan 2024 10:28:23 +0000 Subject: [PATCH 211/274] fix brotli decompression endless loop Signed-off-by: wbpcode --- changelogs/current.yaml | 4 ++ .../decompressor/brotli_decompressor_impl.cc | 51 +++++++++++++------ .../decompressor/brotli_decompressor_impl.h | 5 +- .../brotli_decompressor_impl_test.cc | 45 ++++++++++++++++ 4 files changed, 89 insertions(+), 16 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 943e660501b18..f262b7fa0a853 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -12,6 +12,10 @@ bug_fixes: change: | Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. +- area: decompression + change: | + Fixed a bug where Envoy will go into an endless loop when using the brotli decompressor. If the input stream has + redundant data, the decompressor will loop forever. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.cc b/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.cc index eb1bb144baa51..d096179d17176 100644 --- a/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.cc +++ b/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.cc @@ -60,26 +60,47 @@ void BrotliDecompressorImpl::decompress(const Buffer::Instance& input_buffer, } bool BrotliDecompressorImpl::process(Common::BrotliContext& ctx, Buffer::Instance& output_buffer) { - BrotliDecoderResult result; - result = BrotliDecoderDecompressStream(state_.get(), &ctx.avail_in_, &ctx.next_in_, - &ctx.avail_out_, &ctx.next_out_, nullptr); - if (result == BROTLI_DECODER_RESULT_ERROR) { - // TODO(rojkov): currently the Brotli library doesn't specify possible errors in its API. Add - // more detailed stats when they are documented. - stats_.brotli_error_.inc(); - return false; - } + BrotliDecoderResult result = BrotliDecoderDecompressStream( + state_.get(), &ctx.avail_in_, &ctx.next_in_, &ctx.avail_out_, &ctx.next_out_, nullptr); + + switch (result) { + case BROTLI_DECODER_RESULT_SUCCESS: + // The decompression is done successfully but there is still some input left. + // We treat this as an error and stop the decompression directly to avoid + // possible endless loop. + if (ctx.avail_in_ > 0) { + stats_.brotli_error_.inc(); + stats_.brotli_redundant_input_.inc(); + return false; + } + // The decompression is done successfully and fall through to the next case + // to check if the output buffer is full and flush chunk to the output buffer. + FALLTHRU; + case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: + ASSERT(ctx.avail_in_ == 0); + FALLTHRU; + case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: + // Check if the output buffer is full first. If it is full then we treat it + // as an error and stop the decompression directly to avoid possible decompression + // bomb. + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.enable_compression_bomb_protection") && + (output_buffer.length() > ctx.max_output_size_)) { + stats_.brotli_error_.inc(); + stats_.brotli_output_overflow_.inc(); + return false; + } - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.enable_compression_bomb_protection") && - (output_buffer.length() > ctx.max_output_size_)) { + // If current chunk is full then flush it to the output buffer and reset + // the chunk or do nothing. + ctx.updateOutput(output_buffer); + return true; + case BROTLI_DECODER_RESULT_ERROR: stats_.brotli_error_.inc(); return false; } - ctx.updateOutput(output_buffer); - - return true; + PANIC("Unexpected BrotliDecoderResult"); } } // namespace Decompressor diff --git a/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.h b/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.h index f55f6a7c545ac..1f3328807fb3f 100644 --- a/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.h +++ b/source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.h @@ -17,7 +17,10 @@ namespace Decompressor { /** * All brotli decompressor stats. @see stats_macros.h */ -#define ALL_BROTLI_DECOMPRESSOR_STATS(COUNTER) COUNTER(brotli_error) +#define ALL_BROTLI_DECOMPRESSOR_STATS(COUNTER) \ + COUNTER(brotli_error) /*Decompression error of all.*/ \ + COUNTER(brotli_output_overflow) /*Decompression error because of the overflow output.*/ \ + COUNTER(brotli_redundant_input) /*Decompression error because of the redundant input.*/ /** * Struct definition for brotli decompressor stats. @see stats_macros.h diff --git a/test/extensions/compression/brotli/decompressor/brotli_decompressor_impl_test.cc b/test/extensions/compression/brotli/decompressor/brotli_decompressor_impl_test.cc index 514a5e0fd2b52..4b2451c04d49e 100644 --- a/test/extensions/compression/brotli/decompressor/brotli_decompressor_impl_test.cc +++ b/test/extensions/compression/brotli/decompressor/brotli_decompressor_impl_test.cc @@ -106,6 +106,51 @@ TEST_F(BrotliDecompressorImplTest, CompressAndDecompress) { EXPECT_EQ(original_text, decompressed_text); } +TEST_F(BrotliDecompressorImplTest, CompressAndDecompressWithRedundantInput) { + Buffer::OwnedImpl buffer; + Buffer::OwnedImpl accumulation_buffer; + + Brotli::Compressor::BrotliCompressorImpl compressor{ + default_quality, + default_window_bits, + default_input_block_bits, + false, + Brotli::Compressor::BrotliCompressorImpl::EncoderMode::Default, + 4096}; + + std::string original_text{}; + for (uint64_t i = 0; i < 20; ++i) { + TestUtility::feedBufferWithRandomCharacters(buffer, default_input_size * i, i); + original_text.append(buffer.toString()); + compressor.compress(buffer, Envoy::Compression::Compressor::State::Flush); + accumulation_buffer.add(buffer); + drainBuffer(buffer); + } + + ASSERT_EQ(0, buffer.length()); + + compressor.compress(buffer, Envoy::Compression::Compressor::State::Finish); + ASSERT_GE(10, buffer.length()); + + accumulation_buffer.add(buffer); + accumulation_buffer.add("redundant_input_here"); // Add some redundant input. + + drainBuffer(buffer); + ASSERT_EQ(0, buffer.length()); + + Stats::IsolatedStoreImpl stats_store{}; + BrotliDecompressorImpl decompressor{*stats_store.rootScope(), "test.", 16, false}; + decompressor.decompress(accumulation_buffer, buffer); + std::string decompressed_text{buffer.toString()}; + ASSERT_EQ(original_text.length(), decompressed_text.length()); + EXPECT_EQ(original_text, decompressed_text); + + // Although we finally get the original text, we still have some redundant input and + // the decompression is considered as failed. + EXPECT_EQ(1, stats_store.counterFromString("test.brotli_error").value()); + EXPECT_EQ(1, stats_store.counterFromString("test.brotli_redundant_input").value()); +} + // Exercises decompression with a very small output buffer. TEST_F(BrotliDecompressorImplTest, DecompressWithSmallOutputBuffer) { Buffer::OwnedImpl buffer; From 4017ec6087d1d5f151e1864d196ec3fa45c80f08 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 18 Apr 2024 21:29:11 +0000 Subject: [PATCH 212/274] quic: add 2 quiche patches Signed-off-by: Dan Zhang Signed-off-by: Ryan Northey --- bazel/external/quiche_sequencer_fix.patch | 16 ++++++++ bazel/external/quiche_stream_fix.patch | 50 +++++++++++++++++++++++ bazel/repositories.bzl | 5 +++ changelogs/current.yaml | 3 ++ 4 files changed, 74 insertions(+) create mode 100644 bazel/external/quiche_sequencer_fix.patch create mode 100644 bazel/external/quiche_stream_fix.patch diff --git a/bazel/external/quiche_sequencer_fix.patch b/bazel/external/quiche_sequencer_fix.patch new file mode 100644 index 0000000000000..b4203e92b6e31 --- /dev/null +++ b/bazel/external/quiche_sequencer_fix.patch @@ -0,0 +1,16 @@ +# Fix https://github.com/envoyproxy/envoy-setec/issues/1496#issue-2251291349 + +diff --git a/quiche/quic/core/quic_stream_sequencer_buffer.cc b/quiche/quic/core/quic_stream_sequencer_buffer.cc +index d364d61bc..0966af4b0 100644 +--- a/quiche/quic/core/quic_stream_sequencer_buffer.cc ++++ b/quiche/quic/core/quic_stream_sequencer_buffer.cc +@@ -388,7 +388,8 @@ bool QuicStreamSequencerBuffer::PeekRegion(QuicStreamOffset offset, + + // Determine if entire block has been received. + size_t end_block_idx = GetBlockIndex(FirstMissingByte()); +- if (block_idx == end_block_idx) { ++ if (block_idx == end_block_idx && ++ block_offset < GetInBlockOffset(FirstMissingByte())) { + // Only read part of block before FirstMissingByte(). + iov->iov_len = GetInBlockOffset(FirstMissingByte()) - block_offset; + } else { diff --git a/bazel/external/quiche_stream_fix.patch b/bazel/external/quiche_stream_fix.patch new file mode 100644 index 0000000000000..b5a777a3af083 --- /dev/null +++ b/bazel/external/quiche_stream_fix.patch @@ -0,0 +1,50 @@ +# Fix https://github.com/envoyproxy/envoy-setec/issues/1496#issuecomment-2064844217 + +diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc +index 4a5c2ede2..d69895055 100644 +--- a/quiche/quic/core/http/quic_spdy_stream.cc ++++ b/quiche/quic/core/http/quic_spdy_stream.cc +@@ -1865,6 +1865,18 @@ bool QuicSpdyStream::AreHeaderFieldValuesValid( + return true; + } + ++void QuicSpdyStream::StopReading() { ++ QuicStream::StopReading(); ++ if (GetQuicReloadableFlag( ++ quic_stop_reading_also_stops_header_decompression) && ++ VersionUsesHttp3(transport_version()) && !fin_received() && ++ spdy_session_->qpack_decoder()) { ++ // Clean up Qpack decoding states. ++ spdy_session_->qpack_decoder()->OnStreamReset(id()); ++ qpack_decoded_headers_accumulator_.reset(); ++ } ++} ++ + void QuicSpdyStream::OnInvalidHeaders() { Reset(QUIC_BAD_APPLICATION_PAYLOAD); } + + void QuicSpdyStream::CloseReadSide() { +diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h +index 10c34b10f..5c0cb0128 100644 +--- a/quiche/quic/core/http/quic_spdy_stream.h ++++ b/quiche/quic/core/http/quic_spdy_stream.h +@@ -117,6 +117,7 @@ class QUICHE_EXPORT QuicSpdyStream + + // QuicStream implementation + void OnClose() override; ++ void StopReading() override; + + // Override to maybe close the write side after writing. + void OnCanWrite() override; +diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h +index d2b1864ee..044d9f8ce 100644 +--- a/quiche/quic/core/quic_flags_list.h ++++ b/quiche/quic/core/quic_flags_list.h +@@ -117,6 +117,8 @@ QUIC_FLAG(quic_reloadable_flag_quic_bbr2_probe_two_rounds, true) + QUIC_FLAG(quic_reloadable_flag_quic_bbr2_simplify_inflight_hi, true) + // When true, the BBR4 copt sets the extra_acked window to 20 RTTs and BBR5 sets it to 40 RTTs. + QUIC_FLAG(quic_reloadable_flag_quic_bbr2_extra_acked_window, true) ++// If true, QUIC stream will not continue decompressing buffer headers after StopReading() called. ++QUIC_FLAG(quic_reloadable_flag_quic_stop_reading_also_stops_header_decompression, true) + + #endif + diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 59615dc6bf2c3..b81fc9710c160 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -1003,6 +1003,11 @@ def _com_github_google_quiche(): external_http_archive( name = "com_github_google_quiche", patch_cmds = ["find quiche/ -type f -name \"*.bazel\" -delete"], + patches = [ + "@envoy//bazel/external:quiche_sequencer_fix.patch", + "@envoy//bazel/external:quiche_stream_fix.patch", + ], + patch_args = ["-p1"], build_file = "@envoy//bazel/external:quiche.BUILD", ) native.bind( diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f262b7fa0a853..97c52dfdb6bd5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -12,6 +12,9 @@ bug_fixes: change: | Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. +- area: quic + change: | + Applied 2 QUICHE patches for crash bugs in ``QuicSpdyStream`` ``OnDataAvailable()`` and ``OnInitialHeaderComplete()``. - area: decompression change: | Fixed a bug where Envoy will go into an endless loop when using the brotli decompressor. If the input stream has From 0731ee729838027247406dbf6d045d8eb12ef266 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 18 Mar 2024 15:30:07 +0000 Subject: [PATCH 213/274] quic: fix crash from `EnvoyQuicServerSession::OnConnectionClosed()` Signed-off-by: Dan Zhang Signed-off-by: Ryan Northey --- changelogs/current.yaml | 3 +++ .../common/quic/envoy_quic_server_stream.cc | 5 +++- .../quic/envoy_quic_server_session_test.cc | 4 +-- .../integration/quic_http_integration_test.cc | 26 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 97c52dfdb6bd5..8f5ffb1151ab7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -15,6 +15,9 @@ bug_fixes: - area: quic change: | Applied 2 QUICHE patches for crash bugs in ``QuicSpdyStream`` ``OnDataAvailable()`` and ``OnInitialHeaderComplete()``. +- area: quic + change: | + Fixed crash bug when QUIC downstream stream was read closed and then timed out. - area: decompression change: | Fixed a bug where Envoy will go into an endless loop when using the brotli decompressor. If the input stream has diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index 003ff29c5b777..472011e1dbdef 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -193,10 +193,13 @@ void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { // of propagating original reset reason. In QUICHE if a stream stops reading // before FIN or RESET received, it resets the steam with QUIC_STREAM_NO_ERROR. StopReading(); - runResetCallbacks(Http::StreamResetReason::LocalReset); } else { Reset(envoyResetReasonToQuicRstError(reason)); } + // Run reset callbacks once because HCM calls resetStream() without tearing + // down its own ActiveStream. It might be no-op if it has been called already + // in ResetWithError(). + runResetCallbacks(Http::StreamResetReason::LocalReset); } void EnvoyQuicServerStream::switchStreamBlockState() { diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index 9d2fa1762d346..116c7ca1acea4 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -1018,11 +1018,11 @@ TEST_F(EnvoyQuicServerSessionTest, SendBufferWatermark) { EXPECT_TRUE(stream2->IsFlowControlBlocked()); // Resetting stream3 should lower the buffered bytes, but callbacks will not - // be triggered because end stream is already encoded. + // be triggered because end stream is already decoded and encoded. EXPECT_CALL(stream_callbacks3, onResetStream(Http::StreamResetReason::LocalReset, "")).Times(0); // Connection buffered data book keeping should also be updated. EXPECT_CALL(network_connection_callbacks_, onBelowWriteBufferLowWatermark()); - stream3->resetStream(Http::StreamResetReason::LocalReset); + stream3->Reset(quic::QUIC_STREAM_CANCELLED); // Update flow control window for stream1. quic::QuicWindowUpdateFrame window_update3(quic::kInvalidControlFrameId, stream1->id(), diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 5597b44ef10f3..1b8b1cde1a376 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -1800,5 +1800,31 @@ TEST_P(QuicHttpIntegrationTest, UsesPreferredAddressDualStack) { } } +TEST_P(QuicHttpIntegrationTest, StreamTimeoutWithHalfClose) { + // Tighten the stream idle timeout to 400ms. + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + hcm.mutable_stream_idle_timeout()->set_seconds(0); + hcm.mutable_stream_idle_timeout()->set_nanos(400 * 1000 * 1000); + }); + initialize(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + IntegrationStreamDecoderPtr response = + codec_client_->makeRequestWithBody(default_request_headers_, "partial body", false); + EnvoyQuicClientSession* quic_session = + static_cast(codec_client_->connection()); + quic::QuicStream* stream = quic_session->GetActiveStream(0); + // Only send RESET_STREAM to close write side of this stream. + stream->ResetWriteSide(quic::QuicResetStreamError::FromInternal(quic::QUIC_STREAM_NO_ERROR)); + + // Wait for the server to timeout this request and the local reply. + EXPECT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_idle_timeout")->value()); + codec_client_->close(); +} + } // namespace Quic } // namespace Envoy From ead73f0dcf19be1704184e34e5a37239e89af275 Mon Sep 17 00:00:00 2001 From: Boteng Yao Date: Fri, 16 Feb 2024 14:26:44 +0000 Subject: [PATCH 214/274] websocket handshake check 101 protocol Signed-off-by: Boteng Yao Signed-off-by: Ryan Northey --- changelogs/current.yaml | 5 + envoy/http/filter.h | 5 + envoy/stream_info/stream_info.h | 2 + source/common/router/upstream_codec_filter.cc | 30 ++++ source/common/router/upstream_request.cc | 13 +- source/common/router/upstream_request.h | 9 + source/common/runtime/runtime_features.cc | 1 + test/integration/BUILD | 1 + .../integration/websocket_integration_test.cc | 157 +++++++++++++++++- test/integration/websocket_integration_test.h | 3 +- 10 files changed, 221 insertions(+), 5 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8f5ffb1151ab7..1aab8dd553f5e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -22,6 +22,11 @@ bug_fixes: change: | Fixed a bug where Envoy will go into an endless loop when using the brotli decompressor. If the input stream has redundant data, the decompressor will loop forever. +- area: websocket + change: | + Only 101 is considered a successful response for websocket handshake for HTTP/1.1, and Envoy as a proxy will proxy the response + header from upstream to downstream and then close the request if other status is received. This behavior can be + reverted by ``envoy_reloadable_features_check_switch_protocol_websocket_handshake``. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/envoy/http/filter.h b/envoy/http/filter.h index 361eacc244742..3ed9c7c1db86e 100644 --- a/envoy/http/filter.h +++ b/envoy/http/filter.h @@ -236,6 +236,11 @@ class UpstreamStreamFilterCallbacks { virtual bool pausedForConnect() const PURE; virtual void setPausedForConnect(bool value) PURE; + // Setters and getters to determine if sending body payload is paused on + // confirmation of a WebSocket upgrade. These should only be used by the upstream codec filter. + virtual bool pausedForWebsocketUpgrade() const PURE; + virtual void setPausedForWebsocketUpgrade(bool value) PURE; + // Return the upstreamStreamOptions for this stream. virtual const Http::ConnectionPool::Instance::StreamOptions& upstreamStreamOptions() const PURE; diff --git a/envoy/stream_info/stream_info.h b/envoy/stream_info/stream_info.h index b5fbd9108fd3c..75f35fd3d3346 100644 --- a/envoy/stream_info/stream_info.h +++ b/envoy/stream_info/stream_info.h @@ -144,6 +144,8 @@ struct ResponseCodeDetailValues { const std::string PathNormalizationFailed = "path_normalization_failed"; // The request was rejected because it attempted an unsupported upgrade. const std::string UpgradeFailed = "upgrade_failed"; + // The websocket handshake is unsuccessful and only SwitchingProtocols is considering successful. + const std::string WebsocketHandshakeUnsuccessful = "websocket_handshake_unsuccessful"; // The request was rejected by the HCM because there was no route configuration found. const std::string RouteConfigurationNotFound = "route_configuration_not_found"; diff --git a/source/common/router/upstream_codec_filter.cc b/source/common/router/upstream_codec_filter.cc index 158d2b729713d..a5738bbaa72ad 100644 --- a/source/common/router/upstream_codec_filter.cc +++ b/source/common/router/upstream_codec_filter.cc @@ -83,6 +83,8 @@ Http::FilterHeadersStatus UpstreamCodecFilter::decodeHeaders(Http::RequestHeader } if (callbacks_->upstreamCallbacks()->pausedForConnect()) { return Http::FilterHeadersStatus::StopAllIterationAndWatermark; + } else if (callbacks_->upstreamCallbacks()->pausedForWebsocketUpgrade()) { + return Http::FilterHeadersStatus::StopAllIterationAndWatermark; } return Http::FilterHeadersStatus::Continue; } @@ -154,6 +156,34 @@ void UpstreamCodecFilter::CodecBridge::decodeHeaders(Http::ResponseHeaderMapPtr& filter_.callbacks_->continueDecoding(); } + if (filter_.callbacks_->upstreamCallbacks()->pausedForWebsocketUpgrade()) { + const uint64_t status = Http::Utility::getResponseStatus(*headers); + const auto protocol = filter_.callbacks_->upstreamCallbacks()->upstreamStreamInfo().protocol(); + if (status == static_cast(Http::Code::SwitchingProtocols) || + (protocol.has_value() && protocol.value() != Envoy::Http::Protocol::Http11)) { + // handshake is finished and continue the data processing. + filter_.callbacks_->upstreamCallbacks()->setPausedForWebsocketUpgrade(false); + filter_.callbacks_->continueDecoding(); + } else { + // Other status, e.g., 426 or 200, indicate a failed handshake, Envoy as a proxy will proxy + // back the response header to downstream and then close the request, since WebSocket + // just needs headers for handshake per RFC-6455. Note: HTTP/2 200 will be normalized to + // 101 before this point in codec and this patch will skip this scenario from the above + // proto check. + filter_.callbacks_->sendLocalReply( + static_cast(status), "", + [&headers](Http::ResponseHeaderMap& local_headers) { + headers->iterate([&local_headers](const Envoy::Http::HeaderEntry& header) { + local_headers.addCopy(Http::LowerCaseString(header.key().getStringView()), + header.value().getStringView()); + return Envoy::Http::HeaderMap::Iterate::Continue; + }); + }, + std::nullopt, StreamInfo::ResponseCodeDetails::get().WebsocketHandshakeUnsuccessful); + return; + } + } + maybeEndDecode(end_stream); filter_.callbacks_->encodeHeaders(std::move(headers), end_stream, StreamInfo::ResponseCodeDetails::get().ViaUpstream); diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 653c87d39db9c..385b09af62d87 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -88,7 +88,7 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, encode_trailers_(false), retried_(false), awaiting_headers_(true), outlier_detection_timeout_recorded_(false), create_per_try_timeout_on_request_complete_(false), paused_for_connect_(false), - reset_stream_(false), + paused_for_websocket_(false), reset_stream_(false), record_timeout_budget_(parent_.cluster()->timeoutBudgetStats().has_value()), cleaned_up_(false), had_upstream_(false), stream_options_({can_send_early_data, can_use_http3}), grpc_rq_success_deferred_(false), @@ -372,6 +372,13 @@ void UpstreamRequest::acceptHeadersFromRouter(bool end_stream) { auto* headers = parent_.downstreamHeaders(); if (headers->getMethodValue() == Http::Headers::get().MethodValues.Connect) { paused_for_connect_ = true; + // If this is a websocket upgrade request, pause the request until the upstream sends + // the 101 Switching Protocols response code. Using the else logic here to obey CONNECT + // method which is expecting 2xx response. + } else if ((Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.check_switch_protocol_websocket_handshake")) && + Http::Utility::isWebSocketUpgradeRequest(*headers)) { + paused_for_websocket_ = true; } // Kick off creation of the upstream connection immediately upon receiving headers. @@ -595,8 +602,10 @@ void UpstreamRequest::onPoolReady(std::unique_ptr&& upstream, if (protocol) { stream_info_.protocol(protocol.value()); } else { - // We only pause for CONNECT for HTTP upstreams. If this is a TCP upstream, unpause. + // We only pause for CONNECT and WebSocket for HTTP upstreams. If this is a TCP upstream, + // unpause. paused_for_connect_ = false; + paused_for_websocket_ = false; } StreamInfo::UpstreamInfo& upstream_info = *stream_info_.upstreamInfo(); diff --git a/source/common/router/upstream_request.h b/source/common/router/upstream_request.h index a91b75c833f2a..ff16e1b3b06d4 100644 --- a/source/common/router/upstream_request.h +++ b/source/common/router/upstream_request.h @@ -244,6 +244,7 @@ class UpstreamRequest : public Logger::Loggable, // True if the CONNECT headers have been sent but proxying payload is paused // waiting for response headers. bool paused_for_connect_ : 1; + bool paused_for_websocket_ : 1; bool reset_stream_ : 1; // Sentinel to indicate if timeout budget tracking is configured for the cluster, @@ -359,6 +360,14 @@ class UpstreamRequestFilterManagerCallbacks : public Http::FilterManagerCallback } bool pausedForConnect() const override { return upstream_request_.paused_for_connect_; } void setPausedForConnect(bool value) override { upstream_request_.paused_for_connect_ = value; } + + bool pausedForWebsocketUpgrade() const override { + return upstream_request_.paused_for_websocket_; + } + void setPausedForWebsocketUpgrade(bool value) override { + upstream_request_.paused_for_websocket_ = value; + } + const Http::ConnectionPool::Instance::StreamOptions& upstreamStreamOptions() const override { return upstream_request_.upstreamStreamOptions(); } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index a911a4e1c5fd0..dafe75ac29015 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -33,6 +33,7 @@ RUNTIME_GUARD(envoy_reloadable_features_allow_absolute_url_with_mixed_scheme); RUNTIME_GUARD(envoy_reloadable_features_allow_compact_maglev); RUNTIME_GUARD(envoy_reloadable_features_append_query_parameters_path_rewriter); RUNTIME_GUARD(envoy_reloadable_features_append_xfh_idempotent); +RUNTIME_GUARD(envoy_reloadable_features_check_switch_protocol_websocket_handshake); RUNTIME_GUARD(envoy_reloadable_features_conn_pool_delete_when_idle); RUNTIME_GUARD(envoy_reloadable_features_count_unused_mapped_pages_as_free); RUNTIME_GUARD(envoy_reloadable_features_dfp_mixed_scheme); diff --git a/test/integration/BUILD b/test/integration/BUILD index f2b7b874f6bbb..a425da9927f88 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1328,6 +1328,7 @@ envoy_cc_test( "//source/common/http:header_map_lib", "//source/extensions/access_loggers/file:config", "//source/extensions/filters/http/buffer:config", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc index 66818889e2db6..5853814e3e717 100644 --- a/test/integration/websocket_integration_test.cc +++ b/test/integration/websocket_integration_test.cc @@ -11,6 +11,7 @@ #include "test/integration/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/strings/str_cat.h" @@ -158,7 +159,7 @@ void WebsocketIntegrationTest::initialize() { void WebsocketIntegrationTest::performUpgrade( const Http::TestRequestHeaderMapImpl& upgrade_request_headers, - const Http::TestResponseHeaderMapImpl& upgrade_response_headers) { + const Http::TestResponseHeaderMapImpl& upgrade_response_headers, bool upgrade_should_fail) { // Establish the initial connection. codec_client_ = makeHttpConnection(lookupPort("http")); @@ -180,7 +181,9 @@ void WebsocketIntegrationTest::performUpgrade( // Verify the upgrade response was received downstream. response_->waitForHeaders(); - validateUpgradeResponseHeaders(response_->headers(), upgrade_response_headers); + if (!upgrade_should_fail) { + validateUpgradeResponseHeaders(response_->headers(), upgrade_response_headers); + } } void WebsocketIntegrationTest::sendBidirectionalData() { @@ -242,6 +245,10 @@ TEST_P(WebsocketIntegrationTest, EarlyData) { upstreamProtocol() != Http::CodecType::HTTP1) { return; } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.check_switch_protocol_websocket_handshake", "false"}}); + config_helper_.addConfigModifier(setRouteUsingWebsocket()); initialize(); @@ -630,4 +637,150 @@ TEST_P(WebsocketIntegrationTest, BidirectionalConnectNoContentLengthNoTransferEn ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); } +// Test Websocket Upgrade in HTTP1 with 200 response code. +TEST_P(WebsocketIntegrationTest, Http1UpgradeStatusCodeOK) { + if (downstreamProtocol() != Http::CodecType::HTTP1 || + upstreamProtocol() != Http::CodecType::HTTP1) { + return; + } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.check_switch_protocol_websocket_handshake", "true"}}); + + config_helper_.addConfigModifier(setRouteUsingWebsocket()); + initialize(); + + auto in_correct_status_response_headers = upgradeResponseHeaders(); + in_correct_status_response_headers.setStatus(200); + + // The upgrade should be paused, but the response header is proxied back to downstream. + performUpgrade(upgradeRequestHeaders(), in_correct_status_response_headers, true); + EXPECT_EQ("200", response_->headers().Status()->value().getStringView()); + EXPECT_EQ("upgrade", response_->headers().Connection()->value().getStringView()); + EXPECT_EQ("websocket", response_->headers().Upgrade()->value().getStringView()); + + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 0); + ASSERT_TRUE(codec_client_->waitForDisconnect()); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); +} + +// Test Websocket Upgrade with 200 response code from no HTTP1 upstream and downstream. +TEST_P(WebsocketIntegrationTest, NonHttp1UpgradeStatusCodeOK) { + if (upstreamProtocol() == Http::CodecType::HTTP1 || + downstreamProtocol() == Http::CodecType::HTTP1) { + return; + } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.check_switch_protocol_websocket_handshake", "true"}}); + + config_helper_.addConfigModifier(setRouteUsingWebsocket()); + initialize(); + + auto correct_status_response_headers = upgradeResponseHeaders(); + correct_status_response_headers.setStatus(200); + performUpgrade(upgradeRequestHeaders(), correct_status_response_headers, true); + + // HTTP2 upstream response 200 is converted to 101. + EXPECT_EQ("101", response_->headers().Status()->value().getStringView()); + test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 1); + codec_client_->close(); +} + +// Test Websocket Upgrade with 201 response code from no HTTP1 upstream and downstream. +// This patch will not impact no H/1 behaviors. +TEST_P(WebsocketIntegrationTest, NoHttp1UpstreamUpgradeStatus201) { + if (upstreamProtocol() == Http::CodecType::HTTP1 || + downstreamProtocol() == Http::CodecType::HTTP1) { + return; + } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.check_switch_protocol_websocket_handshake", "true"}}); + + config_helper_.addConfigModifier(setRouteUsingWebsocket()); + initialize(); + + auto correct_status_response_headers = upgradeResponseHeaders(); + correct_status_response_headers.setStatus(201); + performUpgrade(upgradeRequestHeaders(), correct_status_response_headers, true); + + EXPECT_EQ("201", response_->headers().Status()->value().getStringView()); + test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 1); + codec_client_->close(); +} + +// Test Websocket Upgrade in HTTP1 with 426 response code. +// Upgrade is a HTTP1 header. +TEST_P(WebsocketIntegrationTest, Http1UpgradeStatusCodeUpgradeRequired) { + if (downstreamProtocol() != Http::CodecType::HTTP1 || + upstreamProtocol() != Http::CodecType::HTTP1) { + return; + } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.check_switch_protocol_websocket_handshake", "true"}}); + + useAccessLog("%RESPONSE_CODE_DETAILS%"); + config_helper_.addConfigModifier(setRouteUsingWebsocket()); + initialize(); + + auto in_correct_status_response_headers = upgradeResponseHeaders(); + in_correct_status_response_headers.setStatus(426); + + // The upgrade should be paused, but the response header is proxied back to downstream. + performUpgrade(upgradeRequestHeaders(), in_correct_status_response_headers, true); + EXPECT_EQ("426", response_->headers().Status()->value().getStringView()); + EXPECT_EQ("upgrade", response_->headers().Connection()->value().getStringView()); + EXPECT_EQ("websocket", response_->headers().Upgrade()->value().getStringView()); + + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 0); + ASSERT_TRUE(codec_client_->waitForDisconnect()); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); +} + +// Test data flow when websocket handshake failed. +TEST_P(WebsocketIntegrationTest, BidirectionalUpgradeFailedWithPrePayload) { + if (downstreamProtocol() != Http::CodecType::HTTP1 || + upstreamProtocol() != Http::CodecType::HTTP1) { + return; + } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.check_switch_protocol_websocket_handshake", "true"}}); + + config_helper_.addConfigModifier(setRouteUsingWebsocket()); + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("http")); + + // Send upgrade request with additional data. + ASSERT_TRUE(tcp_client->write( + "GET / HTTP/1.1\r\nHost: host\r\nconnection: upgrade\r\nupgrade: websocket\r\n\r\nfoo boo", + false, false)); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT(fake_upstream_connection != nullptr); + std::string received_data; + ASSERT_TRUE(fake_upstream_connection->waitForData( + FakeRawConnection::waitForInexactMatch("\r\n\r\n"), &received_data)); + // Make sure Envoy did not add TE or CL headers + ASSERT_FALSE(absl::StrContains(received_data, "content-length")); + ASSERT_FALSE(absl::StrContains(received_data, "transfer-encoding")); + ASSERT_TRUE(fake_upstream_connection->write( + "HTTP/1.1 426 Upgrade Required\r\nconnection: upgrade\r\nupgrade: websocket\r\n\r\n", false)); + + tcp_client->waitForData("\r\n\r\n", false); + + // Should not receive any data before handshake is finished. + std::string received_data_prepayload; + ASSERT_FALSE(fake_upstream_connection->waitForData( + FakeRawConnection::waitForInexactMatch("foo boo"), nullptr, std::chrono::milliseconds(10))); + + tcp_client->waitForDisconnect(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); +} } // namespace Envoy diff --git a/test/integration/websocket_integration_test.h b/test/integration/websocket_integration_test.h index 4e03ca44bd87a..066c8b3ba1232 100644 --- a/test/integration/websocket_integration_test.h +++ b/test/integration/websocket_integration_test.h @@ -20,7 +20,8 @@ class WebsocketIntegrationTest : public HttpProtocolIntegrationTest { protected: void performUpgrade(const Http::TestRequestHeaderMapImpl& upgrade_request_headers, - const Http::TestResponseHeaderMapImpl& upgrade_response_headers); + const Http::TestResponseHeaderMapImpl& upgrade_response_headers, + bool upgrade_should_fail = false); void sendBidirectionalData(); void validateUpgradeRequestHeaders(const Http::RequestHeaderMap& proxied_request_headers, From 56b4de5babd0400341044a8391526931ae2fb80c Mon Sep 17 00:00:00 2001 From: Boteng Yao Date: Wed, 24 Jan 2024 10:28:23 +0000 Subject: [PATCH 215/274] async http: set buffer limit for response and do not buffer for mirror Signed-off-by: Boteng Yao Signed-off-by: Yan Avlasov Signed-off-by: Ryan Northey --- changelogs/current.yaml | 4 + envoy/http/async_client.h | 14 +- source/common/http/async_client_impl.cc | 34 ++++- source/common/http/async_client_impl.h | 9 +- source/common/router/router.cc | 3 +- source/extensions/common/wasm/context.cc | 5 +- .../rest/rest_api_fetcher.cc | 4 +- .../common/ext_authz/ext_authz_http_impl.cc | 4 +- .../filters/http/gcp_authn/gcp_authn_impl.cc | 5 +- .../tracers/datadog/agent_http_client.cc | 3 + test/common/http/BUILD | 1 + test/common/http/async_client_impl_test.cc | 123 ++++++++++++++++++ .../ext_authz/ext_authz_integration_test.cc | 38 +++++- 13 files changed, 230 insertions(+), 17 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 1aab8dd553f5e..31ce24b5a17ad 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -27,6 +27,10 @@ bug_fixes: Only 101 is considered a successful response for websocket handshake for HTTP/1.1, and Envoy as a proxy will proxy the response header from upstream to downstream and then close the request if other status is received. This behavior can be reverted by ``envoy_reloadable_features_check_switch_protocol_websocket_handshake``. +- area: async http client + change: | + Added one option to disable the response body buffering for mirror request. Also introduced a 32MB cap for the response + buffer, which can be changed by the runtime flag ``http.async_response_buffer_limit`` based on the product needs. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/envoy/http/async_client.h b/envoy/http/async_client.h index 5da55ee23b6f3..8399fbd2ad440 100644 --- a/envoy/http/async_client.h +++ b/envoy/http/async_client.h @@ -45,7 +45,9 @@ class AsyncClient { */ enum class FailureReason { // The stream has been reset. - Reset + Reset, + // The stream exceeds the response buffer limit. + ExceedResponseBufferLimit }; /** @@ -291,6 +293,11 @@ class AsyncClient { return *this; } + StreamOptions& setDiscardResponseBody(bool discard) { + discard_response_body = discard; + return *this; + } + // For gmock test bool operator==(const StreamOptions& src) const { return timeout == src.timeout && buffer_body_for_retry == src.buffer_body_for_retry && @@ -328,6 +335,7 @@ class AsyncClient { OptRef filter_config_; bool is_shadow{false}; + bool discard_response_body{false}; }; /** @@ -391,6 +399,10 @@ class AsyncClient { buffer_limit_ = limit; return *this; } + RequestOptions& setDiscardResponseBody(bool discard) { + discard_response_body = discard; + return *this; + } // For gmock test bool operator==(const RequestOptions& src) const { diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index ce3ca75592b94..ee584bc357287 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -32,6 +32,7 @@ const AsyncStreamImpl::NullPathMatchCriterion const AsyncStreamImpl::RouteEntryImpl::ConnectConfigOptRef AsyncStreamImpl::RouteEntryImpl::connect_config_nullopt_; const std::list AsyncStreamImpl::NullCommonConfig::internal_only_headers_; +const absl::string_view AsyncClientImpl::ResponseBufferLimit = "http.async_response_buffer_limit"; AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster, Stats::Store& stats_store, Event::Dispatcher& dispatcher, @@ -44,7 +45,8 @@ AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster, config_(http_context.asyncClientStatPrefix(), local_info, *stats_store.rootScope(), cm, runtime, random, std::move(shadow_writer), true, false, false, false, false, false, {}, dispatcher.timeSource(), http_context, router_context), - dispatcher_(dispatcher), singleton_manager_(cm.clusterManagerFactory().singletonManager()) {} + dispatcher_(dispatcher), singleton_manager_(cm.clusterManagerFactory().singletonManager()), + runtime_(runtime) {} AsyncClientImpl::~AsyncClientImpl() { while (!active_streams_.empty()) { @@ -96,7 +98,8 @@ AsyncClient::Stream* AsyncClientImpl::start(AsyncClient::StreamCallbacks& callba AsyncStreamImpl::AsyncStreamImpl(AsyncClientImpl& parent, AsyncClient::StreamCallbacks& callbacks, const AsyncClient::StreamOptions& options) - : parent_(parent), stream_callbacks_(callbacks), stream_id_(parent.config_.random_.random()), + : parent_(parent), discard_response_body_(options.discard_response_body), + stream_callbacks_(callbacks), stream_id_(parent.config_.random_.random()), router_(options.filter_config_ ? *options.filter_config_ : parent.config_, parent.config_.async_stats_), stream_info_(Protocol::Http11, parent.dispatcher().timeSource(), nullptr), @@ -274,7 +277,9 @@ void AsyncStreamImpl::resetStream(Http::StreamResetReason, absl::string_view) { AsyncRequestSharedImpl::AsyncRequestSharedImpl(AsyncClientImpl& parent, AsyncClient::Callbacks& callbacks, const AsyncClient::RequestOptions& options) - : AsyncStreamImpl(parent, *this, options), callbacks_(callbacks) { + : AsyncStreamImpl(parent, *this, options), callbacks_(callbacks), + response_buffer_limit_(parent.runtime_.snapshot().getInteger( + AsyncClientImpl::ResponseBufferLimit, kBufferLimitForResponse)) { if (nullptr != options.parent_span_) { const std::string child_span_name = options.child_span_name_.empty() @@ -324,8 +329,23 @@ void AsyncRequestSharedImpl::onHeaders(ResponseHeaderMapPtr&& headers, bool) { } void AsyncRequestSharedImpl::onData(Buffer::Instance& data, bool) { + if (discard_response_body_) { + data.drain(data.length()); + return; + } + streamInfo().addBytesReceived(data.length()); response_->body().move(data); + + if (response_->body().length() + data.length() > response_buffer_limit_) { + ENVOY_LOG_EVERY_POW_2(warn, "the buffer size limit for async client response body " + "has been exceeded, draining data"); + data.drain(data.length()); + response_buffer_overlimit_ = true; + reset(); + } else { + response_->body().move(data); + } } void AsyncRequestSharedImpl::onTrailers(ResponseTrailerMapPtr&& trailers) { @@ -347,8 +367,12 @@ void AsyncRequestSharedImpl::onReset() { Tracing::EgressConfig::get()); if (!cancelled_) { - // In this case we don't have a valid response so we do need to raise a failure. - callbacks_.onFailure(*this, AsyncClient::FailureReason::Reset); + if (response_buffer_overlimit_) { + callbacks_.onFailure(*this, AsyncClient::FailureReason::ExceedResponseBufferLimit); + } else { + // In this case we don't have a valid response so we do need to raise a failure. + callbacks_.onFailure(*this, AsyncClient::FailureReason::Reset); + } } } diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 83cee970b40b1..99eb39dc8ae78 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -51,6 +51,8 @@ namespace { // Limit the size of buffer for data used for retries. // This is currently fixed to 64KB. constexpr uint64_t kBufferLimitForRetry = 1 << 16; +// Response buffer limit 32MB. +constexpr uint64_t kBufferLimitForResponse = 32 * 1024 * 1024; } // namespace class AsyncStreamImpl; @@ -72,6 +74,7 @@ class AsyncClientImpl final : public AsyncClient { OngoingRequest* startRequest(RequestHeaderMapPtr&& request_headers, Callbacks& callbacks, const AsyncClient::RequestOptions& options) override; Event::Dispatcher& dispatcher() override { return dispatcher_; } + static const absl::string_view ResponseBufferLimit; private: template T* internalStartRequest(T* async_request); @@ -80,6 +83,7 @@ class AsyncClientImpl final : public AsyncClient { Event::Dispatcher& dispatcher_; std::list> active_streams_; Singleton::Manager& singleton_manager_; + Runtime::Loader& runtime_; friend class AsyncStreamImpl; friend class AsyncRequestSharedImpl; @@ -92,7 +96,7 @@ class AsyncClientImpl final : public AsyncClient { class AsyncStreamImpl : public virtual AsyncClient::Stream, public StreamDecoderFilterCallbacks, public Event::DeferredDeletable, - Logger::Loggable, + public Logger::Loggable, public LinkedObject, public ScopeTrackedObject { public: @@ -151,6 +155,7 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, absl::optional destructor_callback_; // Callback to listen for low/high/overflow watermark events. absl::optional> watermark_callbacks_; + const bool discard_response_body_; private: struct NullHedgePolicy : public Router::HedgePolicy { @@ -531,6 +536,8 @@ class AsyncRequestSharedImpl : public virtual AsyncClient::Request, Tracing::SpanPtr child_span_; std::unique_ptr response_; bool cancelled_{}; + bool response_buffer_overlimit_{}; + const uint64_t response_buffer_limit_; }; class AsyncOngoingRequestImpl final : public AsyncClient::OngoingRequest, diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 4c45840086b6c..6214464bc3d5d 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -727,7 +727,8 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, .setBufferAccount(callbacks_->account()) // A buffer limit of 1 is set in the case that retry_shadow_buffer_limit_ == 0, // because a buffer limit of zero on async clients is interpreted as no buffer limit. - .setBufferLimit(1 > retry_shadow_buffer_limit_ ? 1 : retry_shadow_buffer_limit_); + .setBufferLimit(1 > retry_shadow_buffer_limit_ ? 1 : retry_shadow_buffer_limit_) + .setDiscardResponseBody(true); options.setFilterConfig(config_); if (end_stream) { // This is a header-only request, and can be dispatched immediately to the shadow diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index adcc4be4c8b3c..7cce4a9d085d1 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1826,8 +1826,9 @@ void Context::onHttpCallFailure(uint32_t token, Http::AsyncClient::FailureReason return; } status_code_ = static_cast(WasmResult::BrokenConnection); - // This is the only value currently. - ASSERT(reason == Http::AsyncClient::FailureReason::Reset); + // TODO(botengyao): handle different failure reasons. + ASSERT(reason == Http::AsyncClient::FailureReason::Reset || + reason == Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); status_message_ = "reset"; // Deferred "after VM call" actions are going to be executed upon returning from // ContextBase::*, which might include deleting Context object via proxy_done(). diff --git a/source/extensions/config_subscription/rest/rest_api_fetcher.cc b/source/extensions/config_subscription/rest/rest_api_fetcher.cc index 92c06f023d191..6b0d63fe73ef4 100644 --- a/source/extensions/config_subscription/rest/rest_api_fetcher.cc +++ b/source/extensions/config_subscription/rest/rest_api_fetcher.cc @@ -50,8 +50,8 @@ void RestApiFetcher::onSuccess(const Http::AsyncClient::Request& request, void RestApiFetcher::onFailure(const Http::AsyncClient::Request&, Http::AsyncClient::FailureReason reason) { - // Currently Http::AsyncClient::FailureReason only has one value: "Reset". - ASSERT(reason == Http::AsyncClient::FailureReason::Reset); + ASSERT(reason == Http::AsyncClient::FailureReason::Reset || + reason == Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); onFetchFailure(Config::ConfigUpdateFailureReason::ConnectionFailure, nullptr); requestComplete(); } diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index 4a519b5050cd3..0adf7e5919c18 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -258,7 +258,9 @@ void RawHttpClientImpl::onSuccess(const Http::AsyncClient::Request&, void RawHttpClientImpl::onFailure(const Http::AsyncClient::Request&, Http::AsyncClient::FailureReason reason) { - ASSERT(reason == Http::AsyncClient::FailureReason::Reset); + // TODO(botengyao): handle different failure reasons. + ASSERT(reason == Http::AsyncClient::FailureReason::Reset || + reason == Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); callbacks_->onComplete(std::make_unique(errorResponse())); callbacks_ = nullptr; } diff --git a/source/extensions/filters/http/gcp_authn/gcp_authn_impl.cc b/source/extensions/filters/http/gcp_authn/gcp_authn_impl.cc index 95ce7104f3666..29bdb92f57e87 100644 --- a/source/extensions/filters/http/gcp_authn/gcp_authn_impl.cc +++ b/source/extensions/filters/http/gcp_authn/gcp_authn_impl.cc @@ -90,8 +90,9 @@ void GcpAuthnClient::onSuccess(const Http::AsyncClient::Request&, void GcpAuthnClient::onFailure(const Http::AsyncClient::Request&, Http::AsyncClient::FailureReason reason) { - // Http::AsyncClient::FailureReason only has one value: "Reset". - ASSERT(reason == Http::AsyncClient::FailureReason::Reset); + // TODO(botengyao): handle different failure reasons. + ASSERT(reason == Http::AsyncClient::FailureReason::Reset || + reason == Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); ENVOY_LOG(error, "Request failed: stream has been reset"); active_request_ = nullptr; onError(); diff --git a/source/extensions/tracers/datadog/agent_http_client.cc b/source/extensions/tracers/datadog/agent_http_client.cc index 37a9fad328cac..8095aa8a15bc7 100644 --- a/source/extensions/tracers/datadog/agent_http_client.cc +++ b/source/extensions/tracers/datadog/agent_http_client.cc @@ -126,6 +126,9 @@ void AgentHTTPClient::onFailure(const Http::AsyncClient::Request& request, case Http::AsyncClient::FailureReason::Reset: message += "The stream has been reset."; break; + case Http::AsyncClient::FailureReason::ExceedResponseBufferLimit: + message += "The stream exceeds the response buffer limit."; + break; default: message += "Unknown error."; } diff --git a/test/common/http/BUILD b/test/common/http/BUILD index b6a1bd4600ef5..3037600048ac5 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -34,6 +34,7 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:test_time_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index af4ae2b450922..080216d17fa93 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -210,6 +210,129 @@ TEST_F(AsyncClientImplTest, Basic) { .value()); } +TEST_F(AsyncClientImplTest, NoResponseBodyBuffering) { + message_->body().add("test body"); + Buffer::Instance& data = message_->body(); + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](ResponseDecoder& decoder, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + TestRequestHeaderMapImpl copy(message_->headers()); + copy.addCopy("x-envoy-internal", "true"); + copy.addCopy("x-forwarded-for", "127.0.0.1"); + copy.addCopy(":scheme", "http"); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(©), false)); + EXPECT_CALL(stream_encoder_, encodeData(BufferEqual(&data), true)); + + auto* request = client_.send(std::move(message_), callbacks_, + AsyncClient::RequestOptions().setDiscardResponseBody(true)); + EXPECT_NE(request, nullptr); + + EXPECT_CALL(callbacks_, onBeforeFinalizeUpstreamSpan(_, _)); + EXPECT_CALL(callbacks_, onSuccess_(_, _)) + .WillOnce(Invoke([](const AsyncClient::Request&, ResponseMessage* response) -> void { + // Verify that there is zero response body. + EXPECT_EQ(response->body().length(), 0); + })); + ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder_->decodeHeaders(std::move(response_headers), false); + response_decoder_->decodeData(data, true); + + EXPECT_EQ( + 1UL, + cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("upstream_rq_200").value()); + EXPECT_EQ(1UL, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("internal.upstream_rq_200") + .value()); +} + +TEST_F(AsyncClientImplTest, LargeResponseBody) { + message_->body().add("test body"); + Buffer::Instance& data = message_->body(); + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](ResponseDecoder& decoder, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + TestRequestHeaderMapImpl copy(message_->headers()); + copy.addCopy("x-envoy-internal", "true"); + copy.addCopy("x-forwarded-for", "127.0.0.1"); + copy.addCopy(":scheme", "http"); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(©), false)); + EXPECT_CALL(stream_encoder_, encodeData(BufferEqual(&data), true)); + ON_CALL(runtime_.snapshot_, + getInteger(AsyncClientImpl::ResponseBufferLimit, kBufferLimitForResponse)) + .WillByDefault(Return(100)); + + auto* request = client_.send(std::move(message_), callbacks_, AsyncClient::RequestOptions()); + EXPECT_NE(request, nullptr); + + EXPECT_CALL(callbacks_, onBeforeFinalizeUpstreamSpan(_, _)); + EXPECT_CALL(callbacks_, onFailure(_, AsyncClient::FailureReason::ExceedResponseBufferLimit)); + + Buffer::InstancePtr large_body{new Buffer::OwnedImpl(std::string(100 + 1, 'a'))}; + ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder_->decodeHeaders(std::move(response_headers), false); + response_decoder_->decodeData(*large_body, true); + EXPECT_EQ(large_body->length(), 0); +} + +TEST_F(AsyncClientImplTest, LargeResponseBodyMultipleRead) { + message_->body().add("test body"); + Buffer::Instance& data = message_->body(); + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](ResponseDecoder& decoder, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + TestRequestHeaderMapImpl copy(message_->headers()); + copy.addCopy("x-envoy-internal", "true"); + copy.addCopy("x-forwarded-for", "127.0.0.1"); + copy.addCopy(":scheme", "http"); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(©), false)); + EXPECT_CALL(stream_encoder_, encodeData(BufferEqual(&data), true)); + ON_CALL(runtime_.snapshot_, + getInteger(AsyncClientImpl::ResponseBufferLimit, kBufferLimitForResponse)) + .WillByDefault(Return(100)); + + auto* request = client_.send(std::move(message_), callbacks_, AsyncClient::RequestOptions()); + EXPECT_NE(request, nullptr); + + EXPECT_CALL(callbacks_, onBeforeFinalizeUpstreamSpan(_, _)); + EXPECT_CALL(callbacks_, onFailure(_, AsyncClient::FailureReason::ExceedResponseBufferLimit)); + + Buffer::InstancePtr large_body{new Buffer::OwnedImpl(std::string(50, 'a'))}; + Buffer::InstancePtr large_body_second{new Buffer::OwnedImpl(std::string(50, 'a'))}; + Buffer::InstancePtr large_body_third{new Buffer::OwnedImpl(std::string(2, 'a'))}; + ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder_->decodeHeaders(std::move(response_headers), false); + response_decoder_->decodeData(*large_body, false); + response_decoder_->decodeData(*large_body_second, false); + response_decoder_->decodeData(*large_body_third, true); +} + TEST_F(AsyncClientImplTest, BasicOngoingRequest) { auto headers = std::make_unique(); HttpTestUtility::addDefaultHeaders(*headers); 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 1881e6766c528..ea82c901770d9 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 @@ -542,7 +542,9 @@ class ExtAuthzHttpIntegrationTest : public HttpIntegrationTest, .get(Http::LowerCaseString(std::string("regex-fool")))[0] ->value() .getStringView()); + } + void sendExtAuthzResponse() { // Send back authorization response with "baz" and "bat" headers. // Also add multiple values "append-foo" and "append-bar" for key "x-append-bat". // Also tell Envoy to remove "remove-me" header before sending to upstream. @@ -567,8 +569,8 @@ class ExtAuthzHttpIntegrationTest : public HttpIntegrationTest, cleanupUpstreamAndDownstream(); } - void initializeConfig(bool legacy_allowed_headers = true) { - config_helper_.addConfigModifier([this, legacy_allowed_headers]( + void initializeConfig(bool legacy_allowed_headers = true, bool failure_mode_allow = true) { + config_helper_.addConfigModifier([this, legacy_allowed_headers, failure_mode_allow]( envoy::config::bootstrap::v3::Bootstrap& bootstrap) { auto* ext_authz_cluster = bootstrap.mutable_static_resources()->add_clusters(); ext_authz_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); @@ -579,6 +581,7 @@ class ExtAuthzHttpIntegrationTest : public HttpIntegrationTest, } else { TestUtility::loadFromYaml(default_config_, proto_config_); } + proto_config_.set_failure_mode_allow(failure_mode_allow); envoy::config::listener::v3::Filter ext_authz_filter; ext_authz_filter.set_name("envoy.filters.http.ext_authz"); ext_authz_filter.mutable_typed_config()->PackFrom(proto_config_); @@ -594,6 +597,7 @@ class ExtAuthzHttpIntegrationTest : public HttpIntegrationTest, initiateClientConnection(); waitForExtAuthzRequest(); + sendExtAuthzResponse(); AssertionResult result = fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_); @@ -893,6 +897,7 @@ TEST_P(ExtAuthzHttpIntegrationTest, DEPRECATED_FEATURE_TEST(LegacyDirectReponse) HttpIntegrationTest::initialize(); initiateClientConnection(); waitForExtAuthzRequest(); + sendExtAuthzResponse(); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_TRUE(response_->complete()); @@ -914,6 +919,7 @@ TEST_P(ExtAuthzHttpIntegrationTest, DEPRECATED_FEATURE_TEST(LegacyRedirectRespon HttpIntegrationTest::initialize(); initiateClientConnection(); waitForExtAuthzRequest(); + sendExtAuthzResponse(); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_TRUE(response_->complete()); @@ -961,12 +967,39 @@ TEST_P(ExtAuthzHttpIntegrationTest, DirectReponse) { HttpIntegrationTest::initialize(); initiateClientConnection(); waitForExtAuthzRequest(); + sendExtAuthzResponse(); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_TRUE(response_->complete()); EXPECT_EQ("204", response_->headers().Status()->value().getStringView()); } +// Test exceeding the async client buffer limit. +TEST_P(ExtAuthzHttpIntegrationTest, ErrorReponseWithDefultBufferLimit) { + initializeConfig(false, /*failure_mode_allow=*/false); + config_helper_.addRuntimeOverride("http.async_response_buffer_limit", "1024"); + + HttpIntegrationTest::initialize(); + initiateClientConnection(); + waitForExtAuthzRequest(); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "200"}, + {"baz", "baz"}, + {"bat", "bar"}, + {"x-append-bat", "append-foo"}, + {"x-append-bat", "append-bar"}, + {"x-envoy-auth-headers-to-remove", "remove-me"}, + }; + ext_authz_request_->encodeHeaders(response_headers, false); + ext_authz_request_->encodeData(2048, true); + + ASSERT_TRUE(response_->waitForEndStream()); + EXPECT_TRUE(response_->complete()); + // A forbidden response since the onFailure is called due to the async client buffer limit. + EXPECT_EQ("403", response_->headers().Status()->value().getStringView()); +} + // (uses new config for allowed_headers). TEST_P(ExtAuthzHttpIntegrationTest, RedirectResponse) { config_helper_.addConfigModifier( @@ -982,6 +1015,7 @@ TEST_P(ExtAuthzHttpIntegrationTest, RedirectResponse) { HttpIntegrationTest::initialize(); initiateClientConnection(); waitForExtAuthzRequest(); + sendExtAuthzResponse(); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_TRUE(response_->complete()); From 84d6d3ae2d687626e9fa0dd6560acc6107ac4c2c Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Tue, 4 Jun 2024 11:30:55 +0000 Subject: [PATCH 216/274] repo: Release v1.27.6 **Summary of changes:** - [CVE-2024-34362: Crash (use-after-free) in EnvoyQuicServerStream](https://github.com/envoyproxy/envoy/security/advisories/GHSA-hww5-43gv-35jv) - [CVE-2024-34364: Envoy OOM vector from HTTP async client with unbounded response buffer for mirror response, and other components](https://github.com/envoyproxy/envoy/security/advisories/GHSA-xcj3-h7vf-fw26) - [CVE-2024-32974: Crash in EnvoyQuicServerStream::OnInitialHeadersComplete()](https://github.com/envoyproxy/envoy/security/advisories/GHSA-mgxp-7hhp-8299) - [CVE-2024-32975: Crash in QuicheDataReader::PeekVarInt62Length()](https://github.com/envoyproxy/envoy/security/advisories/GHSA-g9mq-6v96-cpqc) - [CVE-2024-32976: Endless loop while decompressing Brotli data with extra input](https://github.com/envoyproxy/envoy/security/advisories/GHSA-7wp5-c2vq-4f8m) - [CVE-2024-23326: Envoy incorrectly accepts HTTP 200 response for entering upgrade mode](https://github.com/envoyproxy/envoy/security/advisories/GHSA-vcf8-7238-v74c) **Docker images**: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.6 **Docs**: https://www.envoyproxy.io/docs/envoy/v1.27.6/ **Release notes**: https://www.envoyproxy.io/docs/envoy/v1.27.6/version_history/v1.27/v1.27.6 **Full changelog**: https://github.com/envoyproxy/envoy/compare/v1.27.5...v1.27.6 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/current.yaml | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 7b16a461240e2..2a5aed46be852 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.6-dev +1.27.6 diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 31ce24b5a17ad..cc73ba5da9a92 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,13 +1,6 @@ -date: Pending - -behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* +date: June 4, 2024 bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: router change: | Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is From 22c5a3c9b752279fb12248dfd0d8532139597571 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 5 Jun 2024 14:07:44 +0100 Subject: [PATCH 217/274] repo: Dev v1.27.7 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.6.yaml | 33 +++++++++++++++++++++++++++++++++ changelogs/current.yaml | 32 ++++++++------------------------ 3 files changed, 42 insertions(+), 25 deletions(-) create mode 100644 changelogs/1.27.6.yaml diff --git a/VERSION.txt b/VERSION.txt index 2a5aed46be852..46b75d6e055ea 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.6 +1.27.7-dev diff --git a/changelogs/1.27.6.yaml b/changelogs/1.27.6.yaml new file mode 100644 index 0000000000000..cc73ba5da9a92 --- /dev/null +++ b/changelogs/1.27.6.yaml @@ -0,0 +1,33 @@ +date: June 4, 2024 + +bug_fixes: +- area: router + change: | + Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is + controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. +- area: quic + change: | + Applied 2 QUICHE patches for crash bugs in ``QuicSpdyStream`` ``OnDataAvailable()`` and ``OnInitialHeaderComplete()``. +- area: quic + change: | + Fixed crash bug when QUIC downstream stream was read closed and then timed out. +- area: decompression + change: | + Fixed a bug where Envoy will go into an endless loop when using the brotli decompressor. If the input stream has + redundant data, the decompressor will loop forever. +- area: websocket + change: | + Only 101 is considered a successful response for websocket handshake for HTTP/1.1, and Envoy as a proxy will proxy the response + header from upstream to downstream and then close the request if other status is received. This behavior can be + reverted by ``envoy_reloadable_features_check_switch_protocol_websocket_handshake``. +- area: async http client + change: | + Added one option to disable the response body buffering for mirror request. Also introduced a 32MB cap for the response + buffer, which can be changed by the runtime flag ``http.async_response_buffer_limit`` based on the product needs. + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: + +deprecated: diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cc73ba5da9a92..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,29 +1,13 @@ -date: June 4, 2024 +date: Pending + +behavior_changes: +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +minor_behavior_changes: +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: router - change: | - Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is - controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. -- area: quic - change: | - Applied 2 QUICHE patches for crash bugs in ``QuicSpdyStream`` ``OnDataAvailable()`` and ``OnInitialHeaderComplete()``. -- area: quic - change: | - Fixed crash bug when QUIC downstream stream was read closed and then timed out. -- area: decompression - change: | - Fixed a bug where Envoy will go into an endless loop when using the brotli decompressor. If the input stream has - redundant data, the decompressor will loop forever. -- area: websocket - change: | - Only 101 is considered a successful response for websocket handshake for HTTP/1.1, and Envoy as a proxy will proxy the response - header from upstream to downstream and then close the request if other status is received. This behavior can be - reverted by ``envoy_reloadable_features_check_switch_protocol_websocket_handshake``. -- area: async http client - change: | - Added one option to disable the response body buffering for mirror request. Also introduced a 32MB cap for the response - buffer, which can be changed by the runtime flag ``http.async_response_buffer_limit`` based on the product needs. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` From b7f509607ad860fd6a63cde4f7d6f0197f9f63bb Mon Sep 17 00:00:00 2001 From: Rama Chavali Date: Fri, 28 Jun 2024 00:36:14 +0530 Subject: [PATCH 218/274] http: fix cookie attributes (#34885) --------- Signed-off-by: Rama Chavali --- changelogs/current.yaml | 4 ++ source/common/http/hash_policy.cc | 14 +++++-- .../multiplexed_integration_test.cc | 39 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..820800172a444 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,10 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: http + change: | + Fixed a bug where additional :ref:`cookie attributes ` + are not sent properly to clients. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/http/hash_policy.cc b/source/common/http/hash_policy.cc index ca97775f318ed..7d64ee6bba28e 100644 --- a/source/common/http/hash_policy.cc +++ b/source/common/http/hash_policy.cc @@ -82,7 +82,11 @@ class CookieHashMethod : public HashMethodImplBase { CookieHashMethod(const std::string& key, const std::string& path, const absl::optional& ttl, bool terminal, const CookieAttributeRefVector attributes) - : HashMethodImplBase(terminal), key_(key), path_(path), ttl_(ttl), attributes_(attributes) {} + : HashMethodImplBase(terminal), key_(key), path_(path), ttl_(ttl) { + for (const auto& attribute : attributes) { + attributes_.push_back(attribute); + } + } absl::optional evaluate(const Network::Address::Instance*, const RequestHeaderMap& headers, @@ -91,7 +95,11 @@ class CookieHashMethod : public HashMethodImplBase { absl::optional hash; std::string value = Utility::parseCookieValue(headers, key_); if (value.empty() && ttl_.has_value()) { - value = add_cookie(key_, path_, ttl_.value(), attributes_); + CookieAttributeRefVector attributes; + for (const auto& attribute : attributes_) { + attributes.push_back(attribute); + } + value = add_cookie(key_, path_, ttl_.value(), attributes); hash = HashUtil::xxHash64(value); } else if (!value.empty()) { @@ -104,7 +112,7 @@ class CookieHashMethod : public HashMethodImplBase { const std::string key_; const std::string path_; const absl::optional ttl_; - const CookieAttributeRefVector attributes_; + std::vector attributes_; }; class IpHashMethod : public HashMethodImplBase { diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index c2c9dc5e5486f..de49e8e63ef5f 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -1777,6 +1777,45 @@ TEST_P(MultiplexedRingHashIntegrationTest, CookieRoutingNoCookieWithNonzeroTtlSe EXPECT_EQ(set_cookies.size(), 1); } +TEST_P(MultiplexedRingHashIntegrationTest, + CookieRoutingNoCookieWithNonzeroTtlSetAndWithAttributes) { + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + auto* hash_policy = hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->add_hash_policy(); + auto* cookie = hash_policy->mutable_cookie(); + cookie->set_name("foo"); + cookie->mutable_ttl()->set_seconds(15); + auto* attribute_1 = cookie->mutable_attributes()->Add(); + attribute_1->set_name("test1"); + attribute_1->set_value("value1"); + auto* attribute_2 = cookie->mutable_attributes()->Add(); + attribute_2->set_name("test2"); + attribute_2->set_value("value2"); + }); + + std::set set_cookies; + sendMultipleRequests( + 1024, + Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}, + [&](IntegrationStreamDecoder& response) { + EXPECT_EQ("200", response.headers().getStatusValue()); + std::string value( + response.headers().get(Http::Headers::get().SetCookie)[0]->value().getStringView()); + set_cookies.insert(value); + EXPECT_THAT(value, + MatchesRegex("foo=.*; Max-Age=15; test1=value1; test2=value2; HttpOnly")); + }); + EXPECT_EQ(set_cookies.size(), 1); +} + TEST_P(MultiplexedRingHashIntegrationTest, CookieRoutingNoCookieWithZeroTtlSet) { config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& From 66d96c5f712d95a57b9b99ec3c31860a5738a344 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 28 Jun 2024 19:00:10 +0000 Subject: [PATCH 219/274] repo: Release v1.27.7 *Docker images*: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.7 *Docs*: https://www.envoyproxy.io/docs/envoy/v1.27.7/ *Release notes*: https://www.envoyproxy.io/docs/envoy/v1.27.7/version_history/v1.27/v1.27.7 *Full changelog*: https://github.com/envoyproxy/envoy/compare/v1.27.6...v1.27.7 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/current.yaml | 16 +--------------- docs/inventories/v1.27/objects.inv | Bin 160000 -> 160022 bytes docs/versions.yaml | 2 +- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 46b75d6e055ea..127aeda7e58ae 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.7-dev +1.27.7 diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 820800172a444..81912d9d3b197 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,21 +1,7 @@ -date: Pending - -behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* +date: June 28, 2024 bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: http change: | Fixed a bug where additional :ref:`cookie attributes ` are not sent properly to clients. - -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - -new_features: - -deprecated: diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index 01563bfb768291679df12aaaeea1be817960262f..f875b6a4258958214fe4a9a7d4980f1bc18d54b8 100644 GIT binary patch delta 3582 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 K{hc Date: Tue, 30 Jul 2024 17:47:54 +0800 Subject: [PATCH 220/274] higress envoy patch --- .bazelrc | 2 + api/BUILD | 1 + api/bazel/repository_locations.bzl | 8 +- .../cluster_fallback/v3/BUILD | 9 + .../v3/cluster_fallback.proto | 31 + .../v3/http_dubbo_transcoder.proto | 120 ++ .../upstreams/http/dubbo_tcp/v3/BUILD | 9 + .../dubbo_tcp/v3/tcp_connection_pool.proto | 18 + .../http/jwt_authn/v2alpha/config.proto | 3 +- .../listener/v3/listener_components.proto | 1 - .../config/route/v3/route_components.proto | 135 +- .../filters/http/composite/v3/composite.proto | 27 +- .../custom_response/v3/custom_response.proto | 13 + .../filters/http/jwt_authn/v3/config.proto | 3 +- .../v3/http_connection_manager.proto | 31 + .../redirect_policy/v3/redirect_policy.proto | 7 + .../extension/v3/config_discovery.proto | 7 +- api/versioning/BUILD | 5 +- bazel/BUILD | 7 + bazel/envoy_binary.bzl | 3 +- bazel/envoy_internal.bzl | 7 + bazel/envoy_library.bzl | 6 + bazel/envoy_test.bzl | 17 +- bazel/external/rapidjson.BUILD | 2 +- bazel/foreign_cc/BUILD | 2 +- bazel/repositories.bzl | 12 + bazel/repository_locations.bzl | 34 +- contrib/common/active_redirect/source/BUILD | 34 + .../source/active_redirect_policy_impl.cc | 225 +++ .../source/active_redirect_policy_impl.h | 119 ++ contrib/contrib_build_config.bzl | 22 +- .../cluster_fallback/source/BUILD | 39 + .../cluster_fallback/source/config.cc | 26 + .../cluster_fallback/source/config.h | 37 + .../cluster_fallback/source/filter.cc | 122 ++ .../cluster_fallback/source/filter.h | 45 + .../cluster_fallback/test/BUILD | 37 + .../cluster_fallback/test/config_test.cc | 49 + .../cluster_fallback/test/filter_test.cc | 687 ++++++++++ contrib/envoy/http/BUILD | 23 + contrib/envoy/http/active_redirect_policy.h | 66 + contrib/extensions_metadata.yaml | 13 + .../network/source/codecs/dubbo/config.cc | 2 +- .../network/test/codecs/dubbo/config_test.cc | 2 +- .../filters/http/source/BUILD | 91 ++ .../filters/http/source/config.cc | 35 + .../filters/http/source/config.h | 43 + .../http/source/dubbo_transcoder_filter.cc | 566 ++++++++ .../http/source/dubbo_transcoder_filter.h | 252 ++++ .../filters/http/source/transcoder.h | 60 + .../filters/http/source/utility.cc | 398 ++++++ .../filters/http/source/utility.h | 90 ++ .../filters/http/test/BUILD | 40 + .../filters/http/test/config_test.cc | 80 ++ .../http/test/dubbo_transcoder_filter_test.cc | 1051 ++++++++++++++ .../filters/http/test/test_data/BUILD | 13 + .../http/test/test_data/big_reqeust_body | 1 + .../test/test_data/chunked-request-example.py | 29 + .../filters/http/test/test_data/dubbo.yaml | 100 ++ .../http/test/test_data/dubbo_new.yaml | 228 ++++ .../test/test_data/dubbo_pre_route copy.yaml | 151 ++ .../http/test/test_data/dubbo_pre_route.yaml | 166 +++ .../input_matchers/test/matcher_test.cc | 4 + contrib/upstreams/http/dubbo_tcp/source/BUILD | 54 + .../upstreams/http/dubbo_tcp/source/config.cc | 26 + .../upstreams/http/dubbo_tcp/source/config.h | 37 + .../http/dubbo_tcp/source/upstream_request.cc | 144 ++ .../http/dubbo_tcp/source/upstream_request.h | 114 ++ contrib/upstreams/http/dubbo_tcp/test/BUILD | 33 + .../dubbo_tcp/test/upstream_request_test.cc | 296 ++++ envoy/http/filter.h | 17 + envoy/http/filter_factory.h | 8 + envoy/http/header_map.h | 7 + envoy/redis/BUILD | 14 + envoy/redis/async_client.h | 72 + envoy/router/BUILD | 3 + envoy/router/router.h | 7 + envoy/router/scopes.h | 27 +- envoy/server/BUILD | 1 + envoy/server/factory_context.h | 29 +- envoy/server/filter_config.h | 7 +- envoy/stream_info/stream_info.h | 13 + envoy/upstream/BUILD | 6 + envoy/upstream/outlier_detection.h | 4 + envoy/upstream/thread_local_cluster.h | 4 + .../access_loggers/grpc/v3/als.proto | 98 ++ source/common/common/logger.h | 1 + source/common/filter/config_discovery_impl.h | 86 +- source/common/http/async_client_impl.cc | 5 + source/common/http/async_client_impl.h | 10 + source/common/http/conn_manager_config.h | 8 + source/common/http/conn_manager_impl.cc | 64 +- source/common/http/conn_manager_impl.h | 4 + source/common/http/conn_manager_utility.cc | 5 + source/common/http/filter_chain_helper.cc | 31 +- source/common/http/filter_chain_helper.h | 35 +- source/common/http/filter_manager.cc | 27 + source/common/http/filter_manager.h | 29 +- source/common/http/headers.h | 11 + source/common/http/http1/codec_impl.cc | 5 + source/common/http/utility.h | 15 + source/common/protobuf/utility.cc | 10 + source/common/protobuf/utility.h | 9 + source/common/redis/BUILD | 27 + source/common/redis/async_client_impl.cc | 294 ++++ source/common/redis/async_client_impl.h | 162 +++ source/common/router/BUILD | 11 +- source/common/router/config_impl.cc | 150 ++ source/common/router/config_impl.h | 113 ++ source/common/router/delegating_route_impl.h | 6 + source/common/router/router.cc | 276 ++++ source/common/router/router.h | 23 +- source/common/router/scoped_config_impl.cc | 199 +++ source/common/router/scoped_config_impl.h | 73 +- source/common/router/upstream_codec_filter.h | 2 +- source/common/router/upstream_request.cc | 13 +- source/common/secret/secret_manager_impl.h | 4 + source/common/stream_info/stream_info_impl.h | 18 + source/common/tracing/http_tracer_impl.cc | 10 +- source/common/tracing/tracer_impl.cc | 10 + source/common/upstream/BUILD | 9 +- .../common/upstream/cluster_manager_impl.cc | 23 + source/common/upstream/cluster_manager_impl.h | 6 + .../common/upstream/outlier_detection_impl.cc | 11 + .../common/upstream/outlier_detection_impl.h | 3 + ...impl.h => upstream_factory_context_impl.h} | 6 +- source/common/upstream/upstream_impl.cc | 4 +- source/common/upstream/upstream_impl.h | 8 +- source/exe/BUILD | 6 +- .../extensions/common/dubbo/message_impl.cc | 24 +- source/extensions/common/redis/BUILD | 6 + source/extensions/common/wasm/context.cc | 272 ++++ source/extensions/common/wasm/context.h | 48 + .../extensions/common/wasm/stats_handler.cc | 20 + source/extensions/common/wasm/stats_handler.h | 37 + source/extensions/common/wasm/wasm.cc | 76 +- source/extensions/common/wasm/wasm.h | 15 + source/extensions/common/wasm/wasm_vm.cc | 9 +- source/extensions/extensions_build_config.bzl | 2 + .../filters/common/ext_authz/ext_authz.h | 4 + .../common/ext_authz/ext_authz_http_impl.cc | 25 + .../common/ext_authz/ext_authz_http_impl.h | 4 + .../filters/http/common/factory_base.h | 9 +- .../extensions/filters/http/composite/BUILD | 1 + .../filters/http/composite/action.cc | 31 + .../filters/http/composite/action.h | 1 + .../filters/http/custom_response/config.cc | 9 +- .../filters/http/custom_response/config.h | 6 + .../custom_response/custom_response_filter.cc | 66 +- .../custom_response/custom_response_filter.h | 3 + .../filters/http/custom_response/policy.h | 9 + .../http/on_demand/on_demand_update.cc | 6 + .../filters/http/wasm/wasm_filter.h | 31 +- .../filters/network/common/redis/BUILD | 40 + .../filters/network/common/redis/client.h | 8 + .../filters/network/common/redis/codec.h | 29 + .../network/common/redis/codec_impl.cc | 235 ++++ .../filters/network/common/redis/codec_impl.h | 66 +- .../filters/network/common/redis/raw_client.h | 89 ++ .../network/common/redis/raw_client_impl.cc | 266 ++++ .../network/common/redis/raw_client_impl.h | 116 ++ .../filters/network/common/redis/utility.cc | 36 + .../filters/network/common/redis/utility.h | 7 + .../network/dubbo_proxy/message_impl.cc | 18 +- .../dubbo_proxy/router/route_matcher.cc | 4 +- .../network/http_connection_manager/config.cc | 12 +- .../network/http_connection_manager/config.h | 14 +- .../filters/network/wasm/wasm_filter.h | 30 +- .../tcp/health_checker_impl.cc | 11 + .../redirect_policy/redirect_policy.cc | 145 +- .../redirect_policy/redirect_policy.h | 7 + .../listener_manager/listener_manager_impl.cc | 14 +- source/extensions/tracers/common/BUILD | 1 + .../opentelemetry/grpc_trace_exporter.h | 2 +- .../tracers/opentelemetry/tracer.cc | 3 + .../skywalking/trace_segment_reporter.cc | 5 + .../extensions/tracers/skywalking/tracer.cc | 19 +- .../transport_sockets/tls/context_impl.cc | 2 + source/server/BUILD | 2 + source/server/admin/admin.h | 11 + source/server/server.h | 9 +- test/common/common/BUILD | 8 +- test/common/common/base64_test.cc | 41 - .../filter/config_discovery_impl_test.cc | 33 +- .../formatter/substitution_formatter_test.cc | 7 +- .../http/conn_manager_impl_fuzz_test.cc | 6 + test/common/http/conn_manager_impl_test.cc | 28 + test/common/http/conn_manager_impl_test_2.cc | 46 +- .../common/http/conn_manager_impl_test_base.h | 6 + test/common/http/filter_chain_helper_test.cc | 5 +- test/common/http/header_map_impl_test.cc | 20 + test/common/http/http1/codec_impl_test.cc | 85 ++ test/common/protobuf/utility_test.cc | 13 + test/common/router/BUILD | 6 + test/common/router/config_impl_test.cc | 1209 +++++++++++++++-- test/common/router/router_2_test.cc | 19 + test/common/router/router_test.cc | 262 ++++ test/common/router/router_test_base.cc | 39 + test/common/router/router_test_base.h | 18 + .../common/router/router_upstream_log_test.cc | 10 + test/common/router/scoped_config_impl_test.cc | 70 + test/common/router/scoped_rds_test.cc | 349 ++++- test/common/stream_info/test_util.h | 4 + .../upstream/cluster_manager_impl_test.cc | 2 +- test/config_test/BUILD | 3 +- test/config_test/config_test.cc | 6 +- .../grpc_access_log_impl_test.cc | 1 + test/extensions/bootstrap/wasm/wasm_test.cc | 7 + .../dubbo/hessian2_serializer_impl_test.cc | 96 +- test/extensions/common/dubbo/message_test.cc | 8 +- test/extensions/common/wasm/context_test.cc | 22 + test/extensions/common/wasm/wasm_test.cc | 36 +- .../ext_authz/ext_authz_http_impl_test.cc | 81 ++ .../filters/http/buffer/config_test.cc | 2 +- .../http/common/empty_http_filter_config.h | 6 +- test/extensions/filters/http/composite/BUILD | 4 + .../composite_filter_integration_test.cc | 145 ++ .../filters/http/composite/filter_test.cc | 40 + .../http/cors/cors_filter_integration_test.cc | 6 + .../custom_response_filter_test.cc | 385 ++++++ .../extensions/filters/http/ext_proc/utils.cc | 5 + test/extensions/filters/http/jwt_authn/BUILD | 1 - .../http/on_demand/on_demand_filter_test.cc | 6 + .../http/tap/tap_filter_integration_test.cc | 12 +- .../filters/http/wasm/test_data/BUILD | 2 + .../filters/http/wasm/test_data/test_cpp.cc | 77 +- .../wasm/test_data/test_redis_call_cpp.cc | 69 + .../filters/http/wasm/wasm_filter_test.cc | 237 ++++ .../filters/network/common/redis/BUILD | 6 + .../network/common/redis/client_impl_test.cc | 617 +++++++++ .../network/common/redis/codec_impl_test.cc | 169 +++ .../filters/network/common/redis/mocks.cc | 14 + .../filters/network/common/redis/mocks.h | 23 + .../dubbo_hessian2_serializer_impl_test.cc | 86 +- .../network/dubbo_proxy/message_impl_test.cc | 8 +- .../network/dubbo_proxy/router_test.cc | 20 +- .../filters/network/wasm/config_test.cc | 8 + .../dns_resolver/cares/dns_impl_test.cc | 11 +- .../opentelemetry/grpc_trace_exporter_test.cc | 2 +- .../skywalking/trace_segment_reporter_test.cc | 17 + .../tracers/skywalking/tracer_test.cc | 32 + .../tls/context_impl_test.cc | 19 + test/integration/BUILD | 16 + test/integration/base_integration_test.h | 8 + .../cluster_filter_integration_test.cc | 2 +- test/integration/filters/BUILD | 6 + test/integration/filters/add_header_filter.cc | 47 + .../filters/add_header_filter.proto | 12 + .../filters/test_network_filter.cc | 49 + .../filters/test_network_filter.proto | 7 + test/integration/header_integration_test.cc | 8 + .../http2_flood_integration_test.cc | 4 + test/integration/http_integration.cc | 14 +- ...rk_extension_discovery_integration_test.cc | 801 +++++++++++ test/integration/protocol_integration_test.cc | 87 +- test/integration/redirect_integration_test.cc | 18 + test/mocks/http/mocks.cc | 15 + test/mocks/http/mocks.h | 45 +- test/mocks/redis/BUILD | 22 + test/mocks/redis/mocks.cc | 32 + test/mocks/redis/mocks.h | 77 ++ test/mocks/router/mocks.cc | 13 + test/mocks/router/mocks.h | 38 + test/mocks/server/factory_context.cc | 2 +- test/mocks/server/factory_context.h | 4 +- test/mocks/server/server_factory_context.cc | 4 + test/mocks/server/server_factory_context.h | 13 +- test/mocks/stream_info/mocks.h | 5 + test/mocks/upstream/BUILD | 3 + test/mocks/upstream/host.h | 4 + test/mocks/upstream/thread_local_cluster.cc | 3 + test/mocks/upstream/thread_local_cluster.h | 7 + test/server/BUILD | 3 +- test/server/admin/BUILD | 1 + test/server/admin/admin_test.cc | 56 + test/server/admin/config_dump_handler_test.cc | 47 +- test/server/config_validation/BUILD | 3 +- test/test_common/utility.cc | 5 + test/test_common/wasm_base.h | 12 + tools/project/BUILD | 47 + tools/proto_format/format_api.py | 5 + tools/spelling/spelling_dictionary.txt | 3 + 282 files changed, 15805 insertions(+), 542 deletions(-) create mode 100644 api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/BUILD create mode 100644 api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.proto create mode 100644 api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.proto create mode 100644 api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/BUILD create mode 100644 api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/tcp_connection_pool.proto create mode 100644 contrib/common/active_redirect/source/BUILD create mode 100644 contrib/common/active_redirect/source/active_redirect_policy_impl.cc create mode 100644 contrib/common/active_redirect/source/active_redirect_policy_impl.h create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/source/BUILD create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/source/config.cc create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/source/config.h create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/source/filter.cc create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/source/filter.h create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/test/BUILD create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/test/config_test.cc create mode 100644 contrib/custom_cluster_plugins/cluster_fallback/test/filter_test.cc create mode 100644 contrib/envoy/http/BUILD create mode 100644 contrib/envoy/http/active_redirect_policy.h create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/BUILD create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/config.cc create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/config.h create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.cc create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/transcoder.h create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/utility.cc create mode 100644 contrib/http_dubbo_transcoder/filters/http/source/utility.h create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/BUILD create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/config_test.cc create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/dubbo_transcoder_filter_test.cc create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/test_data/BUILD create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/test_data/big_reqeust_body create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/test_data/chunked-request-example.py create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo.yaml create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_new.yaml create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route copy.yaml create mode 100644 contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route.yaml create mode 100644 contrib/upstreams/http/dubbo_tcp/source/BUILD create mode 100644 contrib/upstreams/http/dubbo_tcp/source/config.cc create mode 100644 contrib/upstreams/http/dubbo_tcp/source/config.h create mode 100644 contrib/upstreams/http/dubbo_tcp/source/upstream_request.cc create mode 100644 contrib/upstreams/http/dubbo_tcp/source/upstream_request.h create mode 100644 contrib/upstreams/http/dubbo_tcp/test/BUILD create mode 100644 contrib/upstreams/http/dubbo_tcp/test/upstream_request_test.cc create mode 100644 envoy/redis/BUILD create mode 100644 envoy/redis/async_client.h create mode 100644 generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto create mode 100644 source/common/redis/BUILD create mode 100644 source/common/redis/async_client_impl.cc create mode 100644 source/common/redis/async_client_impl.h rename source/common/upstream/{upstream_http_factory_context_impl.h => upstream_factory_context_impl.h} (74%) create mode 100644 source/extensions/filters/network/common/redis/raw_client.h create mode 100644 source/extensions/filters/network/common/redis/raw_client_impl.cc create mode 100644 source/extensions/filters/network/common/redis/raw_client_impl.h create mode 100644 test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc create mode 100644 test/integration/filters/add_header_filter.proto create mode 100644 test/integration/network_extension_discovery_integration_test.cc create mode 100644 test/mocks/redis/mocks.cc create mode 100644 test/mocks/redis/mocks.h create mode 100644 tools/project/BUILD diff --git a/.bazelrc b/.bazelrc index 6b080508f38cf..aa22882278f59 100644 --- a/.bazelrc +++ b/.bazelrc @@ -528,3 +528,5 @@ common:debug --config=debug-tests try-import %workspace%/clang.bazelrc try-import %workspace%/user.bazelrc try-import %workspace%/local_tsan.bazelrc + +build --define tcmalloc=gperftools \ No newline at end of file diff --git a/api/BUILD b/api/BUILD index 201c89aaed00e..72caa644e1896 100644 --- a/api/BUILD +++ b/api/BUILD @@ -73,6 +73,7 @@ proto_library( visibility = ["//visibility:public"], deps = [ "//contrib/envoy/extensions/filters/http/dynamo/v3:pkg", + "//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg", "//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/language/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/squash/v3:pkg", diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 903b61f129aee..e89adb51e8793 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -17,14 +17,14 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_desc = "protoc plugin to generate polyglot message validators", project_url = "https://github.com/bufbuild/protoc-gen-validate", use_category = ["api"], - sha256 = "f1ec013cfdfffa7a17d75b55d41265dad47d24e0e9d86c02311562e15be52da9", - version = "1.0.1", + sha256 = "0b1b1ea8c248dce8c7592dc1a93e4adebd116f0d68123f8eb34251e7ce410866", + version = "1.0.2", urls = ["https://github.com/bufbuild/protoc-gen-validate/archive/refs/tags/v{version}.zip"], strip_prefix = "protoc-gen-validate-{version}", - release_date = "2023-05-09", + release_date = "2023-06-26", implied_untracked_deps = [ "com_github_iancoleman_strcase", - "com_github_lyft_protoc_gen_star", + "com_github_lyft_protoc_gen_star_v2", "com_github_spf13_afero", "org_golang_google_genproto", "org_golang_x_text", diff --git a/api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/BUILD b/api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.proto b/api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.proto new file mode 100644 index 0000000000000..2734e32a0ddcb --- /dev/null +++ b/api/contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package envoy.extensions.custom_cluster_plugins.cluster_fallback.v3; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.custom_cluster_plugins.cluster_fallback.v3"; +option java_outer_classname = "ClusterFallbackProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3;cluster_fallbackv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +message ClusterFallbackConfig { + message ClusterConfig { + string routing_cluster = 1; + + repeated string fallback_clusters = 2; + } + + message WeightedClusterConfig { + repeated ClusterConfig config = 1; + } + + oneof config_specifier { + ClusterConfig cluster_config = 1; + + WeightedClusterConfig weighted_cluster_config = 2; + } +} diff --git a/api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.proto b/api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.proto new file mode 100644 index 0000000000000..bde78686e4b3c --- /dev/null +++ b/api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.proto @@ -0,0 +1,120 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.http_dubbo_transcoder.v3; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.http.http_dubbo_transcoder.v3"; +option java_outer_classname = "HttpDubboTranscoderProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/http_dubbo_transcoder/v3;http_dubbo_transcoderv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Buffer] +// Buffer :ref:`configuration overview `. +// [#extension: envoy.filters.http.buffer] +message HttpDubboTranscoder { + enum UrlUnescapeSpec { + // URL path parameters will not decode RFC 6570 reserved characters. + // For example, segment `%2f%23/%20%2523` is unescaped to `%2f%23/ %23`. + ALL_CHARACTERS_EXCEPT_RESERVED = 0; + + // URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // For example, segment `%2f%23/%20%2523` is unescaped to `%2f#/ %23`. + ALL_CHARACTERS_EXCEPT_SLASH = 1; + + // URL path parameters will be fully URI-decoded. + // For example, segment `%2f%23/%20%2523` is unescaped to `/#/ %23`. + ALL_CHARACTERS = 2; + } + + message RequestValidateOptions { + // default this trascoder will pass the request which contains unknown query paramters, + // if this option set to true, the request will be reject with 400 Bad Request. + bool reject_unknown_query_parameters = 1; + + bool reject_unknown_method = 2; + } + + message DubboMethodMapping { + enum MatchHttpMethodSpec { + ALL_GET = 0; + ALL_POST = 1; + ALL_PUT = 2; + ALL_DELETE = 3; + ALL_PATCH = 4; + } + + message ParameterMapping { + enum ExtractKeySpec { + ALL_QUERY_PARAMETER = 0; + ALL_HEADER = 1; + ALL_PATH = 2; + ALL_BODY = 3; + } + + ExtractKeySpec extract_key_spec = 1; + + string extract_key = 2; + + string mapping_type = 3; + } + + message PathMatcher { + string match_pattern = 1; + + MatchHttpMethodSpec match_http_method_spec = 2; + } + + message PassthroughSetting { + message PassthroughHeaders { + repeated string keys = 1; + } + + oneof headers_setting { + bool passthrough_all_headers = 1; + + PassthroughHeaders passthrough_headers = 2; + } + } + + string name = 1 [(validate.rules).string = {min_len: 1}]; + + PathMatcher path_matcher = 2; + + repeated ParameterMapping parameter_mapping = 3; + + PassthroughSetting passthrough_setting = 4; + } + + message DubboServiceMapping { + string name = 1 [(validate.rules).string = {min_len: 1}]; + + string version = 2; + + repeated DubboMethodMapping method_mapping = 3; + + string group = 4; + } + + // Configure the behavior when handling requests that cannot be transcoded. + // + // By default, the transcoder will silently pass through HTTP requests that are malformed. + // This includes requests with unknown query parameters, unregister paths, etc. + RequestValidateOptions request_validation_options = 2; + + // URL unescaping policy. + // This spec is only applied when extracting variable with multiple segments in the URL path. + // For example, in case of `/foo/{x=*}/bar/{y=prefix/*}/{z=**}` `x` variable is single segment and `y` and `z` are multiple segments. + // For a path with `/foo/first/bar/prefix/second/third/fourth`, `x=first`, `y=prefix/second`, `z=third/fourth`. + // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED`. + UrlUnescapeSpec url_unescape_spec = 3 [(validate.rules).enum = {defined_only: true}]; + + repeated DubboServiceMapping services_mapping = 4; +} diff --git a/api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/BUILD b/api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/tcp_connection_pool.proto b/api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/tcp_connection_pool.proto new file mode 100644 index 0000000000000..4fc0440a22615 --- /dev/null +++ b/api/contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/tcp_connection_pool.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package envoy.extensions.upstreams.http.dubbo_tcp.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.upstreams.http.dubbo_tcp.v3"; +option java_outer_classname = "TcpConnectionPoolProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/dubbo_tcp/v3;dubbo_tcpv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Tcp Connection Pool] + +// A connection pool which forwards downstream HTTP as TCP to upstream, +// [#extension: envoy.upstreams.http.tcp] +message DubboTcpConnectionPoolProto { +} diff --git a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto index e87c9478db635..00ce562f8dfb9 100644 --- a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto +++ b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto @@ -391,8 +391,7 @@ message FilterStateRule { // A map of string keys to requirements. The string key is the string value // in the FilterState with the name specified in the *name* field above. - map - requires = 3; + map requires = 3; } // This is the Envoy HTTP filter config for JWT authentication. diff --git a/api/envoy/config/listener/v3/listener_components.proto b/api/envoy/config/listener/v3/listener_components.proto index 150a6851d523e..2adb8bc2c80ce 100644 --- a/api/envoy/config/listener/v3/listener_components.proto +++ b/api/envoy/config/listener/v3/listener_components.proto @@ -45,7 +45,6 @@ message Filter { // Configuration source specifier for an extension configuration discovery // service. In case of a failure and without the default configuration, the // listener closes the connections. - // [#not-implemented-hide:] core.v3.ExtensionConfigSource config_discovery = 5; } } diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 014bb0d9261ab..e545a0137ed23 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -219,6 +219,23 @@ message VirtualHost { // It takes precedence over the route config mirror policy entirely. // That is, policies are not merged, the most specific non-empty one becomes the mirror policies. repeated RouteAction.RequestMirrorPolicy request_mirror_policies = 22; + + // If non-empty, a list of server names (such as SNI for the TLS protocol) is used to determine + // whether this request is allowed to access this VirutalHost. If not allowed, 421 Misdirected Request will be returned. + // + // The server name can be matched whith wildcard domains, i.e. ``www.example.com`` can be matched with + // ``www.example.com``, ``*.example.com`` and ``*.com``. + // + // Note that partial wildcards are not supported, and values like ``*w.example.com`` are invalid. + // + // This is useful when expose all virtual hosts to arbitrary HCM filters (such as using SRDS), and you want to make + // mTLS-protected routes invisible to requests with different SNIs. + // + // .. attention:: + // + // See the :ref:`FAQ entry ` on how to configure SNI for more + // information. + repeated string allow_server_names = 101; } // A filter-defined action type. @@ -367,6 +384,7 @@ message Route { // multiple upstream clusters along with weights that indicate the percentage of // traffic to be forwarded to each cluster. The router selects an upstream cluster based on the // weights. +// [#next-free-field: 102] message WeightedCluster { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.WeightedCluster"; @@ -494,6 +512,16 @@ message WeightedCluster { // ` for how key names map to the underlying implementation. string runtime_key_prefix = 2; + // Name of the cluster specifier plugin to use to determine the cluster for requests on this route. + // The cluster specifier plugin name must be defined in the associated + // :ref:`cluster specifier plugins ` + // in the :ref:`name ` field. + string cluster_specifier_plugin = 100; + + // Custom cluster specifier plugin configuration to use to determine the cluster for requests + // on this route. + ClusterSpecifierPlugin inline_cluster_specifier_plugin = 101; + oneof random_value_specifier { // Specifies the header name that is used to look up the random value passed in the request header. // This is used to ensure consistent cluster picking across multiple proxy levels for weighted traffic. @@ -725,7 +753,7 @@ message CorsPolicy { google.protobuf.BoolValue allow_private_network_access = 12; } -// [#next-free-field: 42] +// [#next-free-field: 1001] message RouteAction { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RouteAction"; @@ -1377,6 +1405,8 @@ message RouteAction { // Specifies the maximum stream duration for this route. MaxStreamDuration max_stream_duration = 36; + + InternalActiveRedirectPolicy internal_active_redirect_policy = 1000; } // HTTP retry :ref:`architecture overview `. @@ -2385,6 +2415,109 @@ message InternalRedirectPolicy { bool allow_cross_scheme_redirect = 4; } +// Redirects to the specified URI based on the response code. +// [#next-free-field: 22] +message InternalActiveRedirectPolicy { + // [#next-free-field: 23] + message RedirectPolicy { + // An internal redirect is not handled, unless the number of previous internal redirects that a + // downstream request has encountered is lower than this value. + // In the case where a downstream request is bounced among multiple routes by internal redirect, + // the first route that hits this threshold, or does not set :ref:`internal_redirect_policy + // ` + // will pass the redirect back to downstream. + // + // If not specified, at most one redirect will be followed. + google.protobuf.UInt32Value max_internal_redirects = 10; + + // Defines what upstream response codes are allowed to trigger internal redirect. + // All response codes support redirection except 200. + repeated uint32 redirect_response_codes = 11 [(validate.rules).repeated = {max_items: 50}]; + + // The URI of the redirect. + oneof redirect_url_rewrite_specifier { + option (validate.required) = true; + + string redirect_url = 12 [(validate.rules).string = {min_len: 1}]; + + type.matcher.v3.RegexMatchAndSubstitute redirect_url_rewrite_regex = 13; + } + + // Specifies a list of predicates that are queried when an upstream response is deemed + // to trigger an internal redirect by all other criteria. Any predicate in the list can reject + // the redirect, causing the response to be proxied to downstream. + repeated core.v3.TypedExtensionConfig predicates = 14; + + // Allow internal redirect to follow a target URI with a different scheme than the value of + // x-forwarded-proto. The default is false. + bool allow_cross_scheme_redirect = 15; + + // HTTP headers to add to a local reply. This allows the response mapper to append, to add + // or to override headers of any local reply before it is sent to a downstream client. + repeated core.v3.HeaderValueOption request_headers_to_add = 16 + [(validate.rules).repeated = {max_items: 1000}]; + + // Indicates that during forwarding, the host header will be swapped with + // this value. + string host_rewrite_literal = 17 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + + // If true, the host name in the downstream request is used for redirection. + bool forced_use_original_host = 20; + + bool forced_add_header_before_route_matcher = 22; + } + + // An internal redirect is not handled, unless the number of previous internal redirects that a + // downstream request has encountered is lower than this value. + // In the case where a downstream request is bounced among multiple routes by internal redirect, + // the first route that hits this threshold, or does not set :ref:`internal_redirect_policy + // ` + // will pass the redirect back to downstream. + // + // If not specified, at most one redirect will be followed. + google.protobuf.UInt32Value max_internal_redirects = 1; + + // Defines what upstream response codes are allowed to trigger internal redirect. + // All response codes support redirection except 200. + repeated uint32 redirect_response_codes = 2 [(validate.rules).repeated = {max_items: 50}]; + + // The URI of the redirect. + oneof redirect_url_rewrite_specifier { + // ption (validate.required) = true; + + string redirect_url = 7 [(validate.rules).string = {min_len: 1}]; + + type.matcher.v3.RegexMatchAndSubstitute redirect_url_rewrite_regex = 8; + } + + // Specifies a list of predicates that are queried when an upstream response is deemed + // to trigger an internal redirect by all other criteria. Any predicate in the list can reject + // the redirect, causing the response to be proxied to downstream. + repeated core.v3.TypedExtensionConfig predicates = 4; + + // Allow internal redirect to follow a target URI with a different scheme than the value of + // x-forwarded-proto. The default is false. + bool allow_cross_scheme_redirect = 5; + + // HTTP headers to add to a local reply. This allows the response mapper to append, to add + // or to override headers of any local reply before it is sent to a downstream client. + repeated core.v3.HeaderValueOption request_headers_to_add = 6 + [(validate.rules).repeated = {max_items: 1000}]; + + // Indicates that during forwarding, the host header will be swapped with + // this value. + string host_rewrite_literal = 9 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + + // If true, the host name in the downstream request is used for redirection. + bool forced_use_original_host = 19; + + bool forced_add_header_before_route_matcher = 21; + + repeated RedirectPolicy policies = 18; +} + // A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the // map value in // :ref:`VirtualHost.typed_per_filter_config`, diff --git a/api/envoy/extensions/filters/http/composite/v3/composite.proto b/api/envoy/extensions/filters/http/composite/v3/composite.proto index 08a72e411b9f7..027c9322532cc 100644 --- a/api/envoy/extensions/filters/http/composite/v3/composite.proto +++ b/api/envoy/extensions/filters/http/composite/v3/composite.proto @@ -2,11 +2,14 @@ syntax = "proto3"; package envoy.extensions.filters.http.composite.v3; +import "envoy/config/core/v3/config_source.proto"; import "envoy/config/core/v3/extension.proto"; import "xds/annotations/v3/status.proto"; +import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; +import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.extensions.filters.http.composite.v3"; option java_outer_classname = "CompositeProto"; @@ -32,8 +35,30 @@ message Composite { option (xds.annotations.v3.message_status).work_in_progress = true; } +// Configuration for an extension configuration discovery service with name. +message DynamicConfig { + // The name of the extension configuration. It also serves as a resource name in ExtensionConfigDS. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Configuration source specifier for an extension configuration discovery + // service. In case of a failure and without the default configuration, + // 500(Internal Server Error) will be returned. + config.core.v3.ExtensionConfigSource config_discovery = 2; +} + // Composite match action (see :ref:`matching docs ` for more info on match actions). // This specifies the filter configuration of the filter that the composite filter should delegate filter interactions to. message ExecuteFilterAction { - config.core.v3.TypedExtensionConfig typed_config = 1; + // Filter specific configuration which depends on the filter being + // instantiated. See the supported filters for further documentation. + // Only one of ``typed_config`` or ``dynamic_config`` can be set. + // [#extension-category: envoy.filters.http] + config.core.v3.TypedExtensionConfig typed_config = 1 + [(udpa.annotations.field_migrate).oneof_promotion = "config_type"]; + + // Dynamic configuration of filter obtained via extension configuration discovery + // service. + // Only one of ``typed_config`` or ``dynamic_config`` can be set. + DynamicConfig dynamic_config = 2 + [(udpa.annotations.field_migrate).oneof_promotion = "config_type"]; } diff --git a/api/envoy/extensions/filters/http/custom_response/v3/custom_response.proto b/api/envoy/extensions/filters/http/custom_response/v3/custom_response.proto index cd28640fefdac..5426e181de057 100644 --- a/api/envoy/extensions/filters/http/custom_response/v3/custom_response.proto +++ b/api/envoy/extensions/filters/http/custom_response/v3/custom_response.proto @@ -6,6 +6,7 @@ import "xds/annotations/v3/status.proto"; import "xds/type/matcher/v3/matcher.proto"; import "udpa/annotations/status.proto"; +import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.extensions.filters.http.custom_response.v3"; option java_outer_classname = "CustomResponseProto"; @@ -105,4 +106,16 @@ message CustomResponse { // documentation for more information on the matcher trees. // [#extension-category: envoy.http.custom_response] xds.type.matcher.v3.Matcher custom_response_matcher = 1; + + // Indicates whether the router filter should cache the body. + BufferSettings with_request_body = 101; +} + + +// Configuration for buffering the request data. +message BufferSettings { + // Sets the maximum size of a message body that the filter will hold in memory. + // Exceeding this size does not result in a ``HTTP 413`` error; however, it prevents + // the full original body from being used during internal redirection. + uint32 max_request_bytes = 1 [(validate.rules).uint32 = {gt: 0}]; } diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index bf88896e70309..24f65514ee471 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -614,8 +614,7 @@ message FilterStateRule { // A map of string keys to requirements. The string key is the string value // in the FilterState with the name specified in the ``name`` field above. - map - requires = 3; + map requires = 3; } // This is the Envoy HTTP filter config for JWT authentication. diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index f86be41f0493c..b5c5ce51fcbcb 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -870,6 +870,14 @@ message HttpConnectionManager { // This should be set to `false` in cases where Envoy's view of the downstream address may not correspond to the // actual client address, for example, if there's another proxy in front of the Envoy. google.protobuf.BoolValue add_proxy_protocol_connection_state = 53; + + // The timeout seconds configured here will be set in the "Keep-Alive" response header. + // For example, configuring 10s will return the response header "Connection: keep-alive" and "Keep-Alive: timeout=10". + // If not specified, the default is 0, which means this behavior is disabled. + // The "Keep-Alive" header field is recognized by Mozilla and Apache HTTPClient. + // Note that the "Connection" and "Keep-Alive" response headers will only be added when the downstream protocol is HTTP1.0 or HTTP1.1 + // and it is not an upgrade connection scenario. + google.protobuf.Duration keepalive_header_timeout = 1058; } // The configuration to customize local reply returned by Envoy. @@ -1052,11 +1060,34 @@ message ScopedRoutes { } } + message HostValueExtractor { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder." + "FragmentBuilder.HostValueExtractor"; + + // The maximum number of host superset recomputes. If not specified, defaults to 100. + google.protobuf.UInt32Value max_recompute_num = 1; + } + + message LocalPortValueExtractor { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder." + "FragmentBuilder.LocalPortValueExtractor"; + } + + oneof type { option (validate.required) = true; // Specifies how a header field's value should be extracted. HeaderValueExtractor header_value_extractor = 1; + + // Extract the fragemnt value from the :authority header, and support recompute with the wildcard domains, + // i.e. ``www.example.com`` can be recomputed with ``*.example.com``, then ``*.com``, then ``*``. + HostValueExtractor host_value_extractor = 101; + + // Extract the fragment value from local port of the connection. + LocalPortValueExtractor local_port_value_extractor = 102; } } diff --git a/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto b/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto index 73cf7ed7a8645..c984b72ec2d5f 100644 --- a/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto +++ b/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto @@ -50,8 +50,15 @@ message RedirectPolicy { // - `prefix_rewrite` // - `regex_rewrite` config.route.v3.RedirectAction redirect_action = 2; + + google.protobuf.BoolValue use_original_request_uri = 107; } + google.protobuf.UInt32Value max_internal_redirects = 108; + google.protobuf.BoolValue keep_original_response_code = 109; + google.protobuf.BoolValue use_original_request_body = 110; + google.protobuf.BoolValue only_redirect_upstream_code = 111; + // The new response status code if specified. This is used to override the // status code of the response from the new upstream if it is not an error status. google.protobuf.UInt32Value status_code = 3 [(validate.rules).uint32 = {lte: 999 gte: 100}]; diff --git a/api/envoy/service/extension/v3/config_discovery.proto b/api/envoy/service/extension/v3/config_discovery.proto index 5801f6946b565..c4d4b93a69777 100644 --- a/api/envoy/service/extension/v3/config_discovery.proto +++ b/api/envoy/service/extension/v3/config_discovery.proto @@ -18,11 +18,12 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Extension config discovery service (ECDS)] // A service that supports dynamic configuration updates for a specific filter. -// Currently, ECDS is supported for HTTP filters and Listener filters. Please check -// :ref:`Extension Config Discovery Service (ECDS) API `. +// Currently, ECDS is supported for downstream network filters, HTTP filters and Listener filters. +// Please check :ref:`Extension Config Discovery Service (ECDS) API `. // The overall extension config discovery service works as follows: // -// 1. A filter (:ref:`Listener ` +// 1. A filter (:ref:`Network `, +// :ref:`Listener ` // or :ref:`HTTP `) // contains a :ref:`config_discovery ` configuration. This configuration // includes a :ref:`config_source `, diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 52f5060b54d37..885d9d7db6176 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -9,8 +9,10 @@ proto_library( name = "active_protos", visibility = ["//visibility:public"], deps = [ - "//contrib/envoy/extensions/config/v3alpha:pkg", + "//contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3:pkg", "//contrib/envoy/extensions/filters/http/dynamo/v3:pkg", + "//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg", + "//contrib/envoy/extensions/config/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/language/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/squash/v3:pkg", @@ -34,6 +36,7 @@ proto_library( "//contrib/envoy/extensions/network/connection_balance/dlb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/qat/v3alpha:pkg", + "//contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3:pkg", "//contrib/envoy/extensions/regex_engines/hyperscan/v3alpha:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", "//contrib/envoy/extensions/vcl/v3alpha:pkg", diff --git a/bazel/BUILD b/bazel/BUILD index 8b15825a9170b..1f4853b1b0277 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -673,6 +673,13 @@ config_setting( define_values = {"FUZZING_ENGINE": "oss-fuzz"}, ) +# By default we enable AliMesh build. If want to build community +# version then build Envoy with flag of '--define alimesh=false'. +config_setting( + name = "alimesh", + values = {"define": "alimesh=false"}, +) + alias( name = "fuzzing_engine", actual = select({ diff --git a/bazel/envoy_binary.bzl b/bazel/envoy_binary.bzl index 58343f8bb3220..b95d36e924981 100644 --- a/bazel/envoy_binary.bzl +++ b/bazel/envoy_binary.bzl @@ -9,6 +9,7 @@ load( "envoy_select_exported_symbols", "envoy_stdlib_deps", "tcmalloc_external_dep", + "envoy_select_alimesh", ) # Envoy C++ binary targets should be specified with this function. @@ -86,7 +87,7 @@ def _envoy_linkopts(): "@envoy//bazel:boringssl_fips": [], "@envoy//bazel:windows_x86_64": [], "//conditions:default": ["-pie"], - }) + envoy_select_exported_symbols(["-Wl,-E"]) + }) + envoy_select_exported_symbols(["-Wl,-E"]) + envoy_select_alimesh(["-lcrypt"]) def _envoy_stamped_deps(): return select({ diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index a1f8f1dc6e50a..a4608d6a7d601 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -126,6 +126,7 @@ def envoy_copts(repository, test = False): _envoy_select_perf_annotation(["-DENVOY_PERF_ANNOTATION"]) + \ _envoy_select_perfetto(["-DENVOY_PERFETTO"]) + \ envoy_select_google_grpc(["-DENVOY_GOOGLE_GRPC"], repository) + \ + envoy_select_alimesh(["-DALIMESH"]) + \ envoy_select_signal_trace(["-DENVOY_HANDLE_SIGNALS"], repository) + \ _envoy_select_path_normalization_by_default(["-DENVOY_NORMALIZE_PATH_BY_DEFAULT"], repository) @@ -192,6 +193,12 @@ def _envoy_select_perf_annotation(xs): "//conditions:default": [], }) +def envoy_select_alimesh(xs): + return select({ + "@envoy//bazel:alimesh": [], + "//conditions:default": xs, + }) + def _envoy_select_perfetto(xs): return select({ "@envoy//bazel:enable_perf_tracing": xs, diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index 152302ed6432b..828e70b15c518 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -103,6 +103,7 @@ def envoy_cc_library( tags = [], deps = [], strip_include_prefix = None, + alimesh_deps = [], include_prefix = None, textual_hdrs = None, alwayslink = None, @@ -111,6 +112,11 @@ def envoy_cc_library( if tcmalloc_dep: deps += tcmalloc_external_deps(repository) + deps = deps + select({ + "@envoy//bazel:alimesh": [], + "//conditions:default": alimesh_deps, + }) + # If alwayslink is not specified, allow turning it off via --define=library_autolink=disabled # alwayslink is defaulted on for envoy_cc_extensions to ensure the REGISTRY macros work. if alwayslink == None: diff --git a/bazel/envoy_test.bzl b/bazel/envoy_test.bzl index ad05121a99223..8bc7bc4327f8e 100644 --- a/bazel/envoy_test.bzl +++ b/bazel/envoy_test.bzl @@ -16,6 +16,7 @@ load( "envoy_select_force_libcpp", "envoy_stdlib_deps", "tcmalloc_external_dep", + "envoy_select_alimesh", ) # Envoy C++ related test infrastructure (that want gtest, gmock, but may be @@ -72,7 +73,7 @@ def _envoy_test_linkopts(): # TODO(mattklein123): It's not great that we universally link against the following libs. # In particular, -latomic and -lrt are not needed on all platforms. Make this more granular. "//conditions:default": ["-pthread", "-lrt", "-ldl"], - }) + envoy_select_force_libcpp([], ["-lstdc++fs", "-latomic"]) + envoy_dbg_linkopts() + envoy_select_exported_symbols(["-Wl,-E"]) + }) + envoy_select_force_libcpp([], ["-lstdc++fs", "-latomic"]) + envoy_select_alimesh(["-lcrypt"]) + envoy_dbg_linkopts() + envoy_select_exported_symbols(["-Wl,-E"]) # Envoy C++ fuzz test targets. These are not included in coverage runs. def envoy_cc_fuzz_test( @@ -151,6 +152,7 @@ def envoy_cc_test( repository = "", external_deps = [], deps = [], + alimesh_deps = [], tags = [], args = [], copts = [], @@ -164,6 +166,11 @@ def envoy_cc_test( exec_properties = {}): coverage_tags = tags + ([] if coverage else ["nocoverage"]) + deps = deps + select({ + "@envoy//bazel:alimesh": [], + "//conditions:default": alimesh_deps, + }) + native.cc_test( name = name, srcs = srcs, @@ -198,13 +205,21 @@ def envoy_cc_test_library( data = [], external_deps = [], deps = [], + alimesh_deps = [], repository = "", tags = [], include_prefix = None, copts = [], alwayslink = 1, **kargs): + + deps = deps + select({ + "@envoy//bazel:alimesh": [], + "//conditions:default": alimesh_deps, + }) + disable_pch = kargs.pop("disable_pch", True) + _envoy_cc_test_infrastructure_library( name, srcs, diff --git a/bazel/external/rapidjson.BUILD b/bazel/external/rapidjson.BUILD index 6138f5fa351fa..9ec0d38a5b226 100644 --- a/bazel/external/rapidjson.BUILD +++ b/bazel/external/rapidjson.BUILD @@ -7,5 +7,5 @@ cc_library( includes = ["include"], # rapidjson is only needed to build external dependency of the Zipkin tracer. # For Envoy source code plese use source/common/json/json_loader.h - visibility = ["@io_opencensus_cpp//opencensus/exporters/trace/zipkin:__pkg__"], + visibility = ["//visibility:public"], ) diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index 3d3b13d969448..a763db8eefde3 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -65,7 +65,7 @@ configure_make( targets = [ "ARFLAGS='' libs install-subdirs", ], - alwayslink = True, + alwayslink = False, ) cc_library( diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 71667227f73c0..271f7199f1471 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -1,4 +1,6 @@ load(":dev_binding.bzl", "envoy_dev_binding") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository") load("@envoy_api//bazel:envoy_http_archive.bzl", "envoy_http_archive") load("@envoy_api//bazel:external_deps.bzl", "load_repository_locations") load(":repository_locations.bzl", "PROTOC_VERSIONS", "REPOSITORY_LOCATIONS_SPEC") @@ -6,6 +8,8 @@ load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_languag PPC_SKIP_TARGETS = ["envoy.filters.http.lua"] +DARWIN_SKIP_TARGETS = [] + WINDOWS_SKIP_TARGETS = [ "envoy.extensions.http.cache.file_system_http_cache", "envoy.filters.http.file_system_buffer", @@ -707,6 +711,10 @@ def _com_github_tencent_rapidjson(): name = "com_github_tencent_rapidjson", build_file = "@envoy//bazel/external:rapidjson.BUILD", ) + native.bind( + name = "rapidjson", + actual = "@com_github_tencent_rapidjson//:rapidjson", + ) def _com_github_nlohmann_json(): external_http_archive( @@ -734,6 +742,10 @@ def _com_github_alibaba_hessian2_codec(): name = "hessian2_codec_codec_impl", actual = "@com_github_alibaba_hessian2_codec//hessian2:codec_impl_lib", ) + native.bind( + name = "hessian2_codec_object_impl", + actual = "@com_github_alibaba_hessian2_codec//hessian2:object_lib", + ) def _com_github_ncopa_suexec(): external_http_archive( diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7f83d0cd5f5aa..0bff828501adb 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -602,8 +602,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( ], release_date = "2021-12-28", cpe = "N/A", - license = "MIT", - license_url = "https://github.com/adrian-thurston/colm/blob/{version}/COPYING", ), net_colm_open_source_ragel = dict( project_name = "Ragel", @@ -748,16 +746,14 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "hessian2-codec", project_desc = "hessian2-codec is a C++ library for hessian2 codec", project_url = "https://github.com/alibaba/hessian2-codec.git", - version = "e9bb36e206f2c5054b50d11f88bb1b95c77766f8", - sha256 = "82743dcbf2bd624a68eb2c0d54963ea87446eba4eb08c117744f0669ddc70786", + version = "dd8e05487a27b367b90ce81f4e6e6f62d693a212", + sha256 = "93260c54406e11b7be078a7ea120f7ab0df475c733e68d010fde400c5c8c8162", strip_prefix = "hessian2-codec-{version}", urls = ["https://github.com/alibaba/hessian2-codec/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.network.dubbo_proxy"], - release_date = "2022-10-10", + release_date = "2021-04-05", cpe = "N/A", - license = "Apache-2.0", - license_url = "https://github.com/alibaba/hessian2-codec/blob/{version}/LICENSE", ), com_github_tencent_rapidjson = dict( project_name = "RapidJSON", @@ -1313,11 +1309,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( proxy_wasm_cpp_sdk = dict( project_name = "WebAssembly for Proxies (C++ SDK)", project_desc = "WebAssembly for Proxies (C++ SDK)", - project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-sdk", - version = "e30535b7c0cd3126e6401bc3769063a74bbb37be", - sha256 = "94e474ebea782225821224734ed5992fa749301e12e06b6520b8b4d4e1c05ffc", + project_url = "https://github.com/higress-group/proxy-wasm-cpp-sdk", + version = "47bb9cd141a151415ad6a597ed60c78bea2ce0b7", + sha256 = "cab5efa54c0cec8eb17c0a2f6ce72b9cd84ebba2b332e919187f963a5d7cfaa1", strip_prefix = "proxy-wasm-cpp-sdk-{version}", - urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/archive/{version}.tar.gz"], + urls = ["https://github.com/higress-group/proxy-wasm-cpp-sdk/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = [ "envoy.access_loggers.wasm", @@ -1331,19 +1327,17 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2022-03-15", + release_date = "2021-06-24", cpe = "N/A", - license = "Apache-2.0", - license_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/blob/{version}/LICENSE", ), proxy_wasm_cpp_host = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", - project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "5d76116c449d6892b298b7ae79a84ef1cf5752bf", - sha256 = "a5825a1a5bbd5b0178c6189b227d5cf4370ac713a883b41f6a54edd768a03cb7", + project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", + version = "db24a6a09455429ef0f9ebd974f9219eb4a8a6c2", + sha256 = "20cd31530c1e5c7aaad8d85597e1d0f7792d76d41c8de31c9125dbdb07d481dc", strip_prefix = "proxy-wasm-cpp-host-{version}", - urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], + urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = [ "envoy.access_loggers.wasm", @@ -1357,10 +1351,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2023-06-01", + release_date = "2024-05-18", cpe = "N/A", - license = "Apache-2.0", - license_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host/blob/{version}/LICENSE", ), proxy_wasm_rust_sdk = dict( project_name = "WebAssembly for Proxies (Rust SDK)", diff --git a/contrib/common/active_redirect/source/BUILD b/contrib/common/active_redirect/source/BUILD new file mode 100644 index 0000000000000..8a770f9b26089 --- /dev/null +++ b/contrib/common/active_redirect/source/BUILD @@ -0,0 +1,34 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "active_redirect_policy_lib", + srcs = ["active_redirect_policy_impl.cc"], + hdrs = ["active_redirect_policy_impl.h"], + external_deps = ["abseil_optional"], + visibility = ["//visibility:public"], + deps = [ + "//contrib/envoy/http:active_redirect_policy_interface", + "//envoy/http:header_map_interface", + "//envoy/router:router_interface", + "//source/common/common:empty_string", + "//source/common/common:utility_lib", + "//source/common/config:utility_lib", + "//source/common/http:header_utility_lib", + "//source/common/http:headers_lib", + "//source/common/http:path_utility_lib", + "//source/common/http:utility_lib", + "//source/common/protobuf:utility_lib", + "//source/common/router:header_parser_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/route/v3:pkg_cc_proto", + "@envoy_api//envoy/type/v3:pkg_cc_proto", + ], +) diff --git a/contrib/common/active_redirect/source/active_redirect_policy_impl.cc b/contrib/common/active_redirect/source/active_redirect_policy_impl.cc new file mode 100644 index 0000000000000..03d9bdeab2ebd --- /dev/null +++ b/contrib/common/active_redirect/source/active_redirect_policy_impl.cc @@ -0,0 +1,225 @@ +#include "contrib/common/active_redirect/source/active_redirect_policy_impl.h" + +#include +#include + +#include "source/common/common/empty_string.h" +#include "source/common/common/regex.h" +#include "source/common/common/utility.h" +#include "source/common/config/utility.h" +#include "source/common/http/path_utility.h" + +namespace Envoy { +namespace Router { + +InternalActiveRedirectPolicyImpl::InternalActiveRedirectPolicyImpl( + const envoy::config::route::v3::InternalActiveRedirectPolicy& policy_config, + ProtobufMessage::ValidationVisitor& validator, absl::string_view current_route_name) + : current_route_name_(current_route_name), + redirect_response_codes_(buildRedirectResponseCodes(policy_config)), + max_internal_redirects_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(policy_config, max_internal_redirects, 1)), + enabled_(true), allow_cross_scheme_redirect_(policy_config.allow_cross_scheme_redirect()), + redirect_url_(policy_config.redirect_url()), + request_headers_parser_(HeaderParser::configure(policy_config.request_headers_to_add())), + redirect_url_rewrite_regex_( + policy_config.has_redirect_url_rewrite_regex() + ? Regex::Utility::parseRegex(policy_config.redirect_url_rewrite_regex().pattern()) + : nullptr), + redirect_url_rewrite_regex_substitution_( + policy_config.has_redirect_url_rewrite_regex() + ? policy_config.redirect_url_rewrite_regex().substitution() + : ""), + host_rewrite_(policy_config.host_rewrite_literal()), + forced_use_original_host_(policy_config.forced_use_original_host()), + forced_add_header_before_route_matcher_(policy_config.forced_add_header_before_route_matcher()) { + for (const auto& predicate : policy_config.predicates()) { + auto& factory = + Envoy::Config::Utility::getAndCheckFactory(predicate); + auto config = factory.createEmptyConfigProto(); + Envoy::Config::Utility::translateOpaqueConfig(predicate.typed_config(), validator, *config); + predicate_factories_.emplace_back(&factory, std::move(config)); + } +} + +InternalActiveRedirectPolicyImpl::InternalActiveRedirectPolicyImpl( + const envoy::config::route::v3::InternalActiveRedirectPolicy::RedirectPolicy& policy_config, + ProtobufMessage::ValidationVisitor& validator, absl::string_view current_route_name) + : current_route_name_(current_route_name), + redirect_response_codes_(buildRedirectResponseCodes(policy_config)), + max_internal_redirects_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(policy_config, max_internal_redirects, 1)), + enabled_(true), allow_cross_scheme_redirect_(policy_config.allow_cross_scheme_redirect()), + redirect_url_(policy_config.redirect_url()), + request_headers_parser_(HeaderParser::configure(policy_config.request_headers_to_add())), + redirect_url_rewrite_regex_( + policy_config.has_redirect_url_rewrite_regex() + ? Regex::Utility::parseRegex(policy_config.redirect_url_rewrite_regex().pattern()) + : nullptr), + redirect_url_rewrite_regex_substitution_( + policy_config.has_redirect_url_rewrite_regex() + ? policy_config.redirect_url_rewrite_regex().substitution() + : ""), + host_rewrite_(policy_config.host_rewrite_literal()) { + for (const auto& predicate : policy_config.predicates()) { + auto& factory = + Envoy::Config::Utility::getAndCheckFactory(predicate); + auto config = factory.createEmptyConfigProto(); + Envoy::Config::Utility::translateOpaqueConfig(predicate.typed_config(), validator, *config); + predicate_factories_.emplace_back(&factory, std::move(config)); + } +} + +std::vector +InternalActiveRedirectPolicyImpl::predicates() const { + std::vector predicates; + for (const auto& predicate_factory : predicate_factories_) { + predicates.emplace_back(predicate_factory.first->createInternalRedirectPredicate( + *predicate_factory.second, current_route_name_)); + } + return predicates; +} + +absl::flat_hash_set InternalActiveRedirectPolicyImpl::buildRedirectResponseCodes( + const envoy::config::route::v3::InternalActiveRedirectPolicy& policy_config) const { + if (policy_config.redirect_response_codes_size() == 0) { + return absl::flat_hash_set{}; + } + + absl::flat_hash_set ret; + std::for_each(policy_config.redirect_response_codes().begin(), + policy_config.redirect_response_codes().end(), [&ret](uint32_t response_code) { + const absl::flat_hash_set valid_redirect_response_code = { + 301, 302, 303, 307, 308, 200}; + if (!valid_redirect_response_code.contains(response_code)) { + ret.insert(static_cast(response_code)); + } + }); + return ret; +} + +absl::flat_hash_set InternalActiveRedirectPolicyImpl::buildRedirectResponseCodes( + const envoy::config::route::v3::InternalActiveRedirectPolicy::RedirectPolicy& policy_config) + const { + if (policy_config.redirect_response_codes_size() == 0) { + return absl::flat_hash_set{}; + } + + absl::flat_hash_set ret; + std::for_each(policy_config.redirect_response_codes().begin(), + policy_config.redirect_response_codes().end(), [&ret](uint32_t response_code) { + const absl::flat_hash_set valid_redirect_response_code = { + 301, 302, 303, 307, 308, 200}; + if (!valid_redirect_response_code.contains(response_code)) { + ret.insert(static_cast(response_code)); + } + }); + return ret; +} + +void InternalActiveRedirectPolicyImpl::evaluateHeaders( + Http::HeaderMap& headers, const StreamInfo::StreamInfo* stream_info) const { + request_headers_parser_->evaluateHeaders(headers, stream_info); + if (!host_rewrite_.empty()) { + Http::RequestHeaderMap& request_headers = dynamic_cast(headers); + request_headers.setHost(host_rewrite_); + } +} + +std::string +InternalActiveRedirectPolicyImpl::redirectUrl(absl::optional current_path) const { + if (!redirect_url_.empty()) { + ENVOY_LOG(debug, "The redirect url: {}", redirect_url_); + return redirect_url_; + } + + RELEASE_ASSERT(current_path.has_value(), + "The internal redirect address uses a regular expression, but does not pass in " + "the current path value"); + auto just_path(Http::PathUtil::removeQueryAndFragment(current_path.value())); + return redirect_url_rewrite_regex_->replaceAll(just_path, + redirect_url_rewrite_regex_substitution_); +} + +bool InternalActiveRedirectPolicyImpl::forcedUseOriginalHost() const { + return forced_use_original_host_; +} + +InternalActiveRedirectPoliciesImpl::InternalActiveRedirectPoliciesImpl( + const envoy::config::route::v3::InternalActiveRedirectPolicy& policy_config, + ProtobufMessage::ValidationVisitor& validator, absl::string_view current_route_name) { + if (policy_config.policies().empty() && !policy_config.redirect_response_codes().empty()) { + ENVOY_LOG(warn, "Please configure the redirection policy using the Policies field, the old " + "configuration will be deprecated"); + auto policy = std::make_unique(policy_config, validator, + current_route_name); + policies_.emplace_back(std::move(policy)); + } + + for (const auto& policy : policy_config.policies()) { + auto policy_impl = + std::make_unique(policy, validator, current_route_name); + policies_.emplace_back(std::move(policy_impl)); + } + + if (policies_.empty()) { + ENVOY_LOG(warn, "No redirection policy is currently configured. A default value is generated"); + auto policy_impl = std::make_unique(); + policies_.emplace_back(std::move(policy_impl)); + } +} + +InternalActiveRedirectPoliciesImpl::InternalActiveRedirectPoliciesImpl() { + auto policy_impl = std::make_unique(); + policies_.emplace_back(std::move(policy_impl)); +} + +std::vector +InternalActiveRedirectPoliciesImpl::predicates() const { + return policies_.at(current_policy_index_)->predicates(); +} + +void InternalActiveRedirectPoliciesImpl::evaluateHeaders( + Http::HeaderMap& headers, const StreamInfo::StreamInfo* stream_info) const { + return policies_.at(current_policy_index_)->evaluateHeaders(headers, stream_info); +} + +std::string +InternalActiveRedirectPoliciesImpl::redirectUrl(absl::optional current_path) const { + return policies_.at(current_policy_index_)->redirectUrl(current_path); +} + +bool InternalActiveRedirectPoliciesImpl::enabled() const { + return policies_.at(current_policy_index_)->enabled(); +} + +bool InternalActiveRedirectPoliciesImpl::shouldRedirectForResponseCode( + const Http::Code& response_code) const { + for (ActiveRedirectPolicies::size_type i = 0; i < policies_.size(); i++) { + if (policies_.at(i)->shouldRedirectForResponseCode(response_code)) { + current_policy_index_ = i; + return true; + } + } + + return false; +} + +uint32_t InternalActiveRedirectPoliciesImpl::maxInternalRedirects() const { + return policies_.at(current_policy_index_)->maxInternalRedirects(); +} + +bool InternalActiveRedirectPoliciesImpl::isCrossSchemeRedirectAllowed() const { + return policies_.at(current_policy_index_)->isCrossSchemeRedirectAllowed(); +} + +bool InternalActiveRedirectPoliciesImpl::forcedUseOriginalHost() const { + return policies_.at(current_policy_index_)->forcedUseOriginalHost(); +} + +bool InternalActiveRedirectPoliciesImpl::forcedAddHeaderBeforeRouteMatcher() const { + return policies_.at(current_policy_index_)->forcedAddHeaderBeforeRouteMatcher(); +} + +} // namespace Router +} // namespace Envoy diff --git a/contrib/common/active_redirect/source/active_redirect_policy_impl.h b/contrib/common/active_redirect/source/active_redirect_policy_impl.h new file mode 100644 index 0000000000000..1facdd3e8cb38 --- /dev/null +++ b/contrib/common/active_redirect/source/active_redirect_policy_impl.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include + +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/route/v3/route.pb.h" +#include "envoy/config/route/v3/route_components.pb.h" +#include "envoy/type/v3/percent.pb.h" + +#include "source/common/http/header_utility.h" +#include "source/common/protobuf/protobuf.h" +#include "source/common/protobuf/utility.h" +#include "source/common/router/header_parser.h" + +#include "absl/container/node_hash_map.h" +#include "absl/types/optional.h" +#include "contrib/envoy/http/active_redirect_policy.h" + +namespace Envoy { +namespace Router { + +/** + * Implementation of InternalActiveRedirectPolicyImpl that reads from the proto + * InternalActiveRedirectPolicyImpl of the RouteAction. + */ +class InternalActiveRedirectPolicyImpl : public InternalActiveRedirectPolicy, + Logger::Loggable { +public: + // Constructor that enables internal redirect with policy_config controlling the configurable + // behaviors. + explicit InternalActiveRedirectPolicyImpl( + const envoy::config::route::v3::InternalActiveRedirectPolicy& policy_config, + ProtobufMessage::ValidationVisitor& validator, absl::string_view current_route_name); + explicit InternalActiveRedirectPolicyImpl( + const envoy::config::route::v3::InternalActiveRedirectPolicy::RedirectPolicy& policy_config, + ProtobufMessage::ValidationVisitor& validator, absl::string_view current_route_name); + // Default constructor that disables internal redirect. + InternalActiveRedirectPolicyImpl() = default; + + bool enabled() const override { return enabled_; } + + bool shouldRedirectForResponseCode(const Http::Code& response_code) const override { + return redirect_response_codes_.contains(response_code); + } + + std::vector predicates() const override; + + uint32_t maxInternalRedirects() const override { return max_internal_redirects_; } + + bool isCrossSchemeRedirectAllowed() const override { return allow_cross_scheme_redirect_; } + + void evaluateHeaders(Http::HeaderMap& headers, + const StreamInfo::StreamInfo* stream_info) const override; + + std::string redirectUrl(absl::optional current_path = absl::nullopt) const override; + + bool forcedUseOriginalHost() const override; + bool forcedAddHeaderBeforeRouteMatcher() const override { + return forced_add_header_before_route_matcher_; + } + +private: + absl::flat_hash_set buildRedirectResponseCodes( + const envoy::config::route::v3::InternalActiveRedirectPolicy& policy_config) const; + absl::flat_hash_set buildRedirectResponseCodes( + const envoy::config::route::v3::InternalActiveRedirectPolicy::RedirectPolicy& policy_config) + const; + + const std::string current_route_name_; + const absl::flat_hash_set redirect_response_codes_; + const uint32_t max_internal_redirects_{1}; + const bool enabled_{false}; + const bool allow_cross_scheme_redirect_{false}; + const std::string redirect_url_; + const HeaderParserPtr request_headers_parser_; + const Regex::CompiledMatcherPtr redirect_url_rewrite_regex_; + const std::string redirect_url_rewrite_regex_substitution_; + const std::string host_rewrite_; + const bool forced_use_original_host_{false}; + const bool forced_add_header_before_route_matcher_{false}; + + std::vector> + predicate_factories_; +}; + +using InternalActiveRedirectPolicySharedPtr = std::shared_ptr; +using ActiveRedirectPolicies = std::vector; +using DefaultInternalActiveRedirectPolicy = ConstSingleton; + +class InternalActiveRedirectPoliciesImpl : public InternalActiveRedirectPolicy, + Logger::Loggable { +public: + // Constructor that enables internal redirect with policy_config controlling the configurable + // behaviors. + explicit InternalActiveRedirectPoliciesImpl( + const envoy::config::route::v3::InternalActiveRedirectPolicy& policy_config, + ProtobufMessage::ValidationVisitor& validator, absl::string_view current_route_name); + // Default constructor that disables internal redirect. + InternalActiveRedirectPoliciesImpl(); + + bool enabled() const override; + bool shouldRedirectForResponseCode(const Http::Code& response_code) const override; + std::vector predicates() const override; + uint32_t maxInternalRedirects() const override; + bool isCrossSchemeRedirectAllowed() const override; + void evaluateHeaders(Http::HeaderMap& headers, + const StreamInfo::StreamInfo* stream_info) const override; + std::string redirectUrl(absl::optional current_path = absl::nullopt) const override; + bool forcedUseOriginalHost() const override; + bool forcedAddHeaderBeforeRouteMatcher() const override; + +private: + ActiveRedirectPolicies policies_; + mutable ActiveRedirectPolicies::size_type current_policy_index_{0}; +}; + +} // namespace Router +} // namespace Envoy diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 7ec170da1aa33..adc5998da57a2 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -5,11 +5,18 @@ CONTRIB_EXTENSIONS = { # "envoy.filters.http.dynamo": "//contrib/dynamo/filters/http/source:config", + "envoy.filters.http.http_dubbo_transcoder": "//contrib/http_dubbo_transcoder/filters/http/source:config", "envoy.filters.http.golang": "//contrib/golang/filters/http/source:config", "envoy.filters.http.language": "//contrib/language/filters/http/source:config_lib", "envoy.filters.http.squash": "//contrib/squash/filters/http/source:config", "envoy.filters.http.sxg": "//contrib/sxg/filters/http/source:config", + # + # Upstreams + # + + "envoy.upstreams.http.dubbo_tcp": "//contrib/upstreams/http/dubbo_tcp/source:config", + # # Network filters # @@ -36,12 +43,23 @@ CONTRIB_EXTENSIONS = { "envoy.tls.key_providers.cryptomb": "//contrib/cryptomb/private_key_providers/source:config", "envoy.tls.key_providers.qat": "//contrib/qat/private_key_providers/source:config", + # + # Tracers + # + + "envoy.tracers.eagleeye": "//contrib/eagleeye/tracers/source:config", + + # + # Custom cluster plugins + # + + "envoy.router.cluster_specifier_plugin.cluster_fallback": "//contrib/custom_cluster_plugins/cluster_fallback/source:config", # # Socket interface extensions # - "envoy.bootstrap.vcl": "//contrib/vcl/source:config", + # "envoy.bootstrap.vcl": "//contrib/vcl/source:config", # # Input matchers @@ -53,7 +71,7 @@ CONTRIB_EXTENSIONS = { # Connection Balance extensions # - "envoy.network.connection_balance.dlb": "//contrib/network/connection_balance/dlb/source:connection_balancer", + # "envoy.network.connection_balance.dlb": "//contrib/network/connection_balance/dlb/source:connection_balancer", # # Regex engines diff --git a/contrib/custom_cluster_plugins/cluster_fallback/source/BUILD b/contrib/custom_cluster_plugins/cluster_fallback/source/BUILD new file mode 100644 index 0000000000000..a9ddb6edf39b2 --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/source/BUILD @@ -0,0 +1,39 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "filter_lib", + srcs = [ + "filter.cc", + ], + hdrs = [ + "filter.h", + ], + repository = "@envoy", + deps = [ + "//envoy/router:cluster_specifier_plugin_interface", + "//source/common/router:config_lib", + "@envoy_api//contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3:pkg_cc_proto", + ], +) + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + repository = "@envoy", + deps = [ + ":filter_lib", + "//source/extensions/filters/network:well_known_names", + "//source/extensions/filters/network/common:factory_base_lib", + "@envoy_api//contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3:pkg_cc_proto", + ], +) diff --git a/contrib/custom_cluster_plugins/cluster_fallback/source/config.cc b/contrib/custom_cluster_plugins/cluster_fallback/source/config.cc new file mode 100644 index 0000000000000..fe5158eeb6785 --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/source/config.cc @@ -0,0 +1,26 @@ +#include "contrib/custom_cluster_plugins/cluster_fallback/source/config.h" + +#include "contrib/custom_cluster_plugins/cluster_fallback/source/filter.h" + +namespace Envoy { +namespace Extensions { +namespace CustomClusterPlugins { +namespace ClusterFallback { + +Envoy::Router::ClusterSpecifierPluginSharedPtr +ClusterFallbackPluginFactoryConfig::createClusterSpecifierPlugin( + const Protobuf::Message& config, Server::Configuration::CommonFactoryContext& context) { + const auto& proto_config = + MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()); + return std::make_shared(proto_config, context); +} + +REGISTER_FACTORY(ClusterFallbackPluginFactoryConfig, + Envoy::Router::ClusterSpecifierPluginFactoryConfig); + +} // namespace ClusterFallback +} // namespace CustomClusterPlugins +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/custom_cluster_plugins/cluster_fallback/source/config.h b/contrib/custom_cluster_plugins/cluster_fallback/source/config.h new file mode 100644 index 0000000000000..d05294c58593f --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/source/config.h @@ -0,0 +1,37 @@ +#pragma once + +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.h" +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.validate.h" + +#include "envoy/router/cluster_specifier_plugin.h" + +namespace Envoy { +namespace Extensions { +namespace CustomClusterPlugins { +namespace ClusterFallback { + +class ClusterFallbackPluginFactoryConfig + : public Envoy::Router::ClusterSpecifierPluginFactoryConfig { +public: + ClusterFallbackPluginFactoryConfig() = default; + + std::string name() const override { + return "envoy.router.cluster_specifier_plugin.cluster_fallback"; + } + + Envoy::Router::ClusterSpecifierPluginSharedPtr + createClusterSpecifierPlugin(const Protobuf::Message& config, + Server::Configuration::CommonFactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::custom_cluster_plugins::cluster_fallback::v3::ClusterFallbackConfig>(); + } +}; + +DECLARE_FACTORY(ClusterFallbackPluginFactoryConfig); + +} // namespace ClusterFallback +} // namespace CustomClusterPlugins +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/custom_cluster_plugins/cluster_fallback/source/filter.cc b/contrib/custom_cluster_plugins/cluster_fallback/source/filter.cc new file mode 100644 index 0000000000000..8343ffd81a4d3 --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/source/filter.cc @@ -0,0 +1,122 @@ +#include "contrib/custom_cluster_plugins/cluster_fallback/source/filter.h" + +#include "source/common/common/assert.h" + +#include "source/common/router/config_impl.h" + +namespace Envoy { +namespace Extensions { +namespace CustomClusterPlugins { +namespace ClusterFallback { + +ClusterFallbackPlugin::ClusterFallbackPlugin( + const envoy::extensions::custom_cluster_plugins::cluster_fallback::v3::ClusterFallbackConfig& + config, + Server::Configuration::CommonFactoryContext& context) + : cluster_manager_(context.clusterManager()) { + if (config.config_specifier_case() == + envoy::extensions::custom_cluster_plugins::cluster_fallback::v3::ClusterFallbackConfig:: + kWeightedClusterConfig) { + for (auto& item : config.weighted_cluster_config().config()) { + clusters_config_.emplace(item.routing_cluster(), + std::vector(item.fallback_clusters().begin(), + item.fallback_clusters().end())); + } + } else { + clusters_config_.emplace( + config.cluster_config().routing_cluster(), + std::vector(config.cluster_config().fallback_clusters().begin(), + config.cluster_config().fallback_clusters().end())); + } + + if (clusters_config_.empty()) { + ENVOY_LOG(info, "there is no fallback cluster"); + } +} + +Envoy::Router::RouteConstSharedPtr +ClusterFallbackPlugin::route(Envoy::Router::RouteConstSharedPtr route, + const Http::RequestHeaderMap&) const { + if (route->routeEntry() != nullptr) { + auto route_entry = route->routeEntry(); + if (typeid(*route_entry) == typeid(Envoy::Router::RouteEntryImplBase::WeightedClusterEntry&) || + typeid(*route_entry) == typeid(Envoy::Router::RouteEntryImplBase::DynamicRouteEntry&)) { + return calculateWeightedClusterFallback(*route_entry); + } + + ASSERT(dynamic_cast(route_entry) != nullptr); + return calculateNormalClusterFallback(*route_entry); + } + PANIC("reached unexpected code"); +} + +Envoy::Router::RouteConstSharedPtr ClusterFallbackPlugin::calculateNormalClusterFallback( + const Envoy::Router::RouteEntry& route_entry) const { + ASSERT(clusters_config_.size() == 1); + + const auto& base = dynamic_cast(route_entry); + auto first_item = clusters_config_.begin(); + if (hasHealthHost(first_item->first)) { + ENVOY_LOG(info, "The target cluster {} has healthy nodes and does not require fallback", + first_item->first); + return base.clone(first_item->first); + } + + for (const auto& cluster_name : first_item->second) { + if (hasHealthHost(cluster_name)) { + return base.clone(cluster_name); + } + } + + ENVOY_LOG(info, "All clusters have no healthy nodes, the original routing cluster is returned"); + return base.clone(first_item->first); +} + +Envoy::Router::RouteConstSharedPtr ClusterFallbackPlugin::calculateWeightedClusterFallback( + const Envoy::Router::RouteEntry& route_entry) const { + const auto& cluster_entry = + dynamic_cast(route_entry); + + auto search = clusters_config_.find(route_entry.clusterName()); + if (search == clusters_config_.end()) { + ENVOY_LOG(warn, "there is no fallback cluster config, the original routing cluster is returned"); + return cluster_entry.getRouteConstSharedPtr(); + } + + if (hasHealthHost(search->first)) { + ENVOY_LOG(info, "The target cluster {} has healthy nodes and does not require fallback", + search->first); + return cluster_entry.getRouteConstSharedPtr(); + } + + for (const auto& cluster_name : search->second) { + if (hasHealthHost(cluster_name)) { + return cluster_entry.clone(cluster_name); + } + } + + ENVOY_LOG(info, "All clusters have no healthy nodes, the original routing cluster is returned"); + return cluster_entry.getRouteConstSharedPtr(); +} + +bool ClusterFallbackPlugin::hasHealthHost(absl::string_view cluster_name) const { + bool has_health_host{false}; + Upstream::ThreadLocalCluster* cluster = cluster_manager_.getThreadLocalCluster(cluster_name); + if (!cluster) { + return has_health_host; + } + + for (auto& i : cluster->prioritySet().hostSetsPerPriority()) { + if (i->healthyHosts().size() > 0) { + has_health_host = true; + break; + } + } + + return has_health_host; +} + +} // namespace ClusterFallback +} // namespace CustomClusterPlugins +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/custom_cluster_plugins/cluster_fallback/source/filter.h b/contrib/custom_cluster_plugins/cluster_fallback/source/filter.h new file mode 100644 index 0000000000000..71aa6db61bc53 --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/source/filter.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.h" +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.validate.h" + +#include "envoy/router/cluster_specifier_plugin.h" +#include "envoy/upstream/cluster_manager.h" + +#include "source/common/common/logger_impl.h" +#include "source/common/common/logger.h" + +namespace Envoy { +namespace Extensions { +namespace CustomClusterPlugins { +namespace ClusterFallback { + +class ClusterFallbackPlugin : public Envoy::Router::ClusterSpecifierPlugin, + public Logger::Loggable { +public: + ClusterFallbackPlugin( + const envoy::extensions::custom_cluster_plugins::cluster_fallback::v3::ClusterFallbackConfig& + config, + Server::Configuration::CommonFactoryContext& context); + + Envoy::Router::RouteConstSharedPtr route(Envoy::Router::RouteConstSharedPtr route, + const Http::RequestHeaderMap&) const; + +private: + bool hasHealthHost(absl::string_view cluster_name) const; + Envoy::Router::RouteConstSharedPtr + calculateWeightedClusterFallback(const Envoy::Router::RouteEntry& route_entry) const; + Envoy::Router::RouteConstSharedPtr + calculateNormalClusterFallback(const Envoy::Router::RouteEntry& route_entry) const; + + Upstream::ClusterManager& cluster_manager_; + std::unordered_map/*fallback clusters*/> clusters_config_; +}; + +} // namespace ClusterFallback +} // namespace CustomClusterPlugins +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/custom_cluster_plugins/cluster_fallback/test/BUILD b/contrib/custom_cluster_plugins/cluster_fallback/test/BUILD new file mode 100644 index 0000000000000..81c939afd6ec6 --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/test/BUILD @@ -0,0 +1,37 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + "//contrib/custom_cluster_plugins/cluster_fallback/source:config", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "filter_test", + srcs = ["filter_test.cc"], + deps = [ + "//contrib/custom_cluster_plugins/cluster_fallback/source:config", + "//contrib/custom_cluster_plugins/cluster_fallback/source:filter_lib", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/upstream:cluster_manager_mocks", + "//test/mocks/upstream:cluster_mocks", + "//test/mocks/upstream:host_mocks", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:thread_local_cluster_mocks", + "//test/test_common:utility_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg_cc_proto", + ], +) \ No newline at end of file diff --git a/contrib/custom_cluster_plugins/cluster_fallback/test/config_test.cc b/contrib/custom_cluster_plugins/cluster_fallback/test/config_test.cc new file mode 100644 index 0000000000000..e1e440c5bee04 --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/test/config_test.cc @@ -0,0 +1,49 @@ +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "contrib/custom_cluster_plugins/cluster_fallback/source/config.h" +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.h" +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.validate.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; + +namespace Envoy { +namespace Extensions { +namespace CustomClusterPlugins { +namespace ClusterFallback { + +TEST(ClusterFallbackPluginFactoryConfigTest, ClusterFallbackPluginCorrectYaml) { + const std::string yaml_string = R"EOF( +extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + cluster_config: + routing_cluster: test + fallback_clusters: + - fallback1 + - fallback2 +)EOF"; + + envoy::config::route::v3::ClusterSpecifierPlugin plugin_config; + TestUtility::loadFromYaml(yaml_string, plugin_config); + + auto* factory = + Envoy::Config::Utility::getFactory( + plugin_config.extension()); + EXPECT_NE(nullptr, factory); + + auto config = Envoy::Config::Utility::translateToFactoryConfig( + plugin_config.extension(), ProtobufMessage::getStrictValidationVisitor(), *factory); + NiceMock context; + auto plugin = factory->createClusterSpecifierPlugin(*config, context); + EXPECT_NE(nullptr, plugin); +} + +} // namespace ClusterFallback +} // namespace CustomClusterPlugins +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/custom_cluster_plugins/cluster_fallback/test/filter_test.cc b/contrib/custom_cluster_plugins/cluster_fallback/test/filter_test.cc new file mode 100644 index 0000000000000..31c8e7e0897b2 --- /dev/null +++ b/contrib/custom_cluster_plugins/cluster_fallback/test/filter_test.cc @@ -0,0 +1,687 @@ +#include "contrib/custom_cluster_plugins/cluster_fallback/source/filter.h" + +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.h" +#include "contrib/envoy/extensions/custom_cluster_plugins/cluster_fallback/v3/cluster_fallback.pb.validate.h" +#include "contrib/custom_cluster_plugins/cluster_fallback/source/config.h" +#include "source/common/router/config_impl.h" + +#include "test/mocks/server/instance.h" +#include "test/mocks/router/mocks.h" +#include "test/mocks/upstream/cluster_manager.h" +#include "test/mocks/upstream/host.h" +#include "test/mocks/upstream/priority_set.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::ReturnRef; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace CustomClusterPlugins { +namespace ClusterFallback { + +Http::TestRequestHeaderMapImpl genHeaders(const std::string& host, const std::string& path, + const std::string& method, const std::string& scheme) { + auto hdrs = + Http::TestRequestHeaderMapImpl{{":authority", host}, {":path", path}, + {":method", method}, {"x-safe", "safe"}, + {"x-global-nope", "global"}, {"x-vhost-nope", "vhost"}, + {"x-route-nope", "route"}, {":scheme", scheme}, + {"x-forwarded-proto", scheme}}; + + if (scheme.empty()) { + hdrs.remove(":scheme"); + } + + return hdrs; +} + +Http::TestRequestHeaderMapImpl genHeaders(const std::string& host, const std::string& path, + const std::string& method) { + return genHeaders(host, path, method, "http"); +} + +TEST(ClusterFallbackPluginTest, NormalWithInlinePlugin) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + inline_cluster_specifier_plugin: + extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + cluster_config: + routing_cluster: test + fallback_clusters: + - fallback1 + - fallback2 + - match: + prefix: "/bar" + route: + cluster_header: some_header + timeout: 0s + )EOF"; + + NiceMock factory_context; + NiceMock stream_info; + + std::shared_ptr test_cluster = + std::make_shared>(); + auto mock_host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{mock_host}; + Envoy::Upstream::MockHostSet* mock_host_set = + test_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*mock_host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(testing::Return(test_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + auto route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("test", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, OnceFallbackWithInlinePlugin) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + inline_cluster_specifier_plugin: + extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + cluster_config: + routing_cluster: test + fallback_clusters: + - fallback1 + - fallback2 + - match: + prefix: "/bar" + route: + cluster_header: some_header + timeout: 0s + )EOF"; + + NiceMock factory_context; + NiceMock stream_info; + + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(Return(nullptr)); + + std::shared_ptr fallback1_cluster = + std::make_shared>(); + auto host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{host}; + Envoy::Upstream::MockHostSet* mock_host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*mock_host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .WillOnce(Return(fallback1_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + auto route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("fallback1", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, TwiceFallbackWithInlinePlugin) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + inline_cluster_specifier_plugin: + extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + cluster_config: + routing_cluster: test + fallback_clusters: + - fallback1 + - fallback2 + - match: + prefix: "/bar" + route: + cluster_header: some_header + timeout: 0s + )EOF"; + + NiceMock factory_context; + + // cluster test does not exist. + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(Return(nullptr)); + + // cluster fallback1 is empty. + std::shared_ptr fallback1_cluster = + std::make_shared>(); + Envoy::Upstream::MockHostSet* mock_host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + Envoy::Upstream::HostVector empty_hosts{}; + EXPECT_CALL(*mock_host_set, healthyHosts()).WillOnce(ReturnRef(empty_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .WillOnce(Return(fallback1_cluster.get())); + + std::shared_ptr fallback2_cluster = + std::make_shared>(); + auto host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{host}; + Envoy::Upstream::MockHostSet* host_set = + fallback2_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback2"))) + .WillOnce(Return(fallback2_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + NiceMock stream_info; + auto route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("fallback2", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, NoHealthClusterWithInlinePlugin) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/bar" + route: + inline_cluster_specifier_plugin: + extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + cluster_config: + routing_cluster: test + fallback_clusters: + - fallback1 + )EOF"; + + NiceMock factory_context; + + // cluster test is empty. + std::shared_ptr test_cluster = + std::make_shared>(); + Envoy::Upstream::MockHostSet* mock_host_set_test = + test_cluster->cluster_.prioritySet().getMockHostSet(0); + Envoy::Upstream::HostVector empty_hosts_test{}; + EXPECT_CALL(*mock_host_set_test, healthyHosts()).WillOnce(ReturnRef(empty_hosts_test)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(testing::Return(test_cluster.get())); + + // cluster fallback1 is empty. + std::shared_ptr fallback1_cluster = + std::make_shared>(); + Envoy::Upstream::MockHostSet* mock_host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + Envoy::Upstream::HostVector empty_hosts{}; + EXPECT_CALL(*mock_host_set, healthyHosts()).WillOnce(ReturnRef(empty_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .WillOnce(Return(fallback1_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + NiceMock stream_info; + auto route = config.route(genHeaders("some_cluster", "/bar", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("test", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, ClusterSpecifierPlugin) { + const std::string yaml = R"EOF( +cluster_specifier_plugins: +- extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + cluster_config: + routing_cluster: test + fallback_clusters: + - fallback1 + - fallback2 +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + - match: + prefix: "/bar" + route: + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + )EOF"; + + NiceMock factory_context; + + // cluster test is empty. + std::shared_ptr test_cluster = + std::make_shared>(); + Envoy::Upstream::MockHostSet* mock_host_set_test = + test_cluster->cluster_.prioritySet().getMockHostSet(0); + Envoy::Upstream::HostVector empty_hosts_test{}; + EXPECT_CALL(*mock_host_set_test, healthyHosts()).Times(2).WillRepeatedly(ReturnRef(empty_hosts_test)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .Times(2).WillRepeatedly(Return(test_cluster.get())); + + // cluster fallback1 is empty. + std::shared_ptr fallback1_cluster = + std::make_shared>(); + Envoy::Upstream::MockHostSet* mock_host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + Envoy::Upstream::HostVector empty_hosts{}; + EXPECT_CALL(*mock_host_set, healthyHosts()).Times(2).WillRepeatedly(ReturnRef(empty_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .Times(2).WillRepeatedly(Return(fallback1_cluster.get())); + + std::shared_ptr fallback2_cluster = + std::make_shared>(); + auto host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{host}; + Envoy::Upstream::MockHostSet* host_set = + fallback2_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*host_set, healthyHosts()).Times(2).WillRepeatedly(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback2"))) + .Times(2).WillRepeatedly(Return(fallback2_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + NiceMock stream_info; + auto route = config.route(genHeaders("some_cluster", "/bar", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("fallback2", route->routeEntry()->clusterName()); + + route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("fallback2", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, WeightedClusterNormalWithInlinePlugin) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + weighted_clusters: + clusters: + - name: test + weight: 100 + - name: cluster2 + weight: 0 + inline_cluster_specifier_plugin: + extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + weighted_cluster_config: + config: + - routing_cluster: test + fallback_clusters: + - fallback1 + - routing_cluster: cluster2 + fallback_clusters: + - fallback2 + - match: + prefix: "/bar" + route: + cluster_header: some_header + timeout: 0s + )EOF"; + + NiceMock factory_context; + NiceMock stream_info; + + std::shared_ptr test_cluster = + std::make_shared>(); + auto mock_host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{mock_host}; + Envoy::Upstream::MockHostSet* mock_host_set = + test_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*mock_host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(testing::Return(test_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + auto route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("test", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, WeightedClusterFallbackWithInlinePlugin) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + weighted_clusters: + clusters: + - name: test + weight: 100 + - name: cluster2 + weight: 0 + inline_cluster_specifier_plugin: + extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + weighted_cluster_config: + config: + - routing_cluster: test + fallback_clusters: + - fallback1 + - routing_cluster: cluster2 + fallback_clusters: + - fallback2 + - match: + prefix: "/bar" + route: + cluster_header: some_header + timeout: 0s + )EOF"; + + NiceMock factory_context; + NiceMock stream_info; + + // cluster test does not exist. + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(Return(nullptr)); + + std::shared_ptr fallback1_cluster = + std::make_shared>(); + auto mock_host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{mock_host}; + Envoy::Upstream::MockHostSet* host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .WillOnce(Return(fallback1_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + auto route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("fallback1", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, WeightedClusterFallback) { + const std::string yaml = R"EOF( +cluster_specifier_plugins: +- extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + weighted_cluster_config: + config: + - routing_cluster: test + fallback_clusters: + - fallback1 + - routing_cluster: cluster2 + fallback_clusters: + - fallback2 +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + weighted_clusters: + clusters: + - name: test + weight: 100 + - name: cluster2 + weight: 0 + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + - match: + prefix: "/bar" + route: + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + )EOF"; + + NiceMock factory_context; + NiceMock stream_info; + + // cluster test does not exist. + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(Return(nullptr)); + + std::shared_ptr fallback1_cluster = + std::make_shared>(); + auto mock_host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{mock_host}; + Envoy::Upstream::MockHostSet* host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .WillOnce(Return(fallback1_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + auto route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("fallback1", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, WeightedClusterNoHealthHost) { + const std::string yaml = R"EOF( +cluster_specifier_plugins: +- extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + weighted_cluster_config: + config: + - routing_cluster: test + fallback_clusters: + - fallback1 + - routing_cluster: cluster2 + fallback_clusters: + - fallback2 +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + weighted_clusters: + clusters: + - name: test + weight: 100 + - name: cluster2 + weight: 0 + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + - match: + prefix: "/bar" + route: + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + )EOF"; + + NiceMock factory_context; + NiceMock stream_info; + + // cluster test does not exist. + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(Return(nullptr)); + + std::shared_ptr fallback1_cluster = + std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts; + Envoy::Upstream::MockHostSet* host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .WillOnce(Return(fallback1_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + auto route = config.route(genHeaders("some_cluster", "/foo", "GET"), stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("test", route->routeEntry()->clusterName()); +} + +TEST(ClusterFallbackPluginTest, WeightedClusterFallbackViaClusterHeader) { + const std::string yaml = R"EOF( +cluster_specifier_plugins: +- extension: + name: envoy.router.cluster_specifier_plugin.cluster_fallback + typed_config: + "@type": type.googleapis.com/envoy.extensions.custom_cluster_plugins.cluster_fallback.v3.ClusterFallbackConfig + weighted_cluster_config: + config: + - routing_cluster: test + fallback_clusters: + - fallback1 + - routing_cluster: cluster2 + fallback_clusters: + - fallback2 +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + weighted_clusters: + clusters: + - cluster_header: cluster + weight: 100 + - name: cluster2 + weight: 0 + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + - match: + prefix: "/bar" + route: + cluster_specifier_plugin: envoy.router.cluster_specifier_plugin.cluster_fallback + )EOF"; + + NiceMock factory_context; + NiceMock stream_info; + + // cluster test does not exist. + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("test"))) + .WillOnce(Return(nullptr)); + + std::shared_ptr fallback1_cluster = + std::make_shared>(); + auto mock_host = std::make_shared>(); + Envoy::Upstream::HostVector mock_hosts{mock_host}; + Envoy::Upstream::MockHostSet* host_set = + fallback1_cluster->cluster_.prioritySet().getMockHostSet(0); + EXPECT_CALL(*host_set, healthyHosts()).WillOnce(ReturnRef(mock_hosts)); + EXPECT_CALL(factory_context.cluster_manager_, getThreadLocalCluster(testing::Eq("fallback1"))) + .WillOnce(Return(fallback1_cluster.get())); + + envoy::config::route::v3::RouteConfiguration route_config; + TestUtility::loadFromYaml(yaml, route_config); + + const Envoy::Router::OptionalHttpFilters& optional_http_filters = + Envoy::Router::OptionalHttpFilters(); + Envoy::Router::ConfigImpl config(route_config, optional_http_filters, factory_context, + ProtobufMessage::getNullValidationVisitor(), false); + + Http::TestRequestHeaderMapImpl header = genHeaders("some_cluster", "/foo", "GET"); + header.setByKey("cluster", "test"); + auto route = config.route(header, stream_info, 0); + EXPECT_NE(nullptr, route); + EXPECT_EQ("fallback1", route->routeEntry()->clusterName()); +} + +} // namespace ClusterFallback +} // namespace CustomClusterPlugins +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/envoy/http/BUILD b/contrib/envoy/http/BUILD new file mode 100644 index 0000000000000..b20612de5806c --- /dev/null +++ b/contrib/envoy/http/BUILD @@ -0,0 +1,23 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "active_redirect_policy_interface", + hdrs = ["active_redirect_policy.h"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/http:codes_interface", + "//envoy/http:header_map_interface", + "//envoy/router:internal_redirect_interface", + "//envoy/stream_info:stream_info_interface", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/route/v3:pkg_cc_proto", + ], +) diff --git a/contrib/envoy/http/active_redirect_policy.h b/contrib/envoy/http/active_redirect_policy.h new file mode 100644 index 0000000000000..7ed5ded7fedec --- /dev/null +++ b/contrib/envoy/http/active_redirect_policy.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/route/v3/route_components.pb.h" +#include "envoy/http/codes.h" +#include "envoy/http/header_map.h" +#include "envoy/router/internal_redirect.h" +#include "envoy/stream_info/stream_info.h" + +namespace Envoy { + +namespace Router { + +/** + * InternalActiveRedirectPolicy from the route configuration. + */ +class InternalActiveRedirectPolicy { +public: + virtual ~InternalActiveRedirectPolicy() = default; + + /** + * @return whether internal redirect is enabled on this route. + */ + virtual bool enabled() const PURE; + + /** + * @param response_code the response code from the upstream. + * @return whether the given response_code should trigger an internal redirect on this route. + */ + virtual bool shouldRedirectForResponseCode(const Http::Code& response_code) const PURE; + + /** + * Creates the target route predicates. This should really be called only once for each upstream + * redirect response. Creating the predicates lazily to avoid wasting CPU cycles on non-redirect + * responses, which should be the most common case. + * @return a vector of newly constructed InternalRedirectPredicate instances. + */ + virtual std::vector predicates() const PURE; + + /** + * @return the maximum number of allowed internal redirects on this route. + */ + virtual uint32_t maxInternalRedirects() const PURE; + + /** + * @return if it is allowed to follow the redirect with a different scheme in + * the target URI than the downstream request. + */ + virtual bool isCrossSchemeRedirectAllowed() const PURE; + + virtual void evaluateHeaders(Http::HeaderMap& headers, + const StreamInfo::StreamInfo* stream_info) const PURE; + + virtual std::string + redirectUrl(absl::optional current_path = absl::nullopt) const PURE; + + virtual bool forcedUseOriginalHost() const PURE; + + virtual bool forcedAddHeaderBeforeRouteMatcher() const PURE; +}; + +} // namespace Router +} // namespace Envoy diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index 1e6a8f3dc0b37..2314202b50f37 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -1,8 +1,18 @@ +envoy.filters.http.http_dubbo_transcoder: + categories: + - envoy.filters.http + security_posture: requires_trusted_downstream_and_upstream + status: stable envoy.filters.http.dynamo: categories: - envoy.filters.http security_posture: requires_trusted_downstream_and_upstream status: stable +envoy.upstreams.http.dubbo_tcp: + categories: + - envoy.upstreams + security_posture: robust_to_untrusted_downstream + status: stable envoy.filters.http.golang: categories: - envoy.filters.http @@ -68,6 +78,9 @@ envoy.tls.key_providers.cryptomb: - envoy.tls.key_providers security_posture: robust_to_untrusted_downstream status: alpha +envoy.router.cluster_specifier_plugin.cluster_fallback: + categories: + - envoy.router envoy.tls.key_providers.qat: categories: - envoy.tls.key_providers diff --git a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc index 441d28ad29964..62e73ec885706 100644 --- a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc +++ b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc @@ -87,7 +87,7 @@ void DubboRequest::forEach(IterateCallback callback) const { pair.second->type() == Hessian2::Object::Type::String) { ASSERT(pair.first->toString().has_value() && pair.second->toString().has_value()); - if (!callback(pair.first->toString().value().get(), pair.second->toString().value().get())) { + if (!callback(*(pair.first->toString().value()), *(pair.second->toString().value()))) { break; } } diff --git a/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc b/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc index 9e5a6c8b8401e..af04c941c6164 100644 --- a/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc +++ b/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc @@ -37,7 +37,7 @@ MessageMetadataSharedPtr createDubboRequst(bool one_way_request) { Hessian2::ObjectPtr key_o = std::make_unique("group"); Hessian2::ObjectPtr val_o = std::make_unique("fake_group"); - map->toMutableUntypedMap().value().get().emplace(std::move(key_o), std::move(val_o)); + map->toMutableUntypedMap()->emplace(std::move(key_o), std::move(val_o)); return std::make_unique(std::move(map), 0); }); diff --git a/contrib/http_dubbo_transcoder/filters/http/source/BUILD b/contrib/http_dubbo_transcoder/filters/http/source/BUILD new file mode 100644 index 0000000000000..19594bbb042f6 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/BUILD @@ -0,0 +1,91 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "dubbo_transcoder_filter_lib", + srcs = ["dubbo_transcoder_filter.cc"], + hdrs = ["dubbo_transcoder_filter.h"], + external_deps = [ + "path_matcher", + "hessian2_codec_codec_impl", + "hessian2_codec_object_codec_lib", + ], + visibility = ["//visibility:public"], + deps = [ + ":transcoder_interface", + ":utility_lib", + "//envoy/event:dispatcher_interface", + "//envoy/http:codes_interface", + "//envoy/http:filter_interface", + "//envoy/http:query_params_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/common:enum_to_int", + "//source/common/common:minimal_logger_lib", + "//source/common/common:regex_lib", + "//source/common/http:codes_lib", + "//source/common/http:header_map_lib", + "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + "//source/common/runtime:runtime_lib", + "//source/common/common:hex_lib", + "//source/extensions/filters/http:well_known_names", + "@com_google_googleapis//google/api:http_cc_proto", + "@envoy_api//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg_cc_proto", + "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", + ], +) + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + visibility = ["//visibility:public"], + deps = [ + ":dubbo_transcoder_filter_lib", + "//envoy/registry", + "//source/extensions/filters/http:well_known_names", + "//source/extensions/filters/http/common:factory_base_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "transcoder_interface", + hdrs = ["transcoder.h"], + deps = [ + "//envoy/http:filter_interface", + "@envoy_api//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "utility_lib", + srcs = ["utility.cc"], + hdrs = ["utility.h"], + external_deps = [ + "hessian2_codec_object_impl", + "hessian2_codec_codec_impl", + "hessian2_codec_object_codec_lib", + "json", + ], + deps = [ + ":transcoder_interface", + "//envoy/http:filter_interface", + "//envoy/http:query_params_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:enum_to_int", + "//source/common/common:regex_lib", + "//source/common/http:codes_lib", + "//source/common/http:utility_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg_cc_proto", + ], +) diff --git a/contrib/http_dubbo_transcoder/filters/http/source/config.cc b/contrib/http_dubbo_transcoder/filters/http/source/config.cc new file mode 100644 index 0000000000000..af8856762e373 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/config.cc @@ -0,0 +1,35 @@ +#include "contrib/http_dubbo_transcoder/filters/http/source/config.h" + +#include "contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +Http::FilterFactoryCb HttpDubboTranscodeFilterFactory::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder& + proto_config, + const std::string&, Server::Configuration::FactoryContext& context) { + DubboTranscoderConfigSharedPtr config = + std::make_shared(proto_config, DUBBO_STATS_PREFIX, context.scope()); + return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared(*config)); + }; +} + +Router::RouteSpecificFilterConfigConstSharedPtr +HttpDubboTranscodeFilterFactory::createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder& + proto_config, + Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) { + return std::make_shared(proto_config, DUBBO_STATS_PREFIX, context.scope()); +}; + +REGISTER_FACTORY(HttpDubboTranscodeFilterFactory, + Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/source/config.h b/contrib/http_dubbo_transcoder/filters/http/source/config.h new file mode 100644 index 0000000000000..46165e3bc072b --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/config.h @@ -0,0 +1,43 @@ +#pragma once + +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.h" +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.validate.h" + +#include "source/extensions/filters/http/common/factory_base.h" +#include "source/extensions/filters/http/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +const std::string DUBBO_STATS_PREFIX = "http_dubbo_transcoder"; + +/** + * Config registration for the buffer filter. + */ +class HttpDubboTranscodeFilterFactory + : public Common::FactoryBase< + envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder> { +public: + HttpDubboTranscodeFilterFactory() : FactoryBase("envoy.filters.http.http_dubbo_transcoder") {} + +private: + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder& + proto_config, + const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; + + Router::RouteSpecificFilterConfigConstSharedPtr createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder& + proto_config, + Server::Configuration::ServerFactoryContext& context, + ProtobufMessage::ValidationVisitor& validator) override; +}; + +DECLARE_FACTORY(HttpDubboTranscodeFilterFactory); + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.cc b/contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.cc new file mode 100644 index 0000000000000..666df08667d13 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.cc @@ -0,0 +1,566 @@ +#include "contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h" + +#include "source/common/common/assert.h" +#include "source/common/common/hex.h" +#include "source/common/common/regex.h" +#include "source/common/http/status.h" +#include "source/common/http/utility.h" +#include "source/extensions/filters/http/well_known_names.h" + +#include "absl/status/status.h" +#include "absl/strings/str_split.h" + +#include "contrib/http_dubbo_transcoder/filters/http/source/utility.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +static const std::string HTTPResponseKey = "result"; +static const std::string HTTPResponseErrorKey = "error"; +static const std::string HTTPResponseAttachmentKey = "attachment"; + +static const std::string DubboGenericMethodName = "$invoke"; +static const std::string DubboGenericParamTypes = + "Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;"; + +static const std::string DubboDefaultProtocolVsersion = "2.7.1"; +static const std::string DubboDefaultMethodVersion = "0.0.0"; + +static const std::string AttachmentPathKey = "path"; +static const std::string AttachmentGenericKey = "generic"; +static const std::string AttachmentInterfaceKey = "interface"; +static const std::string AttachmentVersionKey = "version"; +static const std::string AttachmentTrueValue = "true"; +static const std::string AttachmentGroupKey = "group"; +static const std::string ContentTypeHeaderValue = "application/json; charset=utf-8"; +static std::atomic_ulong RequestId{0}; + +DubboTranscoderConfig::DubboTranscoderConfig( + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder& + proto_config, + const std::string& stat_prefix, Stats::Scope& scope) + : stats_(generateStats(stat_prefix, scope)) { + + disabled_ = proto_config.services_mapping().empty(); + if (disabled_) { + return; + } + + request_validate_options_ = proto_config.request_validation_options(); + + // build path matcher + ::google::grpc::transcoding::PathMatcherBuilder pmb; + for (const auto& service : proto_config.services_mapping()) { + for (const auto& method : service.method_mapping()) { + MethodInfoSharedPtr method_info = + createMethodInfo(service.name(), service.version(), service.group(), method); + pmb.Register(method_info->match_http_method_, method_info->match_pattern_, "", method_info); + } + } + switch (proto_config.url_unescape_spec()) { + PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; + case envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder:: + ALL_CHARACTERS_EXCEPT_RESERVED: + pmb.SetUrlUnescapeSpec( + google::grpc::transcoding::UrlUnescapeSpec::kAllCharactersExceptReserved); + break; + case envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder:: + ALL_CHARACTERS_EXCEPT_SLASH: + pmb.SetUrlUnescapeSpec(google::grpc::transcoding::UrlUnescapeSpec::kAllCharactersExceptSlash); + break; + case envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder:: + ALL_CHARACTERS: + pmb.SetUrlUnescapeSpec(google::grpc::transcoding::UrlUnescapeSpec::kAllCharacters); + break; + } + path_matcher_ = pmb.Build(); +} + +MethodInfoSharedPtr DubboTranscoderConfig::createMethodInfo( + const std::string& service_name, const std::string& service_version, + const std::string& service_group, + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder:: + DubboMethodMapping& method_mapping) { + MethodInfoSharedPtr method_info = std::make_shared(); + method_info->service_name_ = service_name; + method_info->service_version_ = service_version; + method_info->service_group_ = service_group; + method_info->name_ = method_mapping.name(); + + if (method_mapping.has_path_matcher()) { + std::string http_method_spec = envoy::extensions::filters::http::http_dubbo_transcoder::v3:: + HttpDubboTranscoder_DubboMethodMapping_MatchHttpMethodSpec_Name( + method_mapping.path_matcher().match_http_method_spec()); + method_info->match_http_method_ = std::string(absl::StripPrefix(http_method_spec, "ALL_")); + method_info->match_pattern_ = method_mapping.path_matcher().match_pattern(); + } else { + // Default matching path: service/method, http method: get. + std::string http_method_spec = envoy::extensions::filters::http::http_dubbo_transcoder::v3:: + HttpDubboTranscoder_DubboMethodMapping_MatchHttpMethodSpec_Name( + envoy::extensions::filters::http::http_dubbo_transcoder::v3:: + HttpDubboTranscoder_DubboMethodMapping_MatchHttpMethodSpec_ALL_GET); + method_info->match_http_method_ = std::string(absl::StripPrefix(http_method_spec, "ALL_")); + method_info->match_pattern_ = fmt::format("/{}/{}", service_name, method_info->name_); + } + + ENVOY_LOG(debug, "http method: {}, match pattern {}", method_info->match_http_method_, + method_info->match_pattern_); + + if (!method_mapping.parameter_mapping().empty()) { + method_info->parameter_mapping_ = method_mapping.parameter_mapping(); + } + + if (method_mapping.has_passthrough_setting()) { + const auto& passthrough_setting = method_mapping.passthrough_setting(); + + using PassthroughSetting = envoy::extensions::filters::http::http_dubbo_transcoder::v3:: + HttpDubboTranscoder::DubboMethodMapping::PassthroughSetting; + switch (method_mapping.passthrough_setting().headers_setting_case()) { + case PassthroughSetting::kPassthroughAllHeaders: + method_info->passthrough_all_headers_ = passthrough_setting.passthrough_all_headers(); + break; + case PassthroughSetting::kPassthroughHeaders: + method_info->passthrough_header_keys_ = passthrough_setting.passthrough_headers().keys(); + break; + case PassthroughSetting::HEADERS_SETTING_NOT_SET: + PANIC_DUE_TO_PROTO_UNSET; + } + } + + return method_info; +} + +std::tuple +DubboTranscoderConfig::createTranscoder(Http::RequestHeaderMap& headers) const { + ASSERT(!disabled_); + + const std::string method(headers.getMethodValue()); + std::string path(headers.getPathValue()); + std::string args; + + const size_t pos = path.find('?'); + if (pos != std::string::npos) { + args = path.substr(pos + 1); + path = path.substr(0, pos); + } + + ENVOY_LOG(debug, "path is {} args is {} method is {}", path, args, method); + + std::vector variable_bindings; + auto method_info = path_matcher_->Lookup(method, path, args, &variable_bindings, nullptr); + if (!method_info) { + return {absl::NotFoundError(fmt::format("Could not resolve {} to a method", path)), nullptr}; + } + + return {absl::OkStatus(), new Http2DubboTranscoder(*method_info, std::move(variable_bindings))}; +} + +Http2DubboTranscoder::Http2DubboTranscoder(const MethodInfo& method_info, + std::vector&& bindings) + : method_info_(method_info), bindings_(std::move(bindings)) { + ENVOY_LOG(debug, "method name is {} method args count is {}", method_info_.name_, + method_info_.parameter_mapping_.size()); +}; + +absl::Status Http2DubboTranscoder::translateDubboToHttp(Buffer::Instance& data) { + if (data.length() < DUBBO_MAGIC_SIZE || !validateMagicNumber(data)) { + return absl::UnknownError("Service unachievable or not dubbo message"); + } + + if (data.length() < DUBBO_HEADER_SIZE) { + data.drain(data.length()); + return absl::DataLossError("Dubbo message data is incomplete"); + } + + int32_t dubbo_data_length = data.peekBEInt(DUBBO_LENGTH_OFFSET); + data.drain(DUBBO_HEADER_SIZE); + std::string response; + response.reserve(dubbo_data_length); + response.resize(dubbo_data_length); + data.copyOut(0, dubbo_data_length, &response[0]); + data.drain(data.length()); + Hessian2::Decoder decoder(response); + auto type_value = decoder.decode(); + if (type_value == nullptr) { + return absl::InternalError("Cannot parse RpcResult type from buffer"); + } + + auto type = static_cast(*type_value); + auto [has_value, has_exception, has_attachment] = DubboUtility::resolveResponseFlag(type); + json http_json; + if (has_exception || has_value) { + auto response_value = decoder.decode(); + http_json[has_value ? HTTPResponseKey : HTTPResponseErrorKey] = + DubboUtility::hessian2Json(response_value.get()); + } + + if (has_attachment) { + auto attachment_value = decoder.decode(); + http_json[HTTPResponseAttachmentKey] = DubboUtility::hessian2Json(attachment_value.get()); + } + + data.add(http_json.dump()); + return absl::OkStatus(); +} + +absl::Status Http2DubboTranscoder::extractTranscoderParameters(Http::RequestHeaderMap& headers, + Buffer::Instance& body) { + ASSERT(!current_params_.has_value()); + + ENVOY_LOG(debug, "method name is {} method args count is {}", method_info_.name_, + method_info_.parameter_mapping_.size()); + + TypedParamsWithAttachment params_and_attachment; + params_and_attachment.parameter_types_.resize(method_info_.parameter_mapping_.size()); + params_and_attachment.arguments_.resize(method_info_.parameter_mapping_.size()); + + uint8_t current_path_binding_index = 1; + uint8_t current_params_index = 0; + using ParameterMapping = envoy::extensions::filters::http::http_dubbo_transcoder::v3:: + HttpDubboTranscoder::DubboMethodMapping::ParameterMapping; + json body_json; + if (!body.toString().empty()) { + // If the exception is not caught, a core dump error may occur + try { + body_json = json::parse(body.toString()); + } catch (json::parse_error& e) { + ENVOY_LOG(warn, "json::parse throw exception : {}", e.what()); + } + } + + for (const auto& parameter : method_info_.parameter_mapping_) { + const auto& extract_key = parameter.extract_key(); + ENVOY_LOG(debug, "parameter extract key {}", extract_key); + + std::string parameter_value; + switch (parameter.extract_key_spec()) { + case ParameterMapping::ALL_QUERY_PARAMETER: { + Http::Utility::QueryParams params = Http::Utility::parseQueryString(headers.getPathValue()); + if (params.empty()) { + return absl::InternalError("Error parsing query parameters"); + } + + if (!params.count(extract_key)) { + return absl::NotFoundError(fmt::format("The parameter {} could not be found", extract_key)); + } + parameter_value = params[extract_key]; + break; + } + case ParameterMapping::ALL_HEADER: { + auto result = headers.get(Http::LowerCaseString(extract_key)); + if (result.empty()) { + return absl::NotFoundError(fmt::format("The header {} could not be found", extract_key)); + } + parameter_value = std::string(result[0]->value().getStringView()); + break; + } + case ParameterMapping::ALL_PATH: { + if (current_path_binding_index > bindings_.size()) { + return absl::OutOfRangeError("Error parsing query parameters"); + } + parameter_value = bindings_.at(current_path_binding_index - 1).value; + current_path_binding_index++; + break; + } + case ParameterMapping::ALL_BODY: { + if (body_json.is_discarded() || body_json.is_null()) { + return absl::InvalidArgumentError("the body can not be parsed as json or body is empty."); + } else { + if (!extract_key.empty()) { + auto key = body_json.find(extract_key); + if (key == body_json.end()) { + return absl::NotFoundError( + fmt::format("The parameter {} could not be found", extract_key)); + } + params_and_attachment.arguments_[current_params_index] = *key; + } else { + params_and_attachment.arguments_[current_params_index] = body_json; + } + params_and_attachment.parameter_types_[current_params_index] = parameter.mapping_type(); + } + current_params_index++; + continue; + } + default: + return absl::UnimplementedError("Unsupported types"); + } + + ENVOY_LOG(debug, "parameter extract value {}, type {}", parameter_value, + parameter.mapping_type()); + + absl::optional result = + DubboUtility::convertStringToTypeValue(parameter_value, parameter.mapping_type()); + if (!result) { + return absl::InvalidArgumentError( + "can not transcode the request because the given param not match the type"); + } + + params_and_attachment.arguments_[current_params_index] = result.value(); + params_and_attachment.parameter_types_[current_params_index] = parameter.mapping_type(); + current_params_index++; + } + + ENVOY_LOG(debug, "method name is {} method args count is {}", method_info_.name_, + method_info_.parameter_mapping_.size()); + + if (method_info_.passthrough_all_headers_.has_value()) { + if (method_info_.passthrough_all_headers_.value()) { + headers.iterate( + [¶ms_and_attachment](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { + const std::string& header_key = {header.key().getStringView().begin(), + header.key().getStringView().end()}; + params_and_attachment.attachment_[header_key] = header.value().getStringView(); + return Http::HeaderMap::Iterate::Continue; + }); + } + } else { + if (method_info_.passthrough_header_keys_.has_value()) { + for (const auto& key : method_info_.passthrough_header_keys_.value()) { + + auto result = headers.get(Http::LowerCaseString(key)); + if (result.empty()) { + return absl::NotFoundError(fmt::format("The header {} could not be found", key)); + } + params_and_attachment.attachment_[key] = result[0]->value().getStringView(); + } + } else { + ENVOY_LOG(debug, "passthrough_header_keys has no value"); + } + } + + if (!method_info_.service_group_.empty()) { + params_and_attachment.attachment_[AttachmentGroupKey] = method_info_.service_group_; + } + + current_params_.emplace(params_and_attachment); + return absl::OkStatus(); +} + +void Http2DubboTranscoder::encodeDubboFrameWithGenericCall(Buffer::Instance& data) { + // Encode dubbo data. + std::string encoded_data; + Hessian2::Encoder encoder(encoded_data); + + // Write dubbo header. + { + // Write the dubbo protocol: magic-number\type\serialization id\status. + data.writeBEInt(static_cast(DUBBO_MAGIC)); + data.writeBEInt(static_cast(TYPE_INFO)); + data.writeBEInt(static_cast(DEFAULT_REQUEST_STAT)); + + // Write the request id. + // TODO(zhaobingkun.zbk) + if (RequestId == ULONG_MAX) { + RequestId = 0; + } + data.writeBEInt(static_cast(++RequestId)); + } + + // Encode dubbo body. + { + // Encode: dubbo version\service name\service version. + encoder.encode(DubboDefaultProtocolVsersion); + encoder.encode(method_info_.service_name_); + encoder.encode(method_info_.service_version_.empty() ? DubboDefaultMethodVersion + : method_info_.service_version_); + // Encode: method name\parameter type, use generic call. + encoder.encode(DubboGenericMethodName); + encoder.encode(DubboGenericParamTypes); + + // Encode: arguments. + encoder.encode(method_info_.name_); + if (current_params_.has_value()) { + auto type_j = json(current_params_.value().parameter_types_); + DubboUtility::encodeParameterList(type_j, encoder); + + auto params_j = json(current_params_.value().arguments_); + DubboUtility::encodeParameterList(params_j, encoder); + } else { + ENVOY_LOG(debug, "The parameter is empty"); + } + } + + // Encode attachment. + { + if (!current_params_.value().attachment_.is_null()) { + DubboUtility::json2Hessian(current_params_.value().attachment_, encoder); + } else { + encoder.encodeMapBegin(""); + encoder.encodeMapEnd(); + } + } + + // Write the message data length. + data.writeBEInt(static_cast(encoded_data.size())); + + // Write body and attachment data. + data.add(encoded_data.c_str(), encoded_data.size()); + + ENVOY_LOG(debug, "encoded data is {} size is {} ", data.toString(), data.length()); +} + +inline bool Http2DubboTranscoder::validateMagicNumber(Buffer::Instance& data) { + return data.peekBEInt() == DUBBO_MAGIC; +} + +void TranscodeFilter::initPerRouteConfig() { + const auto* route_local = + Http::Utility::resolveMostSpecificPerFilterConfig(decoder_callbacks_); + + per_route_config_ = route_local ? route_local : &config_; +} + +// transcoder filter impl +Http::FilterHeadersStatus TranscodeFilter::decodeHeaders(Http::RequestHeaderMap& header, + bool end_stream) { + initPerRouteConfig(); + if (per_route_config_->disabled()) { + return Http::FilterHeadersStatus::Continue; + } + + per_route_config_->stats_.dubbo_req_total_.inc(); + ENVOY_STREAM_LOG(debug, "decodeHeaders:", *decoder_callbacks_); + + auto [status, transcoder] = per_route_config_->createTranscoder(header); + if (!status.ok()) { + per_route_config_->stats_.resolve_method_error_.inc(); + ENVOY_STREAM_LOG(debug, "Failed to transcode request headers: {}", *decoder_callbacks_, + status.ToString()); + + if (status.code() == absl::StatusCode::kNotFound && + !per_route_config_->requestValidateOptions().reject_unknown_method()) { + ENVOY_LOG(debug, "Request is passed through without transcoding because it cannot be mapped " + "to a Dubbo method."); + return Http::FilterHeadersStatus::Continue; + } + error_ = true; + decoder_callbacks_->sendLocalReply(static_cast(Http::Code::InternalServerError), + status.ToString(), nullptr, absl::nullopt, ""); + return Http::FilterHeadersStatus::StopIteration; + } + + transcoder_.reset(transcoder); + + if (end_stream) { + Buffer::OwnedImpl empty_data; + status = transcoder_->extractTranscoderParameters(header, empty_data); + if (!status.ok()) { + per_route_config_->stats_.extract_parameter_error_.inc(); + ENVOY_LOG(warn, "Failed to resolve headers, error is {}", status.ToString()); + + // TODO(zhaobingkun.zbk) + Http::Code http_code = DubboUtility::convertStatusToHttpCode(status.code()); + error_ = true; + decoder_callbacks_->sendLocalReply(static_cast(http_code), status.ToString(), + nullptr, absl::nullopt, ""); + return Http::FilterHeadersStatus::StopIteration; + } + + Buffer::OwnedImpl data; + transcoder_->encodeDubboFrameWithGenericCall(data); + decoder_callbacks_->addDecodedData(data, true); + ENVOY_STREAM_LOG(debug, "sent dubbo frame", *decoder_callbacks_); + } + + // Modify the request method to use http.tcp connection pools. + header.setMethod(Http::Headers::get().MethodValues.Connect); + request_header_ = &header; + return Http::FilterHeadersStatus::Continue; +} + +Http::FilterDataStatus TranscodeFilter::decodeData(Buffer::Instance& data, bool end_stream) { + if (!transcoder_ || error_) { + ENVOY_STREAM_LOG(debug, "Transcoder does not exist or an error occurred, end_stream: {}", + *decoder_callbacks_, end_stream); + + return Http::FilterDataStatus::Continue; + } + + if (!request_body_buffer_) { + request_body_buffer_ = std::make_unique(); + } + request_body_buffer_->move(data); + if (!end_stream) { + return Http::FilterDataStatus::StopIterationAndBuffer; + } + + const auto status = + transcoder_->extractTranscoderParameters(*request_header_, *request_body_buffer_); + if (!status.ok()) { + per_route_config_->stats_.extract_parameter_error_.inc(); + ENVOY_LOG(warn, "Failed to auto mapping body, error is {}", status.ToString()); + + // TODO(zhaobingkun.zbk) + Http::Code http_code = DubboUtility::convertStatusToHttpCode(status.code()); + error_ = true; + decoder_callbacks_->sendLocalReply(static_cast(http_code), status.ToString(), + nullptr, absl::nullopt, ""); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + + data.drain(data.length()); + transcoder_->encodeDubboFrameWithGenericCall(data); + + ENVOY_STREAM_LOG(debug, "encoded dubbo frame, length: {}, data: {}", *decoder_callbacks_, + data.length(), data.toString()); + + return Http::FilterDataStatus::Continue; +} + +Http::FilterTrailersStatus TranscodeFilter::decodeTrailers(Http::RequestTrailerMap&) { + return Http::FilterTrailersStatus::Continue; +} + +Http::FilterHeadersStatus TranscodeFilter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { + if (transcoder_) { + headers.setReferenceContentType(ContentTypeHeaderValue); + } + return Http::FilterHeadersStatus::Continue; +} + +Http::FilterDataStatus TranscodeFilter::encodeData(Buffer::Instance& data, bool end_stream) { + ENVOY_STREAM_LOG(debug, "Recieve data from remote {} length is {} end_stream is {}", + *decoder_callbacks_, data.toString(), data.length(), end_stream); + + if (transcoder_) { + absl::Status status = transcoder_->translateDubboToHttp(data); + switch (status.code()) { + case absl::StatusCode::kUnknown: + per_route_config_->stats_.response_protocol_error_.inc(); + break; + case absl::StatusCode::kDataLoss: + per_route_config_->stats_.response_incomplete_.inc(); + break; + case absl::StatusCode::kInternal: + per_route_config_->stats_.response_type_error_.inc(); + break; + case absl::StatusCode::kOk: + per_route_config_->stats_.response_success_.inc(); + break; + default: + break; + } + if (status.code() != absl::StatusCode::kOk && status.code() != absl::StatusCode::kUnknown) { + ENVOY_STREAM_LOG(debug, "translateDubboToHttp failed, faliled reason {}", *decoder_callbacks_, + status.message()); + data.add(status.message()); + } + } + + return Http::FilterDataStatus::Continue; +} + +Http::FilterTrailersStatus TranscodeFilter::encodeTrailers(Http::ResponseTrailerMap&) { + if (transcoder_) { + transcoder_.reset(); + } + + return Http::FilterTrailersStatus::Continue; +} + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h b/contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h new file mode 100644 index 0000000000000..b0156e7f3c947 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h @@ -0,0 +1,252 @@ +#pragma once + +#include +#include +#include + +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.h" +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.validate.h" +#include "contrib/http_dubbo_transcoder/filters/http/source/transcoder.h" +#include "contrib/http_dubbo_transcoder/filters/http/source/utility.h" + +#include "envoy/api/api.h" +#include "envoy/http/filter.h" +#include "envoy/type/matcher/v3/regex.pb.h" + +#include "source/common/common/logger_impl.h" +#include "source/common/common/logger.h" +#include "source/common/common/regex.h" +#include "source/common/http/codes.h" +#include "source/common/http/header_map_impl.h" + +#include "grpc_transcoding/path_matcher.h" + +#include "hessian2/basic_codec/object_codec.hpp" +#include "hessian2/codec.hpp" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +class Http2DubboTranscoder; + +/** + * All http_dubbo_transcoder stats. + */ +#define ALL_HTTP_DUBBO_TRANSCODER_STATS(COUNTER) \ + COUNTER(response_protocol_error) \ + COUNTER(response_incomplete) \ + COUNTER(response_type_error) \ + COUNTER(extract_parameter_error) \ + COUNTER(resolve_method_error) \ + COUNTER(response_success) \ + COUNTER(dubbo_req_total) + +struct HttpDubboTranscoderStats { + ALL_HTTP_DUBBO_TRANSCODER_STATS(GENERATE_COUNTER_STRUCT) +}; + +/*** + * transcoder config + */ +class DubboTranscoderConfig : public Router::RouteSpecificFilterConfig, + public Logger::Loggable { +public: + /*** + * resolve the global enable falg in the config + */ + DubboTranscoderConfig( + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder& + config, + const std::string& stat_prefix, Stats::Scope& scope); + + /*** + * this function will create the corresponding transcoder acccording to the + * headers, mainly according to the content-type field + * + * @return nullptr if the any thing wrong when create transcoder + */ + std::tuple + createTranscoder(Http::RequestHeaderMap& headers) const; + + MethodInfoSharedPtr + createMethodInfo(const std::string& service_name, const std::string& service_version, + const std::string& service_group, + const envoy::extensions::filters::http::http_dubbo_transcoder::v3:: + HttpDubboTranscoder::DubboMethodMapping& method_mapping); + + /*** + * wether enable the transcoder + */ + bool disabled() const { return disabled_; } + + const envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder:: + RequestValidateOptions& + requestValidateOptions() const { + return request_validate_options_; + } + + HttpDubboTranscoderStats stats_; + +private: + HttpDubboTranscoderStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return HttpDubboTranscoderStats{ + ALL_HTTP_DUBBO_TRANSCODER_STATS(POOL_COUNTER_PREFIX(scope, prefix))}; + } + + bool disabled_{false}; + envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder:: + RequestValidateOptions request_validate_options_; + google::grpc::transcoding::PathMatcherPtr path_matcher_; +}; + +using DubboTranscoderConfigSharedPtr = std::shared_ptr; + +/*** + * the error maybe occured while peoccsing the header or body + * 0 means no error + */ +enum class ParamsErrorCode : int8_t { + OK = 0, + CountError = 1, + TypeError = 2, + ParseError = 4, + MethodNotFound = 5, + OthersError = 7, +}; + +constexpr uint64_t DUBBO_HEADER_SIZE = 16; +constexpr uint64_t DUBBO_MAGIC_SIZE = 2; +constexpr uint64_t DUBBO_TYPE_SIZE = 1; +constexpr uint64_t DUBBO_STATE_SIZE = 1; +constexpr uint64_t DUBBO_REQID_SIZE = 8; +constexpr uint64_t DUBBO_PACKETLEN_SIZE = 4; +constexpr uint16_t DUBBO_MAGIC = 0xdabb; +constexpr uint64_t DUBBO_LENGTH_OFFSET = 12; +constexpr uint8_t DEFAULT_REQUEST_STAT = 0; +constexpr int64_t DEFAULT_REQUEST_ID = 1; +/** + * + * | req or response | 2 way | event | Serializtion | + * | 1 | 1 | 0 | 2 | + * | 1 | 1 | 0 | 00010 | + * more details: + * https://dubbo.apache.org/en/blog/2018/10/05/introduction-to-the-dubbo-protocol/ + */ +constexpr uint8_t TYPE_INFO = 0xc2; + +// this type point to the state_ field in the Header struct +enum class ResponseStatus : uint8_t { + Ok = 20, + ClientTimeout = 30, + ServerTimeout = 31, + BadRequest = 40, + BadResponse = 50, + ServiceNotFound = 60, + ServiceError = 70, + ServerError = 80, + ClientError = 90, + ServerThreadpoolExhaustedError = 100, +}; + +/*** + * Rpc response represent used by DubboDecoder + */ +struct RpcResponse { + std::string body_; + Envoy::Http::Code code_; +}; + +using RpcResponsePtr = std::unique_ptr; + +/*** + * this class transcode the http request to dubbo request + * the transcode support http2Dubbo specification + * the transcode split into 2 condition: + * 1. service map (one path corresponding to a set methods of a service) + * 2. method map (one path corresponding to one method) + */ +class Http2DubboTranscoder : public Logger::Loggable { +public: + /*** + * @param method_info_vec the correspond methodinfo of the request,come from sharedPtr of + * pathmatcher + * @param config the transcoder config + */ + Http2DubboTranscoder(const MethodInfo& method_info, std::vector&& bindings); + + std::string getName() const { return "http_dubbo_transcoder"; } + + absl::Status translateDubboToHttp(Buffer::Instance& data); + absl::Status extractTranscoderParameters(Http::RequestHeaderMap& headers, Buffer::Instance& body); + + void encodeDubboFrameWithGenericCall(Buffer::Instance& data); + +private: + bool validateMagicNumber(Buffer::Instance& data); + + struct TypedParamsWithAttachment { + std::vector parameter_types_; + std::vector arguments_; + nlohmann::json attachment_; + }; + + const MethodInfo& method_info_; + const std::vector bindings_; + Buffer::OwnedImpl request_buffer_{}; + absl::optional current_params_; +}; + +using Http2DubboTranscoderPtr = std::unique_ptr; + +/*** + * Transcoder Filter + */ +class TranscodeFilter : public Http::StreamFilter, public Logger::Loggable { +public: + TranscodeFilter(DubboTranscoderConfig& config) : config_(config){}; + // Http::StreamFilterBase + void onDestroy() override{}; + + // Http::StreamDecoderFilter + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, + bool end_stream) override; + Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; + Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap&) override; + void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override { + decoder_callbacks_ = &callbacks; + } + + // Http::StreamEncoderFilter + Http::Filter1xxHeadersStatus encode1xxHeaders(Http::ResponseHeaderMap&) override { + return Http::Filter1xxHeadersStatus::Continue; + } + Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, bool) override; + Http::FilterDataStatus encodeData(Buffer::Instance&, bool) override; + Http::FilterTrailersStatus encodeTrailers(Http::ResponseTrailerMap&) override; + Http::FilterMetadataStatus encodeMetadata(Http::MetadataMap&) override { + return Http::FilterMetadataStatus::Continue; + } + void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) override { + encoder_callbacks_ = &callbacks; + } + +private: + void initPerRouteConfig(); + + Http2DubboTranscoderPtr transcoder_; + DubboTranscoderConfig& config_; + const DubboTranscoderConfig* per_route_config_{}; + Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; + Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; + Http::RequestHeaderMap* request_header_{}; + std::unique_ptr request_body_buffer_{}; + + bool error_{false}; +}; + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/source/transcoder.h b/contrib/http_dubbo_transcoder/filters/http/source/transcoder.h new file mode 100644 index 0000000000000..693de1fb4d0d5 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/transcoder.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.h" +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.validate.h" +#include "envoy/http/filter.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/logger.h" +#include "source/common/http/codes.h" +#include "source/common/http/header_map_impl.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "include/nlohmann/json.hpp" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +using Status = absl::Status; + +/*** + * the Rpc invocation meta represent + */ + +struct VariableBinding { + // the location the params in the dubbo request arg + std::vector field_path; + // The value to be inserted. + std::string value; +}; +using VariableBindingVecPtr = std::unique_ptr>; + +using TypeAndFiledPath = std::pair>; + +struct MethodInfo { + std::string service_name_; + std::string service_version_; + std::string service_group_; + std::string name_; + std::string match_http_method_; + std::string match_pattern_; + Protobuf::RepeatedPtrField + parameter_mapping_; + Protobuf::RepeatedPtrField attachment_from_header_keys_; + absl::optional passthrough_all_headers_; + absl::optional> passthrough_header_keys_; + bool passthrough_body_{false}; +}; + +using MethodInfoSharedPtr = std::shared_ptr; + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/source/utility.cc b/contrib/http_dubbo_transcoder/filters/http/source/utility.cc new file mode 100644 index 0000000000000..9744febf0b538 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/utility.cc @@ -0,0 +1,398 @@ +#include "utility.h" + +#include +#include + +#include "envoy/http/codes.h" +#include "envoy/http/query_params.h" + +#include "source/common/common/assert.h" +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/enum_to_int.h" +#include "source/common/common/regex.h" + +#include "absl/strings/str_split.h" +#include "hessian2/object.hpp" +#include "include/nlohmann/json.hpp" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +absl::optional DubboUtility::convertStringToTypeValue(absl::string_view value, + std::string type) { + + // the return value converted value maybe int boolean string, so use json to store the value + if (type == JsonType2JavaType.at(json::value_t::boolean)) { + if (value == "true" || value == "false") { + return {json(value == "true" ? true : false)}; + } + return absl::nullopt; + } else if (type == JsonType2JavaType.at(json::value_t::number_float)) { + envoy::type::matcher::v3::RegexMatcher matcher; + *matcher.mutable_google_re2() = envoy::type::matcher::v3::RegexMatcher::GoogleRE2(); + matcher.set_regex("^-?([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*|0?\\.0+|0)$"); + const auto compiled_matcher = Regex::Utility::parseRegex(matcher); + if (!compiled_matcher->match(value)) { + return absl::nullopt; + } + return {json(strtod(value.data(), nullptr))}; + } else if (type == JsonType2JavaType.at(json::value_t::number_integer)) { + envoy::type::matcher::v3::RegexMatcher matcher; + *matcher.mutable_google_re2() = envoy::type::matcher::v3::RegexMatcher::GoogleRE2(); + matcher.set_regex("^(0|[1-9][0-9]*|-[1-9][0-9]*)$"); + const auto compiled_matcher = Regex::Utility::parseRegex(matcher); + if (!compiled_matcher->match(value)) { + return absl::nullopt; + } + return {json(strtoll(value.data(), nullptr, 10))}; + } else if (type == JsonType2JavaType.at(json::value_t::string)) { + return {json(value)}; + } else if (type == JsonType2JavaType.at(json::value_t::array)) { + json array_json; + array_json.emplace_back(std::string(value)); + return array_json; + } else { + return absl::nullopt; + } +} + +Http::Code DubboUtility::convertStatusToHttpCode(absl::StatusCode status) { + Http::Code ret_http_code; + + switch (status) { + case absl::StatusCode::kInternal: + ret_http_code = Http::Code::InternalServerError; + break; + case absl::StatusCode::kNotFound: + ret_http_code = Http::Code::NotFound; + break; + case absl::StatusCode::kOutOfRange: + ret_http_code = Http::Code::RangeNotSatisfiable; + break; + case absl::StatusCode::kUnimplemented: + ret_http_code = Http::Code::NotImplemented; + break; + case absl::StatusCode::kInvalidArgument: + ret_http_code = Http::Code::BadRequest; + break; + case absl::StatusCode::kDataLoss: + ret_http_code = Http::Code::BadRequest; + break; + default: + ret_http_code = Http::Code::NotFound; + } + + return ret_http_code; +} + +std::tuple DubboUtility::resolveResponseFlag(RpcResponseType flag) { + bool has_value = false, has_excption = false, has_attachment = false; + + switch (flag) { + case RpcResponseType::ResponseWithException: + has_excption = true; + break; + case RpcResponseType::ResponseWithExceptionWithAttachments: + has_excption = true; + has_attachment = true; + break; + case RpcResponseType::ResponseWithNullValue: + has_value = false; + break; + case RpcResponseType::ResponseNullValueWithAttachments: + has_value = false; + has_attachment = true; + break; + case RpcResponseType::ResponseWithValue: + has_value = true; + break; + case RpcResponseType::ResponseValueWithAttachments: + has_value = true; + has_attachment = true; + break; + } + + return {has_value, has_excption, has_attachment}; +} + +std::string DubboUtility::hessianType2String(Hessian2::Object::Type type) { + switch (type) { + case Hessian2::Object::Type::Binary: + return "Binary"; + case Hessian2::Object::Type::Boolean: + return "Boolean"; + case Hessian2::Object::Type::Date: + return "Date"; + case Hessian2::Object::Type::Double: + return "Double"; + case Hessian2::Object::Type::Integer: + return "Integer"; + case Hessian2::Object::Type::Long: + return "Long"; + case Hessian2::Object::Type::Null: + return "Null"; + case Hessian2::Object::Type::Ref: + return "Ref"; + case Hessian2::Object::Type::String: + return "String"; + case Hessian2::Object::Type::TypedList: + return "TypedList"; + case Hessian2::Object::Type::UntypedList: + return "UntypedList"; + case Hessian2::Object::Type::TypedMap: + return "TypedMap"; + case Hessian2::Object::Type::UntypedMap: + return "UntypedMap"; + case Hessian2::Object::Type::Class: + return "Class"; + default: + return "Unknown"; + } +} + +json DubboUtility::badCastErrorMessageJson(const std::string& type) { + json error_message_json; + error_message_json["error"] = absl::StrFormat( + "The data returned by dubbo service does not comply with the hessian protocol, data type: %s", + type); + return error_message_json; +} + +json DubboUtility::hessian2Json(Object* input) { + json out; + + if (input == nullptr) { + return nullptr; + } + + switch (input->type()) { + case Object::Type::TypedMap: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + for (auto& item : *(static_cast(input))) { + Hessian2::StringObject& key = item.first->asType(); + if (key.toMutableString() != nullptr) { + out[*(key.toMutableString())] = hessian2Json(item.second.get()); + } + } + } + } break; + case Object::Type::UntypedMap: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + for (auto& item : *(static_cast(input))) { + Hessian2::StringObject& key = item.first->asType(); + if (key.toMutableString() != nullptr && *(key.toMutableString()) != ClassKey) { + out[*(key.toMutableString())] = hessian2Json(item.second.get()); + } + } + } + } break; + case Object::Type::UntypedList: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + for (auto& item : *(static_cast(input))) { + json j = hessian2Json(item.get()); + out.push_back(j); + } + } + } break; + case Object::Type::TypedList: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + for (auto& item : *(static_cast(input))) { + json j = hessian2Json(item.get()); + out.push_back(j); + } + } + } break; + + case Object::Type::String: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + out = *(static_cast(input)->toMutableString()); + } + } break; + + case Object::Type::Double: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + out = *(static_cast(input)->toMutableDouble()); + } + } break; + + case Object::Type::Integer: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + out = *(static_cast(input)->toMutableInteger()); + } + } break; + + case Object::Type::Long: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + out = *(static_cast(input)->toMutableLong()); + } + } break; + + case Object::Type::Boolean: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + out = *(static_cast(input)->toMutableBoolean()); + } + } break; + + case Object::Type::Ref: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + Hessian2::Object* obj = static_cast(input)->toRefDest().value(); + out = absl::StrFormat("Type: Ref, target Object Type: %s", hessianType2String(obj->type())); + } + } break; + + case Object::Type::Class: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + const Hessian2::Object::ClassInstance* class_instance = + static_cast(input)->toClassInstance().value(); + RELEASE_ASSERT(class_instance->def_->field_names_.size() == class_instance->data_.size(), + "The size of def_->field_names_ and data_ of class_instance is inconsistent"); + out[ClassKey] = class_instance->def_->type_; + for (int i = 0; i < static_cast(class_instance->def_->field_names_.size()); i++) { + out[class_instance->def_->field_names_[i]] = hessian2Json(class_instance->data_[i].get()); + } + } + } break; + + case Object::Type::Date: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + out = static_cast(input)->toMutableDate()->count(); + } + } break; + + case Object::Type::Null: { + out = nullptr; + } break; + + case Object::Type::Binary: { + if (dynamic_cast(input) == nullptr) { + out = badCastErrorMessageJson(hessianType2String(input->type())); + } else { + out = *(static_cast(input)->toMutableBinary()); + } + } break; + + default: + break; + } + + return out; +} + +void DubboUtility::json2Hessian(json&& object, Hessian2::Encoder& encoder) { + auto type = object.type(); + switch (type) { + case json::value_t::object: { + encoder.encodeMapBegin(""); + for (auto& el : object.items()) { + encoder.encode(el.key()); + json2Hessian(el.value(), encoder); + } + encoder.encodeMapEnd(); + } break; + + case json::value_t::boolean: + encoder.encode(object.get()); + break; + + case json::value_t::number_integer: + case json::value_t::number_unsigned: + encoder.encode(object.get()); + break; + + case json::value_t::number_float: + encoder.encode(object.get()); + break; + + case json::value_t::array: { + Hessian2::Object::UntypedList untyped_list; + for (auto& item : object.items()) { + createUntypedListObjcet(item.value(), untyped_list); + } + Hessian2::UntypedListObject untyped_list_object(std::move(untyped_list)); + encoder.encode(untyped_list_object); + } break; + + case json::value_t::string: + encoder.encode(object.get()); + break; + + case json::value_t::binary: + encoder.encode>(object.get_binary()); + break; + + case json::value_t::null: + default: + encoder.encode(Hessian2::NullObject()); + break; + } +} + +void DubboUtility::json2Hessian(json& j, Hessian2::Encoder& encoder) { + DubboUtility::json2Hessian(std::move(j), encoder); +} + +void DubboUtility::encodeParameterList(json& j, Hessian2::Encoder& encoder) { + encoder.encodeVarListBegin(""); + for (auto& item : j.items()) { + json2Hessian(item.value(), encoder); + } + encoder.encodeVarListEnd(); +} + +void DubboUtility::createUntypedListObjcet(const json& object, + Hessian2::Object::UntypedList& untyped_list) { + auto type = object.type(); + switch (type) { + case json::value_t::string: + untyped_list.emplace_back(std::make_unique(object.get())); + break; + case json::value_t::number_unsigned: + case json::value_t::number_integer: + untyped_list.emplace_back(std::make_unique(object.get())); + break; + case json::value_t::number_float: + untyped_list.emplace_back(std::make_unique(object.get())); + break; + case json::value_t::boolean: + untyped_list.emplace_back(std::make_unique(object.get())); + break; + case json::value_t::binary: + untyped_list.emplace_back(std::make_unique(object.get_binary())); + break; + case json::value_t::null: + default: + untyped_list.emplace_back(std::make_unique()); + break; + } +} + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/source/utility.h b/contrib/http_dubbo_transcoder/filters/http/source/utility.h new file mode 100644 index 0000000000000..35190db54c0e7 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/source/utility.h @@ -0,0 +1,90 @@ +#pragma once +#include +#include + +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.h" +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.validate.h" +#include "envoy/http/codes.h" +#include "envoy/http/query_params.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/enum_to_int.h" +#include "source/common/common/regex.h" +#include "source/common/http/utility.h" + +#include "hessian2/basic_codec/object_codec.hpp" +#include "hessian2/object.hpp" +#include "include/nlohmann/json.hpp" +#include "transcoder.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +using json = nlohmann::json; +using Object = Hessian2::Object; +using ObjectPtr = std::unique_ptr; +using ListObject = Hessian2::UntypedListObject; +using ListObjectPtr = std::unique_ptr; +using StringObject = Hessian2::StringObject; +using StringObjectPtr = std::unique_ptr; +using MapObject = Hessian2::UntypedMapObject; +using MapObjectPtr = std::unique_ptr; + +static const std::string ClassKey = "class"; + +static const absl::flat_hash_map JsonType2JavaType{ + {json::value_t::number_integer, "java.lang.Long"}, + {json::value_t::number_unsigned, "java.lang.Long"}, + {json::value_t::string, "java.lang.String"}, + {json::value_t::boolean, "java.lang.Boolean"}, + {json::value_t::array, "java.util.List"}, + {json::value_t::object, "java.util.Map"}, + {json::value_t::number_float, "java.lang.Double"}, + {json::value_t::null, ""}, +}; + +// the first byte in the response body express the response type +enum class RpcResponseType : uint8_t { + ResponseWithException = 0, + ResponseWithValue = 1, + ResponseWithNullValue = 2, + ResponseWithExceptionWithAttachments = 3, + ResponseValueWithAttachments = 4, + ResponseNullValueWithAttachments = 5, +}; + +class DubboUtility { +public: + /*** + * this is a tool funtion convert a string view value to the given type value + * now just support base type: + * 1. Integer + * 2. Boolean + * 3. Double + * 4. String + * + * other types will return absl::nullopt + * @return the true value represent by json + * + */ + static absl::optional convertStringToTypeValue(absl::string_view, std::string); + static Http::Code convertStatusToHttpCode(absl::StatusCode status); + + // hessian2 json translate + static json hessian2Json(Object* obj); + static void json2Hessian(json&& j, Hessian2::Encoder& encoder); + static void json2Hessian(json& j, Hessian2::Encoder& encoder); + static void encodeParameterList(json& j, Hessian2::Encoder& encoder); + static void createUntypedListObjcet(const json& object, + Hessian2::Object::UntypedList& untyped_list); + static std::string hessianType2String(Hessian2::Object::Type type); + static json badCastErrorMessageJson(const std::string& type); + static std::tuple resolveResponseFlag(RpcResponseType flag); +}; + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/test/BUILD b/contrib/http_dubbo_transcoder/filters/http/test/BUILD new file mode 100644 index 0000000000000..dbadc1de8a461 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/BUILD @@ -0,0 +1,40 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + "//contrib/http_dubbo_transcoder/filters/http/source:config", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "dubbo_transcoder_filter_test", + data = [ + "//contrib/http_dubbo_transcoder/filters/http/test/test_data:http2dubbo_test_data" + ], + external_deps = [ + "hessian2_codec_object_impl", + "hessian2_codec_codec_impl", + "hessian2_codec_object_codec_lib", + ], + srcs = ["dubbo_transcoder_filter_test.cc"], + deps = [ + "//contrib/http_dubbo_transcoder/filters/http/source:config", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", + "//test/test_common:environment_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg_cc_proto", + ], +) diff --git a/contrib/http_dubbo_transcoder/filters/http/test/config_test.cc b/contrib/http_dubbo_transcoder/filters/http/test/config_test.cc new file mode 100644 index 0000000000000..a8a224284d4b9 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/config_test.cc @@ -0,0 +1,80 @@ +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.h" +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.validate.h" +#include "contrib/http_dubbo_transcoder/filters/http/source/config.h" +#include "contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +TEST(HttpDubboTranscodeFilterFactoryTest, HttpDubboTranscodeFilterCorrectYaml) { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: true +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" +)EOF"; + + envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + NiceMock context; + HttpDubboTranscodeFilterFactory factory; + Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, "stats", context); + Http::MockFilterChainFactoryCallbacks filter_callback; + EXPECT_CALL(filter_callback, addStreamFilter(_)); + cb(filter_callback); +} + +TEST(HttpDubboTranscodeFilterFactoryTest, HttpDubboTranscodePerFilterCorrectYaml) { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: true +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" +)EOF"; + + envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + NiceMock context; + HttpDubboTranscodeFilterFactory factory; + auto route_config = factory.createRouteSpecificFilterConfig( + proto_config, context, ProtobufMessage::getStrictValidationVisitor()); + const auto* config = dynamic_cast(route_config.get()); + EXPECT_FALSE(config->disabled()); +} + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/test/dubbo_transcoder_filter_test.cc b/contrib/http_dubbo_transcoder/filters/http/test/dubbo_transcoder_filter_test.cc new file mode 100644 index 0000000000000..93d16f4087744 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/dubbo_transcoder_filter_test.cc @@ -0,0 +1,1051 @@ +#include +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" +#include "test/test_common/environment.h" + +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.h" +#include "contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/http_dubbo_transcoder.pb.validate.h" +#include "contrib/http_dubbo_transcoder/filters/http/source/config.h" +#include "contrib/http_dubbo_transcoder/filters/http/source/dubbo_transcoder_filter.h" +#include "contrib/http_dubbo_transcoder/filters/http/source/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "hessian2/object.hpp" + +using testing::_; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace HttpDubboTranscoder { + +class TranscodeFilterTest : public testing::Test { +public: + TranscodeFilterTest() = default; + + void setConfiguration() { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" +)EOF"; + + setConfiguration(yaml_string); + } + + void setConfiguration(const std::string& yaml_string) { + envoy::extensions::filters::http::http_dubbo_transcoder::v3::HttpDubboTranscoder proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + + time_system_.setSystemTime(std::chrono::seconds(1610503040)); + config_ = + std::make_shared(proto_config, "http_dubbo_transcoder", *scope_.rootScope()); + } + + void setFilter() { setFilter(std::make_shared(*config_)); } + + void setFilter(std::shared_ptr filter) { + filter_ = filter; + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + } + + std::string readHexStream(std::string hex_stream) { + ASSERT(hex_stream.size() % 2 == 0); + std::stringstream ss; + for (size_t i = 0; i < hex_stream.size(); i += 2) { + std::string str_byte = hex_stream.substr(i, 2); + char chr = static_cast(strtol(str_byte.c_str(), NULL, 16)); + ss << chr; + } + return ss.str(); + } + + Stats::TestUtil::TestStore scope_; + Event::SimulatedTimeSystem time_system_; + NiceMock decoder_callbacks_; + NiceMock encoder_callbacks_; + std::shared_ptr config_; + std::shared_ptr filter_; +}; + +class MockObject : public Hessian2::Object { +public: + MOCK_METHOD(Hessian2::Object::Type, type, (), (const)); + MOCK_METHOD(size_t, hash, (), (const)); + MOCK_METHOD(bool, equal, (const Object&), (const)); +}; + +TEST_F(TranscodeFilterTest, NormalHttpGetMethod) { + setConfiguration(); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(1); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_param=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); +} + +TEST_F(TranscodeFilterTest, AllowUnknownMethodAndParameter) { + setConfiguration(); + setFilter(); + + { + // the path mismatch. + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/mytest.service/test?my_param=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Post, request_headers.getMethodValue()); + } + + { + // the parameter mismatch. + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/mytest.service/test?my_test=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request_headers.getMethodValue()); + } +} + +TEST_F(TranscodeFilterTest, RejectUnknownMethodAndParameter) { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: true + reject_unknown_method: true +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" +)EOF"; + + setConfiguration(yaml_string); + setFilter(); + + { + // the path mismatch. + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(1); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/mytest.service/test?my_param=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Post, request_headers.getMethodValue()); + } + + { + // the parameter mismatch. + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(1); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_test=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request_headers.getMethodValue()); + } +} + +TEST_F(TranscodeFilterTest, ExtractParameterKeyFromQuery) { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param1 + mapping_type: "java.lang.String" + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param2 + mapping_type: "java.lang.Long" +)EOF"; + setConfiguration(yaml_string); + setFilter(); + + { + // normal request + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(1); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_param1=test&my_param2=12345"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + } + { + // the request path don't include a query + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)) + .Times(1); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/mytest.service/sayHello"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request_headers.getMethodValue()); + } + + { + // query key don't match the extract_key + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::NotFound, _, _, _, _)).Times(1); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_param1=test&my_param4=45645"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request_headers.getMethodValue()); + } +} + +TEST_F(TranscodeFilterTest, ExtractParameterKeyFromHeader) { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: my_param1 + mapping_type: "java.lang.String" + - extract_key_spec: ALL_HEADER + extract_key: my_param2 + mapping_type: "java.lang.Double" +)EOF"; + setConfiguration(yaml_string); + setFilter(); + { + // normal request + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(1); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/mytest.service/sayHello"}, + {"my_param1", "test"}, + {"my_param2", "0.234"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + } + { + // extract_key my_param1 cannot be found in headers + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::NotFound, _, _, _, _)).Times(1); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/mytest.service/sayHello"}, + {"param", "test"}, + {"my_param2", "0.234"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request_headers.getMethodValue()); + } + { + // my_param2's mapping type is Double, but given String + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::BadRequest, _, _, _, _)).Times(1); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/mytest.service/sayHello"}, + {"my_param1", "test"}, + {"my_param2", "abc"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request_headers.getMethodValue()); + } +} + +TEST_F(TranscodeFilterTest, DefaultMatchingPathAndHttpMethod) { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: my_param1 + mapping_type: "java.lang.String" +)EOF"; + setConfiguration(yaml_string); + setFilter(); + + { + // normal request + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(1); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/common.sayHello/sayHello"}, {"my_param1", "test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + } + + { + // extract_key my_param1 cannot be found in headers + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::NotFound, _, _, _, _)).Times(1); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(0); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/common.sayHello/sayHello"}, {"param", "test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request_headers.getMethodValue()); + } +} + +TEST_F(TranscodeFilterTest, ExtractParameterKeyFromBody) { + { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_BODY + extract_key: name + mapping_type: "java.lang.String" + - extract_key_spec: ALL_HEADER + extract_key: my_param1 + mapping_type: "java.lang.String" +)EOF"; + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)).Times(0); + std::string json_string = R"EOF( + { + "age": 10, + "name" : "test" + } + )EOF"; + Buffer::OwnedImpl data(json_string); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, + {":path", "/common.sayHello/sayHello"}, + {"my_param1", "test"}, + {"Content-Length", std::to_string(data.length())}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, true)); + + std::string encoded_data(data.toString()); + EXPECT_TRUE(encoded_data.find("test") != std::string::npos); + } + + { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_BODY + mapping_type: "java.util.Map" +)EOF"; + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)).Times(0); + + // if there is not Content-Length header, filter will conly parse the first package + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/common.sayHello/sayHello"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + + std::string json_string = R"EOF( + { + "age": 10, + "name" : "test" + } + )EOF"; + Buffer::OwnedImpl data(json_string); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, true)); + + std::string encoded_data(data.toString()); + EXPECT_TRUE(encoded_data.find("age") != std::string::npos); + } +} + +TEST_F(TranscodeFilterTest, ExtractParameterKeyFromBigBody) { + { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/common.sayHello/sayHello" + match_http_method_spec: ALL_POST + parameter_mapping: + - extract_key_spec: ALL_BODY + mapping_type: "java.util.Map" + - extract_key_spec: ALL_HEADER + extract_key: my_param1 + mapping_type: "java.lang.String" +)EOF"; + + std::ifstream file(TestEnvironment::substitute( + "{{ test_rundir " + "}}/contrib/http_dubbo_transcoder/filters/http/test/test_data/big_reqeust_body")); + ASSERT_TRUE(file.fail() == false); + + std::string json_body; + std::getline(file, json_body); + int pos = json_body.length() / 2; + std::string body_part1 = json_body.substr(0, pos); + std::string body_part2 = json_body.substr(pos); + Buffer::OwnedImpl data_part1(body_part1); + Buffer::OwnedImpl data_part2(body_part2); + + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)).Times(0); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, + {":path", "/common.sayHello/sayHello"}, + {"my_param1", "test"}, + {"Content-Length", std::to_string(data_part1.length() + data_part2.length())}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + + // The first part of body will be buffed, the second part of body will be parsed along with the + // first part. + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, + filter_->decodeData(data_part1, false)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_part2, true)); + } +} + +TEST_F(TranscodeFilterTest, PassthroughSetting) { + { + const std::string yaml_string = R"EOF( + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_BODY + extract_key: name + mapping_type: "java.lang.String" + passthrough_setting: + passthrough_all_headers: true + )EOF"; + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + std::string json_string = R"EOF( + { + "age": 10, + "name" : "test" + } + )EOF"; + Buffer::OwnedImpl data(json_string); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, + {":path", "/common.sayHello/sayHello"}, + {"my_param1", "test"}, + {"Content-Length", std::to_string(data.length())}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, true)); + + std::string encoded_data(data.toString()); + EXPECT_TRUE(encoded_data.find("my_param1") != std::string::npos); + EXPECT_TRUE(encoded_data.find("sayHello") != std::string::npos); + } + + { + const std::string yaml_string = R"EOF( + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + group: "dev" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_BODY + extract_key: name + mapping_type: "java.lang.String" + passthrough_setting: + passthrough_all_headers: false + )EOF"; + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + std::string json_string = R"EOF( + { + "age": 10, + "name" : "test" + } + )EOF"; + Buffer::OwnedImpl data(json_string); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, + {":path", "/common.sayHello/sayHello"}, + {"my_param1", "test"}, + {"Content-Length", std::to_string(data.length())}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, true)); + + std::string encoded_data(data.toString()); + EXPECT_TRUE(encoded_data.find("my_param1") == std::string::npos); + EXPECT_TRUE(encoded_data.find("GET") == std::string::npos); + } +} + +TEST(DobboUtilityTest, convertStringToTypeValueTest) { + { + absl::string_view value{"0.234234"}; + std::string type{"java.lang.Double"}; + nlohmann::json result = 0.234234; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } + { + absl::string_view value{"-0.234234"}; + std::string type{"java.lang.Double"}; + nlohmann::json result = -0.234234; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } + { + absl::string_view value{"0.0"}; + std::string type{"java.lang.Double"}; + nlohmann::json result = 0; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } + + { + absl::string_view value{"837465"}; + std::string type{"java.lang.Long"}; + nlohmann::json result = 837465; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } + { + absl::string_view value{"-34534"}; + std::string type{"java.lang.Long"}; + nlohmann::json result = -34534; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } + { + absl::string_view value{"0"}; + std::string type{"java.lang.Long"}; + nlohmann::json result = 0; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } + { + absl::string_view value{"true"}; + std::string type{"java.lang.Boolean"}; + nlohmann::json result = true; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } + { + absl::string_view value{"false"}; + std::string type{"java.lang.Boolean"}; + nlohmann::json result = false; + EXPECT_EQ(result, DubboUtility::convertStringToTypeValue(value, type).value()); + } +} + +TEST(DobboUtilityTest, HessianToJsonBadCast) { + const std::string ERROR_KEY = "error"; + const std::string ERROR_VALUE_TEMP = + "The data returned by dubbo service does not comply with the hessian protocol, data type: "; + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::Binary)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::Binary)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::Boolean)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::Boolean)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::Date)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::Date)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::Double)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::Double)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::Integer)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::Integer)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::Long)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::Long)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::Ref)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::Ref)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::String)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], + ERROR_VALUE_TEMP + DubboUtility::hessianType2String(Hessian2::Object::Type::String)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::TypedList)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], ERROR_VALUE_TEMP + DubboUtility::hessianType2String( + Hessian2::Object::Type::TypedList)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::UntypedList)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], ERROR_VALUE_TEMP + DubboUtility::hessianType2String( + Hessian2::Object::Type::UntypedList)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::TypedMap)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], ERROR_VALUE_TEMP + DubboUtility::hessianType2String( + Hessian2::Object::Type::TypedMap)); + } + { + NiceMock mock_obj; + EXPECT_CALL(mock_obj, type()).WillRepeatedly(Return(Hessian2::Object::Type::UntypedMap)); + nlohmann::json error_json = DubboUtility::hessian2Json(&mock_obj); + EXPECT_EQ(error_json[ERROR_KEY], ERROR_VALUE_TEMP + DubboUtility::hessianType2String( + Hessian2::Object::Type::UntypedMap)); + } +} // namespace HttpDubboTranscoder + +TEST_F(TranscodeFilterTest, EncodeDataFromDubboServer) { + setConfiguration(); + setFilter(); + + // initialize filter_->transcoder by calling filter->decodeHeaders + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_param=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + { + // 1. Normal dubbo request message + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xda', '\xbb', 0x42, 20})); + buffer.writeBEInt(static_cast(1)); + std::string content({'I', 0x00, 0x00, 0x00, 0x01, 0x05, 'h', 'e', 'l', 'l', 'o'}); + buffer.writeBEInt(static_cast(content.size())); + buffer.add(content); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + } + + { + // 2. Protocol error + Buffer::OwnedImpl buffer; + buffer.add("Not dubbo message"); + filter_->encodeData(buffer, true); + EXPECT_EQ(buffer.toString(), "Not dubbo message"); + } + + { + // 3. The length of dubbo message is less than DUBBO_HEADER_SIZE + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xda', '\xbb', 0x42})); + filter_->encodeData(buffer, true); + EXPECT_EQ(buffer.toString(), "Dubbo message data is incomplete"); + } + + { + // 4. Cannot parse RpcResult type from buffer + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xda', '\xbb', 0x42, 20})); + buffer.writeBEInt(static_cast(1)); + std::string content({0x00, 0x00, 0x00, 0x01, 0x05, 'h', 'e', 'l', 'l', 'o'}); + buffer.writeBEInt(static_cast(content.size())); + buffer.add(content); + filter_->encodeData(buffer, true); + EXPECT_EQ(buffer.toString(), "Cannot parse RpcResult type from buffer"); + } + + { + // 5. In the Hessian protocol, if an object is empty, it is represented by the character 'N'. + // When decoding, we interpret it as null instead of the string "null". + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb02140000000000000001000000509148046e616d654e05636c617373302e636f6" + "d2e616c69626162612e6e61636f732e6578616d706c652e647562626f2e7365727669" + "63652e506572736f6e03616765900a7365636f6e644e616d654e5a")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":{\"age\":0,\"name\":null,\"secondName\":null}}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 6. The java backend returns a fastjson object, which corresponds to TypedMap in hessian + // serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream( + "dabb0214000000000000000c0000021e914d1f636f6d2e616c69626162612e666173746a736f6e2e4a534f4e4f" + "626a656374036d7367024f4b04636f6465c8c804646174614d90036d7367077375636365737304636f64659104" + "6461746172136a6176612e7574696c2e41727261794c6973744d90046d6369641e597a703763587736496a6736" + "4c6930714b69346f5231394b4f6d5525334403696d67304868747470733a2f2f646174612e30303776696e2e63" + "6f6d2f737463696d67732f696d672f343738636164373934653466623164633965373836613464303637613563" + "38322e6a70670a636f6c6f7276616c756591036e756d0131056c6162656c1954686520656e67696e652f467565" + "6c20747970652f746f6f6c096272616e64436f646506746f796f74615a4d90046d6369641e597a703763587736" + "496a67364c6930714b69346f5231394b4f6d5525334403696d67304868747470733a2f2f646174612e30303776" + "696e2e636f6d2f737463696d67732f696d672f6565666438376236326436363539316566616336303835356261" + "6232656163622e6a70670a636f6c6f7276616c756591036e756d0132056c6162656c1944726976652074726169" + "6e2f4368617373697320636c617373096272616e64436f646506746f796f74615a066c656e677468940474696d" + "651231303139383730362e3131333138323234350a71756572795f74696d6514302e3031333330303230363531" + "323231323735335a0773756363657373545a")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = + "{\"result\":{\"code\":200,\"data\":{\"code\":1,\"data\":[{\"brandCode\":\"toyota\"," + "\"colorvalue\":1,\"img\":\"https://data.007vin.com/stcimgs/img/" + "478cad794e4fb1dc9e786a4d067a5c82.jpg\",\"label\":\"The engine/Fuel " + "type/" + "tool\",\"mcid\":\"Yzp7cXw6Ijg6Li0qKi4oR19KOmU%3D\",\"num\":\"1\"},{\"brandCode\":" + "\"toyota\",\"colorvalue\":1,\"img\":\"https://data.007vin.com/stcimgs/img/" + "eefd87b62d66591efac60855bab2eacb.jpg\",\"label\":\"Drive train/Chassis " + "class\",\"mcid\":\"Yzp7cXw6Ijg6Li0qKi4oR19KOmU%3D\",\"num\":\"2\"}],\"length\":4,\"msg\":" + "\"success\",\"query_time\":\"0.013300206512212753\",\"time\":\"10198706.113182245\"}," + "\"msg\":\"OK\",\"success\":true}}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 7. The java backend returns a java.math.BigDecimal object, which corresponds to ClassInstance + // in hessian serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb02140000000000000001000000329143146a6176612e6d6174682e42696744656" + "3696d616c910576616c7565601231303139383730362e313133313832323435")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = + "{\"result\":{\"class\":\"java.math.BigDecimal\",\"value\":\"10198706.113182245\"}}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 8. The java backend returns a byte[] object, which corresponds to Binary in hessian + // serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb0214000000000000000a00000011912f010203010203010203010203010203")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":[1,2,3,1,2,3,1,2,3,1,2,3,1,2,3]}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 9. The java backend returns a java.util.Date object, which corresponds to Date in hessian + // serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream( + "dabb021400000000000000020000009b914806706572736f6e48046974656d5190046e616d654e05636c617373" + "302e636f6d2e616c69626162612e6e61636f732e6578616d706c652e647562626f2e736572766963652e506572" + "736f6e03616765900a7365636f6e644e616d654e5a05636c617373302c636f6d2e616c69626162612e6e61636f" + "732e6578616d706c652e647562626f2e736572766963652e4974656d056f726465724e5a")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = + "{\"result\":{\"order\":null,\"person\":{\"age\":0,\"item\":\"Type: Ref, target Object " + "Type: UntypedMap\",\"name\":null,\"secondName\":null}}}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 10. The java backend returns an object, which has a circular reference problem. At this time, + // a Ref object will appear in the Hessian serialization protocol + Buffer::OwnedImpl buffer; + buffer.add(readHexStream( + "dabb021400000000000000020000009b914806706572736f6e48046974656d5190046e616d654e05636c617373" + "302e636f6d2e616c69626162612e6e61636f732e6578616d706c652e647562626f2e736572766963652e506572" + "736f6e03616765900a7365636f6e644e616d654e5a05636c617373302c636f6d2e616c69626162612e6e61636f" + "732e6578616d706c652e647562626f2e736572766963652e4974656d056f726465724e5a")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = + "{\"result\":{\"order\":null,\"person\":{\"age\":0,\"item\":\"Type: Ref, target Object " + "Type: UntypedMap\",\"name\":null,\"secondName\":null}}}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 11. The java backend returns a boolean object, which corresponds to Boolean in hessian + // serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb02140000000000000002000000029146")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":false}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 12. The java backend returns a double object, which corresponds to Double in hessian + // serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb021400000000000000040000000a9144402877e90ff97247")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":12.2342}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 13. The java backend returns a int object, which corresponds to Integer in hessian + // serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb021400000000000000060000000491d5e162")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":123234}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 14. The java backend returns a long object, which corresponds to Long in hessian + // serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb02140000000000000008000000069159117e0c07")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":293473287}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 15. The java backend returns a java.lang.String object, which corresponds to String in + // hessian serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb0214000000000000000a00000021911f6162636465736173636e756b736e63697" + "57366686175686461657569646861")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":\"abcdesascnuksnciusfhauhdaeuidha\"}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 16. The java backend returns a ArrayList, which corresponds to TypedList in + // hessian serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb02140000000000000002000000229173136a6176612e7574696c2e41727261794" + "c697374036162630362636403636465")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":[\"abc\",\"bcd\",\"cde\"]}"; + EXPECT_EQ(buffer.toString(), expected_response); + } + + { + // 17. The java backend returns a Map, which corresponds to UntypedMap in + // hessian serialization protocol. + Buffer::OwnedImpl buffer; + buffer.add(readHexStream("dabb021400000000000000020000001c91480373696649000e51d4036a6e6749000f0" + "32703616263d5e1625a")); + filter_->encodeData(buffer, true); + EXPECT_EQ(filter_->encodeData(buffer, true), Http::FilterDataStatus::Continue); + std::string expected_response = "{\"result\":{\"abc\":123234,\"jng\":983847,\"sif\":938452}}"; + EXPECT_EQ(buffer.toString(), expected_response); + } +} + +TEST_F(TranscodeFilterTest, ServiceVersionAndGroup) { + { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" +)EOF"; + + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(1); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_param=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + } + { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" +)EOF"; + + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(1); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_param=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + } + { + const std::string yaml_string = R"EOF( +url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED +request_validation_options: + reject_unknown_query_parameters: false + reject_unknown_method: false +services_mapping: +- name: "common.sayHello" + group: "dev" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" +)EOF"; + + setConfiguration(yaml_string); + setFilter(); + + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, true)).Times(1); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/mytest.service/sayHello?my_param=test"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::Headers::get().MethodValues.Connect, request_headers.getMethodValue()); + } +} + +} // namespace HttpDubboTranscoder +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/http_dubbo_transcoder/filters/http/test/test_data/BUILD b/contrib/http_dubbo_transcoder/filters/http/test/test_data/BUILD new file mode 100644 index 0000000000000..0602b99b93221 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/test_data/BUILD @@ -0,0 +1,13 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +filegroup( + name = "http2dubbo_test_data", + srcs = glob(["big_reqeust_body"]), +) diff --git a/contrib/http_dubbo_transcoder/filters/http/test/test_data/big_reqeust_body b/contrib/http_dubbo_transcoder/filters/http/test/test_data/big_reqeust_body new file mode 100644 index 0000000000000..edd9710a32ca0 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/test_data/big_reqeust_body @@ -0,0 +1 @@ +{"gebfjh": ["Z8Phit1r1yqTYRu_N",false,-1874609963.6780415,{"wvhipdzn": {"fxulpeqbbv": "R0"},"uacfnggi": "s","tjgrp": {"kzufxpapsi": {"hvmeworf": {"kvcjigtpkq": "jHdOx1UubMiElwn","zkpxlen": [true,-1696836778,674138528,1814293593,"f8RZR9VPtqls1Mee8Cy7",-1777251289],"xppzpkag": "Csb3EJJguhwcHxylCi0"},"bxrvsqdbmab": 1546418242,"oaiwuyxg": ["kSC8"],"ccmposyf": -1966816994,"cwqsjpdmsyo": [{"hztrgiesabn": true,"dupigo": false,"gzteg": "QMiN8fWzukSbNE","hgpomys": false,"rbsij": false,"rvzllj": false,"bzdvycmcjmp": -789627763},-17410347,[-1839055094,true,-1698623779,true,-353931240.3622999,true,544112146.7967532,431864659.3216296],424546576.86917436],"apeouad": false,"tfctvrnpa": "J1C3cemW19vIljmfp9UL","ipxejcteqv": [[false,true,-743430571.3896658]]},"eabslckqne": false,"dozmicm": {"szjtejfwzzz": true}},"lwopbg": true,"zfpafmvycht": "K6Zuhfh1cz"},{"njfwjqqjv": [true,false,{"zwxjbj": true,"ipvedxwxvn": false,"mnyhzfl": {"zixonhitu": true,"akyehtmcivuw": -1164644185.000639,"paonvucc": 1399763731.031868,"vhpjirdne": true,"egjmsbm": false,"scgoz": [true,2130356151.8818755,"yiFxdfBEc0p7i02rPGe",-286610574.46345943,-256386296.7373656,261835722,-1122995147,"9",false],"jvfysgqoecb": 353170534.65448564},"plyqgqi": "fCdz0hIvLm2T"},"bgcwZ1YRN",-2045418785.3451211,"Df6QFTnQLtXbBafM"],"yxeiooeceq": [true,1520799628.8768916,[-233199273.01942745,[[false]],false,{"zhbgqn": [false,"Bqw",true,true,"_4CU_pcpu","m-UZZHqyCuR","FfylJS",876309508.4970961],"oxvwtlidr": true,"jggaue": [2104138652.6818604,-1838286814],"thmtnvai": [-915348228],"mkneelldacpe": 361113991,"omgfp": [true,false,452865285.04284465,"rJ1WL7tGjtXTzYOj","0Xz",-458065016,"8AvOlHR"]},{"gyexywddinvw": false,"jvfbqnq": [false,"DQVlySmgTdR43B_","s_2",1654005717.7170205,false,-1514754868.9750252],"ikpxldqpy": false,"xsqbp": ["tPynSvy",-1001103628,true,-515241904.0306626,"4gQxpB563S3vh","xo33FTe1f2ZKb",true,true],"kkbzvul": false,"feeifcfwnc": {"vxitzzasgsx": false,"xbhzmyt": true}},"1Ifr"],"Nx","Sdb9lQ",false],"xjbmaxfj": 1546702803.505219,"gvfazuymxaj": [true,{"uopixgpehkmn": [[-1706501649.3347661,939444184,-489900868,"ty9Vh-VzLeJXA",814859737,false,1433198291.4491618,"5xxeodB9"],true,{"cnkaknt": 936088172.6826627,"zsfawghvq": false},"wYe",true],"eiwthpkjawn": [true,-1303434176,["OjQpdi",true,true,-1003624606.982674,-1828129590.757288,true,968197052],false,{"opxdfkjedbe": false,"jxrbmskmsh": "u1","kjplsndmcg": "2yE","tmpir": false,"zajjchtqeu": -1024544796,"nqavzii": "o_r9tq","blkhqcncu": "C_zLGhAP31mXNCx3SLh","cmwwiqixbwj": "z7BEnIRFs"},[-1761202743.514457,605963876.5721357,false,1142071654,-29341830.63987344,327729485.0430406,"a2ijBnlWa4cz",-1849564593,"W7KkH-KSTHK"]],"ymippdwakyb": "UbaOZ","pmhcv": false,"vdozsn": "ICp2p-FFPPqU_8E","njsbad": [[false,-231830047,"mQr-TSDVGvGB9KA2dvca",false,"N80rfENLbORWO",1448153667,-874578340.9833133,"QANtPJC"],-2036906953,{"dtrpcmwssww": false},true,-1771872323.877133,530887849],"fobvrcvwayuf": -174080491.97444606},["XQ",[{"avukqwnhl": false,"hqlpucgopn": -2046507753}],"Ku2gLNBW9cARY","Vus",[false,{"rcrjnmim": true,"rlggpl": "ZqkExAQ_W"},[61185980],"o4wvk3OZ__T",-1530084203.818758,true,"UOIuU",["o8-g9u3L4toOHX",false,"VXBi-5B46","Z87dyr6qU6w8C"]],"5oC81qY0W",["ccpuyX2FAInsBS1",["fnSSElF70rs3fwl3Eb",1038842378.3845956],{"fyotmznlhyiz": true,"ntdondmmmo": true,"jhqhiybx": "N5MgM-dGEduRDl-","rqlghpjap": "Dqras4qcX","ssgrwctxy": -1648400789,"fhtof": "4DQRw3FZI"},[false,false],[-1703717935.6076155,"XqAra","wblCqBkIR",822744070.6647763,"sHKkjFXFoB6sM0G6QO0","yBrubVz9J6Dt48mE",544621099.5012496,"iTCS0jRNT2i"]],2034789463],{"gqblenask": 602793888,"fshoncj": {"zyzklqrtffj": "zk4zok"},"vgelpbu": {"wachgrgx": "aTXzKVFk","ctbihuaplcsn": ["I_8W6Ih7fw0sSyzM3H"],"znepguyij": false},"alqvpkc": {"abqegvzvqurx": "e"}},{"pliniyj": true}]},{"jdkchmkmrbe": [{"zskwnuhvr": [1172496051.556019,true,1742687675.7930105],"hbwvvuseguh": true,"foduzezw": [{"qikdoghvf": false,"nlzfovx": "P7cNonFX8VyoDprf","sqzrqmgko": false,"yqeqau": -428385274.4653239,"rghonok": 491882583.1680483,"uwsqn": false,"vohxzzha": false,"csjlhyi": true},"yLxlayVsEG",false],"kdgrjkvwumfb": false,"dhyqulmztc": {"mrnvqvilx": -1338477178.0823896,"cdrrkooukuxd": [-169312265,"d09-sKvdr5ZeEwuR",true,"IDjf0nU_fGyDQxy",false,false],"ywlhqqzj": "W_rtl5oD2YRF_"}}],"zorbzvsti": "cm3pQpkkC2r33QY","fzfgqsst": 1271514452,"muepqivgbsxs": {"lkmljsbfqtam": false,"xqzzcknbu": [["cUW5MRzyZIEwCIZD",1831885823,"_CVv",true,{"muuirzviabdj": -164761922,"gnzxnjun": true,"jdelzbjjkwal": true,"iwqmkgzv": false,"vgrxzrdq": "c6_6Mr_GtfM4np-tSY7","faispvnzao": false},[true],false],[{"pbtkbkd": 605858835.278347,"clmxrmbzze": -631980698.7533013,"ohohrmqbe": false,"cmbsotwslb": "pr9w-j","mibjubgi": true,"vdsuvqtp": "1E0GUtEX6psfEInuhR","ikejbasdq": false,"uzmlwppov": "OM6o"},[626892233,false],false,"UXoAUo1idUy"],true,["OFduNJsu1ihNs"],569447293.7961918,[{"unrnvbmtrps": true,"fwvvpwtuy": 1300863561,"bufnkhbmuf": 1178293198.8882728,"wnptntgxf": 727491964.5889429,"ypnldkomd": true,"vypbfgoridnk": 1102836917.2110972,"tldjuhiw": -914671684,"xcasfkmo": "Z-YghQCeLOPEJPX_ZcWD","dsyltunqbxue": -230049304},"tMdMqKVJ8",{"xbeguteh": "c2Ky6U2-","iiqnrtptdcz": -590935560,"fkkkm": "5fGuYXA"},"SQd8Eu28R",true],false],"wkdsubyzoxf": "ZE_Y8","steedeb": [-1232996748,{"xcuqlvi": false,"pfjhpnr": false,"bsfusjksuqzr": {"mhcmtaygvhz": true},"jvicfuyea": 543821155.3341044,"uygbi": {"rkfauvom": -1026747765},"bsslzomqn": 551981527},-505453035.0982449,[false,746648276],{"gywjxgqwddf": -1236177377,"ozzylbonhgh": "Cds","couxvxncrk": "9yK3ToQGFCxT0l3ZrZ","obberiofmzg": ["Ic7MeULG","1PVjV4hLvk3_aVl1yZ","3XxiGZWfhq6o",false,-842072922,false],"wukfgwj": -215418126,"wqvjzhaffdl": [false,false,-2009241616.1072602],"pzmtkzpup": {"lmoatm": false,"wefocf": "uNu7WTfcpEaVnuc","kzaydd": -1576476013.4600675,"pdywza": "ZOwcHoSAUrJ-","rhtzamc": true},"psyuf": {"xobzn": 1563974237,"mfjtkl": 1842290314.8467672,"jedaaayp": 1467490916,"slddpl": true,"siqbojitbe": false},"nmrubqyiwot": {"mpmnmpj": -1051343423,"ciisxfjsqh": -1886929142}},1233314403],"mmyxuqbe": false,"pxprxlt": "5rdkAyBjFBKE9sH","ohxurytrc": [950184048.3243737,true,true],"gviolfvkuh": false},"tmbaooe": -2131828352,"oopjxvrnsd": -1078841971.8488972,"oaricvubn": 558318781,"nvjrtinlo": -1508415360.153841},584420524,[1024497481.9758645],"20McfuEaA"],"wxauhh": true,"coiitbxtotu": {"zcccjsb": "V_JN2"},"xqrpwjdvz": {"ulsrelib": true,"ukkvypsrh": [false,"sOjyjAITLdloeQaQahKn","wWhHRL_aie",false,true]}} \ No newline at end of file diff --git a/contrib/http_dubbo_transcoder/filters/http/test/test_data/chunked-request-example.py b/contrib/http_dubbo_transcoder/filters/http/test/test_data/chunked-request-example.py new file mode 100644 index 0000000000000..6dcfb42a7f39b --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/test_data/chunked-request-example.py @@ -0,0 +1,29 @@ +import http.client +import time + +chunk1 = b"{\"param\":\"test\",\"tlpaswrkzvgm\":[\"rsI7oOynRBvhkiPt11\",[true,\"cAWo8\",\"aK_X\",[-1685443876.064494,[1031696055,\"PuuLC_ESZk2u\",{\"ybktpto\":-1510012696.718328},[-1679072553.2494242],{\"zdbuuwg\":\"9_\",\"cfphfbcg\":[-2079751769.952206],\"icwjp\":-1688792506,\"nmhwftax\":1357267626.9394467,\"eiprar\":\"rjULnyiItGzhh\"}]]],[true,\"YE6M5m887BhofWvOpHHP\",{\"hjuwr\":\"FXDdd17Oe\",\"gtjavuo\":{\"uiulkhvhllz\":\"h4Jo4pOuW5ok\",\"cugcakaoaapm\":{\"beyhzerxqf\":\"wgs10O7jU\",\"lhyyxycp\":false,\"fzvtrexqvd\":[true],\"ltbupeknzp\":1851678186.0895836,\"ukumrddrxlrq\":-1670169632.0619402,\"neryh\":\"yGSLwOVLRDooMmI\"},\"dvjsviet\":{\"kxqgayxkp\":{\"rjzchszdhogr\":\"WNaFYtLfd\",\"bgzvzfzbve\":[-1168320228,\"idFrYqWCBZxX\",\"EUTnN2i3ludtQSmgEGjl\",{\"odrilcyexn\":[\"21n\",\"QGB\",-1556388375.728976,\"KHkcFTLe\",false,false],\"hmpudwjzkzhd\":false,\"ffniyq\":-2104854252,\"dhisssctcfh\":[\"Qmu5x\",true,-1273493308.7666578,272628328.31476897,\"ztRmfZyuivMlp\"],\"olpwgjd\":-1213943612.067658,\"cvjbummpm\":[\"EUJowcktOHKDEy0WCc\"]},[[-908379258,\"L_rGg\",2022908648.1843808,\"xR\"],[true,true,\"f9zRJua5c\",true]],[-799096090,[720032595.4601699,false,100216509,1970143871.9869635,\"INpSO\",true,748973667.5967033]],[[-923670980.294544,false,-157019469,996746363.0668201,false,\"eyXjEUsBok76KpqpRM\",\"vpnpXcPN1Hm5JUB54M\"],{\"gzhesnaop\":1841359376},192050598,true,\"jAYyARc00RxWM\",{\"sqsyjxf\":false,\"jaehy\":false,\"daxjwezfhr\":\"d6WOFR-tJ\",\"qggjqmuvar\":true,\"mepulhdyinhh\":912111278,\"jvrtqsd\":true,\"yehvfmvk\":true},{\"ouroptnblhr\":1829684558,\"bxxlir\":-192744298,\"xzyvw\":\"aaftqPUdCBBZdf4jyr\",\"anfjranbmcql\":2046215020,\"knavzevkvha\":-1983746717,\"ywtukjrbkpa\":true,\"lbiyrhs\":-1179615220,\"sziktxmnv\":true}],false]},\"rtqdrrofur\":\"dWzx661\",\"yybzvuuohfw\":[187000069.02621114,[true,{\"dxwrfys\":1537602484.97164,\"cnyffimp\":true,\"ryrppvc\":-760808681.5212592,\"kmmdvj\":{\"bihmqd\":-836543857,\"ighgxlkgt\":\"vd7MbAYxiOCJo\",\"ifyexprnnh\":false,\"dibktvvf\":false,\"wwdysx\":\"RuHzw86e3\",\"zkwzxpsr\":true,\"wligwko\":false,\"jitpyieszk\":\"c\"}},[true,\"sQCfmAbu_\",[\"79cXBEgKYanbaEjs\"],true,2005191304.288275,\"JiJL_D4IaaQLNI\"]],624610701,{\"oyzcizqaqzym\":\"0My8zHCKDLhznn_MB\"}],\"ptmayz\":false,\"mwmmii\":-1755213072.4266756,\"usxqkds\":{\"jsgulqk\":\"vIjrnx\",\"bqwlzb\":\"co1z4YrN\",\"gfojrngugz\":true,\"amgfziuff\":1766061744.0741117,\"xuwkwy\":{\"isqztcbzr\":false,\"uqrljseni\":-1726745541,\"wifsh\":\"1pJpW66\",\"ckdeb\":{\"jabrrlb\":-1813679449.6838596,\"jfuxo\":\"upwReuLosd80\",\"kuipeib\":1721794897,\"fagfn\":true,\"deooho\":false,\"eazyf\":\"EYbwt\"}}},\"jqzihruydait\":{\"vimirogwsi\":1662030745.347155,\"idujn\":\"VN4e\",\"theqn\":\"dGb5YZbSY7pdV\",\"ivogkkrgo\":true}},\"gjggrk\":{\"nbzospfbcwp\":{\"athlyitixa\":-1267581530.136882,\"mmbvosqkxj\":-2093243909.7416456},\"qbescjjvtfu\":\"GBTYqAOY0\",\"pslmni\":true,\"wqzjad\":\"p5QFJ7Jv3mpGrNx7BT\",\"fgysagdcs\":{\"bzmcwo\":\"-8GTaE\",\"brsuxniyi\":\"i3ecX2A6BF\",\"ygcycbxl\":-1554910535.2226431,\"eagtct\":[{\"wrqsqyibh\":true,\"fgdrdwjxoahw\":1291910224,\"seokc\":\"1_STl__0_0_sdE-SMam\",\"ksgthytmbegp\":\"n8vaRkRzW6MryZy\",\"wpkoop\":-1115107820},{\"igufssmnpcea\":true,\"hxdocxp\":false,\"cqznysanw\":{\"ixsejrj\":466550864.29654735,\"gdryaq\":false,\"hpsltuhfxj\":\"plhh5B9Cf8xJ0aM2YU\",\"qxzwffn\":\"zHfstoA-2lHEUWCQXWWc\"},\"jnygzkvdffoi\":-909433723},[\"xNbZiOGrQ5GnlRaR\",{\"iiwsxtdafrgd\":\"dqGJyBYdgTStbG-gB\",\"uovomzosrg\":-877532993,\"vcqwavpqn\":-1421890683.4836087,\"igkzlcddhgkw\":true},false],[[318106516,true],[false,1497313498.498252,-1369082579,false,1861263920,\"pjV\",\"cm\"],\"m1OFWOfE9b0\",true,[\"23ZHpaxwb\",true]],\"eCk1h3Rb\",true,{\"zorgpgpynb\":{\"qrbnwc\":\"ZD8e3I-xOcRrLQj6Jrh\",\"imxeezsqch\":false,\"vsyow\":false,\"zzzkbgc\":-1610622705},\"xejmsji\":\"o2\",\"kkfpu\":[true,\"I_gYUlg\",\"_IsrGcLfqHg9Qn\",false],\"hfqqnok\":\"SNGYydcG\",\"fzmsthisxi\":[true,1847482778.5109255,-1762011745,\"tdaiM3\"]}],\"bmvazmnu\":{\"iyzvdp\":true,\"muzoblyhys\":-415483247}},\"nwdxjvaflfn\":\"tFPHRGA63mI\",\"zhxaoemx\":708746492},\"okzuzlrhhf\":{\"wxbecirumd\":{\"oybxzzzj\":[\"iK_TrODTNRfOQ\",1648979465,183734153,[\"WVclxdn3clPXf7JgB\",\"dhVDo6c8O5y\"],[{\"czsjviyxyksc\":false,\"lvbrqxowg\":67459806,\"ynuid\":\"Y93z\",\"hektkw\":\"jp_k\",\"pxxjmx\":false},[-1168147785.4213145,-982798625],\"rPlcUha0n2LRp4mpzA\",{\"lbsjh\":\"zb8DdKeBI4\",\"gibyyqzqg\":\"M4WAhTq2WzbsGfI\",\"zrfbjic\":false,\"batjvatqtvdh\":true,\"enirstzcseo\":true,\"jepxjvjfm\":true,\"xnsfcrmc\":\"_\",\"psnpnzbxkfra\":\"N4msAA1OH\"},\"prfaNeg\",[\"_TJLFjzr4K1\",\"0Erc4KC\",true,true]],[-745237090,\"I-rUDcJ_pf\",false,\"JvnUaCcv6D\",true],[[true,\"KMcK4z\",23150055,true,true,false,1687782815.2155254],121741271,true,\"QI6EQm4g-w6ZN22Xz\",-808817753,[\"G9sjiX-SYphVu_9XOyX\",1571529608.7637177,\"dTMDoKztg\",687840877.5395725,\"BGMSNOsQy5N8ld7D\",1337427356.3401787],329813779.3271028,\"stNDpdcBMyQC59ccwo\"]],\"fakbp\":\"RoJMvBoKK5vpMnA\",\"fvnqwdedh\":true,\"abpjsouy\":-221184750.69589102,\"huktaqlsutj\":false},\"gkzjej\":false,\"zgegw\":-2005866738,\"bwfapq\":true,\"aiwrsng\":-189537935.3466422,\"jukwftrhnw\":{\"cekwtdg\":{\"nfowxio\":{\"kmtesjwrinwz\":{\"hdvjyzvlcod\":\"A4I9MdkwI-h\",\"rabainn\":false,\"aitepbgojj\":\"q3\",\"nickmhmww\":\"Oiiq5SjiEHOIiG64_F2s\",\"guigrte\":\"Mu17wt0WgOWd5OjwiO-\",\"qjfixapz\":\"PDbQxLFJRl5S_jqTVrPO\",\"bgrjldd\":2057393354.0354586,\"jxemz\":false},\"ejoxvsrc\":\"77VQu_XB0y1Bu\",\"nnuedkrq\":[false,\"Ab5GoZ33Z\"],\"bmtsax\":1718766108,\"pokbe\":835367233.1427072,\"domeesvldc\":false,\"pwerxjife\":\"4IKOuHPGlmDS-y-effa\"},\"huobzlzwwse\":2048078234.2850854,\"gqkmnvamo\":161399816.38731146},\"hopvqyfm\":60213304.99912259,\"idsdkwxb\":-774387530,\"hqlmrbmlpp\":\"c_p\",\"wuzygopzhro\":[582817217,{\"yoysepzkm\":false,\"ctriyqlsbvh\":true,\"jbjzhxpdloyf\":[\"Wp5ugL2oDIZ\",true,\"e2EWg\",true,1434051980.2693498,\"ouVF\"]}],\"ufsmgn\":-2049811560.7421832},\"gygatsoi\":{\"bgmgq\":-2127486336},\"ehhuag\":[\"7malcdnDEYU0Ln9kv\",\"2QL9t0Qmc_CRariIJ0k\",true,[\"BRFSDX\",{\"wlcxd\":false,\"sqceehvjp\":\"U\",\"rshbpfg\":[true,-784180017,true,true,1871379228,-1285025510,true,-681491791],\"ttlyyyhsfn\":\"tZ\",\"mzuijt\":-925643619,\"isnyjz\":\"IVDCnyIKLRGfN3v_\",\"tydgp\":{\"grakfhmn\":false,\"kueadszlif\":943484363,\"ruhdsxiyp\":333685551.2431532,\"zenutbvbmj\":96558887.56017886},\"uzyrbgmrl\":false},-1403830414,[-1210327434,1994805024,-387769757.5286892,\"tjt8bV\",\"c\",\"iw3nVjpk17i\",\"Msl_ldBaWIujkMk5K\",\"p8svC-MsVd\"],[\"mg3rwmcdoeFiJY\"],{\"jehtq\":[false,\"nZc-NPjhRjedqSp8M\",true,true,\"Qe\"]},false,false]]}},\"zpwfjgvk\":[[\"uag\",{\"xriafgq\":{\"xkclafbjohmb\":\"AlQsBztIqZxs5me939\",\"azzmra\":577893740.7126344},\"oqgikxqjj\":203132016.92817968,\"fjbjhuasipw\":[1785487893,true,[[\"NdmINg6FwMiXq9K\",-1779768963,true,\"6RwIFKbOAV3OYj2\",-906608004.0128973,\"qX6MS_tndlTGiNhsMop\"],-567342992.0620136,-1763137524,-458691323.8858577,{\"vxksklo\":true,\"pgssczmzt\":\"s11uOWC7J0iOIwjv0p\",\"zeqgngulm\":-267340388},\"RlabK2KkOyVPk06qHnX\"],{\"tchmlz\":false,\"abhnmhtlc\":true,\"ahnrszqnzwr\":\"g_2\",\"sottoyjrnkkh\":[87203253,true,false,\"QB3ZzjSltU7fDKDI_P\"],\"ivzgfyca\":true,\"wfcjhhsqwma\":\"K1far-NBKI5k4OHHd\",\"btlyzr\":\"Blo5to2oYSc1Zmspj\"}],\"dpksax\":[1156344543.8139546,true],\"gyexozn\":82808776,\"hibfmxepcs\":1029563097,\"thfpmdgmqwjp\":[759366455.3587943],\"wrtnu\":false}],[{\"faufpzuj\":-1522858185,\"fjsbbwvrvvx\":\"h\",\"zqypsdtmhm\":504162994,\"kfejylyyct\":{\"bqlxbqfsgeoe\":-1642068642,\"ecfhjp\":-1236243189,\"nxmpwlmfi\":{\"lbhkdkqtwl\":248921904.57794866,\"ltrjjkmway\":false,\"xvirplo\":\"U5VrnZr6RP\",\"xcoizabhhol\":{\"vdtpda\":\"woJ-z61DN\",\"pntkaxytt\":\"HpaBohjKBe\",\"uhmowxswum\":\"zd4F47PXbDB\",\"rniudegqosey\":true,\"xvjkuejvib\":false,\"oiuzbqwkv\":false},\"vknsymmo\":[\"Zdahz6yr\",true,74362186.83334717],\"ohwkh\":{\"irjocj\":\"hF\",\"zmqhzgd\":\"Cu176PVZxN\",\"ciuhtpbvw\":\"VLiZq1MWLXtvLXLA4\",\"oalzgwwolffq\":\"nmrIRrcY\"},\"aomcngbsbdsx\":[-1899510575.969307,114038203,false,\"o9Y6QcowaX\",\"F6NLT5nvJVVsg6pvfnmO\"],\"wpglznhzegmr\":\"DW1vG6ArKHbiylrOPXpu\"},\"zbdnnx\":\"aYFRfz\",\"hcqgkxcau\":[{\"xzrtaouao\":\"34oIZrtHKnV9f\"},false,\"iO2sHqz1Wr6euzvK34\"]}},[\"H\",\"W8lQTEn64SLRKsd\",-2130192407.7993803]]],\"zgrvo\":\"oi5Qwi\",\"sossdrrgzy\":\"DwZs\",\"lyvrpzdn\":1825408198.2253885},{\"fbwda\":[[[false,[1807648765.671054,{\"xqnejig\":\"IfoHNEapoCFHJZ4IVIv\",\"bkqrpicq\":[\"qc1fVYsgcMl0PJqwpFA\"],\"motuzjhrtdal\":[\"BwblvpG6DsN\",\"8Srp7McMez4cwgTIkA_\",\"9l-Rt7PtaCU01\",966460870,false,true,\"K\",\"H-\"],\"fcratmu\":\"9sT6\",\"panub\":{\"qhxctgvqfn\":-26912422.010112446,\"trnxqmkckyaq\":366687956,\"altuqzmpq\":false,\"hxvavzna\":true},\"meqluacyev\":false,\"okmuq\":[\"GPMAUPStU9D_\",true,true,1669426286,\"sfo2ZhH0N\",699561731.1627408,-2040980360],\"wfbtlkzsa\":631998360},-1118162568,true,{\"uldhho\":\"s9o0WEQ\",\"ldofugypxrj\":{\"kfsovruccf\":\"LD\",\"vpufs\":true,\"rfzscm\":false,\"etweo\":true,\"gjdedpbfsa\":\"8Q2HPl5lCf\"},\"luoyyedr\":1397270583},[-865286105.2934418,\"DA6I6byU9L_5AfG07E\",{\"srnig\":false,\"zangsk\":\"dRPn3qgn6sFV1atgmNO\",\"thcepu\":-476645457,\"fdxtfnp\":\"YooBK\"},[false,-1946205335],[true,1448523487.3499863],[true,false,true,true],[\"j0-6zQ2P37\",\"6sjBkaYRG\"],\"jp6tS2jbIrHkAUl\"]],true,[-1701858352.1347232,\"xM3UnxJm2-yCObMA3\",{\"adxxenikru\":-1286147304,\"buucswocv\":-177675563,\"eymyqo\":[true,2018301964.1650534,-1686448419.1847203,2094536350.4671328,-675246410,\"3WIuEJIyf2d0psq7H5\"],\"biora\":{\"kpewoyyz\":\"GDdZXF6iR96cwH39gKy\",\"qofutijk\":-1878366447,\"jhalnnju\":\"snSxLK\",\"hsmlyrdc\":\"_5afZ-1NP1K\",\"zwenajkm\":false,\"qnhnpjfyue\":1087059436.537689,\"wewscmwkrbo\":-2142357108.939378,\"bxqnsihqy\":true},\"bryjkrfuggp\":{\"pvohbl\":true,\"tajdlbio\":false,\"mndrtzvwp\":false,\"drsdzgd\":\"m3pWju1qb_EZlaYXEvQ3\",\"ixehhjyeokbl\":\"zcGDq\",\"zkqnknkeuq\":-1451071233.1490717,\"ngxmuoxm\":\"gSoK7ICg7luvVHS5\"},\"qvblzdxfm\":-268268778.19301927,\"qkpgzknc\":[1198211243.831933,false,2102547706.821031,true],\"cwmrcpsebp\":true},[{\"ouvskyv\":false},\"A6i\",[-489875261],{\"tpiuweb\":\"PV8NR16Z8Az\",\"rpvam\":\"S\",\"hktymnrmm\":\"bo7N2g86VJfl\"}]]],1906878989],299230789.0154969,false],\"simnpxwlbjfr\":true,\"wyjohbc\":[{\"wlukpd\":[[{\"kfyun\":536272736.5400892,\"lvncfh\":[true,false,\"tIpNd0w0wah\",1448949405.8210304,1835967976.7601235,\"z18ALI\",-712225784.0911126,\"cQ6waeQhzSq_k8mJb\"],\"iviobgvnjlf\":[\"0xMfN1nlXr8JiIMCjlkV\",-1200249147.3486779,\"i\",true,-1937612296.8718336,-1927599802,-1351221321.9912443],\"zwvzvpyaeu\":\"IWlC0xcFgYN4C1MUMvJ\",\"ytknvgztl\":\"aKZ8m6GSJutgP\",\"oamaka\":{\"ndiccsfdtyn\":\"a-0n\"},\"masksxruig\":\"uWR_icvgP\"},{\"sigzyljvta\":[-1113692454.9914238,1672870640.9865139],\"nymyvxrvphl\":false,\"qzujdbufocve\":336675635.16809475,\"sivev\":{\"ogabdkgw\":true,\"owobjkbzsvs\":false,\"xyezizr\":-1460084665,\"ipbigyuugd\":\"wPvzd2x0o_nO1E\"},\"uhcaiyojn\":1907500051,\"prpexm\":false},{\"ehrrcvgyglyc\":true,\"mmsxgd\":true,\"sxbygqkhmb\":false,\"vdaaycbtar\":{\"jiqxnqs\":368511777.20204127,\"evbcnblhouvb\":\"2VD8d5cK8\",\"azimjvqbjaol\":-1984491908,\"iyxlkjqxps\":true,\"qyechwgdvnd\":-1345431526,\"unszaovxycv\":1321045039,\"vnaqgmpdgq\":1877157631.0116699},\"vvzjdzgveobi\":[583274524.0704403,true,479080256.24483305,498955504,-1955693129.7320988],\"cyekxkocg\":-490765520.3874687,\"xrwgv\":[\"zs1Od\",\"Js-3at-2\",false],\"rugiui\":-950096377.2833874},false],false,{\"xlfqpvl\":{\"bwfihm\":1738202017.6295872,\"wjevgnbmrnw\":false,\"wlrkmfzua\":[true]},\"ruqfar\":{\"jcvna\":{\"hbeeqfhuy\":\"zqB9T4KACdB7q4o\",\"mxzqscuu\":1522991013.577457,\"cbsckeeq\":-10427576.462020312,\"ztnimshd\":\"Pp\",\"ibnjus\":-526278732,\"vanfp\":1221731288.0879946,\"sfohodyhmcgv\":\"nyesrSBx\"},\"bunolufrwl\":\"8wfrpv0Baeo\",\"mwrat\":\"Vd2yYV7x0TRXhODQ_MA4\",\"ikktatuk\":[true,\"HFxCN3zdRCNKaneTbeZC\",\"aiA1gfZCgZCw\",-2053939233.6144342,\"KUlt6f5xj7qZ3Qml59R\"],\"kkraemfqvcn\":false,\"spvoj\":{\"wzbnsy\":\"r9MoQjLA5t7szRxwga\",\"wtrpvqwvn\":\"jediJNw\",\"vfitrpeg\":-459909905,\"jbqczhad\":1495436885,\"vbbhpdgw\":36094748.94275151,\"mxrheslyxeex\":445545456},\"tgvexthdtz\":{\"ydgsrfusyovs\":\"ltX41yB55EwYVwDg\",\"niwiygfxhl\":\"82dNa\",\"hsjhl\":false,\"wcxtywcu\":false,\"vzkuiab\":true}}},{\"jtwkzyarij\":1496691476,\"yksrpbcbnj\":false,\"bqeragsjphx\":[95188595.1232621,\"YSdmwL\"]},false,\"OylJ0xP\",\"G-fWsJNa6QM4mxj\"],\"auujl\":99127409.17961067,\"qtiynchcorc\":true,\"fcddala\":[{\"pwfnhjosfmp\":[{\"uljgo\":false}],\"wsbom\":[1062550799.0776719,true,\"eQim\",1157749646.5235963,false,-2123346961.0351987],\"elnaottkj\":1799699972}],\"zxbxemjwja\":-1471998211.3909867},{\"hketqvf\":\"iBi8eOwbKR2ajDDV\",\"ddrapyikqg\":[-1639538327],\"hfelmixrjipn\":{\"zvkztirlkmwj\":false,\"aydukboz\":570067247.9517177,\"rkfrjavfqv\":\"rU3TlJnJZWFRO\",\"dihjeefydxm\":true,\"yvxmmnoohzpp\":[[[1883323111.7451074,true,-822361277.9737557,-1405487988.5254102,false],2047162684,\"QVmSuO9q\",-457932130,true,415771710.41272783,[-2137015453,\"3m\",-823292919.1552734]],-1854275486],\"mhnyxqyar\":[{\"zjrbxneshozi\":false,\"osdoqx\":[\"Gg0TdsxSQn\",-220276001,\"sAFf8ao\",false,\"KqLUSimR\",true,\"2Ku9QmyI5fljv\",true],\"yqbui\":-1764786078,\"akfpjpaes\":[\"rrNxHHl7JIRsM5\",\"V6iYPq9kwi_BWGvRhy\",\"uSFh4FnQNPIic\",\"imRnPd8fpuALJEtou\",\"OmDKZxc3J2hS3MfsnrF\",1289256530],\"nrzydeysuoqf\":false,\"adcbpd\":712175397.1796571,\"enpfqbererf\":[174121810.27050313,\"xZDg0YMk7Ss03SXJFpmk\",\"Sg9cvbzzrvl\",\"nUEct_ABlGSDg-iJ\",\"1ZO6oNvMM6aiMMdiM\",-432238628],\"zfaqmf\":\"CgpEqgYub1D\"},true,false],\"vnmqlu\":{\"anvqyssvypfo\":false,\"mcxuc\":\"KDRJ0tBOTJVo1w2cZW\",\"bpmiesz\":-1833252893.3851323,\"xzanuduthzzg\":[\"J_EMsfaJp__sy8Jg\"],\"ikfvystuse\":\"fJp\",\"dpkkvyjen\":{\"nopsn\":{\"gojtntmchh\":\"b\",\"vgkfvpd\":\"XTVOHdIlklCiZPhP\",\"eagdhoqoxwt\":\"5bmQn7th3bXLIc-Kqf\"},\"vgpwe\":false,\"ggdalkuucb\":false,\"bykltxjv\":{\"bbmfoblcawd\":\"xOUr0zrG1lVtPXypc\",\"xldvfexxphi\":-1906119263.2094867,\"yknbgpjex\":false,\"iencjxmhhp\":\"sSg\",\"zbaduvgynfnt\":\"fRIMkJ\",\"sgeldxs\":false},\"pchuzz\":{\"hnbprm\":true,\"wlprpe\":false,\"pzsfmpjbdsxe\":false,\"pgecamn\":true,\"jpaqk\":\"NyaV_\"},\"nbzxdng\":\"OVUlNQTlOYYfPlUoo\",\"chifmkkajnxh\":\"YTI20MFsTbE_2ghsb\"},\"jswascrwurv\":\"d\",\"etrclmz\":true},\"mzsdo\":false},\"qzurmiha\":[true,-2146258234.0748904,{\"uuvqrwlpfv\":-877083105.1699274,\"myasmtjlz\":\"R6x8REU-m7Tu01QvBjD\",\"urxoidbt\":false,\"szwbclkvpbsh\":{\"lnbfxz\":2145353028,\"zwpyy\":504791499,\"enzjokxajmjt\":\"5A3\",\"dsrapjdueww\":-599259627.0802419,\"lzbzs\":[1919284306,\"xckIQgNOyyq\"]},\"eipybenmhvv\":[{\"fcpazwsusno\":false,\"urzdrmnl\":true,\"zynvdqshtcw\":-1941481590.0488482,\"wqvvobabjju\":true,\"tbxyyxclcnx\":\"0\"},{\"pplicrz\":true,\"wagndj\":\"b0LRoWWm2Ztd\",\"zztgmzwf\":false,\"cimvdkn\":\"3wy\",\"uclndtmuwbo\":1495324659.352004,\"dqvda\":\"NjGyfbbK3OSOQ7S0q\",\"pqghbakpvi\":\"O-5G2PsZTrFsdEHM\"}],\"xzvvzwq\":\"oSC1MBBq-E6c\",\"cwbfsobqcf\":[[\"PQK3focBg2Gp\",true,true,1004838860,\"JrLi0nB0He9ghhpS8uc\",true],\"N5JgUxr6Scf0fnkEBEL\",{\"zyviehisu\":true,\"nnagbnbjxt\":-1252472916.9399571},{\"jlvwt\":\"3YWkeBNrHtl_6kng1S3_\",\"ugogbvgelw\":false,\"krvtuamqofi\":\"8bl\",\"jldwhzpv\":true,\"hqegcymaoof\":true,\"poobweejyfo\":true},false,{\"vndkvyv\":550016401,\"hlecdqnl\":-422034957,\"phayfmtbuptm\":1641944425},\"T\",{\"aydhbhkpmcd\":\"yfeknGKQU2gYqjubg6dE\",\"csxavsqsxx\":true,\"kpsnnv\":944339004.2245059}]},{\"dvyfgypqrcbl\":\"HgIHFWizu6\",\"jwjjnhm\":\"lEoEf_\"}],\"ayyyelplxo\":true},[true,-300550108.89930344,[1317545879.2367544,\"CVWfmfWqpIn2\",\"TJ5X\",[{\"qlcuztjpld\":\"l8bLGorS7qjjMU4ZfHVo\",\"eibxxo\":{\"gifekpyafb\":1713381946,\"oecjidgwk\":\"7_Z6k4mD6iwBLRSt\",\"vzycggobtf\":\"q\",\"ujyrvln\":false,\"amyoihh\":\"1oLHpUEC3dbTh\"},\"uxaxdxsxsk\":-1537928713.628843,\"uheyvucjftkh\":-1532453905.592184,\"gxfogftq\":false,\"wfjdwou\":{\"antcnyqg\":706814327.8249477,\"ggwyrehckcq\":1825416858.4464476,\"dljuobe\":false,\"ghqlrqrbrq\":297261003,\"npsajsyiaoh\":\"ng3C3y_zlpou1dRs6Qi\",\"ybeqgkf\":-2144167599,\"ruzaqnxgse\":\"JN6\"},\"txrsvss\":false,\"snknlvahep\":\"17V5L\"},\"Y0X\",\"0q9S9GkC5gvvl\",[true,false,-1898353979.244672],\"OZmJu4ET\",-2121043194],\"A67vrf0wp-FXkr\",1796349556],[{\"pmkji\":-978635550,\"umjuesdyxii\":{\"nqulycxkws\":\"fyDMe4XQgstVg6Wy\",\"znxgtqvrju\":{\"thcxzgqf\":-102962129.86471988,\"vgardbwtot\":false,\"ljevydjbhcmr\":\"JENrRB\",\"oiufyv\":true,\"ooviu\":\"4i5LOwIaLKvv\",\"uxzcxhpwnag\":true,\"acccrke\":false,\"htckqrpadw\":true},\"cbheaivvwh\":\"YSRsl27S\",\"ojqgbuiws\":true},\"qedamqkjbj\":{\"kjwgcjujif\":{\"wanqdsofrz\":-1269450440,\"ijddgzqwjt\":-325104601.04279137,\"qkondgzl\":\"8Zd49_MmT6t744XOH\",\"tjavnjdetm\":\"jVmMzTDf5m369ihIRCgn\",\"tkwzfpgjg\":false,\"qvbvvwfp\":35584226,\"vpsdx\":\"hP9VVRe2z3uNxwT4H2\",\"evwgmcbjzu\":508135904},\"hjnqrbuk\":-575370305,\"ybqpolryid\":\"caPh0arMEeZzo5Cg\",\"anjbhfuxpm\":\"JQw\",\"tuadebzynir\":[164102087,true,-511189333,\"8ReU\",true],\"ceewakqpenm\":-1850356278.3204591,\"nighot\":false},\"efakofxcfd\":\"vxakfbruzuf93\",\"fjdzhkj\":[[\"WP1Q9q1sYr8JSRAAB\",1011093204,false,\"g2CxXSYK0LQwMAbT9\"],-1292100242],\"pjdtimp\":true,\"ivphlnkt\":{\"ycufdc\":\"ZRwDQzY0rPvE-\"}}],[{\"mcbgpynjiwd\":[true,\"ruVdZIQsnmdENFyJ\",\"dpD3wCjhD40kO0cq3k\",[\"_avcpOXnLF-DJNn\",1547535063.164533,\"efocmYD\",true,false,true,\"UkywQ7Eyh5j\"],\"MxIdAGMIm7xoU9ix\",true,862702809],\"swkztzred\":false,\"mxyebxhh\":{\"xhiplzvjjblj\":[false,true],\"vndhf\":false,\"wysbgglu\":[true,-2078005937,false,false,\"0FCf7O1pWE1mQoD\",\"j3euPq3_hqEM\",-1231501438.727005],\"dhqxnzjzsw\":[\"wF7zZNaehiw4AJYcKQ\",true,\"Vt_MuYvQxqA26e7kvoe\",\"PZhKCjP1BJ1U\",\"q6uZsT6yy\",\"5f3N9XoPNWfgekp\",true],\"ixvsg\":1773584094.328894,\"hpuyevtxtoif\":{\"zllddqvytqq\":1768210261,\"jmznlzhiqu\":\"2edtqGuAPj4cAhpB\",\"hcrdcw\":\"Ics_0jphXbLH_\",\"zhear\":\"wUewPT_v7aiOX0F9T2\",\"dtyobyxygna\":1565005789.272149},\"pzaujwgbhrq\":{\"dobslzgbofyv\":-781164203.3296458,\"fmsxclyjpp\":true},\"bfvde\":[\"rUvLAZaGKI1S\",true,-1834977031.0718312,-872190164.7888888,true]},\"stpbromndd\":\"0ifx3d7\"},{\"mosme\":false,\"tahxkcfdbwae\":-1223933595},false,\"RN\"]],-1690185674,[\"camLJF5RTe\"],\"Rx_w1iLgUIshQi5jL\",\"Fg\"],\"emgikfz\":-381042878.6997356,\"efbscc\":[72191200,[[[true,{\"sqkzuzjwuya\":{\"mdyzhbtjr\":-1335243342.2741792,\"iuuyazhcl\":true,\"hejfvsgsqqhs\":false,\"mdtdt\":\"ex85LnmBQcrmXIc\",\"arqwjrnnzr\":true,\"hrlxpyucm\":685433027},\"oyjhgxgvj\":\"C7-N3mCuztecLk29gq\",\"gunkdymhlgq\":\"ms_Yu\"}],\"NOhUR0VkZ9AxYl\",1543269755,-1447953675.7846606,\"Lo9n\",\"cJlq60ILPf1ZCXbxzhse\"],false,[{\"ivgbzeflx\":true,\"logxkuflp\":\"P4uV8Lux5zlWU_L\",\"ryfwpxdbju\":\"z8QA\",\"ogfdjdpbm\":\"Pm\"},false,437129218.916399,\"I_thbSbi4QwLK7j6O\",\"Q1B34gr5JxCnyRT\"],[[{\"izwlvsgxg\":[false,\"Ssoywa2ze37Z_RlF\",-1168530627,-720874248],\"xhbmu\":\"bgV_L5MRJc-3d\",\"gkizi\":[1691880407.0434313,true,true,-2036385193,false],\"fzbysxolawl\":[\"Sq1rNnZbui\",748582724.0143782],\"rrvqjtjnxh\":false},1716290391,false,990585462.1418431,true],[false,true]],\"5I9tUXhCBQIb\",245229034,{\"rrtbkxe\":{\"vcfqdxchq\":1682998926.834265,\"nvyyyctup\":-1563042894.962963},\"npehmciaubh\":-1958971372,\"tanmpyz\":true},\"6oW1BCc7MzNcOM\"],[\"WJvvuh3hyo_Do35y\"],true,{\"slhgmy\":true,\"osnjzycqow\":[true,1600071509,{\"ixbdkx\":{\"egdzw\":true,\"dbgfrdaulyxj\":[\"_RK0u\",\"8OpZCbw08Ulvk28\",\"-Ax\",false],\"yfpxdqkpa\":724393426,\"flagvwnz\":{\"cmhoua\":false,\"zcnnmqwrtiq\":-23193883,\"gntuiecz\":true},\"goctv\":\"Fr\"},\"cdkuanonv\":[[1325613116,true,-1782376956,false,\"G\",-495596656,1626327781.5350764,false],true,{\"npccpdbuim\":298337042.9282495,\"vbnxwrtt\":\"U0KLhG_8kG1\",\"hooyc\":false,\"agnilnmuocm\":-1710549089.1421533,\"shwogsx\":-1795872455,\"pdikyt\":true,\"dpbbcdln\":-1147883330.3829007,\"phoqelixkxe\":false},{\"ayfmxaz\":true,\"pnhpftaoje\":33464670.370539024,\"cywiivmyulq\":false,\"sfbjf\":\"3QE-QLJlAjkGxW\"}],\"egwtgn\":-1067795512,\"zlbtycc\":[\"xvYAVB32CI0tuNRDk\",\"KVVGuvYN0dzw2\",{\"bfomurparf\":\"c4UbQR2Zofay\",\"ccjdwjzgje\":false,\"nqviidoum\":false,\"hccuhnspu\":\"bumazkLgUoW\"},true,\"mOHOlSqRPsYSMLN\",false,\"p150NuZjUtrQx6GJ\",1010658652.943376],\"yroubyzplg\":1329831293,\"mpcybwq\":\"3rFDouaFGeq\",\"mymvfgjpmndx\":{\"eknydl\":360852020.81164,\"sxcuollvqzc\":[\"jCCf7f2zc\"],\"lihscjo\":[false,false,false,false,true,-2090304980],\"fewtzzs\":\"FVy1jp\",\"usndipillkgk\":11879507,\"hvqmkpsbb\":[\"eqxxLPw3R9C5OPRpLL2\",506184838,false,\"DTWNK_dFU2rayYd0HTE6\",1813683451.736847,\"-OdpFbcrkbNUW\",false,\"VMJgFuBH95GTs\"]},\"ggnytcsj\":[\"ggmq7EeyJ8P\",[\"ldAQRpUt\",\"3Mkp\",\"U-031gh\",\"Tn42gDXM1da\"],\"-qa9giEQDNAEzgZg\",\"tbDm2SpnnERLlqex\",false,{\"vfbxub\":true,\"iguctzqhjvx\":\"-Q_deAQrO\",\"tofdubxulig\":-1780656225.739828}]},false]},true,true,[2001676240.4400687,\"DCPQTTkusc1\",1035429679.7134472,-1339397883,true]]}],1354744622,\"Z\"],\"eteofbukvd\":{\"xcygeq\":[-1573384653,false,false,[[false,2092805150,-603848597,false,\"I2\",false],false,false,false,true,{\"dqmsioser\":107437364.78354678,\"mfpdfwbkfo\":[\"yT-oMhJgBgqW0\",-1382477963,\"e81_\",1430275558.519975,\"uW7TfQeRby\",{\"afvkxexxhzjw\":-2025593808},1966793421.8756416],\"jyibqhqofevh\":false,\"ytkxixmid\":{\"snfwt\":true,\"flgixxe\":\"5vb57\",\"rhqgkdrmyctd\":{\"mivii\":[-1175492667.035983,false],\"xoaajb\":\"Za_0yv4\",\"cwzeg\":{\"xbaxwwhsaxpv\":[[\"_KziUDTfyw_\",\"COec0fP\",-728888968.0843807,-163512312.1774335,true],{\"cgzfy\":\"1k8kXzC_rFc2xHVj60MV\",\"hqxywthbmgr\":2054477998.6406093}],\"cqpkoaelfj\":822284452,\"tdgskbeqyunk\":563850923,\"lmioguzppko\":{\"dwcaresauhc\":{\"doiock\":\"BbLdcUHEt\",\"hiaemnx\":-734491688,\"eplhylnjn\":-497546277,\"xibfn\":\"tYMbB\",\"ftqyxnhj\":false,\"kbrydkmoyf\":\"HoepH2ev6uOz\"},\"erxzmqmtk\":\"PZAlfBj1EyEqRn\"},\"usftutsrsa\":false},\"pntiggv\":{\"bgyvleivtia\":{\"rfcvwemg\":\"x1Z2UBhTfsUg-L9R5Rtu\",\"fwuiwv\":{\"boukiflz\":false,\"jzckdqdos\":\"95Hp-w\",\"vrrwrdcfcnrz\":-1208916500.0584147,\"ydegwinma\":false,\"scpelyspixt\":true,\"znxuzvipfj\":1513947800.0775602},\"rfnjiyiexfup\":265941595.05743906,\"bwglewnqr\":true,\"maceojrlzewi\":\"jNmGUX\",\"llokijqcp\":true,\"tooij\":[\"ALa\",1015446923,-1686664589,false,1521714487,false,-1833935086],\"djzivmsg\":\"KonJFZRlFdFkFLYT3iI\"},\"daltebwho\":\"e8P5365ANdXH1\",\"gboylripmeth\":{\"lylexzjjp\":-949593078.1804109,\"jzrydlerweuc\":\"rRcqyQ7cizNf\",\"mpizl\":[\"K20bA\",true,\"0U2RC\"],\"cbppfkeot\":{\"vazzaahldt\":2086745950,\"hczyjfhk\":\"G5TPrJ\",\"bnlsxgrg\":273291974,\"rbfxcrhy\":-1110441877,\"efdraymnnrnu\":\"ji9VwVn\",\"mahviggds\":1548077035.1517725}},\"kkjpda\":[{\"yrzhnrkhnrzd\":true,\"jcqfu\":\"CkI-jhLlcXL_My\",\"htqjks\":987591344,\"iymoyd\":\"316G1jESnXhLy_6coH\"},\"MN7J25oKjKqRh\",-71194870.85329139,{\"mrqcik\":1038646369.0071654,\"bcntkfx\":false,\"ajqiyaqnlpd\":-433332257,\"dpasgvj\":1150303846},false,[false,false,false,\"VgjlWGrrptqC\",1524636144.8404028],false,1058334877]}," +chunk2 = b"\"xccthhassnth\":{\"zaamank\":[false,[true,\"muqZSm4vQkoA2v1r5\",true,true,false,\"l8tCmuyU3U171nm\",true],[true,-232954684,false,1659571833,\"WNgpXVF2tHgVvYynl\"],false,{\"hwxckwcty\":false,\"kkghfy\":true,\"wpximznagn\":-1500160475,\"dfwczkuzdjje\":\"mXofI1vlyTtV4zqHhVR\",\"liiwsvtoc\":1990038101,\"ghjtffev\":true,\"zvbcjvt\":\"qxKrunSH3-M2Cg41QX\",\"apwcfenz\":-1235037138},true,{\"gypenth\":false,\"gfswbd\":true,\"uynhfcwswo\":false,\"fxjtyxfzr\":345726325.6933864,\"bifce\":\"RBTkbwG9hp-Rhz20l084\",\"fxepjhpvewn\":false},{\"oexmbdm\":true,\"sunyjktdvd\":\"ylFDnk5Th3F\",\"ighmkurteqi\":\"aiJMzE72jDu6qa1W\"}],\"avefshorgvz\":{\"oabujryea\":\"4Kqnc1EZ3xDZP\",\"rhbrvrqvgm\":[false,1464921169,-1352914514.6189935,true,\"7Ih9LVuoWU6\",false,true,true],\"lzhcder\":[false,\"hm4Cn\",1478561590,\"tGWz_SkyrPd5g8NP8pU\",true,true,false],\"gkikwcify\":\"ghOn2kSY\",\"huaygoqg\":false,\"cjgfo\":1096591234.8473735,\"fqkfdpyhgyy\":\"MGIYRySrwbjOgNOQ3zv\"},\"nprqopp\":[\"PBg_RF3C1\",\"sygkjeUgXx4l1jLw8H\",964043184,{\"ptobejasvad\":-2127752286.8216696,\"suonzhnpk\":false,\"gfntskhxggun\":true,\"qehucm\":349554245.4184968,\"hcjfcffxl\":1937459687.7666032,\"ykgrsjkmkcuo\":false,\"qceqz\":\"loUifh1ZTm\"},{\"nnniqb\":93715451,\"vzevokxxuj\":false,\"wapjtgrueyvg\":852465535,\"inmgztojdx\":-1155264602.0556374,\"kpcetrh\":\"BB2vTsYaIAKg4TS5b7st\",\"onqzm\":\"RopKamoYckdcx85GU3\",\"iphwfxybdqdu\":\"VE\"},{\"cqsrh\":-1620267328.323558,\"nbtkbfsu\":true,\"sbghjx\":1348479553}],\"dciobqvipmy\":-1959110467.0494561,\"tpaerndjs\":false}},\"jmojada\":true,\"kjinhv\":{\"zvuyiij\":{\"cgdppxcczxn\":false,\"pctmupfktykk\":false,\"ahrtnazuidgf\":true,\"wgnbd\":true},\"xcnondquz\":true,\"olffes\":[692183872.671826,[\"umikNWUfJdAbjpnH\",{\"szsomzuxjv\":\"d4KSNbFWxkenu-lrP\",\"ellstrjk\":false},-1161236496.4138455,[-255384585],\"a\",1917800491,[true,false,\"z9b2Gz\",false,true,true,\"qiIk6w\"],[\"Nsh3wFoL7AmQ72z2X\",true]],[true],\"B\"],\"kjlxbmpgfzo\":{\"njtxf\":1194508828,\"aqbxuqtyti\":\"H\",\"tsfurr\":{\"umrqcyfh\":false,\"pcikchnyga\":-193180285},\"xwzdb\":-1959462087,\"ymgdocdfhq\":[-122714350.94902076,{\"rutswysmiyhg\":false},[true,\"IT0UBL\",false,1320189432.853742,false,-2079407488,true,true],true],\"hlspkktpudx\":true,\"uljyodgr\":-1413280874.5823383},\"phjcntlhy\":false,\"qiagjchfv\":{\"qhawoydcqj\":[[523980897.1355204,-1890453633,true,886610017.5856152],false,-1159547749.990652,\"RzAdWpQ5W3TMfZL4b\",-111877485,\"f8QA2wQKMF\"],\"efragswm\":[true],\"jzrchkgu\":true,\"hdiod\":false,\"tokjj\":2052573734},\"dykkuqt\":{\"ienxynmln\":-1712349997.5342517,\"vtvropeomhyw\":[[1785424871.4819608,\"J_8sBn-d\",false,-46546425.56714463],false,-592006182,-1632967494.5798006],\"kpvwkvtu\":\"v4SI7C0vm\",\"crftchkexszu\":\"7tFFgImTsKJ\"},\"cxjamw\":true},\"wceuzflhrbi\":{\"dctqpsarhvpi\":[-699810128,{\"szrfkogw\":{\"wxkisayo\":true,\"ayhuedjeg\":\"LrVu\",\"ufxkpeawkd\":false,\"romjffxeq\":566782181,\"csremp\":\"N5WU-\",\"svujxzeiz\":153458741.83463427},\"bkaug\":\"k\",\"hrbdgg\":{\"wudstgl\":\"o\"},\"ftcslpai\":796595041.8604599,\"bilfwjlo\":-711377874.1887994,\"nrhgo\":{\"nusbiuaneb\":-646948573.040052,\"pybtertngns\":-2025838569,\"pmlaqpklvat\":\"rDw\",\"vxxrdcacw\":\"BG9u5MVMav\",\"nnslluzwffaj\":142952985.411031,\"osvfqpyq\":\"SqbuNhyGH\",\"smvkbefj\":-913859526.7776299},\"bmeiuozuc\":\"yJhSpcYu\"},1688693672.4344697],\"qmmaya\":-158825769,\"ftndipvdbr\":{\"gozkraofvvu\":true,\"trfvdizymq\":\"9NMj-XZVcxRJuabjW\",\"zycwjomazaxq\":[{\"bzyukxtaqb\":true,\"zvrgek\":false,\"fpsggqjhxket\":false,\"nhqjqvkf\":\"I\",\"dvjfthsm\":true,\"tnwplurraqq\":false},false],\"afkgpstrql\":false,\"llnhmh\":{\"wihicbgvoafg\":[-283361085,\"98BL3\",-1628613321,-1017097371.7725061,1319177272,\"fjo\",\"_FxOuZXAy1OgrEPHPdKO\",25010698.02974629],\"iydwkusixb\":false,\"ibtpyfo\":true}},\"ziwfgmpyde\":\"bEs0T29O8jVN7xYR2C-0\"},\"qgefszt\":\"8YTJah\"}},\"2Gb6w\",704271568],\"4qUep9-rvR3O-kzgC\",[{\"haywhswtkyq\":{\"rnhep\":false,\"ipvueoeh\":456345822,\"uzvvlezksnyf\":\"IthKvq6TdjJWz7-e40\"},\"pdbtbyxbepcl\":\"1EcfMHXf8\",\"vvtwiyidi\":[[[[true,false,1285645676,{\"lrawcuf\":-1839447221,\"nvwsmclwiswb\":true,\"imatben\":false,\"gitsaspelmp\":true,\"itjjkg\":true,\"nkzojcrdf\":false,\"rptqabhsag\":1510911443.0173252},[true,1069450427,1007238538.9397852,297843020.7814086,true,true,\"D8RrCA4wOiOoNTlQ9I\"]],2082670090,true,{\"dohnznlw\":{\"zilplcgapgbu\":true,\"tfucayrn\":\"dUv5oMDHZPUDg5\",\"clomf\":\"cr4uEMhUM6pxOy3D\",\"bkhdid\":false}},false,[{\"jktalubsuini\":170217680,\"zwhcvkmwxdre\":1333233081.8373058},[\"oAaWVqMZL\",-1955036780,-922301126.1056982,\"z\",714005228,-650600838.1472309],\"p_OOUqmqFBV\",\"f1Ylt\",-1192800025.9576578,{\"ibbbwpi\":true},-107553885,[\"9mBB0k2rrVyX4pnfGg\"]]],{\"jtwxgqp\":{\"tznizolcyfc\":{\"njhufwahqky\":\"SWhZ1e7-43hzeSd0bY\",\"jhvyexh\":\"1\",\"cqwxajjcnmog\":false,\"hollbrjxi\":true,\"dfooll\":-86615793.68168604},\"gpmkq\":true,\"nkbmv\":true,\"wkkqbv\":\"O-\",\"aiyret\":true,\"kslhdrx\":{\"vqcwxid\":\"oKfKInvwVOEvsGJRE\",\"xumopgc\":-1621779007.3644912,\"anmgl\":\"56LvaxFLSR4\",\"ehyzyoqebqxu\":true,\"ezvxiz\":false,\"ucnrtpfdwnw\":1073246354,\"grdzxusoepzw\":\"4qyOOsHU4AJ\"},\"qgqwdeseuh\":-1505477461.128098,\"yqeskufokxt\":[false,-768027022,\"6TtZmJ6\",false,false,-357709374,\"jKVK1EiijNlk\",false]},\"cvjxyabaisuf\":{\"giawcxw\":{\"wsopixy\":\"6PEJB\",\"yaxhzkezi\":-1680524773,\"grpez\":482847930,\"dycaifnv\":true,\"xocuvzrot\":true,\"uiahrhc\":\"svYyn--Yydpd41Z8qDg\",\"cyopt\":-886330109.6705366,\"payiwpzywfk\":-687113918.0448986},\"zhihldiayun\":{\"dnlnebnjdja\":-1739680750,\"xuwtwxlqpy\":true,\"leuaywoodgu\":\"vnBIb5\",\"gediisjsqogk\":\"5OFiP\"},\"aohwwokhi\":[-1323408527,\"I9_U1uk_3WfCQd8Fetm\",\"EnrN4sivVsNjaOh\",-1337874344,false]},\"nuafaksnsty\":[2049398889,[-1418003140,false,\"QI\",false,-1275670734],\"_6GjEeCflNdlcNa\",true,-1671383982,\"Yf5o72991e\"],\"qaafghyajbr\":true,\"bnoxsnevhufs\":[\"LhlOvbx\",\"7HrKX\"],\"aftmj\":-999195286.3155743,\"rmsqepefs\":[{\"brltygoxj\":true,\"ukbctse\":1143225876,\"rnreuhsrme\":\"ZkTT8ptn0BG\",\"areegxo\":true,\"wqpwki\":\"X9REYFF20SoEjbbih\",\"acokexlz\":386165047.2803682},[true,-858925198.6267022,\"iJ\",\"9\",-1496696400.9504488],false,-1293569505,[false,false]]}],false,[[[true,true,\"IXgo\",1823922808,\"rXOJ8UP\"],1360775489,\"IaI0q5-2AlXmYoG\",-464585794,false,true],{\"yljdylzpbdk\":[[\"zCJu47Go_q8\",true,-1065551268.3564312,\"1MXY3PQnrRAX9jMWDP_P\",\"2_xT4OFTWInwbwdtf7\",1341813636.5036178,1843319129,\"AHhOUj0KqYYk\"],\"U38mZNwYT_F\",[50891974,true,-990864440,\"BeD7J3Idy5jvUVSi\",\"hgn\",\"-JZjapO\",\"3v8NR_h_q6\",-1673363791.3454182],\"aDkbqjRo\",\"O\",460705339.6075232,-1280541705.972414,\"eJ077KT1hUzjwdgQa_Q\"],\"owxbbfxqa\":{\"otitl\":{\"tlqwgxnjtt\":false,\"ufkydliml\":true,\"wprui\":-1390872958.9616954,\"cqtbfgwsx\":1025290630,\"bsozbze\":false,\"vbkmnurt\":false},\"rqubpxphd\":false,\"wpiqzxqh\":[\"ze2E67M9Wmi-f1s5El\",1219485490,1623639456.1836274,false,1517180980,\"RKFyWjv6R_h\",false]},\"kkbjuuzba\":{\"hngdzyrmrg\":\"4Dr5Ilr3\",\"ssvsraji\":{\"kexlg\":\"zS5K1DyRPdCm1F\",\"lhyuog\":true,\"bcyscq\":1238085093.3138793,\"ftpcgyslrex\":-824076578,\"xorzxucgvqo\":494286543}},\"rfybq\":{\"mfnzfekkmv\":true,\"etxwjd\":{\"zargmnwlzklr\":\"FyCh48k\",\"cwnvtt\":false,\"xjtbpeupdss\":-804032210.3925759,\"nazdnvgvr\":true,\"unnkyluhngmq\":\"8ya2TlwXGsndVr0Gx1eB\"},\"apmdp\":{\"jewmhlycewg\":1798024222,\"pxvac\":false,\"mmgsfjlzdt\":false,\"owxcz\":false,\"zttwkaw\":645806765,\"izvuahglmr\":false},\"wtycpp\":2102082055,\"rwqeeyhdityi\":\"vVWR\",\"ciuxeedeu\":\"jI76bhA\",\"ywbbdraqea\":148509069},\"tcqmqrko\":\"fHCcLwNU_eW1m3R2ZzOY\",\"pztkmdd\":411681438,\"kmapmvb\":false,\"ayzrub\":{\"qviuoardrlk\":{\"eojqqtjgl\":false,\"ousqwkealjp\":\"T8g\",\"gpmzc\":1703025097.7800539,\"mmyhd\":\"Lm\",\"zqrwkkbnk\":52533644},\"hhzyoblghyff\":false}},[[false,[-710308147,\"3sJ3Tqbn\",false,263766317.3158277,\"Uh3efhTFfkAWRxC\",\"5LB3Ikg\",\"0XdwECEXKX0\",true],{\"abgropvpfj\":\"ac5\",\"ydknlygc\":\"gl-cJ-QE1dz\",\"uiqmzf\":\"2XFAVrNsIByGGXtjt0Tz\",\"qzsvof\":\"JFfNtQ\",\"tkksgvmwxv\":\"hnbIr9G0lDwdC\",\"easrn\":-1569991468.24268,\"yiavwrrj\":false},[false,295399953.7474086,false,1792749077,true],{\"cgyxqrobbwk\":true,\"txmisx\":-549358196,\"apyjssf\":true,\"yyvkgup\":true,\"nvmemjxezcym\":\"Y41ozRWZ6YTEjemxXw2U\"},2008390595.132691,true,false],{\"twggnf\":[\"uT58b9wKQFed\",false,-1068776479,\"qeZOFUwr3qAs6L\"],\"zbylzs\":{\"aqddpjtrfg\":\"otVj\",\"hadsgvisr\":true,\"xqtrijq\":265375833.06787774},\"fgrrafng\":[1951070082.5843506,true,\"xMl9\",\"W3I_O0L59O_d6SOcxf\"],\"pgazypjer\":true,\"fzqgqix\":true,\"uqxiphbo\":false,\"ksaisjlg\":-137892957},\"r\",-697119506,\"hUeznVPLUl9\",{\"vqtexf\":-1060441139.6577497,\"dbkgclqxpyhc\":120310184,\"rwximlvy\":\"MX-_7hSw41z\",\"ipgmh\":-1307174688.1218433,\"hlrrvnovywwl\":{\"qolqvltbua\":\"0nYnoC\"},\"pbbwoozfxf\":{\"ycuoqkxvhi\":false}},false,\"lTzOdE1Bp_rw62PO\"],false,\"l\",\"9TSub\",-51721659.552684575],{\"vhoygv\":[[[true],[true,\"k\",false],false,-143712837,[\"2A-\",true,-280721300.41835696,\"pdiq\"]],\"LqhZg7nAb7r3\",[-467604591,\"4ixLN\",[\"zcE5jQHWWU4S\",true,false,\"W2GS7U6GN\",true,\"LL9rcxO5h\"],false,273456364],[{\"fbjcojep\":\"Xl24I4RghpTM9DB\",\"usogkl\":\"5IdTmSlh\",\"jvbsqe\":true,\"bnhzsyevepr\":false},\"grY1UwBWuVXszuI\",[false,291916786,\"8eFANhD6iZfBiq\",false,true,\"7JDqRVK9JyHVrvBh8_K\",false],[\"J7Eu5-FQc0To7\",\"J5d-LpKYq_hs2-vAQs\",false,418590313.89976186,false,\"2DapYD\",false],\"My\",[\"BLX8EAs3xoexVQW\",1558686895.9899993],\"RRwKMjhubnoaD\"],false]}],\"mahnatcx\":{\"xxiqjqysky\":\"p2-_\"},\"cimtnrvv\":true,\"uftwvwwle\":-899341436},[42267602,[2077168369.7349398,[\"RF8r8xygFOMoX1\",[\"fzmyYPCqSBpX\",[\"U0a\",[true,-2068337135,998794055.252226,-1439385854,\"KdeTKRd\",-830738616.4292334,-125685566,702238309.5721414],\"Ij_bX1\",false,true,\"0RSeYPmmbRktJ\",\"_KyxjsQVt5CPalAdq\",{\"qqzcnudiouz\":true}],\"UGKok_-H3pzMC5D\",\"wwQs-YIA\",\"DJkpchSJsn\",{\"ahyvbzdr\":[\"RxF8\",false,false,true,false,-97134647,\"HEndDoGwlcMEKYBcYk\"],\"whpzzypfb\":{\"dnpowxzok\":\"hYtM\",\"qfsmhxuzvj\":true,\"rmqqeswqm\":false,\"rrznlj\":-526514337.4016451},\"jpszhswsjlw\":[false,true,\"LsVQ8\",true,962696626,1225518649,false,736436560.2796444],\"sjcplrragodm\":\"VUuFQux5a\"},[\"74DpPwrFJksBrHoqx\"]],1888346029.1199446,\"NhqpD8Fa\",{\"rdyaqljjwjp\":1706808954,\"snequvosomm\":true}],1809054906.3203444,false,true,{\"akuzqfebhst\":\"wFLH\",\"chsgu\":true,\"ixkab\":{\"vrsqju\":\"D\",\"odlhkhfbfzb\":{\"junmdwvnja\":\"eFFph8FY\",\"ocdafqciinl\":[\"Z0fuwLXH5b0HtACA_I3\",-1787011617.05362,\"Z\"],\"vwjfsbac\":false,\"aklwnhgv\":false},\"idrvr\":-1630540321.7321465,\"ctrjl\":false,\"kpmsa\":868012478},\"wmjoywa\":{\"koutlhf\":1333902768.019479,\"ahsbfglrxzs\":false,\"mzkmiyarjf\":[false,-1077877259.3211162,true],\"hcwxdvf\":15609610.658389682,\"rosvvcttmcj\":-832208952,\"vbzmbqd\":{\"zgovhkfuwh\":2101352740,\"ibpihcx\":{\"rswdqcxgfep\":true,\"buogk\":false,\"zgvxkycijdl\":-1921740686,\"jwxxv\":false,\"vqydnpubyd\":-1617539070,\"lmqaydf\":\"vqQboY3CAOg_BM\",\"pusiojz\":-1478441770.1368904,\"xstfybbc\":true},\"rycfdzkb\":-1882118827,\"nnfchcpfy\":{\"ypbtsstg\":1750597023.4220426,\"pggvypkmsk\":false,\"rdhszaojivl\":1279529038},\"satgieg\":true,\"zsszx\":\"Fvb1wGR6My9FCN\",\"nkcbntzv\":{\"kayrygacdr\":-1317986372.197722,\"yqiyany\":\"fzG8xld\",\"sczbrgqs\":-1749745051,\"xcphrdpk\":\"2K-S_\",\"scqhw\":\"_4KL5sHSoUY_\"}},\"bifmcgogw\":1999927188.9281383,\"gfofkuchaiwq\":[\"sxQzAdX7a4\",\"odn2WML\",{\"gozyjw\":\"g3ML4SFYB\",\"bhssdtnx\":true,\"rksfymmuvss\":1182204675},false,\"RnisFwU5hjY_R\",true]},\"ckapedwvecg\":-480686288.1928786,\"pzisxdlnr\":[[false,[-991496322.4166974,-1217764212.8600802,\"WDLMmCBkHArYK0JQ\",true,false,false],1732720183,\"8fI18UOBG\",true],{\"bjjbxke\":2114328067,\"qwlvx\":true,\"xhpkp\":{\"ivqeuwjf\":1413940030},\"jmfvyoxnafo\":[false,false,-724135297.295327,71042993,-137129745,-297355049,\"OMGrmKImD213j7GP\",false],\"yhjhyfe\":{\"odmwrmmtnzkq\":\"YH8f\",\"wcixvgpyy\":false,\"nxsuroso\":true,\"bsxjup\":false,\"wcbpyutshnk\":14382112.84948586,\"trvzn\":\"3rVW9N01dHVGfAVlVTs3\"}}],\"lmssssnc\":{\"uatqs\":true,\"zzank\":[false],\"lnlmxwexagia\":[{\"ldrnuyevgdu\":true,\"qvpqdjumqgi\":\"CBAtMXsvxRvbW5\",\"eqrdinwper\":true},true],\"zvmjhx\":\"CD1KliC\",\"mkjyqutsdpy\":[{\"xyznwbgsiu\":\"bGR\",\"zuoiy\":1899501847,\"iqzycekg\":false,\"pavrl\":\"lH9bJyVr8\",\"hczvz\":\"-8XAjAy87WjWJK\",\"ovigdtamgthv\":\"Azf81Xgs1\"},{\"cxtwkdrree\":-917698938,\"cxzvve\":55788282,\"cfjsfkheg\":\"BNRb2y\"},[\"jBCReuYOaPTvb\",false,\"NH5u75LqnwttsBhZJ\",1786370764.5405252,\"1P67DSaPbbefM\",\"izav3Lpal\",\"30LTfE\",false],{\"qzwksjtlf\":true,\"gqqfanczjaq\":-94779381.70111275},true],\"iqwxnrjkpu\":-1933245943,\"yfdpzx\":\"1UyW0W9CuFqMGSU6S\",\"vqoothvcm\":-2132324211.086656},\"zzjlqpw\":-1541973302.5930307},[false,1902613136.7965968,[\"lx0\",\"sDyxzdJ86_\",109817174.82858928,1512596056,true,true,757943899.6448529,[-1914146519,false,{\"oeynl\":174525722.4941448,\"ckiyvaykoijd\":\"yCYo0TP8WgksfYpYz-6\",\"aiwmbyuoqn\":\"0-H47yvTkv0Vz_9Bm\",\"oovfqi\":\"dXDOla\"}]],{\"lrxlnsyufam\":[-909771912.226755,[true,-1764455450.74269,1658682294.8610492,\"_yDiX-rqKQPGlfxLFf\",469067261,900766133,1614137897,\"pr\"],[true,\"7aK69NEW\",\"eyfazhio30\",-1354775538,false,true,1517826876,\"Tcuv4AgXXrbvJQj\"],25104568,{\"ctoftc\":true,\"rrjwmbwd\":false}],\"zrciyblkn\":346028292,\"inaqgxhwevp\":[[false,\"LjL4x5PzNFY-\",-796279217.8947146,\"5_Ug1pWZ6KKPorJn3Mja\",\"BPcE9fzkbN9HzwzwtIeG\",false],-1389494202]},762394936.085164,[505442315.88406277,[[true,false,-1279643483.44039],{\"fgjzlsqb\":471131655.3076618,\"mtngdylmj\":\"oV8NGKbq0N9E\",\"kjctiehlhho\":-1613156681.5674756,\"khbwqnbkxoji\":false,\"kdeqkbmuwyg\":true,\"siiax\":\"gBewK\",\"rmrxxut\":1983504772.0211809},\"ZG28UkMBjI\",[\"B-JOh\",\"v7EjCiypnRygWmq_XnAu\",\"hL8a\",-1193081313,\"IC59gxNA9Yj8\"],146100040.24615726,true,{\"icppo\":false,\"bgimz\":163642387.18194628,\"zgvsflknrtbx\":false,\"uiabqbxgqx\":\"WWXiBmdXjeCYEds\",\"vgxzryqedv\":false},{\"mxgbbafstlsy\":-1953610461}],{\"bspgjfli\":-407859781,\"rfzqri\":{\"kngtjgfdea\":false,\"sgicyevzu\":true,\"bthzo\":687411750.0927956,\"yqmujyfnayt\":-557615504.1888149,\"zsyaiveuvfa\":false,\"dzjvf\":\"LbIU85ZA\",\"yamtbb\":true}},\"5kPcxU8iH55tIy\",{\"szvcarn\":[false,true,\"3tO5UL7wje3JTkT\",-2018140435.8208373,true],\"lpodhtzplbhs\":-1576600832.6918094,\"vvvzaeoquf\":{\"flkfxovkhxms\":false,\"wwmkwspbuas\":104651008.80518949,\"sbhql\":false}},\"lsysVePJGc8v7X4bgee\",\"ilpEcsztIAr\"]],-399816416.68950045],false,false,[1019268196.7597919],\"Fg7J\",-1207255548],-460770884,[\"lVT37AkR0eNFdgMNe0\",[1768943330.1518362,[true,true,false,\"vimk\",false,\"pf\",{\"nxagwephwi\":{\"mtpsjgiptfzf\":\"oN\",\"ilxbagqvex\":[true]},\"ukdastoj\":2140330065,\"klspvkymdmj\":-1456005831,\"hqibel\":\"sQhZ\",\"dzzqgpsh\":-682023126.0974258},true],{\"wspntmjod\":[{\"hutlfw\":61001762.92198466,\"rkfvjyriylod\":{\"ptfofmwftp\":false,\"pwwshuoyc\":\"JfSssBDdG5oa\"},\"qovjzuirm\":{\"muyaeezsyqjg\":false,\"taoprnpny\":-1953777879,\"jvwackzppci\":\"KwO_vHJ2EFcH\",\"rxxrjlw\":true,\"icgydvzpzaw\":\"MCDL\"},\"toozixz\":[1112954268.9729292,-271095453.3726141,\"sn5F4\"],\"fgafnp\":{\"brvdo\":-872690868.8834294,\"lpwitnffk\":false,\"lmspdfembyu\":634123596.5438365,\"frdmou\":true,\"dlxgjppzqvls\":\"l-6ij_ZWKsI1iDn0\",\"xshdafxyhuy\":672593578,\"fvruiy\":885078993,\"klnofm\":-1828966390}},false,[false,-836558286.6728561,false,-405117609.08555955,\"_WeDZhqe5Bi7e-oz\",[1667339178.4757032,false,1333009194],[866931466.806759,\"Or5KkDSO\",665565850,-1704618136,false,true,\"fpi8BA3HeqcdVQPfM\"],true],true],\"eowrwhadjokd\":{\"uebuv\":-658165996.5464467,\"utexz\":-1257504657,\"jrwmrbev\":false,\"recxsre\":\"MHXuM0GIkQwMDQrvtbe\",\"lzvrdtxdjqwp\":\"CniGruaoxrKW1LcXkPr\",\"egbzww\":\"Cc87Jv2O_2KQjJn\"},\"gxvwftq\":{\"jrsaxl\":[[false,\"ni5uYVeCKs_\",\"uXGUGMB5t8l\",\"Isnjw2HbCYZZCJ\"],{\"dqmvp\":1521401875,\"zxhwugsb\":-1220787657,\"oedttydeo\":\"pkvRNoBWxxs3yYz20vrV\",\"nrxdrs\":\"Tuv1LX5gyig_1rhtvFJ\",\"eeqvf\":\"-UMES\",\"bcdktznlobqb\":1535993137,\"lbjkc\":\"iue0V0Eu\",\"crzdouip\":\"egfQh_DAuzc8DvEX_75\"},-987516702,-944862261.4301643,{\"xyypwu\":true,\"euspxfklk\":2093125278.3380926,\"zzellea\":1426415910,\"meezlorldlkp\":\"w5CCV-IIxYFJ\",\"njimxcssanl\":-253968512.89771503,\"hmhyo\":true,\"nduukediecvy\":true,\"csxgooa\":true},{\"lmrsmot\":-416243958.2710694,\"pxctjdmftn\":198216431,\"cwktx\":\"DVSU1s64xNri8CfNWtE\",\"optiztik\":\"mnVjtVao8xAsq6\",\"jiztqaar\":true,\"bvjrxr\":\"xe6nfjzCesYBu5e\"}],\"mmxfq\":1227996577.9875567},\"rctfmrpnpgw\":true,\"ekpouyfgksp\":\"KKndDCC9J5Sai7\"},[-1860689442.552952,{\"ybleplfwcrao\":\"g5ijqk5ZelBASMqM4NW\",\"fuvnqz\":false,\"yyoveimt\":862412884.3803153},\"AkL\",1955691591,274542757]],true,\"o9i\",\"zgbAmuKD00Cl\"],false,false,[\"knIIm_\",false,\"NgH0n6g8K5Ady2XPs4t\",{\"eodwdctdgiih\":\"yOTQAtz28SviQK6qB7u1\",\"gbgixnvdgxyr\":[[-1114684040,-75601670,1949372986,false,[[1863302473],[\"nco7e84G\",\"Y1PcB4Pzbwu0RLofi\",-1994336014.12614,false,true,2090517218.717384,\"eF8ucQjs\"]],\"-buS8e6\",-118393046.2717432],{\"osrkgzlpk\":\"wVtDWVAt33EB1tbPktm\",\"zslbl\":635273448,\"ywfuvpdryn\":true,\"lykarbrerwsf\":\"F-2FXWDTL9\",\"anjkmfhdcen\":\"P\",\"wtenrgxg\":false,\"kvxiu\":\"mBOPEYYHhm6i6\",\"mqaskvxambxs\":false},{\"wqtvdjsba\":true},false,-854681500.4103575],\"zdhhoq\":true},{\"smikylko\":[[-1794459065,{\"mgkukdd\":{\"qkxlkjxr\":\"FE78AzupYSauGpt\",\"pyunlxsersxw\":-302047473.7541317,\"vobjij\":false,\"rleqiescv\":true,\"hmicqq\":\"Y-lRJ\",\"wecwj\":\"9ZbeXNIhTKoHi5dPP-60\",\"xgfjwzy\":\"CEAjBqRZPo8HV3hwPF\"},\"stbygzdfbe\":{\"sadspeyshkj\":\"YqMjivP\"},\"gukvw\":[false,-1646909471.0203028],\"vzshmmortmw\":\"A5DWJ7V6pT\"},{\"atwwt\":-770602655.7952262,\"kjmbeob\":false,\"otufmsefqg\":\"wQ6J\"},-217697591,{\"osmvlzcwyzf\":{\"jznklsxnkm\":-2143795128,\"sugxanzi\":true},\"qldfzsbblpr\":[-601895189,442058918,true,true],\"nrbxs\":false,\"axpwtixjni\":{\"uynthiyhaek\":\"jLEo1bRuCXgzT0Ymk\",\"rafeozol\":-1670772613,\"exwxpngyuj\":true,\"rrgbpbuo\":true},\"ygwly\":1974525158.6800365}],[[2089149265,{\"grkiroczeq\":false,\"vdtkzd\":-1872995647.9584873,\"gklqsxd\":false,\"hqrpbgbc\":\"HHK\",\"qxzbe\":\"t0ajUuIax03U3xlrQuGn\",\"xgqafmu\":-1962987613.2006474,\"jhitkkaxog\":-805220230.3039737,\"zbttpya\":\"8V4cPpkamI4HF\"},\"SZuauMQvl4Bj\",[true,\"2I1CQ767p\",-1039544980.0137268,358618160.60600233],true,\"5d8pk41YO18I_\",-84687246.29565221,566726900],\"ErA8p-R-Gu\",\"pR\",-1316011216,\"Xw\",\"z76cbcf5J16Fmg7luHM\",{\"ujzgetcv\":[true],\"cxabkhgrua\":-216566308},[{\"qaydbpegf\":true,\"evypplnfuovq\":\"JKzGfniaJBSGemoAvS\",\"tpolqvmaup\":\"qALOgk9\",\"htocronslz\":-1533563494.078809,\"hnkvzfodw\":\"Ni\",\"ccfgsua\":1168556515.563556},[false,-1142407142,true],\"Dbzu9Cxp\",{\"ykpumck\":false,\"hmsrejiobzy\":\"dPqrUHchg\",\"fbeygxfvgkxj\":false,\"rddjok\":\"7Yxn9YK5ew\"},false,[1246770784,true,-486372812.33258176],\"cvH4d5I_VkNxEndbU\",[\"SDPQflxlH82IlBqd\"]]],[{\"bnmff\":\"OK5Id5MDj8cIm_E\",\"yxzfngvkfh\":781034822.027912}]],\"jmmxho\":[-1976079254,711281759.082715],\"rouxjkzjiz\":\"4_YFq_cPi_q\",\"ukkaxrnpz\":\"RiQAxBkv4fejOL\",\"rrqcvs\":false,\"zbxhjzooymrt\":1079771085.5580583,\"rgnoitv\":-177332270},-42730221.643623956]],\"MOgA-XF5vqI3gJ\",\"Vy6LA9FJokKUQ\"],\"lzaqnb\":[\"S6YkdmKiMqPB\",true,[false,\"3\",[false,\"MNG\"],true,[1670877170.2691236,1720190566,-1345478965.7560961,false],true],150161601,false,\"LuUR4F8\",false]},\"zxpiqpm\":{\"mihokuekyrl\":\"Q5h\"},\"ylimvdasgfsa\":{\"qmjldi\":[2128385846,744782183.3012446,false,-828926084.4567646,\"AxSh9Z0FnyYPh3yy3\",false,[\"x5R_tIm5Qvoo1FHYoSKp\",2010961878.1921215],{\"xrrkzfoeysay\":{\"ryjhnlr\":{\"vmmhr\":[-1515249438.6834478,false,[false,\"Aw1XaJ\"],true,[[{\"xpblr\":1342822550.0925407,\"vfjiub\":true,\"yicgsbh\":true,\"vbworopjpmku\":false,\"bbqfvsxfcq\":943329937,\"ikveyfwzkci\":\"57w9\",\"wiplzsaq\":\"e\"},[false,true,\"AWGDXn_fM\",false],true,{\"lzxexzqf\":false,\"hjnwysxzdgb\":-1886708986},\"-EBVH\"],{\"uyxgofcgc\":1889960426.403663,\"sdqdqvmfx\":292791564.8590025,\"uwtxaquybl\":true},[[-1567656717.0889773,1833234929.2248228],{\"csssdxrsh\":\"wOs0cIP3mBHrCtUCBLD\"}],{\"ihaeuaweq\":1147429125,\"kfmwfnhsh\":{\"ejmev\":true,\"fjzgctgxpsqb\":1846447036.5501394,\"ibqatw\":\"csX7VXO9XL\"}},true,[{\"sdbipuvdcd\":-850275361,\"jwkldg\":\"_5\",\"rqrxuh\":\"-07wz\",\"goqzefld\":true,\"oybfdvzo\":false,\"fcanwrhgt\":\"9-vno0i-vaJHy-KEp2\",\"fememk\":971120129.5817635},\"cRK9Xp06GsBL\",\"q1KEQt2zKGtqznV0\",true]]]},\"mhzvtqc\":\"kS_gltBC\",\"gczruv\":{\"ioivkagdb\":false,\"bedzysi\":-1261424778,\"xyjdgyk\":\"6LZL_UOw_PY_qpU\",\"cguipeebhksi\":120024372.66253027,\"hklzsubmh\":-2094602822.5544899,\"fqepv\":[1509285639,{\"ezsthhszf\":{\"pesauwkr\":[true,\"SXliD\",\"dcG1cJsQSD2Nb8Bq\",\"kx1bIL7OL_tBgdt4Jn\",\"9HLxaae4\",\"PrhQUc6TUaPn\",true],\"nolvcj\":\"G2WK0PDv3H59\"},\"gvqqhglakpie\":\"_Fs\",\"pdmuowuseztr\":\"bOEjWJEDrRsPMfh_S\",\"ngzohkqtpn\":{\"tahvyaczrx\":false,\"gkboqx\":{\"dtruqbohcu\":-291386660},\"ynayqaehx\":135795861,\"viyyeuxiqljp\":\"uUsCCWb0D0vMjSBA0hfK\",\"bjwtsw\":335509984,\"dtmleerzwzsg\":-2025388926.1660218,\"pnwkbogep\":-324078526.05327517},\"crvgiumkqwd\":\"N67htCY1sOOiutni1quj\"}],\"bxluewxt\":\"2I4pXnJiuA6Sui\"},\"iedsrp\":1098389734.8326893,\"lhuyjugjlm\":{\"txssz\":false,\"kayxiirwvs\":{\"yhatx\":{\"vwhwihyf\":[\"ZHyTCk4BktxN0J\",[false,67146966,\"P-TmV0dAy\",false]],\"xbgxiwupy\":\"cCjXQ9SZl8DWqHDe\",\"bruyups\":true,\"bvzedqcn\":false},\"mrjjei\":[\"auCSlAdRsHkGVBTrv\",{\"cptfxjirml\":\"aHb6i4s94caItgvirfMv\"}],\"simttrvdktgb\":{\"mhzvzxkrxy\":\"5rLIoeZW\",\"jxcbujbesyfd\":-474544470,\"rfpmaqcmnbcd\":false}},\"rhyhhhavessm\":\"rko5t6H\",\"uocxru\":{\"pmfrhwjdkyk\":[true,{\"ajgzljr\":1630118146,\"xabgphdc\":true,\"ubnzcslfkav\":false,\"pffmbwqzunfh\":{\"letelfdpdly\":true,\"hsxgzzxfuwu\":false}},{\"haaxyidlzeet\":1208256135.6025312},-848595761.3890954,{\"qzxloju\":false,\"iwlwo\":[false,-80596837,1211696662,\"AsCoSBl6Qv7Sr6Uhim\",\"z0d0GXFg\",-1527363329.7248015,false],\"ouqiy\":-1802315321.3998902,\"oubsptazbqut\":true}],\"ddzjrpd\":-292733662.05407774,\"mpavcizxdo\":{\"rgaddp\":-1741782528.8920853,\"yeoeknvaz\":858796810.7039539,\"pvapafh\":-1519206503.0197697,\"lqhpbshalah\":{\"rpeuxdfk\":[1225262705,false,\"wlcZ-DBnH6c2Np0Xp5\",-1098562280,\"w2bLIX7mi\",true,\"fwm\",\"8\"],\"tnxoz\":true,\"rmwpmkn\":\"wO7spytTEjHgYoF1\",\"qiyswypwf\":{\"hlamrawohhhc\":1400354060,\"udwfbe\":\"XHnRbPdWZhUuu\",\"unsibpodvuo\":-541943801.4142895,\"gyulexdd\":1933949239,\"ytkdrr\":true,\"wuafknt\":1172210799,\"kedfjokuewh\":-2039754870.271679},\"dacox\":-1437084116},\"olgdgwapfof\":{\"tglkzegp\":\"2pyBZ5F\",\"tcsstjzn\":{\"kysmhp\":\"Isfvvh2bUe3c7A\"},\"xyufe\":\"diWoD6pAbrbqAbedQ5d\",\"mrkgpyh\":149606483.4432244,\"kfbgrn\":[false,\"WZanhfS\",\"6Un8wPLqEG_mHA\",false,true,-2044765262.7453732]},\"nvlbjcgcynz\":1990203942,\"vvdjt\":{\"ftsqkgb\":[-424043989.5172975,-122040546.96606101,-1245364642,true]}},\"tngqntrgdw\":1916647978,\"aekbqwvety\":true},\"getzdyr\":true,\"ptskbbgj\":[-1789719407.528976,[\"u-H2dg_N1Cls0\",\"UO9tvLwU5\"]],\"yyzogsh\":-1864896338.9288921,\"jftgl\":\"pFA23r0_Uv8tBCK00Xdi\"}},\"axneti\":false}]},\"olgvwmm\":\"xqE7J40QH\"}" + + +if __name__ == "__main__": + conn = http.client.HTTPConnection('11.164.30.21', 3344) + conn.connect() + conn.putrequest('POST', '/mytest.service/echoMyList') + conn.putheader('Transfer-Encoding', 'chunked') + conn.putheader('Content-Type', 'application/json') + conn.endheaders() + + conn.send(b"%s\r\n" % hex(len(chunk1))[2:].encode()) + conn.send(b"%s\r\n" % chunk1) + + time.sleep(5) + + conn.send(b"%s\r\n" % hex(len(chunk2))[2:].encode()) + conn.send(b"%s\r\n" % chunk2) + + time.sleep(5) + # last chunk + conn.send(b"0\r\n\r\n") + + r = conn.getresponse() + print(r.status, r.reason, r.read().decode()) diff --git a/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo.yaml b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo.yaml new file mode 100644 index 0000000000000..ba754d86e1ebb --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo.yaml @@ -0,0 +1,100 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + http_protocol_options: + accept_http_10: true + route_config: + name: local_route + virtual_hosts: + - name: service + domains: + - "*" + routes: + - match: + prefix: "/abc" + route: + cluster: local_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + - match: + prefix: "/mytest_1" + route: + cluster: local_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + http_filters: + - name: envoy.filters.http.http_dubbo_transcoder + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.Transcoder + disable: false + auto_map: true + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: true + services: + - name: "common.sayHello" + version: "0.0.0" + methods: + get: "/abc/{path.name}" + name: "sayHello" + maps: + - name: "path.name" + mapTo: "params.0" + type: "java.lang.String" + - name: "common.sayHello" + version: "0.0.0" + map_service_url: /mytest_* + methods: + get: "{header.name}" + name: "sayHello" + body_template: "" + maps: + - name: "header.name" + mapTo: "params.0" + type: "java.lang.String" + - name: envoy.filters.http.router + typed_config: {} + clusters: + - name: local_service + connect_timeout: 5s + type: strict_dns + lb_policy: round_robin + upstream_config: + name: envoy.upstreams.http.tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.tcp.v3.TcpConnectionPoolProto + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 11.164.30.21 + port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 139.162.123.134 + # port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 114.55.31.224 + # port_value: 20880 \ No newline at end of file diff --git a/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_new.yaml b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_new.yaml new file mode 100644 index 0000000000000..81c0578135d55 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_new.yaml @@ -0,0 +1,228 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 3344 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + http_protocol_options: + accept_http_10: true + route_config: + name: local_route + virtual_hosts: + - name: service + domains: + - "*" + routes: + - match: + prefix: "/demoservice.DemoService" + route: + cluster: local_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + - match: + prefix: "/mytest.service" + route: + cluster: local_test_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + http_filters: + - name: envoy.filters.http.http_dubbo_transcoder + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: true + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + - name: "org.apache.dubbo.samples.basic.api.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello11" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + passthrough_setting: + passthrough_all_headers: true + passthrough_body: true + - name: "org.apache.dubbo.samples.basic.api.DemoService" + version: "0.0.0" + method_mapping: + name: "getEchoxx" + path_matcher: + match_pattern: "/mytest.service/sayHello22" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.util.List" + passthrough_setting: + passthrough_all_headers: true + passthrough_body: true + - name: "org.apache.dubbo.samples.basic.api.DemoService" + version: "0.0.0" + method_mapping: + name: "getEcho" + path_matcher: + match_pattern: "/mytest.service/sayHello33" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param1 + mapping_type: "java.util.List" + passthrough_setting: + passthrough_all_headers: true + passthrough_body: true + - name: "org.apache.dubbo.samples.basic.api.DemoService" + version: "0.0.0" + method_mapping: + name: "getEcho" + path_matcher: + match_pattern: "/mytest.service/sayHello44" + match_http_method_spec: ALL_POST + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + passthrough_setting: + passthrough_all_headers: true + passthrough_body: true + - name: "org.apache.dubbo.samples.basic.api.DemoService" + version: "0.0.0" + method_mapping: + name: "mapEcho" + path_matcher: + match_pattern: "/mytest.service/sayHello55" + match_http_method_spec: ALL_POST + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + passthrough_setting: + passthrough_headers: + keys: + - "token" + passthrough_body: true + - name: "org.apache.dubbo.demo.DemoService" + version: "0.0.0" + method_mapping: + name: "getEcho" + path_matcher: + match_pattern: "/mytest.service/sayHelloxxxxx" + match_http_method_spec: ALL_POST + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + - name: "org.apache.dubbo.demo.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_POST + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: my_param_1 + mapping_type: "java.lang.String" + - extract_key_spec: ALL_HEADER + extract_key: my_param_2 + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/demoservice.DemoService/sayHello/{my_param}" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_PATH + extract_key: my_param + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/demoservice.DemoService/sayHello/{my_param}" + match_http_method_spec: ALL_GET + - name: envoy.filters.http.router + typed_config: {} + clusters: + - name: local_test_service + connect_timeout: 5s + type: strict_dns + lb_policy: round_robin + upstream_config: + name: envoy.upstreams.http.dubbo_tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.dubbo_tcp.v3.DubboTcpConnectionPoolProto + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 30.225.8.108 + port_value: 20880 + - name: local_service + connect_timeout: 5s + type: strict_dns + lb_policy: round_robin + upstream_config: + name: envoy.upstreams.http.dubbo_tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.dubbo_tcp.v3.DubboTcpConnectionPoolProto + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 30.225.8.108 + port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 139.162.123.134 + # port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 114.55.31.224 + # port_value: 20880 \ No newline at end of file diff --git a/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route copy.yaml b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route copy.yaml new file mode 100644 index 0000000000000..1e928ae8a0fff --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route copy.yaml @@ -0,0 +1,151 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + http_protocol_options: + accept_http_10: true + route_config: + name: local_route + virtual_hosts: + - name: service + domains: + - "*" + routes: + - match: + prefix: "/demoservice.DemoService" + route: + cluster: local_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + - match: + prefix: "/mytest.service" + route: + cluster: local_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + typed_per_filter_config: + envoy.filters.http.http_dubbo_transcoder: + "@type": type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder + auto_mapping: true + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: true + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: key1 + mapping_type: "java.lang.String" + http_filters: + - name: envoy.filters.http.http_dubbo_transcoder + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder + auto_mapping: true + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: true + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_POST + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: my_param_1 + mapping_type: "java.lang.String" + - extract_key_spec: ALL_HEADER + extract_key: my_param_2 + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/demoservice.DemoService/sayHello/{my_param}" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_PATH + extract_key: my_param + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/demoservice.DemoService/sayHello/{my_param}" + match_http_method_spec: ALL_GET + attachment_from_header_keys: + - header_key_1 + - name: envoy.filters.http.router + typed_config: {} + clusters: + - name: local_service + connect_timeout: 5s + type: strict_dns + lb_policy: round_robin + upstream_config: + name: envoy.upstreams.http.tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.tcp.v3.TcpConnectionPoolProto + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 11.164.30.21 + port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 139.162.123.134 + # port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 114.55.31.224 + # port_value: 20880 \ No newline at end of file diff --git a/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route.yaml b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route.yaml new file mode 100644 index 0000000000000..b574b70de14b9 --- /dev/null +++ b/contrib/http_dubbo_transcoder/filters/http/test/test_data/dubbo_pre_route.yaml @@ -0,0 +1,166 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + http_protocol_options: + accept_http_10: true + route_config: + name: local_route + virtual_hosts: + - name: service + domains: + - "*" + typed_per_filter_config: + envoy.filters.http.http_dubbo_transcoder: + "@type": type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder + auto_mapping: true + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: true + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: key1 + mapping_type: "java.lang.String" + routes: + - match: + prefix: "/demoservice.DemoService" + route: + cluster: local_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + - match: + prefix: "/mytest.service" + route: + cluster: local_service + upgrade_configs: + - upgrade_type: "CONNECT" + connect_config: + allow_post: true + typed_per_filter_config: + envoy.filters.http.http_dubbo_transcoder: + "@type": type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello222" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: key1 + mapping_type: "java.lang.String" + http_filters: + - name: envoy.filters.http.http_dubbo_transcoder + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder + auto_mapping: true + url_unescape_spec: ALL_CHARACTERS_EXCEPT_RESERVED + request_validation_options: + reject_unknown_query_parameters: true + services_mapping: + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + - name: "common.sayHello" + version: "0.0.0" + method_mapping: + name: "getEcho" + path_matcher: + match_pattern: "/mytest.service/sayHello" + match_http_method_spec: ALL_POST + parameter_mapping: + - extract_key_spec: ALL_QUERY_PARAMETER + extract_key: my_param + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + parameter_mapping: + - extract_key_spec: ALL_HEADER + extract_key: my_param_1 + mapping_type: "java.lang.String" + - extract_key_spec: ALL_HEADER + extract_key: my_param_2 + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/demoservice.DemoService/sayHello/{my_param}" + match_http_method_spec: ALL_GET + parameter_mapping: + - extract_key_spec: ALL_PATH + extract_key: my_param + mapping_type: "java.lang.String" + - name: "demoservice.DemoService" + version: "0.0.0" + method_mapping: + name: "sayHello" + path_matcher: + match_pattern: "/demoservice.DemoService/sayHello/{my_param}" + match_http_method_spec: ALL_GET + attachment_from_header_keys: + - header_key_1 + - name: envoy.filters.http.router + typed_config: {} + clusters: + - name: local_service + connect_timeout: 5s + type: strict_dns + lb_policy: round_robin + upstream_config: + name: envoy.upstreams.http.tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.tcp.v3.TcpConnectionPoolProto + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 11.164.30.21 + port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 139.162.123.134 + # port_value: 20880 + # - lb_endpoints: + # - endpoint: + # address: + # socket_address: + # address: 114.55.31.224 + # port_value: 20880 \ No newline at end of file diff --git a/contrib/hyperscan/matching/input_matchers/test/matcher_test.cc b/contrib/hyperscan/matching/input_matchers/test/matcher_test.cc index dcd2e9667753c..6fb4ff25cc4c6 100644 --- a/contrib/hyperscan/matching/input_matchers/test/matcher_test.cc +++ b/contrib/hyperscan/matching/input_matchers/test/matcher_test.cc @@ -49,6 +49,10 @@ TEST(ThreadLocalTest, RaceScratchCreation) { for (auto& thread : threads) { thread->join(); } + + if (database) { + hs_free_database(database); + } } // Verify that even if thread local is not initialized, matcher can work and create thread local diff --git a/contrib/upstreams/http/dubbo_tcp/source/BUILD b/contrib/upstreams/http/dubbo_tcp/source/BUILD new file mode 100644 index 0000000000000..6c07446efa6ba --- /dev/null +++ b/contrib/upstreams/http/dubbo_tcp/source/BUILD @@ -0,0 +1,54 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_contrib_extension( + name = "config", + srcs = [ + "config.cc", + ], + hdrs = [ + "config.h", + ], + visibility = ["//visibility:public"], + deps = [ + ":upstream_request_lib", + "@envoy_api//contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "upstream_request_lib", + srcs = [ + "upstream_request.cc", + ], + hdrs = [ + "upstream_request.h", + ], + visibility = ["//visibility:public"], + deps = [ + "//envoy/http:codes_interface", + "//envoy/http:filter_interface", + "//envoy/upstream:upstream_interface", + "//source/common/common:assert_lib", + "//source/common/common:minimal_logger_lib", + "//source/common/common:utility_lib", + "//source/common/http:codes_lib", + "//source/common/http:header_map_lib", + "//source/common/http:headers_lib", + "//source/common/http:message_lib", + "//source/common/network:application_protocol_lib", + "//source/common/network:transport_socket_options_lib", + "//source/common/router:router_lib", + "//source/common/upstream:load_balancer_lib", + "//source/extensions/common/proxy_protocol:proxy_protocol_header_lib", + "//source/extensions/upstreams/http/tcp:upstream_request_lib", + ], +) diff --git a/contrib/upstreams/http/dubbo_tcp/source/config.cc b/contrib/upstreams/http/dubbo_tcp/source/config.cc new file mode 100644 index 0000000000000..78b75c774a389 --- /dev/null +++ b/contrib/upstreams/http/dubbo_tcp/source/config.cc @@ -0,0 +1,26 @@ +#include "config.h" + +#include "upstream_request.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Http { +namespace DubboTcp { + +Router::GenericConnPoolPtr DubboTcpGenericConnPoolFactory::createGenericConnPool( + Upstream::ThreadLocalCluster& thread_local_cluster, UpstreamProtocol, + const Router::RouteEntry& route_entry, + absl::optional, + Upstream::LoadBalancerContext* ctx) const { + auto ret = std::make_unique(thread_local_cluster, route_entry, ctx); + return (ret->valid() ? std::move(ret) : nullptr); +} + +REGISTER_FACTORY(DubboTcpGenericConnPoolFactory, Router::GenericConnPoolFactory); + +} // namespace DubboTcp +} // namespace Http +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/upstreams/http/dubbo_tcp/source/config.h b/contrib/upstreams/http/dubbo_tcp/source/config.h new file mode 100644 index 0000000000000..d9008f9d6ea4b --- /dev/null +++ b/contrib/upstreams/http/dubbo_tcp/source/config.h @@ -0,0 +1,37 @@ +#pragma once + +#include "contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3/tcp_connection_pool.pb.h" +#include "envoy/registry/registry.h" +#include "envoy/router/router.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Http { +namespace DubboTcp { + +/** + * Config registration for the TcpConnPool. @see Router::GenericConnPoolFactory + */ +class DubboTcpGenericConnPoolFactory : public Router::GenericConnPoolFactory { +public: + std::string name() const override { return "envoy.filters.connection_pools.http.dubbo_tcp"; } + std::string category() const override { return "envoy.upstreams"; } + Router::GenericConnPoolPtr + createGenericConnPool(Upstream::ThreadLocalCluster& thread_local_cluster, UpstreamProtocol upstream_protocol, + const Router::RouteEntry& route_entry, + absl::optional downstream_protocol, + Upstream::LoadBalancerContext* ctx) const override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::upstreams::http::dubbo_tcp::v3::DubboTcpConnectionPoolProto>(); + } +}; + +DECLARE_FACTORY(DubboTcpGenericConnPoolFactory); + +} // namespace DubboTcp +} // namespace Http +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/upstreams/http/dubbo_tcp/source/upstream_request.cc b/contrib/upstreams/http/dubbo_tcp/source/upstream_request.cc new file mode 100644 index 0000000000000..d1c625e773d6a --- /dev/null +++ b/contrib/upstreams/http/dubbo_tcp/source/upstream_request.cc @@ -0,0 +1,144 @@ +#include "upstream_request.h" + +#include +#include + +#include "envoy/upstream/upstream.h" + +#include "source/common/common/assert.h" +#include "source/common/common/utility.h" +#include "source/common/http/codes.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include "source/common/network/transport_socket_options_impl.h" +#include "source/common/router/router.h" +#include "source/extensions/common/proxy_protocol/proxy_protocol_header.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Http { +namespace DubboTcp { + +void TcpConnPool::onPoolReady(Envoy::Tcp::ConnectionPool::ConnectionDataPtr&& conn_data, + Upstream::HostDescriptionConstSharedPtr host) { + upstream_handle_ = nullptr; + Network::Connection& latched_conn = conn_data->connection(); + auto upstream = + std::make_unique(&callbacks_->upstreamToDownstream(), std::move(conn_data)); + callbacks_->onPoolReady(std::move(upstream), host, latched_conn.connectionInfoProvider(), + latched_conn.streamInfo(), {}); +} + +TcpUpstream::TcpUpstream(Router::UpstreamToDownstream* upstream_request, + Envoy::Tcp::ConnectionPool::ConnectionDataPtr&& upstream) + : upstream_request_(upstream_request), upstream_conn_data_(std::move(upstream)) { + upstream_conn_data_->connection().enableHalfClose(false); + upstream_conn_data_->addUpstreamCallbacks(*this); +} + +void TcpUpstream::encodeData(Buffer::Instance& data, bool end_stream) { + end_stream = false; + upstream_conn_data_->connection().write(data, end_stream); +} + +Envoy::Http::Status TcpUpstream::encodeHeaders(const Envoy::Http::RequestHeaderMap&, + bool end_stream) { + // Headers should only happen once, so use this opportunity to add the proxy + // proto header, if configured. + const Router::RouteEntry* route_entry = upstream_request_->route().routeEntry(); + ASSERT(route_entry != nullptr); + if (route_entry->connectConfig().has_value()) { + Buffer::OwnedImpl data; + const auto& connect_config = route_entry->connectConfig(); + if (connect_config->has_proxy_protocol_config()) { + Extensions::Common::ProxyProtocol::generateProxyProtoHeader( + connect_config->proxy_protocol_config(), *upstream_request_->connection(), data); + } + + if (data.length() != 0 || end_stream) { + upstream_conn_data_->connection().write(data, end_stream); + } + } + + // TcpUpstream::encodeHeaders is called after the UpstreamRequest is fully initialized. Alsoc use + // this time to synthesize the 200 response headers downstream to complete the CONNECT handshake. + Envoy::Http::ResponseHeaderMapPtr headers{ + Envoy::Http::createHeaderMap( + {{Envoy::Http::Headers::get().Status, "200"}})}; + upstream_request_->decodeHeaders(std::move(headers), false); + return Envoy::Http::okStatus(); +} + +void TcpUpstream::encodeTrailers(const Envoy::Http::RequestTrailerMap&) { + Buffer::OwnedImpl data; + upstream_conn_data_->connection().write(data, true); +} + +void TcpUpstream::readDisable(bool disable) { + if (upstream_conn_data_->connection().state() != Network::Connection::State::Open) { + return; + } + upstream_conn_data_->connection().readDisable(disable); +} + +void TcpUpstream::resetStream() { + upstream_request_ = nullptr; + upstream_conn_data_->connection().close(Network::ConnectionCloseType::NoFlush); +} + +void TcpUpstream::onUpstreamData(Buffer::Instance& data, bool end_stream) { + end_stream = true; + if (response_buffer_.length() == 0) { + if (data.length() < DUBBO_MAGIC_SIZE || data.peekBEInt() != DUBBO_MAGIC_NUMBER) { + data.drain(data.length()); + data.add(ProtocolErrorMessage); + upstream_request_->decodeData(data, end_stream); + return; + } + } + if (decodeDubboFrame(data) == DubboFrameDecodeStatus::Ok) { + uint32_t body_length_ = response_buffer_.peekBEInt(DUBBO_LENGTH_OFFSET); + data.move(response_buffer_, body_length_ + DUBBO_HEADER_SIZE); + upstream_request_->decodeData(data, end_stream); + } +} + +DubboFrameDecodeStatus TcpUpstream::decodeDubboFrame(Buffer::Instance& data) { + response_buffer_.move(data); + if (response_buffer_.length() < DUBBO_HEADER_SIZE) { + return DubboFrameDecodeStatus::NeedMoreData; + } + + uint32_t body_length_ = response_buffer_.peekBEInt(DUBBO_LENGTH_OFFSET); + if (response_buffer_.length() < body_length_ + DUBBO_HEADER_SIZE) { + return DubboFrameDecodeStatus::NeedMoreData; + } + + return DubboFrameDecodeStatus::Ok; +} + +void TcpUpstream::onEvent(Network::ConnectionEvent event) { + if (event != Network::ConnectionEvent::Connected && upstream_request_) { + upstream_request_->onResetStream(Envoy::Http::StreamResetReason::ConnectionTermination, ""); + } +} + +void TcpUpstream::onAboveWriteBufferHighWatermark() { + if (upstream_request_) { + upstream_request_->onAboveWriteBufferHighWatermark(); + } +} + +void TcpUpstream::onBelowWriteBufferLowWatermark() { + if (upstream_request_) { + upstream_request_->onBelowWriteBufferLowWatermark(); + } +} + +} // namespace DubboTcp +} // namespace Http +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/upstreams/http/dubbo_tcp/source/upstream_request.h b/contrib/upstreams/http/dubbo_tcp/source/upstream_request.h new file mode 100644 index 0000000000000..12d608276be85 --- /dev/null +++ b/contrib/upstreams/http/dubbo_tcp/source/upstream_request.h @@ -0,0 +1,114 @@ +#pragma once + +#include +#include + +#include "envoy/http/codec.h" +#include "envoy/tcp/conn_pool.h" +#include "envoy/upstream/thread_local_cluster.h" + +#include "source/common/buffer/watermark_buffer.h" +#include "source/common/common/cleanup.h" +#include "source/common/common/logger.h" +#include "source/common/config/well_known_names.h" +#include "source/common/router/upstream_request.h" +#include "source/common/stream_info/stream_info_impl.h" +#include "source/extensions/upstreams/http/tcp/upstream_request.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Http { +namespace DubboTcp { + +enum class DubboFrameDecodeStatus : uint8_t { + Ok = 0, + NeedMoreData = 1, + InvalidHeader = 2, +}; + +constexpr uint64_t DUBBO_HEADER_SIZE = 16; +constexpr uint64_t DUBBO_MAGIC_SIZE = 2; +constexpr uint16_t DUBBO_MAGIC_NUMBER = 0xdabb; +constexpr uint64_t DUBBO_LENGTH_OFFSET = 12; +constexpr absl::string_view ProtocolErrorMessage = "Not dubbo message"; + +class TcpConnPool : public Router::GenericConnPool, public Envoy::Tcp::ConnectionPool::Callbacks { +public: + TcpConnPool(Upstream::ThreadLocalCluster& thread_local_cluster, + const Router::RouteEntry& route_entry, Upstream::LoadBalancerContext* ctx) { + conn_pool_data_ = thread_local_cluster.tcpConnPool(route_entry.priority(), ctx); + } + 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; + } + Upstream::HostDescriptionConstSharedPtr host() const override { + return conn_pool_data_.value().host(); + } + + bool valid() { return conn_pool_data_.has_value(); } + + // Tcp::ConnectionPool::Callbacks + void onPoolFailure(ConnectionPool::PoolFailureReason reason, + absl::string_view transport_failure_reason, + Upstream::HostDescriptionConstSharedPtr host) override { + upstream_handle_ = nullptr; + callbacks_->onPoolFailure(reason, transport_failure_reason, host); + } + + void onPoolReady(Envoy::Tcp::ConnectionPool::ConnectionDataPtr&& conn_data, + Upstream::HostDescriptionConstSharedPtr host) override; + +private: + absl::optional conn_pool_data_; + Envoy::Tcp::ConnectionPool::Cancellable* upstream_handle_{}; + Router::GenericConnectionPoolCallbacks* callbacks_{}; +}; + +class TcpUpstream : public Router::GenericUpstream, + public Envoy::Tcp::ConnectionPool::UpstreamCallbacks { +public: + TcpUpstream(Router::UpstreamToDownstream* upstream_request, + Envoy::Tcp::ConnectionPool::ConnectionDataPtr&& upstream); + + // GenericUpstream + void encodeData(Buffer::Instance& data, bool end_stream) override; + void encodeMetadata(const Envoy::Http::MetadataMapVector&) override {} + Envoy::Http::Status encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_stream) override; + void encodeTrailers(const Envoy::Http::RequestTrailerMap&) override; + void readDisable(bool disable) override; + void resetStream() override; + void setAccount(Buffer::BufferMemoryAccountSharedPtr) override {} + + // Tcp::ConnectionPool::UpstreamCallbacks + void onUpstreamData(Buffer::Instance& data, bool end_stream) override; + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override; + void onBelowWriteBufferLowWatermark() override; + const StreamInfo::BytesMeterSharedPtr& bytesMeter() override { return bytes_meter_; } + +private: + DubboFrameDecodeStatus decodeDubboFrame(Buffer::Instance& data); + +private: + Router::UpstreamToDownstream* upstream_request_; + Envoy::Tcp::ConnectionPool::ConnectionDataPtr upstream_conn_data_; + StreamInfo::BytesMeterSharedPtr bytes_meter_{std::make_shared()}; + Buffer::OwnedImpl response_buffer_{}; +}; + +} // namespace DubboTcp +} // namespace Http +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/upstreams/http/dubbo_tcp/test/BUILD b/contrib/upstreams/http/dubbo_tcp/test/BUILD new file mode 100644 index 0000000000000..3629142ff6e3b --- /dev/null +++ b/contrib/upstreams/http/dubbo_tcp/test/BUILD @@ -0,0 +1,33 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "upstream_request_test", + srcs = ["upstream_request_test.cc"], + deps = [ + "//contrib/upstreams/http/dubbo_tcp/source:upstream_request_lib", + "//source/common/buffer:buffer_lib", + "//source/common/network:address_lib", + "//source/common/router:router_lib", + "//source/common/upstream:upstream_includes", + "//source/common/upstream:upstream_lib", + "//test/common/http:common_lib", + "//test/mocks:common_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/router:router_filter_interface", + "//test/mocks/router:router_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:instance_mocks", + "//test/mocks/upstream:upstream_mocks", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/contrib/upstreams/http/dubbo_tcp/test/upstream_request_test.cc b/contrib/upstreams/http/dubbo_tcp/test/upstream_request_test.cc new file mode 100644 index 0000000000000..b025a45111181 --- /dev/null +++ b/contrib/upstreams/http/dubbo_tcp/test/upstream_request_test.cc @@ -0,0 +1,296 @@ +#include "source/common/buffer/buffer_impl.h" +#include "source/common/network/address_impl.h" +#include "source/common/router/config_impl.h" +#include "source/common/router/router.h" +#include "source/common/router/upstream_request.h" +#include "source/extensions/common/proxy_protocol/proxy_protocol_header.h" + +#include "contrib/upstreams/http/dubbo_tcp/source/upstream_request.h" + +#include "test/common/http/common.h" +#include "test/mocks/common.h" +#include "test/mocks/router/mocks.h" +#include "test/mocks/router/router_filter_interface.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/instance.h" +#include "test/mocks/tcp/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using Envoy::Http::TestRequestHeaderMapImpl; +using Envoy::Router::UpstreamRequest; +using testing::_; +using testing::AnyNumber; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Http { +namespace DubboTcp { + +class TcpConnPoolTest : public ::testing::Test { +public: + TcpConnPoolTest() : host_(std::make_shared>()) { + NiceMock route_entry; + NiceMock cm; + cm.initializeThreadLocalClusters({"fake_cluster"}); + EXPECT_CALL(cm.thread_local_cluster_, tcpConnPool(_, _)) + .WillOnce(Return(Upstream::TcpPoolData([]() {}, &mock_pool_))); + conn_pool_ = std::make_unique(cm.thread_local_cluster_, route_entry, nullptr); + } + + std::unique_ptr conn_pool_; + Envoy::Tcp::ConnectionPool::MockInstance mock_pool_; + Router::MockGenericConnectionPoolCallbacks mock_generic_callbacks_; + std::shared_ptr> host_; + NiceMock cancellable_; +}; + +TEST_F(TcpConnPoolTest, Basic) { + NiceMock connection; + + EXPECT_CALL(mock_pool_, newConnection(_)).WillOnce(Return(&cancellable_)); + conn_pool_->newStream(&mock_generic_callbacks_); + + EXPECT_CALL(mock_generic_callbacks_, upstreamToDownstream()); + EXPECT_CALL(mock_generic_callbacks_, onPoolReady(_, _, _, _, _)); + auto data = std::make_unique>(); + EXPECT_CALL(*data, connection()).Times(AnyNumber()).WillRepeatedly(ReturnRef(connection)); + conn_pool_->onPoolReady(std::move(data), host_); +} + +TEST_F(TcpConnPoolTest, OnPoolFailure) { + EXPECT_CALL(mock_pool_, newConnection(_)).WillOnce(Return(&cancellable_)); + conn_pool_->newStream(&mock_generic_callbacks_); + + EXPECT_CALL(mock_generic_callbacks_, onPoolFailure(_, "foo", _)); + conn_pool_->onPoolFailure(Envoy::Tcp::ConnectionPool::PoolFailureReason::LocalConnectionFailure, + "foo", host_); + + // Make sure that the pool failure nulled out the pending request. + EXPECT_FALSE(conn_pool_->cancelAnyPendingStream()); +} + +TEST_F(TcpConnPoolTest, Cancel) { + // Initially cancel should fail as there is no pending request. + EXPECT_FALSE(conn_pool_->cancelAnyPendingStream()); + + EXPECT_CALL(mock_pool_, newConnection(_)).WillOnce(Return(&cancellable_)); + conn_pool_->newStream(&mock_generic_callbacks_); + + // Canceling should now return true as there was an active request. + EXPECT_TRUE(conn_pool_->cancelAnyPendingStream()); + + // A second cancel should return false as there is not a pending request. + EXPECT_FALSE(conn_pool_->cancelAnyPendingStream()); +} + +class TcpUpstreamTest : public ::testing::Test { +public: + TcpUpstreamTest() { + EXPECT_CALL(mock_router_filter_, downstreamHeaders()) + .Times(AnyNumber()) + .WillRepeatedly(Return(&request_)); + EXPECT_CALL(mock_router_filter_, cluster()).Times(AnyNumber()); + EXPECT_CALL(mock_router_filter_, callbacks()).Times(AnyNumber()); + mock_router_filter_.requests_.push_back(std::make_unique( + mock_router_filter_, std::make_unique>(), false, + false)); + auto data = std::make_unique>(); + EXPECT_CALL(*data, connection()).Times(AnyNumber()).WillRepeatedly(ReturnRef(connection_)); + tcp_upstream_ = + std::make_unique(mock_router_filter_.requests_.front().get(), std::move(data)); + } + ~TcpUpstreamTest() override { EXPECT_CALL(mock_router_filter_, config()).Times(AnyNumber()); } + +protected: + TestRequestHeaderMapImpl request_{{":method", "CONNECT"}, + {":path", "/"}, + {":protocol", "bytestream"}, + {":scheme", "https"}, + {":authority", "host"}}; + NiceMock connection_; + NiceMock mock_router_filter_; + Envoy::Tcp::ConnectionPool::MockConnectionData* mock_connection_data_; + std::unique_ptr tcp_upstream_; +}; + +TEST_F(TcpUpstreamTest, Basic) { + // Swallow the request headers and generate response headers. + EXPECT_CALL(connection_, write(_, false)).Times(0); + EXPECT_CALL(mock_router_filter_, onUpstreamHeaders(200, _, _, false)); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); + + // Proxy the data. + EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); + Buffer::OwnedImpl buffer("foo"); + tcp_upstream_->encodeData(buffer, false); + + // Metadata is swallowed. + Envoy::Http::MetadataMapVector metadata_map_vector; + tcp_upstream_->encodeMetadata(metadata_map_vector); + + // Forward data. + Buffer::OwnedImpl response1("bar"); + // The dubbo forces end_stream to be true in the onupStreamData function. + // If data is incompatible with dubbo protocol, data will be set to 'Not dubbo message'. + EXPECT_CALL(mock_router_filter_, onUpstreamData(BufferStringEqual("Not dubbo message"), _, true)); + tcp_upstream_->onUpstreamData(response1, false); +} + +TEST_F(TcpUpstreamTest, V1Header) { + envoy::config::core::v3::ProxyProtocolConfig* proxy_config = + mock_router_filter_.route_.route_entry_.connect_config_->mutable_proxy_protocol_config(); + proxy_config->set_version(envoy::config::core::v3::ProxyProtocolConfig::V1); + mock_router_filter_.client_connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(std::make_shared("1.2.3.4", 5)); + mock_router_filter_.client_connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(std::make_shared("4.5.6.7", 8)); + + Buffer::OwnedImpl expected_data; + Extensions::Common::ProxyProtocol::generateProxyProtoHeader( + *proxy_config, mock_router_filter_.client_connection_, expected_data); + + // encodeHeaders now results in the proxy proto header being sent. + EXPECT_CALL(connection_, write(BufferEqual(&expected_data), false)); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); + + // Data is proxied as usual. + EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); + Buffer::OwnedImpl buffer("foo"); + tcp_upstream_->encodeData(buffer, false); +} + +TEST_F(TcpUpstreamTest, V2Header) { + envoy::config::core::v3::ProxyProtocolConfig* proxy_config = + mock_router_filter_.route_.route_entry_.connect_config_->mutable_proxy_protocol_config(); + proxy_config->set_version(envoy::config::core::v3::ProxyProtocolConfig::V2); + mock_router_filter_.client_connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(std::make_shared("1.2.3.4", 5)); + mock_router_filter_.client_connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(std::make_shared("4.5.6.7", 8)); + + Buffer::OwnedImpl expected_data; + Extensions::Common::ProxyProtocol::generateProxyProtoHeader( + *proxy_config, mock_router_filter_.client_connection_, expected_data); + + // encodeHeaders now results in the proxy proto header being sent. + EXPECT_CALL(connection_, write(BufferEqual(&expected_data), false)); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); + + // Data is proxied as usual. + EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); + Buffer::OwnedImpl buffer("foo"); + tcp_upstream_->encodeData(buffer, false); +} + +TEST_F(TcpUpstreamTest, TrailersEndStream) { + // Swallow the headers. + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); + + EXPECT_CALL(connection_, write(BufferStringEqual(""), true)); + Envoy::Http::TestRequestTrailerMapImpl trailers{{"foo", "bar"}}; + tcp_upstream_->encodeTrailers(trailers); +} + +TEST_F(TcpUpstreamTest, HeaderEndStreamHalfClose) { + EXPECT_CALL(connection_, write(BufferStringEqual(""), true)); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, true).ok()); +} + +TEST_F(TcpUpstreamTest, ReadDisable) { + EXPECT_CALL(connection_, readDisable(true)); + tcp_upstream_->readDisable(true); + + EXPECT_CALL(connection_, readDisable(false)); + tcp_upstream_->readDisable(false); + + // Once the connection is closed, don't touch it. + connection_.state_ = Network::Connection::State::Closed; + EXPECT_CALL(connection_, readDisable(_)).Times(0); + tcp_upstream_->readDisable(true); +} + +TEST_F(TcpUpstreamTest, UpstreamEvent) { + // Make sure upstream disconnects result in stream reset. + EXPECT_CALL(mock_router_filter_, + onUpstreamReset(Envoy::Http::StreamResetReason::ConnectionTermination, "", _)); + tcp_upstream_->onEvent(Network::ConnectionEvent::RemoteClose); +} + +TEST_F(TcpUpstreamTest, Watermarks) { + EXPECT_CALL(mock_router_filter_, callbacks()).Times(AnyNumber()); + EXPECT_CALL(mock_router_filter_.callbacks_, onDecoderFilterAboveWriteBufferHighWatermark()); + tcp_upstream_->onAboveWriteBufferHighWatermark(); + + EXPECT_CALL(mock_router_filter_.callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); + tcp_upstream_->onBelowWriteBufferLowWatermark(); +} + +TEST_F(TcpUpstreamTest, EmptyConnectConfig) { + NiceMock route_entry; + EXPECT_FALSE(route_entry.connect_config_.has_value()); + EXPECT_CALL(mock_router_filter_.route_, routeEntry()).WillOnce(Return(&route_entry)); + + // Swallow the request headers and generate response headers. + EXPECT_CALL(connection_, write(_, false)).Times(0); + EXPECT_CALL(mock_router_filter_, onUpstreamHeaders(200, _, _, false)); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); + + // Proxy the data. + EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); + Buffer::OwnedImpl buffer("foo"); + tcp_upstream_->encodeData(buffer, false); + + // Metadata is swallowed. + Envoy::Http::MetadataMapVector metadata_map_vector; + tcp_upstream_->encodeMetadata(metadata_map_vector); + + // Forward data. + Buffer::OwnedImpl response1("bar"); + // The dubbo forces end_stream to be true in the onupStreamData function. + // If data is incompatible with dubbo protocol, data will be set to 'Not dubbo message'. + EXPECT_CALL(mock_router_filter_, onUpstreamData(BufferStringEqual("Not dubbo message"), _, true)); + tcp_upstream_->onUpstreamData(response1, false); +} + +TEST_F(TcpUpstreamTest, DubboMessage) { + // If data is dubbo message, it will be sent to filter. + Buffer::OwnedImpl response2; + response2.add(std::string({'\xda', '\xbb', 0x42, 20})); + response2.writeBEInt(static_cast(1)); + std::string content({'I', 0x00, 0x00, 0x00, 0x01, 0x05, 'h', 'e', 'l', 'l', 'o'}); + response2.writeBEInt(static_cast(content.size())); + response2.add(content); + EXPECT_CALL(mock_router_filter_, + onUpstreamData(BufferStringEqual(response2.toString()), _, true)); + tcp_upstream_->onUpstreamData(response2, false); +} + +TEST_F(TcpUpstreamTest, ConnectConfig) { + NiceMock route_entry; + route_entry.connect_config_ = absl::make_optional(); + EXPECT_TRUE(route_entry.connect_config_.has_value()); + EXPECT_CALL(mock_router_filter_.route_, routeEntry()).WillOnce(Return(&route_entry)); + + // Swallow the request headers and generate response headers. + EXPECT_CALL(connection_, write(_, false)).Times(0); + EXPECT_CALL(mock_router_filter_, onUpstreamHeaders(200, _, _, false)); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); + + // Proxy the data. + EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); + Buffer::OwnedImpl buffer("foo"); + tcp_upstream_->encodeData(buffer, false); +} + +} // namespace DubboTcp +} // namespace Http +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/envoy/http/filter.h b/envoy/http/filter.h index 361eacc244742..39ef8d12182d5 100644 --- a/envoy/http/filter.h +++ b/envoy/http/filter.h @@ -494,6 +494,12 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, * Allows modifying the decoding buffer. May only be called before any data has been continued * past the calling filter. */ +#if defined(ALIMESH) + virtual void modifyDecodingBuffer(std::function callback, + bool /* backup_for_replace */) { + return modifyDecodingBuffer(callback); + } +#endif virtual void modifyDecodingBuffer(std::function callback) PURE; /** @@ -723,6 +729,12 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, * @param original_response_headers Headers used for logging in the access logs and for charging * stats. Ignored if null. */ +#if defined(ALIMESH) + virtual bool recreateStream(const ResponseHeaderMap* original_response_headers, + bool /* use_original_request_body */) { + return recreateStream(original_response_headers); + } +#endif virtual bool recreateStream(const ResponseHeaderMap* original_response_headers) PURE; /** @@ -751,6 +763,11 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, * load balancing. */ virtual absl::optional upstreamOverrideHost() const PURE; + +#if defined(ALIMESH) + virtual bool needBuffering() const { return false; } + virtual void setNeedBuffering(bool) {} +#endif }; /** diff --git a/envoy/http/filter_factory.h b/envoy/http/filter_factory.h index 71d6bc51f19fe..a6ca0755a0798 100644 --- a/envoy/http/filter_factory.h +++ b/envoy/http/filter_factory.h @@ -22,6 +22,14 @@ class FilterChainFactoryCallbacks; */ using FilterFactoryCb = std::function; +// Struct of canonical filter name and HTTP stream filter factory callback. +struct NamedHttpFilterFactoryCb { + // Canonical filter name. + std::string name; + // Factory function used to create filter instances. + Http::FilterFactoryCb factory_cb; +}; + /** * Simple struct of additional contextual information of HTTP filter, e.g. filter config name * from configuration, canonical filter name, etc. diff --git a/envoy/http/header_map.h b/envoy/http/header_map.h index 44dddc49d52f2..1d45282f9c4ba 100644 --- a/envoy/http/header_map.h +++ b/envoy/http/header_map.h @@ -84,7 +84,14 @@ class LowerCaseString { // Implicit conversion to absl::string_view. operator absl::string_view() const { return string_; } +#if defined(ALIMESH) + virtual ~LowerCaseString() = default; + +protected: +#else private: +#endif + void lower() { std::transform(string_.begin(), string_.end(), string_.begin(), absl::ascii_tolower); } diff --git a/envoy/redis/BUILD b/envoy/redis/BUILD new file mode 100644 index 0000000000000..619ad8ccdb6b9 --- /dev/null +++ b/envoy/redis/BUILD @@ -0,0 +1,14 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "async_client_interface", + hdrs = ["async_client.h"], +) diff --git a/envoy/redis/async_client.h b/envoy/redis/async_client.h new file mode 100644 index 0000000000000..98e8d88ebc00b --- /dev/null +++ b/envoy/redis/async_client.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Envoy { + +namespace Event { + +class Dispatcher; +} + +namespace Redis { + +struct AsyncClientConfig { +public: + AsyncClientConfig(std::string&& username, std::string&& password, int op_timeout_milliseconds, + std::map&& params) + : auth_username_(std::move(username)), auth_password_(std::move(password)), + op_timeout_(op_timeout_milliseconds), buffer_flush_timeout_(3), params_(std::move(params)) { + } + const std::string auth_username_; + const std::string auth_password_; + + const std::chrono::milliseconds op_timeout_; + const uint32_t max_buffer_size_before_flush_{1024}; + const std::chrono::milliseconds buffer_flush_timeout_; + const uint32_t max_upstream_unknown_connections_{100}; + const bool enable_command_stats_{false}; + const std::map params_; +}; + +/** + * A handle to an outbound request. + */ +class PoolRequest { +public: + virtual ~PoolRequest() = default; + + /** + * Cancel the request. No further request callbacks will be called. + */ + virtual void cancel() PURE; +}; + +class AsyncClient { +public: + class Callbacks { + public: + virtual ~Callbacks() = default; + + virtual void onSuccess(std::string_view query, std::string&& response) PURE; + + virtual void onFailure(std::string_view query) PURE; + }; + + virtual ~AsyncClient() = default; + + virtual void initialize(AsyncClientConfig config) PURE; + + virtual PoolRequest* send(std::string&& query, Callbacks& callbacks) PURE; + + virtual Event::Dispatcher& dispatcher() PURE; +}; + +using AsyncClientPtr = std::unique_ptr; + +} // namespace Redis +} // namespace Envoy diff --git a/envoy/router/BUILD b/envoy/router/BUILD index 28de863193e02..4f90118315209 100644 --- a/envoy/router/BUILD +++ b/envoy/router/BUILD @@ -61,6 +61,9 @@ envoy_cc_library( envoy_cc_library( name = "router_interface", hdrs = ["router.h"], + alimesh_deps = [ + "//contrib/envoy/http:active_redirect_policy_interface", + ], external_deps = ["abseil_optional"], deps = [ ":internal_redirect_interface", diff --git a/envoy/router/router.h b/envoy/router/router.h index 7f9326160ba56..db7b71832f8c1 100644 --- a/envoy/router/router.h +++ b/envoy/router/router.h @@ -33,6 +33,10 @@ #include "absl/types/optional.h" +#if defined(ALIMESH) +#include "contrib/envoy/http/active_redirect_policy.h" +#endif + namespace Envoy { namespace Upstream { @@ -1098,6 +1102,9 @@ class RouteEntry : public ResponseEntry { */ virtual const std::string& routeName() const PURE; +#if defined(ALIMESH) + virtual const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const PURE; +#endif /** * @return RouteStatsContextOptRef the config needed to generate route level stats. */ diff --git a/envoy/router/scopes.h b/envoy/router/scopes.h index 47ba039756eb1..5dbe09e25d256 100644 --- a/envoy/router/scopes.h +++ b/envoy/router/scopes.h @@ -8,6 +8,12 @@ namespace Envoy { namespace Router { +class ScopedConfig; +class ScopeKeyBuilder; + +using ScopedConfigConstSharedPtr = std::shared_ptr; +using ScopeKeyBuilderPtr = std::unique_ptr; + /** * Scope key fragment base class. */ @@ -86,12 +92,19 @@ class ScopeKeyBuilder { public: virtual ~ScopeKeyBuilder() = default; +#if defined(ALIMESH) + virtual ScopeKeyPtr computeScopeKey(const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info, + std::function& recompute) const PURE; + virtual ScopeKeyPtr computeScopeKey(const Http::HeaderMap&) const PURE; +#else /** * Based on the incoming HTTP request headers, returns the hash value of its scope key. * @param headers the request headers to match the scoped routing configuration against. * @return unique_ptr of the scope key computed from header. */ virtual ScopeKeyPtr computeScopeKey(const Http::HeaderMap&) const PURE; +#endif }; /** @@ -100,7 +113,6 @@ class ScopeKeyBuilder { class ScopedConfig : public Envoy::Config::ConfigProvider::Config { public: ~ScopedConfig() override = default; - /** * Based on the scope key, returns the configuration to use for selecting a target route. * The scope key can be got via ScopeKeyBuilder. @@ -109,10 +121,17 @@ class ScopedConfig : public Envoy::Config::ConfigProvider::Config { * @return ConfigConstSharedPtr the router's Config matching the request headers. */ virtual ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr& scope_key) const PURE; -}; -using ScopedConfigConstSharedPtr = std::shared_ptr; -using ScopeKeyBuilderPtr = std::unique_ptr; +#if defined(ALIMESH) + virtual ConfigConstSharedPtr + getRouteConfig(const ScopeKeyBuilder* builder, const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info = nullptr) const PURE; + virtual ScopeKeyPtr computeScopeKey(const ScopeKeyBuilder*, const Http::HeaderMap&, + const StreamInfo::StreamInfo* = nullptr) const { + return {}; + }; +#endif +}; } // namespace Router } // namespace Envoy diff --git a/envoy/server/BUILD b/envoy/server/BUILD index 6361b6958a9f9..35ce2e3ef9153 100644 --- a/envoy/server/BUILD +++ b/envoy/server/BUILD @@ -183,6 +183,7 @@ envoy_cc_library( ":process_context_interface", "//envoy/access_log:access_log_interface", "//envoy/api:api_interface", + "//envoy/config:dynamic_extension_config_provider_interface", "//envoy/config:typed_config_interface", "//envoy/config:typed_metadata_interface", "//envoy/grpc:context_interface", diff --git a/envoy/server/factory_context.h b/envoy/server/factory_context.h index 6384230c571d1..baed5778bd4af 100644 --- a/envoy/server/factory_context.h +++ b/envoy/server/factory_context.h @@ -7,6 +7,7 @@ #include "envoy/access_log/access_log.h" #include "envoy/common/random_generator.h" #include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/dynamic_extension_config_provider.h" #include "envoy/config/typed_config.h" #include "envoy/config/typed_metadata.h" #include "envoy/grpc/context.h" @@ -36,9 +37,15 @@ #include "source/common/protobuf/protobuf.h" namespace Envoy { +namespace Filter { +template class FilterConfigProviderManager; +} // namespace Filter namespace Server { namespace Configuration { +using HttpExtensionConfigProviderSharedPtr = + std::shared_ptr>; + // Shared factory context between server factories and cluster factories class FactoryContextBase { public: @@ -144,6 +151,14 @@ class CommonFactoryContext : public FactoryContextBase { virtual Init::Manager& initManager() PURE; }; +class FactoryContext; + +using DownstreamHTTPFilterConfigProviderManager = + Filter::FilterConfigProviderManager; +using DownstreamHTTPFilterConfigProviderManagerSharedPtr = + std::shared_ptr; + /** * ServerFactoryContext is an specialization of common interface for downstream and upstream network * filters. The implementation guarantees the lifetime is no shorter than server. It could be used @@ -177,6 +192,14 @@ class ServerFactoryContext : public virtual CommonFactoryContext { * @return envoy::config::bootstrap::v3::Bootstrap& the servers bootstrap configuration. */ virtual envoy::config::bootstrap::v3::Bootstrap& bootstrap() PURE; + + /** + * Returns the downstream HTTP filter config provider manager. + * + * @return DownstreamHTTPFilterConfigProviderManagerSharedPtr + */ + virtual DownstreamHTTPFilterConfigProviderManagerSharedPtr + downstreamHttpFilterConfigProviderManager() PURE; }; /** @@ -321,11 +344,11 @@ class ListenerFactoryContext : public virtual FactoryContext { using ProtocolOptionsFactoryContext = Server::Configuration::TransportSocketFactoryContext; /** - * FactoryContext for upstream HTTP filters. + * FactoryContext for upstream filters. */ -class UpstreamHttpFactoryContext { +class UpstreamFactoryContext { public: - virtual ~UpstreamHttpFactoryContext() = default; + virtual ~UpstreamFactoryContext() = default; /** * @return ServerFactoryContext which lifetime is no shorter than the server. diff --git a/envoy/server/filter_config.h b/envoy/server/filter_config.h index 4ce60c523085e..44fd27ee858ce 100644 --- a/envoy/server/filter_config.h +++ b/envoy/server/filter_config.h @@ -150,8 +150,9 @@ class NamedUpstreamNetworkFilterConfigFactory : public ProtocolOptionsFactory { * unable to produce a factory with the provided parameters, it should throw an EnvoyException in * the case of general error. The returned callback should always be initialized. */ - virtual Network::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message& config, - CommonFactoryContext& context) PURE; + virtual Network::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& config, + UpstreamFactoryContext& context) PURE; std::string category() const override { return "envoy.filters.upstream_network"; } @@ -291,7 +292,7 @@ class UpstreamHttpFilterConfigFactory : public virtual HttpFilterConfigFactoryBa */ virtual Http::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message& config, const std::string& stat_prefix, - Server::Configuration::UpstreamHttpFactoryContext& context) PURE; + Server::Configuration::UpstreamFactoryContext& context) PURE; }; } // namespace Configuration diff --git a/envoy/stream_info/stream_info.h b/envoy/stream_info/stream_info.h index b5fbd9108fd3c..d2f0bae9cfb7c 100644 --- a/envoy/stream_info/stream_info.h +++ b/envoy/stream_info/stream_info.h @@ -910,6 +910,19 @@ class StreamInfo { * @param failure_reason the downstream transport failure reason. */ virtual void setDownstreamTransportFailureReason(absl::string_view failure_reason) PURE; + +#ifdef ALIMESH + /** + * @param key the filter state key set by wasm filter. + * @param value the filter state value set by wasm filter. + */ + virtual void setCustomSpanTag(std::string_view key, std::string_view value) PURE; + + /** + * @return the key-value map of filter states set by wasm filter. + */ + virtual const absl::flat_hash_map& getCustomSpanTagMap() const PURE; +#endif }; // An enum representation of the Proxy-Status error space. diff --git a/envoy/upstream/BUILD b/envoy/upstream/BUILD index 8178b9d494e81..9b43ecc3bc2eb 100644 --- a/envoy/upstream/BUILD +++ b/envoy/upstream/BUILD @@ -39,6 +39,9 @@ envoy_cc_library( "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], + alimesh_deps = [ + "//envoy/redis:async_client_interface", + ], ) envoy_cc_library( @@ -142,6 +145,9 @@ envoy_cc_library( "//envoy/http:async_client_interface", "//envoy/tcp:async_tcp_client_interface", ], + alimesh_deps = [ + "//envoy/redis:async_client_interface", + ], ) envoy_cc_library( diff --git a/envoy/upstream/outlier_detection.h b/envoy/upstream/outlier_detection.h index 038e81b88e29a..aefd450cac1da 100644 --- a/envoy/upstream/outlier_detection.h +++ b/envoy/upstream/outlier_detection.h @@ -110,6 +110,10 @@ class DetectorHostMonitor { * and LocalOrigin type returns success rate for local origin errors. */ virtual double successRate(SuccessRateMonitorType type) const PURE; + +#if defined(ALIMESH) + virtual void forceEjectHost() PURE; +#endif }; using DetectorHostMonitorPtr = std::unique_ptr; diff --git a/envoy/upstream/thread_local_cluster.h b/envoy/upstream/thread_local_cluster.h index 7b356942c9a8c..0dd4162950c52 100644 --- a/envoy/upstream/thread_local_cluster.h +++ b/envoy/upstream/thread_local_cluster.h @@ -2,6 +2,7 @@ #include "envoy/common/pure.h" #include "envoy/http/async_client.h" +#include "envoy/redis/async_client.h" #include "envoy/tcp/async_tcp_client.h" #include "envoy/upstream/load_balancer.h" #include "envoy/upstream/upstream.h" @@ -142,6 +143,9 @@ class ThreadLocalCluster { * owns the client. */ virtual Http::AsyncClient& httpAsyncClient() PURE; +#if defined(ALIMESH) + virtual Redis::AsyncClient& redisAsyncClient() PURE; +#endif /** * @param context the optional load balancer context. diff --git a/generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto b/generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto new file mode 100644 index 0000000000000..d8f2175dc8898 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto @@ -0,0 +1,98 @@ +syntax = "proto3"; + +package envoy.extensions.access_loggers.grpc.v3; + +import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/config_source.proto"; +import "envoy/config/core/v3/grpc_service.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.access_loggers.grpc.v3"; +option java_outer_classname = "AlsProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: gRPC Access Log Service (ALS)] + +// Configuration for the built-in *envoy.access_loggers.http_grpc* +// :ref:`AccessLog `. This configuration will +// populate :ref:`StreamAccessLogsMessage.http_logs +// `. +// [#extension: envoy.access_loggers.http_grpc] +message HttpGrpcAccessLogConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.accesslog.v2.HttpGrpcAccessLogConfig"; + + CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message = {required: true}]; + + // Additional request headers to log in :ref:`HTTPRequestProperties.request_headers + // `. + repeated string additional_request_headers_to_log = 2; + + // Additional response headers to log in :ref:`HTTPResponseProperties.response_headers + // `. + repeated string additional_response_headers_to_log = 3; + + // Additional response trailers to log in :ref:`HTTPResponseProperties.response_trailers + // `. + repeated string additional_response_trailers_to_log = 4; +} + +// Configuration for the built-in *envoy.access_loggers.tcp_grpc* type. This configuration will +// populate *StreamAccessLogsMessage.tcp_logs*. +// [#extension: envoy.access_loggers.tcp_grpc] +message TcpGrpcAccessLogConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.accesslog.v2.TcpGrpcAccessLogConfig"; + + CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message = {required: true}]; +} + +// Common configuration for gRPC access logs. +// [#next-free-field: 8] +message CommonGrpcAccessLogConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.accesslog.v2.CommonGrpcAccessLogConfig"; + + // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier + // `. This allows the + // access log server to differentiate between different access logs coming from the same Envoy. + string log_name = 1 [(validate.rules).string = {min_len: 1}]; + + // The gRPC service for the access log service. + config.core.v3.GrpcService grpc_service = 2 [(validate.rules).message = {required: true}]; + + // API version for access logs service transport protocol. This describes the access logs service + // gRPC endpoint and version of messages used on the wire. + config.core.v3.ApiVersion transport_api_version = 6 + [(validate.rules).enum = {defined_only: true}]; + + // Interval for flushing access logs to the gRPC stream. Logger will flush requests every time + // this interval is elapsed, or when batch size limit is hit, whichever comes first. Defaults to + // 1 second. + google.protobuf.Duration buffer_flush_interval = 3 [(validate.rules).duration = {gt {}}]; + + // Soft size limit in bytes for access log entries buffer. Logger will buffer requests until + // this limit it hit, or every time flush interval is elapsed, whichever comes first. Setting it + // to zero effectively disables the batching. Defaults to 16384. + google.protobuf.UInt32Value buffer_size_bytes = 4; + + // Additional filter state objects to log in :ref:`filter_state_objects + // `. + // Logger will call `FilterState::Object::serializeAsProto` to serialize the filter state object. + repeated string filter_state_objects_to_log = 5; + + // Sets the retry policy when the establishment of a gRPC stream fails. + // If the stream succeeds once in establishing If the stream succeeds + // at least once in establishing itself, no retry will be performed + // no matter what gRPC status is received. Note that only + // :ref:`num_retries ` + // will be used in this configuration. + config.core.v3.RetryPolicy grpc_stream_retry_policy = 7; +} diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 85e240221347b..cab4e9731a18f 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -71,6 +71,7 @@ const static bool should_log = true; FUNCTION(matcher) \ FUNCTION(misc) \ FUNCTION(mongo) \ + FUNCTION(overload) \ FUNCTION(multi_connection) \ FUNCTION(oauth2) \ FUNCTION(quic) \ diff --git a/source/common/filter/config_discovery_impl.h b/source/common/filter/config_discovery_impl.h index 232285f57e933..2d413fb21a0e7 100644 --- a/source/common/filter/config_discovery_impl.h +++ b/source/common/filter/config_discovery_impl.h @@ -87,7 +87,8 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImplBa if (!tls->isShutdown()) { tls->runOnAllThreads([](OptRef tls) { tls->config_ = {}; }, // Extend the lifetime of TLS by capturing main_config_, because - // otherwise, the callback to clear TLS worker content is not executed. + // otherwise, the callback to clear TLS worker content is not + // executed. [main_config = main_config_]() { // Explicitly delete TLS on the main thread. main_config->tls_.reset(); @@ -174,20 +175,12 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImplBa const ProtobufTypes::MessagePtr default_configuration_; }; -// Struct of canonical filter name and HTTP stream filter factory callback. -struct NamedHttpFilterFactoryCb { - // Canonical filter name. - std::string name; - // Factory function used to create filter instances. - Http::FilterFactoryCb factory_cb; -}; - // Implementation of a HTTP dynamic filter config provider. // NeutralHttpFilterConfigFactory can either be a NamedHttpFilterConfigFactory // or an UpstreamHttpFilterConfigFactory. template class HttpDynamicFilterConfigProviderImpl - : public DynamicFilterConfigProviderImpl { + : public DynamicFilterConfigProviderImpl { public: HttpDynamicFilterConfigProviderImpl( FilterConfigSubscriptionSharedPtr& subscription, @@ -211,7 +204,7 @@ class HttpDynamicFilterConfigProviderImpl } private: - NamedHttpFilterFactoryCb + Http::NamedHttpFilterFactoryCb instantiateFilterFactory(const Protobuf::Message& message) const override { auto* factory = Registry::FactoryRegistry::getFactoryByType( message.GetTypeName()); @@ -224,10 +217,10 @@ class HttpDynamicFilterConfigProviderImpl }; template -class NetworkDynamicFilterConfigProviderImpl +class NetworkDynamicFilterConfigProviderImplBase : public DynamicFilterConfigProviderImpl { public: - NetworkDynamicFilterConfigProviderImpl( + NetworkDynamicFilterConfigProviderImplBase( FilterConfigSubscriptionSharedPtr& subscription, const absl::flat_hash_set& require_type_urls, Server::Configuration::ServerFactoryContext& server_context, FactoryCtx& factory_context, @@ -239,14 +232,6 @@ class NetworkDynamicFilterConfigProviderImpl last_filter_in_filter_chain, filter_chain_type, stat_prefix, listener_filter_matcher), server_context_(server_context), factory_context_(factory_context) {} - void validateMessage(const std::string& config_name, const Protobuf::Message& message, - const std::string& factory_name) const override { - auto* factory = - Registry::FactoryRegistry::getFactory(factory_name); - const bool is_terminal_filter = factory->isTerminalFilterByProto(message, server_context_); - Config::Utility::validateTerminalFilters(config_name, factory_name, filter_chain_type_, - is_terminal_filter, last_filter_in_filter_chain_); - } private: Network::FilterFactoryCb @@ -256,10 +241,45 @@ class NetworkDynamicFilterConfigProviderImpl return factory->createFilterFactoryFromProto(message, factory_context_); } +protected: Server::Configuration::ServerFactoryContext& server_context_; FactoryCtx& factory_context_; }; +template +class DownstreamNetworkDynamicFilterConfigProviderImpl + : public NetworkDynamicFilterConfigProviderImplBase { +public: + using NetworkDynamicFilterConfigProviderImplBase< + FactoryCtx, NeutralNetworkFilterConfigFactory>::NetworkDynamicFilterConfigProviderImplBase; + + void validateMessage(const std::string& config_name, const Protobuf::Message& message, + const std::string& factory_name) const override { + auto* factory = + Registry::FactoryRegistry::getFactory(factory_name); + const bool is_terminal_filter = + factory->isTerminalFilterByProto(message, this->server_context_); + Config::Utility::validateTerminalFilters(config_name, factory_name, this->filter_chain_type_, + is_terminal_filter, + this->last_filter_in_filter_chain_); + } +}; + +template +class UpstreamNetworkDynamicFilterConfigProviderImpl + : public NetworkDynamicFilterConfigProviderImplBase { +public: + using NetworkDynamicFilterConfigProviderImplBase< + FactoryCtx, NeutralNetworkFilterConfigFactory>::NetworkDynamicFilterConfigProviderImplBase; + + void validateMessage(const std::string&, const Protobuf::Message&, + const std::string&) const override { + // Upstream network filters don't use the concept of terminal filters. + } +}; + // Implementation of a listener dynamic filter config provider. template class ListenerDynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImpl { @@ -592,7 +612,7 @@ class FilterConfigProviderManagerImpl : public FilterConfigProviderManagerImplBa // HTTP filter class HttpFilterConfigProviderManagerImpl : public FilterConfigProviderManagerImpl< - Server::Configuration::NamedHttpFilterConfigFactory, NamedHttpFilterFactoryCb, + Server::Configuration::NamedHttpFilterConfigFactory, Http::NamedHttpFilterFactoryCb, Server::Configuration::FactoryContext, HttpDynamicFilterConfigProviderImpl< Server::Configuration::FactoryContext, @@ -619,10 +639,10 @@ class HttpFilterConfigProviderManagerImpl // HTTP filter class UpstreamHttpFilterConfigProviderManagerImpl : public FilterConfigProviderManagerImpl< - Server::Configuration::UpstreamHttpFilterConfigFactory, NamedHttpFilterFactoryCb, - Server::Configuration::UpstreamHttpFactoryContext, + Server::Configuration::UpstreamHttpFilterConfigFactory, Http::NamedHttpFilterFactoryCb, + Server::Configuration::UpstreamFactoryContext, HttpDynamicFilterConfigProviderImpl< - Server::Configuration::UpstreamHttpFactoryContext, + Server::Configuration::UpstreamFactoryContext, Server::Configuration::UpstreamHttpFilterConfigFactory>> { public: absl::string_view statPrefix() const override { return "http_filter."; } @@ -648,7 +668,7 @@ class NetworkFilterConfigProviderManagerImpl : public FilterConfigProviderManagerImpl< Server::Configuration::NamedNetworkFilterConfigFactory, Network::FilterFactoryCb, Server::Configuration::FactoryContext, - NetworkDynamicFilterConfigProviderImpl< + DownstreamNetworkDynamicFilterConfigProviderImpl< Server::Configuration::FactoryContext, Server::Configuration::NamedNetworkFilterConfigFactory>> { public: @@ -674,9 +694,9 @@ class NetworkFilterConfigProviderManagerImpl class UpstreamNetworkFilterConfigProviderManagerImpl : public FilterConfigProviderManagerImpl< Server::Configuration::NamedUpstreamNetworkFilterConfigFactory, Network::FilterFactoryCb, - Server::Configuration::CommonFactoryContext, - NetworkDynamicFilterConfigProviderImpl< - Server::Configuration::CommonFactoryContext, + Server::Configuration::UpstreamFactoryContext, + UpstreamNetworkDynamicFilterConfigProviderImpl< + Server::Configuration::UpstreamFactoryContext, Server::Configuration::NamedUpstreamNetworkFilterConfigFactory>> { public: absl::string_view statPrefix() const override { return "upstream_network_filter."; } @@ -688,11 +708,9 @@ class UpstreamNetworkFilterConfigProviderManagerImpl Server::Configuration::ServerFactoryContext& factory_context) const override { return default_factory->isTerminalFilterByProto(message, factory_context); } - void validateFilters(const std::string& filter_config_name, const std::string& filter_type, - const std::string& filter_chain_type, bool is_terminal_filter, - bool last_filter_in_filter_chain) const override { - Config::Utility::validateTerminalFilters(filter_config_name, filter_type, filter_chain_type, - is_terminal_filter, last_filter_in_filter_chain); + void validateFilters(const std::string&, const std::string&, const std::string&, bool, + bool) const override { + // Upstream network filters don't use the concept of terminal filters. } const std::string getConfigDumpType() const override { return "ecds_filter_upstream_network"; } }; diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index ce3ca75592b94..977f897f7f695 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -33,6 +33,11 @@ const AsyncStreamImpl::RouteEntryImpl::ConnectConfigOptRef AsyncStreamImpl::RouteEntryImpl::connect_config_nullopt_; const std::list AsyncStreamImpl::NullCommonConfig::internal_only_headers_; +#if defined(ALIMESH) +const Router::InternalActiveRedirectPoliciesImpl + AsyncStreamImpl::RouteEntryImpl::internal_active_redirect_policy_; +#endif + AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster, Stats::Store& stats_store, Event::Dispatcher& dispatcher, const LocalInfo::LocalInfo& local_info, diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 83cee970b40b1..68f7f33014e55 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -324,6 +324,12 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, const ConnectConfigOptRef connectConfig() const override { return connect_config_nullopt_; } +#if defined(ALIMESH) + const Router::InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { + return internal_active_redirect_policy_; + } +#endif + bool includeAttemptCountInRequest() const override { return false; } bool includeAttemptCountInResponse() const override { return false; } const Router::RouteEntry::UpgradeMap& upgradeMap() const override { return upgrade_map_; } @@ -343,6 +349,10 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, static const std::multimap opaque_config_; static const NullPathMatchCriterion path_match_criterion_; +#if defined(ALIMESH) + static const Router::InternalActiveRedirectPoliciesImpl internal_active_redirect_policy_; +#endif + Router::RouteEntry::UpgradeMap upgrade_map_; const std::string& cluster_name_; absl::optional timeout_; diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index a07eb825f789b..7d4ec929aecfe 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -538,6 +538,14 @@ class ConnectionManagerConfig { * Connection Lifetime. */ virtual bool addProxyProtocolConnectionState() const PURE; + +#if defined(ALIMESH) + /** + * @return the timeout seconds will be set in the "Keep-Alive" response header. + * Zero indicates this behavior is disabled. + */ + virtual std::chrono::seconds keepaliveHeaderTimeout() const PURE; +#endif }; } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index f567d659f7022..97bdcc5372725 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -750,8 +750,12 @@ void ConnectionManagerImpl::RdsRouteConfigUpdateRequester::requestRouteConfigUpd const auto& host_header = absl::AsciiStrToLower(parent_.request_headers_->getHostValue()); requestVhdsUpdate(host_header, thread_local_dispatcher, std::move(route_config_updated_cb)); return; - } else if (scope_key_builder_.has_value()) { +#if defined(ALIMESH) + Router::ScopeKeyPtr scope_key = parent_.snapped_scoped_routes_config_->computeScopeKey( + scope_key_builder_.ptr(), *parent_.request_headers_, &parent_.connection()->streamInfo()); +#else Router::ScopeKeyPtr scope_key = scope_key_builder_->computeScopeKey(*parent_.request_headers_); +#endif // If scope_key is not null, the scope exists but RouteConfiguration is not initialized. if (scope_key != nullptr) { requestSrdsUpdate(std::move(scope_key), thread_local_dispatcher, @@ -998,6 +1002,16 @@ void ConnectionManagerImpl::ActiveStream::onStreamMaxDurationReached() { void ConnectionManagerImpl::ActiveStream::chargeStats(const ResponseHeaderMap& headers) { uint64_t response_code = Utility::getResponseStatus(headers); + +#if defined(ALIMESH) + if (Grpc::Common::hasGrpcContentType(headers)) { + absl::optional grpc_status = Grpc::Common::getGrpcStatus(headers); + if (grpc_status.has_value()) { + response_code = Grpc::Utility::grpcToHttpStatus(grpc_status.value()); + } + } +#endif + filter_manager_.streamInfo().response_code_ = response_code; if (filter_manager_.streamInfo().health_check_request_) { @@ -1502,11 +1516,18 @@ void ConnectionManagerImpl::startDrainSequence() { } void ConnectionManagerImpl::ActiveStream::snapScopedRouteConfig() { +#if defined(ALIMESH) + snapped_route_config_ = snapped_scoped_routes_config_->getRouteConfig( + connection_manager_.config_.scopeKeyBuilder().ptr(), *request_headers_, + &connection()->streamInfo()); +#else // NOTE: if a RDS subscription hasn't got a RouteConfiguration back, a Router::NullConfigImpl is // returned, in that case we let it pass. auto scope_key = connection_manager_.config_.scopeKeyBuilder()->computeScopeKey(*request_headers_); snapped_route_config_ = snapped_scoped_routes_config_->getRouteConfig(scope_key); +#endif + if (snapped_route_config_ == nullptr) { ENVOY_STREAM_LOG(trace, "can't find SRDS scope.", *this); // TODO(stevenzzzz): Consider to pass an error message to router filter, so that it can @@ -1775,6 +1796,21 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ResponseHeaderMap& heade blockRouteCache(); } +#if defined(ALIMESH) + if (!state_.is_tunneling_ && connection_manager_.codec_->protocol() < Protocol::Http2) { + if (connection_manager_.drain_state_ != DrainState::NotDraining) { + // If the connection manager is draining send "Connection: Close" on HTTP/1.1 connections. + // Do not do this for H2 (which drains via GOAWAY) or Upgrade or CONNECT (as the + // payload is no longer HTTP/1.1) + headers.setReferenceConnection(Headers::get().ConnectionValues.Close); + } else if (connection_manager_.config_.keepaliveHeaderTimeout().count() != 0) { + headers.setKeepAlive(absl::StrCat( + "timeout=", + std::to_string(connection_manager_.config_.keepaliveHeaderTimeout().count()))); + headers.setReferenceConnection(Headers::get().ConnectionValues.KeepAlive); + } + } +#else if (connection_manager_.drain_state_ != DrainState::NotDraining && connection_manager_.codec_->protocol() < Protocol::Http2) { // If the connection manager is draining send "Connection: Close" on HTTP/1.1 connections. @@ -1784,6 +1820,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ResponseHeaderMap& heade headers.setReferenceConnection(Headers::get().ConnectionValues.Close); } } +#endif if (connection_manager_tracing_config_.has_value()) { if (connection_manager_tracing_config_->operation_name_ == Tracing::OperationName::Ingress) { @@ -2116,17 +2153,42 @@ void ConnectionManagerImpl::ActiveStream::onRequestDataTooLarge() { connection_manager_.stats_.named_.downstream_rq_too_large_.inc(); } +#if defined(ALIMESH) void ConnectionManagerImpl::ActiveStream::recreateStream( StreamInfo::FilterStateSharedPtr filter_state) { + return recreateStream(filter_state, false); +} +void ConnectionManagerImpl::ActiveStream::recreateStream( + StreamInfo::FilterStateSharedPtr filter_state, bool use_original_request_body) { +#else +void ConnectionManagerImpl::ActiveStream::recreateStream( + StreamInfo::FilterStateSharedPtr filter_state) { +#endif ResponseEncoder* response_encoder = response_encoder_; response_encoder_ = nullptr; Buffer::InstancePtr request_data = std::make_unique(); +#if defined(ALIMESH) + bool proxy_body = false; + const auto& original_buffered_request_data = filter_manager_.originalBufferedRequestData(); + if (use_original_request_body && original_buffered_request_data != nullptr && + original_buffered_request_data->length() > 0) { + proxy_body = true; + request_data->move(*original_buffered_request_data); + } else { + const auto& buffered_request_data = filter_manager_.bufferedRequestData(); + proxy_body = buffered_request_data != nullptr && buffered_request_data->length() > 0; + if (proxy_body) { + request_data->move(*buffered_request_data); + } + } +#else const auto& buffered_request_data = filter_manager_.bufferedRequestData(); const bool proxy_body = buffered_request_data != nullptr && buffered_request_data->length() > 0; if (proxy_body) { request_data->move(*buffered_request_data); } +#endif response_encoder->getStream().removeCallbacks(*this); diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index e79a6a81c0826..9cd02c97fbe35 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -285,6 +285,10 @@ class ConnectionManagerImpl : Logger::Loggable, } void disarmRequestTimeout() override; void resetIdleTimer() override; +#if defined(ALIMESH) + void recreateStream(StreamInfo::FilterStateSharedPtr filter_state, + bool backup_for_replace) override; +#endif void recreateStream(StreamInfo::FilterStateSharedPtr filter_state) override; void resetStream(Http::StreamResetReason reset_reason = Http::StreamResetReason::LocalReset, absl::string_view transport_failure_reason = "") override; diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 107cdd9fe2700..810e41081f214 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -242,6 +242,11 @@ ConnectionManagerUtility::MutateRequestHeadersResult ConnectionManagerUtility::m cleanInternalHeaders(request_headers, edge_request, route_config.internalOnlyHeaders()); } +#if defined(ALIMESH) + request_headers.setReferenceKey(Http::CustomHeaders::get().AliExtendedValues.XEnvoyOriginalHost, + request_headers.getHostValue()); +#endif + if (config.userAgent()) { request_headers.setEnvoyDownstreamServiceCluster(config.userAgent().value()); const HeaderEntry* user_agent_header = request_headers.UserAgent(); diff --git a/source/common/http/filter_chain_helper.cc b/source/common/http/filter_chain_helper.cc index 80281d098ca80..8a7086f1d6fbc 100644 --- a/source/common/http/filter_chain_helper.cc +++ b/source/common/http/filter_chain_helper.cc @@ -5,32 +5,14 @@ #include "envoy/registry/registry.h" -#include "source/common/common/empty_string.h" #include "source/common/common/fmt.h" #include "source/common/config/utility.h" #include "source/common/http/utility.h" #include "source/common/protobuf/utility.h" -#include "source/extensions/filters/http/common/pass_through_filter.h" namespace Envoy { namespace Http { -// Allows graceful handling of missing configuration for ECDS. -class MissingConfigFilter : public Http::PassThroughDecoderFilter { -public: - Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override { - decoder_callbacks_->streamInfo().setResponseFlag(StreamInfo::ResponseFlag::NoFilterConfigFound); - decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError, EMPTY_STRING, nullptr, - absl::nullopt, EMPTY_STRING); - return Http::FilterHeadersStatus::StopIteration; - } -}; - -static Http::FilterFactoryCb MissingConfigFilterFactory = - [](Http::FilterChainFactoryCallbacks& cb) { - cb.addStreamDecoderFilter(std::make_shared()); - }; - void FilterChainUtility::createFilterChainForFactories( Http::FilterChainManager& manager, const FilterChainOptions& options, const FilterFactoriesList& filter_factories) { @@ -43,7 +25,7 @@ void FilterChainUtility::createFilterChainForFactories( auto config = filter_config_provider->config(); if (config.has_value()) { - Filter::NamedHttpFilterFactoryCb& factory_cb = config.value().get(); + Http::NamedHttpFilterFactoryCb& factory_cb = config.value().get(); manager.applyFilterFactoryCb({filter_config_provider->name(), factory_cb.name}, factory_cb.factory_cb); continue; @@ -75,16 +57,5 @@ FilterChainUtility::createSingletonUpstreamFilterConfigProviderManager( return upstream_filter_config_provider_manager; } -std::shared_ptr -FilterChainUtility::createSingletonDownstreamFilterConfigProviderManager( - Server::Configuration::ServerFactoryContext& context) { - std::shared_ptr - downstream_filter_config_provider_manager = - context.singletonManager().getTyped( - SINGLETON_MANAGER_REGISTERED_NAME(downstream_filter_config_provider_manager), - [] { return std::make_shared(); }); - return downstream_filter_config_provider_manager; -} - } // namespace Http } // namespace Envoy diff --git a/source/common/http/filter_chain_helper.h b/source/common/http/filter_chain_helper.h index fe925ef306109..3a8926b2fa54d 100644 --- a/source/common/http/filter_chain_helper.h +++ b/source/common/http/filter_chain_helper.h @@ -6,24 +6,39 @@ #include "envoy/filter/config_provider_manager.h" #include "envoy/http/filter.h" +#include "source/common/common/empty_string.h" #include "source/common/common/logger.h" #include "source/common/filter/config_discovery_impl.h" #include "source/common/http/dependency_manager.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" namespace Envoy { namespace Http { -using DownstreamFilterConfigProviderManager = - Filter::FilterConfigProviderManager; using UpstreamFilterConfigProviderManager = - Filter::FilterConfigProviderManager; + Filter::FilterConfigProviderManager; + +// Allows graceful handling of missing configuration for ECDS. +class MissingConfigFilter : public Http::PassThroughDecoderFilter { +public: + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override { + decoder_callbacks_->streamInfo().setResponseFlag(StreamInfo::ResponseFlag::NoFilterConfigFound); + decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError, EMPTY_STRING, nullptr, + absl::nullopt, EMPTY_STRING); + return Http::FilterHeadersStatus::StopIteration; + } +}; + +static Http::FilterFactoryCb MissingConfigFilterFactory = + [](Http::FilterChainFactoryCallbacks& cb) { + cb.addStreamDecoderFilter(std::make_shared()); + }; class FilterChainUtility : Logger::Loggable { public: using FilterFactoriesList = - std::list>; + std::list>; using FiltersList = Protobuf::RepeatedPtrField< envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter>; @@ -31,10 +46,6 @@ class FilterChainUtility : Logger::Loggable { const FilterChainOptions& options, const FilterFactoriesList& filter_factories); - static std::shared_ptr - createSingletonDownstreamFilterConfigProviderManager( - Server::Configuration::ServerFactoryContext& context); - static std::shared_ptr createSingletonUpstreamFilterConfigProviderManager( Server::Configuration::ServerFactoryContext& context); @@ -49,9 +60,9 @@ template class FilterChainHelper : Logger::Loggable { public: using FilterFactoriesList = - std::list>; + std::list>; using FilterConfigProviderManager = - Filter::FilterConfigProviderManager; + Filter::FilterConfigProviderManager; FilterChainHelper(FilterConfigProviderManager& filter_config_provider_manager, Server::Configuration::ServerFactoryContext& server_context, diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 5f71e1cfb5f4b..051c2b2258dfb 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -421,8 +421,22 @@ const Buffer::Instance* ActiveStreamDecoderFilter::decodingBuffer() { return parent_.buffered_request_data_.get(); } +#if defined(ALIMESH) void ActiveStreamDecoderFilter::modifyDecodingBuffer( std::function callback) { + modifyDecodingBuffer(callback, false); +} +void ActiveStreamDecoderFilter::modifyDecodingBuffer( + std::function callback, bool backup_for_replace) { + // Backup the original buffer only during the first replacement. + if (backup_for_replace && !parent_.original_buffered_request_data_) { + parent_.original_buffered_request_data_ = std::make_unique(); + parent_.original_buffered_request_data_->move(*parent_.buffered_request_data_.get()); + } +#else +void ActiveStreamDecoderFilter::modifyDecodingBuffer( + std::function callback) { +#endif ASSERT(parent_.state_.latest_data_decoding_filter_ == this); callback(*parent_.buffered_request_data_.get()); } @@ -1535,7 +1549,15 @@ void ActiveStreamDecoderFilter::setDecoderBufferLimit(uint32_t limit) { uint32_t ActiveStreamDecoderFilter::decoderBufferLimit() { return parent_.buffer_limit_; } +#if defined(ALIMESH) +bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) { + return recreateStream(headers, false); +} +bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers, + bool use_original_request_body) { +#else bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) { +#endif // Because the filter's and the HCM view of if the stream has a body and if // the stream is complete may differ, re-check bytesReceived() to make sure // there was no body from the HCM's point of view. @@ -1558,7 +1580,12 @@ bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) parent_.filter_manager_callbacks_.chargeStats(*headers); } +#if defined(ALIMESH) + parent_.filter_manager_callbacks_.recreateStream(parent_.streamInfo().filterState(), + use_original_request_body); +#else parent_.filter_manager_callbacks_.recreateStream(parent_.streamInfo().filterState()); +#endif return true; } diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 610ca49fd9ea3..83f4e885cb14c 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -210,6 +210,10 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, void continueDecoding() override; const Buffer::Instance* decodingBuffer() override; +#if defined(ALIMESH) + void modifyDecodingBuffer(std::function callback, + bool backup_for_replace) override; +#endif void modifyDecodingBuffer(std::function callback) override; void sendLocalReply(Code code, absl::string_view body, @@ -232,6 +236,10 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, removeDownstreamWatermarkCallbacks(DownstreamWatermarkCallbacks& watermark_callbacks) override; void setDecoderBufferLimit(uint32_t limit) override; uint32_t decoderBufferLimit() override; +#if defined(ALIMESH) + bool recreateStream(const Http::ResponseHeaderMap* original_response_headers, + bool use_original_request_body) override; +#endif bool recreateStream(const Http::ResponseHeaderMap* original_response_headers) override; void addUpstreamSocketOptions(const Network::Socket::OptionsSharedPtr& options) override; @@ -240,7 +248,10 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, Buffer::BufferMemoryAccountSharedPtr account() const override; void setUpstreamOverrideHost(absl::string_view host) override; absl::optional upstreamOverrideHost() const override; - +#if defined(ALIMESH) + bool needBuffering() const override { return need_buffering_; } + void setNeedBuffering(bool need) override { need_buffering_ = need; } +#endif // Each decoder filter instance checks if the request passed to the filter is gRPC // so that we can issue gRPC local responses to gRPC requests. Filter's decodeHeaders() // called here may change the content type, so we must check it before the call. @@ -255,6 +266,9 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, StreamDecoderFilterSharedPtr handle_; bool is_grpc_request_{}; +#if defined(ALIMESH) + bool need_buffering_{}; +#endif }; using ActiveStreamDecoderFilterPtr = std::unique_ptr; @@ -452,6 +466,12 @@ class FilterManagerCallbacks { /** * Called when the stream should be re-created, e.g. for an internal redirect. */ +#if defined(ALIMESH) + virtual void recreateStream(StreamInfo::FilterStateSharedPtr filter_state, + bool /* use_original_request_body */) { + recreateStream(filter_state); + } +#endif virtual void recreateStream(StreamInfo::FilterStateSharedPtr filter_state) PURE; /** @@ -817,6 +837,10 @@ class FilterManager : public ScopeTrackedObject, Buffer::InstancePtr& bufferedRequestData() { return buffered_request_data_; } +#if defined(ALIMESH) + Buffer::InstancePtr& originalBufferedRequestData() { return original_buffered_request_data_; } +#endif + void contextOnContinue(ScopeTrackedObjectStack& tracked_object_stack); void onDownstreamReset() { state_.saw_downstream_reset_ = true; } @@ -999,6 +1023,9 @@ class FilterManager : public ScopeTrackedObject, std::unique_ptr request_metadata_map_vector_; Buffer::InstancePtr buffered_response_data_; Buffer::InstancePtr buffered_request_data_; +#if defined(ALIMESH) + Buffer::InstancePtr original_buffered_request_data_; +#endif uint32_t buffer_limit_{0}; uint32_t high_watermark_count_{0}; std::list watermark_callbacks_; diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 5c1252a8e2c69..37fdfea890ae4 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -128,6 +128,17 @@ class CustomHeaderValues { const std::string AcceptEncoding{"Accept-Encoding"}; const std::string Wildcard{"*"}; } VaryValues; + +#if defined(ALIMESH) + struct { + const LowerCaseString TriArriveTime{"req-arrive-time"}; + const LowerCaseString TriCostTime{"req-cost-time"}; + const LowerCaseString TriStartTime{"req-start-time"}; + const LowerCaseString TriRespStartTime{"resp-start-time"}; + const LowerCaseString EnvoyOriginalHost{"original-host"}; + const LowerCaseString XEnvoyOriginalHost{"x-envoy-original-host"}; + } AliExtendedValues; +#endif }; using CustomHeaders = ConstSingleton; diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 7fe3e00f61680..fe78afe0b2df6 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -549,6 +549,7 @@ Status ConnectionImpl::completeCurrentHeader() { // Account for ":" and "\r\n" bytes between the header key value pair. getBytesMeter().addHeaderBytesReceived(CRLF_SIZE + 1); + ENVOY_LOG(trace, "CRLF_SIZE + 1"); // TODO(10646): Switch to use HeaderUtility::checkHeaderNameForUnderscores(). RETURN_IF_ERROR(checkHeaderNameForUnderscores()); @@ -783,6 +784,8 @@ Status ConnectionImpl::onHeaderFieldImpl(const char* data, size_t length) { ASSERT(dispatching_); getBytesMeter().addHeaderBytesReceived(length); + absl::string_view log_header_field{data, length}; + ENVOY_LOG(trace, "count header filed: {}, length: {}", log_header_field, length); // We previously already finished up the headers, these headers are // now trailers. @@ -808,6 +811,8 @@ Status ConnectionImpl::onHeaderValueImpl(const char* data, size_t length) { ASSERT(dispatching_); getBytesMeter().addHeaderBytesReceived(length); + absl::string_view log_header_value{data, length}; + ENVOY_LOG(trace, "count header value: {}, length: {}", log_header_value, length); if (header_parsing_state_ == HeaderParsingState::Done && !enableTrailers()) { // Ignore trailers. diff --git a/source/common/http/utility.h b/source/common/http/utility.h index 57b0e435ed032..1e638508bc970 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -223,6 +223,21 @@ class PercentEncoding { */ static std::string urlDecodeQueryParameter(absl::string_view encoded); + /** + * Encodes string view for storing it as a query parameter according to the + * x-www-form-urlencoded spec: + * https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm + * @param value supplies string to be encoded. + * @return std::string encoded string according to + * https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm + * + * Summary: + * The x-www-form-urlencoded spec mandates that all ASCII codepoints are %-encoded except the + * following: ALPHA | DIGIT | * | - | . | _ + * + * NOTE: the space character is encoded as %20, NOT as the + character + */ + private: // Encodes string view to its percent encoded representation, with start index. static std::string encode(absl::string_view value, const size_t index, diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index 19c3acf919cb7..1f9df858683d0 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -508,7 +508,17 @@ void redact(Protobuf::Message* message, bool ancestor_is_sensitive) { redact(reflection->MutableRepeatedMessage(message, field_descriptor, i), sensitive); } } else if (reflection->HasField(*message, field_descriptor)) { +#if defined(ALIMESH) + // The content of the poll_delay field cannot be displayed because the typed_config field of + // PrivateKeyProvider is set to "udpa.annotations.sensitive". However, the content of the + // poll_delay field is not sensitive data. To facilitate debugging, we support outputting + // the value of the poll_delay field to the config_dump file. + if (field_descriptor->name() != "poll_delay") { + redact(reflection->MutableMessage(message, field_descriptor), sensitive); + } +#else redact(reflection->MutableMessage(message, field_descriptor), sensitive); +#endif } } else if (sensitive) { // Base case: replace strings and bytes with "[redacted]" and clear all others. diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 57b2f86787319..eb15f9a3d0f2e 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -39,6 +39,15 @@ ((message).has_##field_name() ? DurationUtil::durationToMilliseconds((message).field_name()) \ : (default_value)) +#if defined(ALIMESH) +// Obtain the seconds value of a google.protobuf.Duration field if set. Otherwise, return the +// default value. +#define PROTOBUF_GET_SECONDS_OR_DEFAULT(message, field_name, default_value) \ + ((message).has_##field_name() ? DurationUtil::durationToSeconds((message).field_name()) \ + : (default_value)) + +#endif + // Obtain the string value if the field is set. Otherwise, return the default value. #define PROTOBUF_GET_STRING_OR_DEFAULT(message, field_name, default_value) \ (!(message).field_name().empty() ? (message).field_name() : (default_value)) diff --git a/source/common/redis/BUILD b/source/common/redis/BUILD new file mode 100644 index 0000000000000..7687e3a4b92aa --- /dev/null +++ b/source/common/redis/BUILD @@ -0,0 +1,27 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", + "envoy_select_enable_http3", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "async_client_lib", + srcs = ["async_client_impl.cc"], + hdrs = ["async_client_impl.h"], + deps = [ + "//envoy/stats:stats_macros", + "//envoy/redis:async_client_interface", + + "//source/common/network:address_lib", + "//source/common/upstream:upstream_lib", + "//source/common/upstream:load_balancer_lib", + + "//source/extensions/common/redis:cluster_refresh_manager_lib", + "//source/extensions/filters/network/common/redis:raw_client_lib", + ], +) diff --git a/source/common/redis/async_client_impl.cc b/source/common/redis/async_client_impl.cc new file mode 100644 index 0000000000000..334fcc8443e56 --- /dev/null +++ b/source/common/redis/async_client_impl.cc @@ -0,0 +1,294 @@ +#include "source/common/redis/async_client_impl.h" + +#include +#include +#include +#include + +#include "source/common/common/assert.h" +#include "source/common/common/logger.h" +#include "source/common/stats/utility.h" + +namespace Envoy { +namespace Redis { + +AsyncClientImpl::AsyncClientImpl( + Upstream::ThreadLocalCluster* cluster, Event::Dispatcher& dispatcher, + RawClientFactory& client_factory, Stats::ScopeSharedPtr&& stats_scope, + RedisCommandStatsSharedPtr redis_command_stats, + Extensions::Common::Redis::ClusterRefreshManagerSharedPtr refresh_manager) + : cluster_name_(cluster->info()->name()), cluster_(cluster), dispatcher_(dispatcher), + drain_timer_(dispatcher.createTimer([this]() -> void { drainClients(); })), + client_factory_(client_factory), config_(new ConfigImpl()), stats_scope_(stats_scope), + redis_command_stats_(std::move(redis_command_stats)), + redis_cluster_stats_{REDIS_CLUSTER_STATS(POOL_COUNTER(*stats_scope_))}, + refresh_manager_(std::move(refresh_manager)) { + + host_set_member_update_cb_handle_ = cluster_->prioritySet().addMemberUpdateCb( + [this](const std::vector& hosts_added, + const std::vector& hosts_removed) -> void { + onHostsAdded(hosts_added); + onHostsRemoved(hosts_removed); + }); + + for (const auto& i : cluster_->prioritySet().hostSetsPerPriority()) { + for (auto& host : i->hosts()) { + host_address_map_[host->address()->asString()] = host; + } + } +} + +AsyncClientImpl::~AsyncClientImpl() { + while (!pending_requests_.empty()) { + pending_requests_.pop_front(); + } + while (!client_map_.empty()) { + client_map_.begin()->second->redis_client_->close(); + } + while (!clients_to_drain_.empty()) { + (*clients_to_drain_.begin())->redis_client_->close(); + } +} + +void AsyncClientImpl::initialize(AsyncClientConfig config) { + while (!client_map_.empty()) { + client_map_.begin()->second->redis_client_->close(); + } + while (!clients_to_drain_.empty()) { + (*clients_to_drain_.begin())->redis_client_->close(); + } + + config_ = std::make_shared(config); + auth_username_ = config.auth_username_; + auth_password_ = config.auth_password_; + params_ = config.params_; +} + +PoolRequest* AsyncClientImpl::send(std::string&& query, AsyncClient::Callbacks& callbacks) { + if (cluster_ == nullptr) { + ASSERT(client_map_.empty()); + ASSERT(host_set_member_update_cb_handle_ == nullptr); + return nullptr; + } + + Upstream::LoadBalancerContextBase lb_context; + Upstream::HostConstSharedPtr host = cluster_->loadBalancer().chooseHost(&lb_context); + if (!host) { + ENVOY_LOG(debug, "no available host"); + return nullptr; + } + pending_requests_.emplace_back(*this, std::move(query), callbacks); + PendingRequest& pending_request = pending_requests_.back(); + ThreadLocalActiveClientPtr& client = this->threadLocalActiveClient(host); + pending_request.request_handler_ = + client->redis_client_->makeRawRequest(pending_request.incoming_request_, pending_request); + if (pending_request.request_handler_) { + return &pending_request; + } else { + onRequestCompleted(); + return nullptr; + } +} + +PoolRequest* AsyncClientImpl::sendToHost(const std::string& host_address, std::string_view request, + RawClientCallbacks& callbacks) { + if (cluster_ == nullptr) { + ASSERT(client_map_.empty()); + ASSERT(host_set_member_update_cb_handle_ == nullptr); + return nullptr; + } + + auto colon_pos = host_address.rfind(':'); + if ((colon_pos == std::string::npos) || (colon_pos == (host_address.size() - 1))) { + return nullptr; + } + + const std::string ip_address = host_address.substr(0, colon_pos); + const bool ipv6 = (ip_address.find(':') != std::string::npos); + std::string host_address_map_key; + Network::Address::InstanceConstSharedPtr address_ptr; + + if (!ipv6) { + host_address_map_key = host_address; + } else { + const auto ip_port = absl::string_view(host_address).substr(colon_pos + 1); + uint32_t ip_port_number; + if (!absl::SimpleAtoi(ip_port, &ip_port_number) || (ip_port_number > 65535)) { + return nullptr; + } + try { + address_ptr = std::make_shared(ip_address, ip_port_number); + } catch (const EnvoyException&) { + return nullptr; + } + host_address_map_key = address_ptr->asString(); + } + + auto it = host_address_map_.find(host_address_map_key); + if (it == host_address_map_.end()) { + // This host is not known to the cluster manager. Create a new host and insert it into the map. + if (created_via_redirect_hosts_.size() == config_->maxUpstreamUnknownConnections()) { + // Too many upstream connections to unknown hosts have been created. + redis_cluster_stats_.max_upstream_unknown_connections_reached_.inc(); + return nullptr; + } + if (!ipv6) { + // Only create an IPv4 address instance if we need a new Upstream::HostImpl. + const auto ip_port = absl::string_view(host_address).substr(colon_pos + 1); + uint32_t ip_port_number; + if (!absl::SimpleAtoi(ip_port, &ip_port_number) || (ip_port_number > 65535)) { + return nullptr; + } + try { + address_ptr = std::make_shared(ip_address, ip_port_number); + } catch (const EnvoyException&) { + return nullptr; + } + } + Upstream::HostSharedPtr new_host{new Upstream::HostImpl( + cluster_->info(), "", address_ptr, nullptr, 1, envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN, dispatcher_.timeSource())}; + host_address_map_[host_address_map_key] = new_host; + created_via_redirect_hosts_.push_back(new_host); + it = host_address_map_.find(host_address_map_key); + } + + ThreadLocalActiveClientPtr& client = threadLocalActiveClient(it->second); + + return client->redis_client_->makeRawRequest(request, callbacks); +} + +void AsyncClientImpl::ThreadLocalActiveClient::onEvent(Network::ConnectionEvent event) { + if (event == Network::ConnectionEvent::RemoteClose || + event == Network::ConnectionEvent::LocalClose) { + auto client_to_delete = parent_.client_map_.find(host_); + if (client_to_delete != parent_.client_map_.end()) { + parent_.dispatcher_.deferredDelete(std::move(redis_client_)); + parent_.client_map_.erase(client_to_delete); + } else { + for (auto it = parent_.clients_to_drain_.begin(); it != parent_.clients_to_drain_.end(); + it++) { + if ((*it).get() == this) { + if (!redis_client_->active()) { + parent_.redis_cluster_stats_.upstream_cx_drained_.inc(); + } + parent_.dispatcher_.deferredDelete(std::move(redis_client_)); + parent_.clients_to_drain_.erase(it); + break; + } + } + } + } +} + +AsyncClientImpl::PendingRequest::PendingRequest(AsyncClientImpl& parent, + std::string&& incoming_request, + Callbacks& callbacks) + : parent_(parent), incoming_request_(incoming_request), callbacks_(callbacks) {} + +AsyncClientImpl::PendingRequest::~PendingRequest() { + if (request_handler_) { + request_handler_->cancel(); + request_handler_ = nullptr; + + // treat canceled request as failure + callbacks_.onFailure(incoming_request_); + } +} + +void AsyncClientImpl::PendingRequest::onResponse(std::string&& response) { + request_handler_ = nullptr; + callbacks_.onSuccess(incoming_request_, std::move(response)); + parent_.onRequestCompleted(); +} + +void AsyncClientImpl::PendingRequest::onFailure() { + request_handler_ = nullptr; + callbacks_.onFailure(incoming_request_); + // refresh_manager is not constructed + // parent.refresh_manager_->onFailure(parent_.cluster_name); + parent_.onRequestCompleted(); +} + +void AsyncClientImpl::PendingRequest::cancel() { + request_handler_->cancel(); + request_handler_ = nullptr; + parent_.onRequestCompleted(); +} + +void AsyncClientImpl::onHostsAdded(const std::vector& host_added) { + for (const auto& host : host_added) { + std::string host_address = host->address()->asString(); + // Insert new host into address map, possibly overwriting a previous host's entry. + host_address_map_[host_address] = host; + for (const auto& created_host : created_via_redirect_hosts_) { + if (created_host->address()->asString() == host_address) { + // Remove our "temporary" host create in sendRequestToHost(). + onHostsRemoved({created_host}); + created_via_redirect_hosts_.remove(created_host); + break; + } + } + } +} + +void AsyncClientImpl::onHostsRemoved(const std::vector& host_removed) { + for (const auto& host : host_removed) { + auto it = client_map_.find(host); + if (it != client_map_.end()) { + if (it->second->redis_client_->active()) { + clients_to_drain_.push_back(std::move(it->second)); + client_map_.erase(it); + if (!drain_timer_->enabled()) { + drain_timer_->enableTimer(std::chrono::seconds(1)); + } + } else { + // There is no pending requests so close the connection + it->second->redis_client_->close(); + } + } + // There is the possibility that multiple hosts with the same address + // are registered in host_address_map_ given that hosts may be created + // upon redirection or supplied as part of the cluster's definition. + // only remove cluster defined host here. + auto it2 = host_address_map_.find(host->address()->asString()); + if (it2 != host_address_map_.end() && (it2->second == host)) { + host_address_map_.erase(it2); + } + } +} + +void AsyncClientImpl::drainClients() { + while (!clients_to_drain_.empty() && !(*clients_to_drain_.begin())->redis_client_->active()) { + (*clients_to_drain_.begin())->redis_client_->close(); + } + if (!clients_to_drain_.empty()) { + drain_timer_->enableTimer(std::chrono::seconds(1)); + } +} + +AsyncClientImpl::ThreadLocalActiveClientPtr& +AsyncClientImpl::threadLocalActiveClient(Upstream::HostConstSharedPtr host) { + ThreadLocalActiveClientPtr& client = client_map_[host]; + if (!client) { + client = std::make_unique(*this); + client->host_ = host; + client->redis_client_ = + client_factory_.create(host, dispatcher_, *config_, redis_command_stats_, *(stats_scope_), + auth_username_, auth_password_, params_); + client->redis_client_->addConnectionCallbacks(*client); + } + return client; +} + +void AsyncClientImpl::onRequestCompleted() { + ASSERT(!pending_requests_.empty()); + + while (!pending_requests_.empty() && !pending_requests_.front().request_handler_) { + pending_requests_.pop_front(); + } +} + +} // namespace Redis +} // namespace Envoy diff --git a/source/common/redis/async_client_impl.h b/source/common/redis/async_client_impl.h new file mode 100644 index 0000000000000..8dbf4f0046f34 --- /dev/null +++ b/source/common/redis/async_client_impl.h @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "envoy/stats/stats_macros.h" +#include "envoy/redis/async_client.h" + +#include "source/common/network/address_impl.h" +#include "source/common/upstream/load_balancer_impl.h" +#include "source/common/upstream/upstream_impl.h" +#include "source/extensions/common/redis/cluster_refresh_manager.h" +#include "source/extensions/filters/network/common/redis/raw_client_impl.h" + +#include "absl/container/node_hash_map.h" + +namespace Envoy { +namespace Upstream { +class ThreadLocalCluster; +} +namespace Redis { + +#define REDIS_CLUSTER_STATS(COUNTER) \ + COUNTER(upstream_cx_drained) \ + COUNTER(max_upstream_unknown_connections_reached) + +struct RedisClusterStats { + REDIS_CLUSTER_STATS(GENERATE_COUNTER_STRUCT) +}; + +using Envoy::Extensions::NetworkFilters::Common::Redis::RedisCommandStatsSharedPtr; +using Envoy::Extensions::NetworkFilters::Common::Redis::Client::Config; +using Envoy::Extensions::NetworkFilters::Common::Redis::Client::ConfigSharedPtr; +using Envoy::Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy; + +using Envoy::Extensions::NetworkFilters::Common::Redis::Client::RawClientCallbacks; +using Envoy::Extensions::NetworkFilters::Common::Redis::Client::RawClientFactory; +using Envoy::Extensions::NetworkFilters::Common::Redis::Client::RawClientPtr; + +class ConfigImpl : public Config { +public: + ConfigImpl() + : op_timeout_(std::chrono::milliseconds(1000)), max_buffer_size_before_flush_(1024), + buffer_flush_timeout_(3), max_upstream_unknown_connections_(100), + enable_command_stats_(true) {} + explicit ConfigImpl(const AsyncClientConfig& config) + : op_timeout_(config.op_timeout_), + max_buffer_size_before_flush_(config.max_buffer_size_before_flush_), + buffer_flush_timeout_(config.buffer_flush_timeout_), + max_upstream_unknown_connections_(config.max_upstream_unknown_connections_), + enable_command_stats_(config.enable_command_stats_) {} + + std::chrono::milliseconds opTimeout() const override { return op_timeout_; } + bool disableOutlierEvents() const override { return false; } + bool enableHashtagging() const override { return false; } + bool enableRedirection() const override { return false; } + uint32_t maxBufferSizeBeforeFlush() const override { return max_buffer_size_before_flush_; } + std::chrono::milliseconds bufferFlushTimeoutInMs() const override { + return buffer_flush_timeout_; + } + uint32_t maxUpstreamUnknownConnections() const override { + return max_upstream_unknown_connections_; + } + bool enableCommandStats() const override { return enable_command_stats_; } + ReadPolicy readPolicy() const override { return ReadPolicy::Primary; } + + bool connectionRateLimitEnabled() const override { return false; } + uint32_t connectionRateLimitPerSec() const override { return 0; } + + const std::chrono::milliseconds op_timeout_; + const uint32_t max_buffer_size_before_flush_; + const std::chrono::milliseconds buffer_flush_timeout_; + const uint32_t max_upstream_unknown_connections_; + const bool enable_command_stats_; +}; + +class AsyncClientImpl : public AsyncClient, + public std::enable_shared_from_this, + public Logger::Loggable { +public: + AsyncClientImpl(Upstream::ThreadLocalCluster* cluster, Event::Dispatcher& dispatcher, + RawClientFactory& client_factory, Stats::ScopeSharedPtr&& stats_scope, + RedisCommandStatsSharedPtr redis_command_stats, + Extensions::Common::Redis::ClusterRefreshManagerSharedPtr refresh_manager); + ~AsyncClientImpl() override; + + // Envoy::Redis::AsyncClient + void initialize(AsyncClientConfig config) override; + PoolRequest* send(std::string&& query, Callbacks& callbacks) override; + PoolRequest* sendToHost(const std::string& host_address, std::string_view request, + RawClientCallbacks& callbacks); + Event::Dispatcher& dispatcher() override { return dispatcher_; } + +private: + struct ThreadLocalActiveClient : public Network::ConnectionCallbacks { + ThreadLocalActiveClient(AsyncClientImpl& parent) : parent_(parent) {} + + // Network::ConnectionCallbacks + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + + AsyncClientImpl& parent_; + Upstream::HostConstSharedPtr host_; + RawClientPtr redis_client_; + }; + + using ThreadLocalActiveClientPtr = std::unique_ptr; + + struct PendingRequest : public RawClientCallbacks, public PoolRequest { + PendingRequest(AsyncClientImpl& parent, std::string&& incoming_request, Callbacks& callbacks); + ~PendingRequest() override; + + // Common::Redis::Client::RawClientCallbacks + void onResponse(std::string&& response) override; + void onFailure() override; + + // PoolRequest + void cancel() override; + + AsyncClientImpl& parent_; + std::string incoming_request_; + PoolRequest* request_handler_; + Callbacks& callbacks_; + }; + + void onHostsAdded(const std::vector& host_added); + void onHostsRemoved(const std::vector& host_removed); + void drainClients(); + + ThreadLocalActiveClientPtr& threadLocalActiveClient(Upstream::HostConstSharedPtr host); + + void onRequestCompleted(); + + const std::string cluster_name_; + Upstream::ThreadLocalCluster* cluster_{}; + Event::Dispatcher& dispatcher_; + absl::node_hash_map client_map_; + Envoy::Common::CallbackHandlePtr host_set_member_update_cb_handle_; + absl::node_hash_map host_address_map_; + std::string auth_username_; + std::string auth_password_; + std::map params_; + std::list created_via_redirect_hosts_; + std::list clients_to_drain_; + std::list pending_requests_; + + Event::TimerPtr drain_timer_; + RawClientFactory& client_factory_; + ConfigSharedPtr config_; + Stats::ScopeSharedPtr stats_scope_; + RedisCommandStatsSharedPtr redis_command_stats_; + RedisClusterStats redis_cluster_stats_; + const Extensions::Common::Redis::ClusterRefreshManagerSharedPtr refresh_manager_; +}; + +} // namespace Redis +} // namespace Envoy diff --git a/source/common/router/BUILD b/source/common/router/BUILD index 1900065f68b0f..44c48aca4971a 100644 --- a/source/common/router/BUILD +++ b/source/common/router/BUILD @@ -35,6 +35,9 @@ envoy_cc_library( name = "config_lib", srcs = ["config_impl.cc"], hdrs = ["config_impl.h"], + alimesh_deps = [ + "//contrib/common/active_redirect/source:active_redirect_policy_lib", + ], external_deps = ["abseil_optional"], deps = [ ":config_utility_lib", @@ -214,6 +217,9 @@ envoy_cc_library( "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], + alimesh_deps = [ + "//source/common/http:header_utility_lib", + ], ) envoy_cc_library( @@ -281,6 +287,9 @@ envoy_cc_library( "router.h", "upstream_request.h", ], + alimesh_deps = [ + "//envoy/stats:timespan_interface", + ], deps = [ ":config_lib", ":context_lib", @@ -335,7 +344,7 @@ envoy_cc_library( "//source/common/stream_info:uint32_accessor_lib", "//source/common/tracing:http_tracer_lib", "//source/common/upstream:load_balancer_lib", - "//source/common/upstream:upstream_http_factory_context_lib", + "//source/common/upstream:upstream_factory_context_lib", "//source/extensions/common/proxy_protocol:proxy_protocol_header_lib", "//source/extensions/filters/http/common:factory_base_lib", "@envoy_api//envoy/extensions/filters/http/router/v3:pkg_cc_proto", diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 6205ebe2ce33f..2934520bd5977 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -525,7 +525,13 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, vhost_->globalRouteConfig().maxDirectResponseBodySizeBytes())), per_filter_configs_(route.typed_per_filter_config(), optional_http_filters, factory_context, validator), +#if !defined(ALIMESH) route_name_(route.name()), time_source_(factory_context.mainThreadDispatcher().timeSource()), +#else + route_name_(route.name()), time_source_(factory_context.mainThreadDispatcher().timeSource()), + internal_active_redirect_policy_( + buildActiveInternalRedirectPolicy(route.route(), validator, route.name())), +#endif retry_shadow_buffer_limit_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( route, per_request_buffer_limit_bytes, vhost->retryShadowBufferLimit())), direct_response_code_(ConfigUtility::parseDirectResponseCode(route)), @@ -600,6 +606,17 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, weighted_clusters_config_ = std::make_unique( weighted_clusters, total_weight, route.route().weighted_clusters().header_name()); +#if defined(ALIMESH) + if (route.route().weighted_clusters().has_inline_cluster_specifier_plugin()) { + cluster_specifier_plugin_ = getClusterSpecifierPluginByTheProto( + route.route().weighted_clusters().inline_cluster_specifier_plugin(), validator, + factory_context); + } else if (!route.route().weighted_clusters().cluster_specifier_plugin().empty()) { + cluster_specifier_plugin_ = vhost_->globalRouteConfig().clusterSpecifierPlugin( + route.route().weighted_clusters().cluster_specifier_plugin()); + } +#endif + } else if (route.route().cluster_specifier_case() == envoy::config::route::v3::RouteAction::ClusterSpecifierCase:: kInlineClusterSpecifierPlugin) { @@ -701,11 +718,19 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, "not be stripped: {}", redirect_config_->path_redirect_); } + ENVOY_LOG(info, "route stats is {}, name is {}", route.stat_prefix(), route.name()); if (!route.stat_prefix().empty()) { route_stats_context_ = std::make_unique( factory_context.scope(), factory_context.routerContext().routeStatNames(), vhost->statName(), route.stat_prefix()); + } else if (!route.name().empty()) { + // Added by Ingress + // use route_name as default stat_prefix + route_stats_context_ = std::make_unique( + factory_context.scope(), factory_context.routerContext().routeStatNames(), + vhost->statName(), route.name()); } + // End Added if (route.route().has_early_data_policy()) { auto& factory = Envoy::Config::Utility::getAndCheckFactory( @@ -855,6 +880,16 @@ void RouteEntryImplBase::finalizeRequestHeaders(Http::RequestHeaderMap& headers, absl::optional container; if (!getPathRewrite(headers, container).empty() || regex_rewrite_ != nullptr || path_rewriter_ != nullptr) { +#if defined(ALIMESH) + // We need to store the original path of access log when user enable the suppress_envoy_headers + // option. + if (!insert_envoy_original_path) { + const_cast(stream_info) + .setDynamicMetadata( + "mse.data", + MessageUtil::keyValueStruct("original_path", std::string(headers.getPathValue()))); + } +#endif rewritePathHeader(headers, insert_envoy_original_path); } } @@ -1111,6 +1146,32 @@ std::unique_ptr RouteEntryImplBase::buildInternalRed return std::make_unique(policy_config, validator, current_route_name); } +#if defined(ALIMESH) +std::unique_ptr +RouteEntryImplBase::buildActiveInternalRedirectPolicy( + const envoy::config::route::v3::RouteAction& route_config, + ProtobufMessage::ValidationVisitor& validator, absl::string_view current_route_name) const { + if (route_config.has_internal_active_redirect_policy()) { + return std::make_unique( + route_config.internal_active_redirect_policy(), validator, current_route_name); + } + envoy::config::route::v3::InternalActiveRedirectPolicy policy_config; + switch (route_config.internal_redirect_action()) { + case envoy::config::route::v3::RouteAction::HANDLE_INTERNAL_REDIRECT: + break; + case envoy::config::route::v3::RouteAction::PASS_THROUGH_INTERNAL_REDIRECT: + FALLTHRU; + default: + return nullptr; + } + if (route_config.has_max_internal_redirects()) { + *policy_config.mutable_max_internal_redirects() = route_config.max_internal_redirects(); + } + return std::make_unique(policy_config, validator, + current_route_name); +} +#endif + RouteEntryImplBase::OptionalTimeouts RouteEntryImplBase::buildOptionalTimeouts( const envoy::config::route::v3::RouteAction& route) const { // Calculate how many values are actually set, to initialize `OptionalTimeouts` packed_struct, @@ -1320,6 +1381,18 @@ RouteConstSharedPtr RouteEntryImplBase::pickWeightedCluster(const Http::HeaderMa } if (selected_value >= begin && selected_value < end) { +#if defined(ALIMESH) + if (cluster_specifier_plugin_ != nullptr) { + auto request_header = dynamic_cast(&headers); + if (!cluster->clusterHeaderName().get().empty() && + !headers.get(cluster->clusterHeaderName()).empty()) { + auto route = pickClusterViaClusterHeader(cluster->clusterHeaderName(), headers, + static_cast(cluster.get())); + return cluster_specifier_plugin_->route(route, *request_header); + } + return cluster_specifier_plugin_->route(cluster, *request_header); + } +#endif if (!cluster->clusterHeaderName().get().empty() && !headers.get(cluster->clusterHeaderName()).empty()) { return pickClusterViaClusterHeader(cluster->clusterHeaderName(), headers, @@ -1809,11 +1882,41 @@ VirtualHostImpl::VirtualHostImpl( validation_clusters)); } } + +#if defined(ALIMESH) + for (const auto& server_name : virtual_host.allow_server_names()) { + auto isWildcardServerName = absl::StartsWith(server_name, "*."); + if (absl::StrContains(server_name, '*') && !isWildcardServerName) { + throw EnvoyException( + fmt::format("partial wildcards are not supported in \"allow_server_names\"")); + } + if (isWildcardServerName) { + // Add for the wildcard domain, i.e. ".example.com" for "*.example.com". + allow_server_names_.push_back(server_name.substr(1)); + } else { + allow_server_names_.push_back(server_name); + } + } +#endif } const std::shared_ptr VirtualHostImpl::SSL_REDIRECT_ROUTE{ new SslRedirectRoute()}; +#if defined(ALIMESH) +const SslPermanentRedirector SslPermanentRedirectRoute::SSL_PERMANENT_REDIRECTOR; +const std::shared_ptr + VirtualHostImpl::SSL_PERMANENT_REDIRECT_ROUTE{new SslPermanentRedirectRoute}; + +const SNIRedirector SNIRedirectRoute::SNI_REDIRECTOR; +const envoy::config::core::v3::Metadata SNIRedirectRoute::metadata_; +const Envoy::Config::TypedMetadataImpl + SNIRedirectRoute::typed_metadata_({}); + +const std::shared_ptr VirtualHostImpl::SNI_REDIRECT_ROUTE{ + new SNIRedirectRoute()}; +#endif + RouteConstSharedPtr VirtualHostImpl::getRouteFromRoutes( const RouteCallback& cb, const Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo& stream_info, uint64_t random_value, @@ -1866,6 +1969,52 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb return nullptr; } +#if defined(ALIMESH) + // First check for sni redirect. + if (allow_server_names_.empty()) { + goto SNI_CHECK_PASS; + } + if (stream_info.downstreamAddressProvider().sslConnection() == nullptr) { + ENVOY_LOG(warn, "allow_server_names field is ignored, because it's not a ssl " + "connection."); + goto SNI_CHECK_PASS; + } + { + auto server_name = stream_info.downstreamAddressProvider().requestedServerName(); + auto it = std::find(allow_server_names_.begin(), allow_server_names_.end(), server_name); + if (it != allow_server_names_.end()) { + goto SNI_CHECK_PASS; + } else { + // Match on all wildcard domains, i.e. ".example.com" and ".com" for "www.example.com". + size_t pos = server_name.find('.', 1); + while (pos < server_name.size() - 1 && pos != absl::string_view::npos) { + auto wildcard = server_name.substr(pos); + auto it = std::find(allow_server_names_.begin(), allow_server_names_.end(), wildcard); + if (it != allow_server_names_.end()) { + goto SNI_CHECK_PASS; + } + pos = server_name.find('.', pos + 1); + } + } + } + return SNI_REDIRECT_ROUTE; + +SNI_CHECK_PASS: + // Second check for ssl redirect + RouteConstSharedPtr redirect_route = SSL_PERMANENT_REDIRECT_ROUTE; + // only return 301 when http method is GET or HEAD + if (headers.Method() && (headers.Method()->value() == Http::Headers::get().MethodValues.Get || + headers.Method()->value() == Http::Headers::get().MethodValues.Head)) { + redirect_route = SSL_REDIRECT_ROUTE; + } + if (ssl_requirements_ == SslRequirements::All && scheme != "https") { + return redirect_route; + } else if (ssl_requirements_ == SslRequirements::ExternalOnly && scheme != "https" && + !Http::HeaderUtility::isEnvoyInternalRequest(headers)) { + return redirect_route; + } +#else + // First check for ssl redirect. if (ssl_requirements_ == SslRequirements::All && scheme != "https") { return SSL_REDIRECT_ROUTE; @@ -1873,6 +2022,7 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb !Http::HeaderUtility::isEnvoyInternalRequest(headers)) { return SSL_REDIRECT_ROUTE; } +#endif if (matcher_) { Http::Matching::HttpMatchingDataImpl data(stream_info); diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 65cdfb86996d1..53a7519dfe009 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -39,6 +39,10 @@ #include "absl/container/node_hash_map.h" #include "absl/types/optional.h" +#if defined(ALIMESH) +#include "contrib/common/active_redirect/source/active_redirect_policy_impl.h" +#endif + namespace Envoy { namespace Router { @@ -158,6 +162,65 @@ class SslRedirectRoute : public Route { typed_metadata_; }; +#if defined(ALIMESH) +class SslPermanentRedirector : public SslRedirector { +public: + Http::Code responseCode() const override { return Http::Code::PermanentRedirect; } +}; +class SslPermanentRedirectRoute : public SslRedirectRoute { +public: + const DirectResponseEntry* directResponseEntry() const override { + return &SSL_PERMANENT_REDIRECTOR; + } + +private: + static const SslPermanentRedirector SSL_PERMANENT_REDIRECTOR; +}; + +class SNIRedirector : public DirectResponseEntry { +public: + // Router::DirectResponseEntry + void finalizeResponseHeaders(Http::ResponseHeaderMap&, + const StreamInfo::StreamInfo&) const override {} + Http::HeaderTransforms responseHeaderTransforms(const StreamInfo::StreamInfo&, + bool) const override { + return {}; + } + std::string newUri(const Http::RequestHeaderMap&) const override { return ""; }; + void rewritePathHeader(Http::RequestHeaderMap&, bool) const override {} + Http::Code responseCode() const override { return Http::Code::MisdirectedRequest; } + const std::string& responseBody() const override { return EMPTY_STRING; } + const std::string& routeName() const override { return route_name_; } + +private: + const std::string route_name_; +}; + +class SNIRedirectRoute : public Route { +public: + // Router::Route + const DirectResponseEntry* directResponseEntry() const override { return &SNI_REDIRECTOR; } + const RouteEntry* routeEntry() const override { return nullptr; } + const Decorator* decorator() const override { return nullptr; } + const RouteTracing* tracingConfig() const override { return nullptr; } + const RouteSpecificFilterConfig* mostSpecificPerFilterConfig(const std::string&) const override { + return nullptr; + } + bool filterDisabled(absl::string_view) const override { return false; } + void traversePerFilterConfig( + const std::string&, + std::function) const override {} + const envoy::config::core::v3::Metadata& metadata() const override { return metadata_; } + const Envoy::Config::TypedMetadata& typedMetadata() const override { return typed_metadata_; } + +private: + static const SNIRedirector SNI_REDIRECTOR; + static const envoy::config::core::v3::Metadata metadata_; + static const Envoy::Config::TypedMetadataImpl + typed_metadata_; +}; +#endif + /** * Implementation of CorsPolicy that reads from the proto route and virtual host config. * TODO(wbpcode): move all cors interfaces and implementation to 'extensions/filters/http/cors'. @@ -383,6 +446,10 @@ class VirtualHostImpl : Logger::Loggable { enum class SslRequirements : uint8_t { None, ExternalOnly, All }; static const std::shared_ptr SSL_REDIRECT_ROUTE; +#if defined(ALIMESH) + static const std::shared_ptr SSL_PERMANENT_REDIRECT_ROUTE; + static const std::shared_ptr SNI_REDIRECT_ROUTE; +#endif CommonVirtualHostSharedPtr shared_virtual_host_; @@ -390,6 +457,9 @@ class VirtualHostImpl : Logger::Loggable { std::vector routes_; Matcher::MatchTreeSharedPtr matcher_; +#if defined(ALIMESH) + std::vector allow_server_names_; +#endif }; using VirtualHostSharedPtr = std::shared_ptr; @@ -700,6 +770,18 @@ class RouteEntryImplBase : public RouteEntryAndRoute, } return DefaultInternalRedirectPolicy::get(); } +#if defined(ALIMESH) + const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { + if (internal_active_redirect_policy_ != nullptr) { + return *internal_active_redirect_policy_; + } + return DefaultInternalActiveRedirectPolicy::get(); + } + + RouteConstSharedPtr clone(const std::string& name) const { + return std::make_shared(this, shared_from_this(), name); + } +#endif const PathMatcherSharedPtr& pathMatcher() const override { return path_matcher_; } const PathRewriterSharedPtr& pathRewriter() const override { return path_rewriter_; } @@ -809,7 +891,12 @@ class RouteEntryImplBase : public RouteEntryAndRoute, // path matching to ignore the path-parameters. absl::string_view sanitizePathBeforePathMatching(const absl::string_view path) const; +#if defined(ALIMESH) + class DynamicRouteEntry : public RouteEntryAndRoute, + public std::enable_shared_from_this { +#else class DynamicRouteEntry : public RouteEntryAndRoute { +#endif public: DynamicRouteEntry(const RouteEntryAndRoute* parent, RouteConstSharedPtr owner, const std::string& name) @@ -941,6 +1028,19 @@ class RouteEntryImplBase : public RouteEntryAndRoute, parent_->traversePerFilterConfig(filter_name, cb); }; +#if defined(ALIMESH) + const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { + return parent_->internalActiveRedirectPolicy(); + } + + RouteConstSharedPtr clone(const std::string& name) const { + return std::make_shared( + parent_, shared_from_this(), name); + } + + virtual RouteConstSharedPtr getRouteConstSharedPtr() const { return shared_from_this(); } +#endif + private: const RouteEntryAndRoute* parent_; @@ -1035,6 +1135,10 @@ class RouteEntryImplBase : public RouteEntryAndRoute, const Http::LowerCaseString& clusterHeaderName() const { return cluster_header_name_; } +#if defined(ALIMESH) + RouteConstSharedPtr getRouteConstSharedPtr() const override { return shared_from_this(); } +#endif + private: const std::string runtime_key_; Runtime::Loader& loader_; @@ -1164,6 +1268,12 @@ class RouteEntryImplBase : public RouteEntryAndRoute, PathRewriterSharedPtr buildPathRewriter(envoy::config::route::v3::Route route, ProtobufMessage::ValidationVisitor& validator) const; +#if defined(ALIMESH) + std::unique_ptr + buildActiveInternalRedirectPolicy(const envoy::config::route::v3::RouteAction& route_config, + ProtobufMessage::ValidationVisitor& validator, + absl::string_view current_route_name) const; +#endif RouteConstSharedPtr pickClusterViaClusterHeader(const Http::LowerCaseString& cluster_header_name, const Http::HeaderMap& headers, @@ -1219,6 +1329,9 @@ class RouteEntryImplBase : public RouteEntryAndRoute, PerFilterConfigs per_filter_configs_; const std::string route_name_; TimeSource& time_source_; +#if defined(ALIMESH) + std::unique_ptr internal_active_redirect_policy_; +#endif EarlyDataPolicyPtr early_data_policy_; // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. diff --git a/source/common/router/delegating_route_impl.h b/source/common/router/delegating_route_impl.h index fff2306dfcf7a..0e77d79b4f489 100644 --- a/source/common/router/delegating_route_impl.h +++ b/source/common/router/delegating_route_impl.h @@ -117,6 +117,12 @@ class DelegatingRouteEntry : public Router::RouteEntry { const EarlyDataPolicy& earlyDataPolicy() const override; const RouteStatsContextOptRef routeStatsContext() const override; +#if defined(ALIMESH) + const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { + return base_route_->routeEntry()->internalActiveRedirectPolicy(); + } +#endif + private: const Router::RouteConstSharedPtr base_route_; }; diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 82dea84326498..72727c6512c1f 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -42,6 +42,10 @@ #include "source/common/stream_info/uint32_accessor_impl.h" #include "source/common/tracing/http_tracer_impl.h" +#if defined(ALIMESH) +#include "source/common/http/path_utility.h" +#endif + namespace Envoy { namespace Router { namespace { @@ -315,6 +319,61 @@ Stats::StatName Filter::upstreamZone(Upstream::HostDescriptionConstSharedPtr ups return upstream_host ? upstream_host->localityZoneStatName() : config_.empty_stat_name_; } +#if defined(ALIMESH) +void Filter::chargeUpstreamGrpcCode(uint64_t http_status_code, uint64_t grpc_response_code, + const Http::ResponseHeaderMap& response_headers, + Upstream::HostDescriptionConstSharedPtr upstream_host, + bool dropped) { + ASSERT(Grpc::Common::getGrpcStatus(response_headers).has_value()); + if (config_.emit_dynamic_stats_ && !callbacks_->streamInfo().healthCheck()) { + const Http::HeaderEntry* upstream_canary_header = response_headers.EnvoyUpstreamCanary(); + const bool is_canary = (upstream_canary_header && upstream_canary_header->value() == "true") || + (upstream_host ? upstream_host->canary() : false); + const bool internal_request = Http::HeaderUtility::isEnvoyInternalRequest(*downstream_headers_); + + Stats::StatName upstream_zone = upstreamZone(upstream_host); + Http::CodeStats::ResponseStatInfo info{ + config_.scope_, + cluster_->statsScope(), + config_.empty_stat_name_, + grpc_response_code, + internal_request, + route_entry_->virtualHost().statName(), + request_vcluster_ ? request_vcluster_->statName() : config_.empty_stat_name_, + route_stats_context_.has_value() ? route_stats_context_->statName() + : config_.empty_stat_name_, + config_.zone_name_, + upstream_zone, + is_canary}; + + Http::CodeStats& code_stats = httpContext().codeStats(); + code_stats.chargeResponseStat(info, exclude_http_code_stats_); + + if (alt_stat_prefix_ != nullptr) { + Http::CodeStats::ResponseStatInfo alt_info{config_.scope_, + cluster_->statsScope(), + alt_stat_prefix_->statName(), + grpc_response_code, + internal_request, + config_.empty_stat_name_, + config_.empty_stat_name_, + config_.empty_stat_name_, + config_.zone_name_, + upstream_zone, + is_canary}; + code_stats.chargeResponseStat(alt_info, exclude_http_code_stats_); + } + + if (dropped) { + cluster_->loadReportStats().upstream_rq_dropped_.inc(); + } + if (upstream_host && Http::CodeUtility::is5xx(http_status_code)) { + upstream_host->stats().rq_error_.inc(); + } + } +} +#endif + void Filter::chargeUpstreamCode(uint64_t response_status_code, const Http::ResponseHeaderMap& response_headers, Upstream::HostDescriptionConstSharedPtr upstream_host, @@ -661,6 +720,15 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, } callbacks_->streamInfo().setAttemptCount(attempt_count_); +#if defined(ALIMESH) + Http::HeaderString start_time; + start_time.setInteger(std::chrono::duration_cast( + callbacks_->streamInfo().startTime().time_since_epoch()) + .count()); + downstream_headers_->setReferenceKey(Http::CustomHeaders::get().AliExtendedValues.TriStartTime, + start_time.getStringView()); +#endif + route_entry_->finalizeRequestHeaders(headers, callbacks_->streamInfo(), !config_.suppress_envoy_headers_); FilterUtility::setUpstreamScheme( @@ -803,9 +871,15 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea // a backoff timer. ASSERT(upstream_requests_.size() <= 1); +#if defined(ALIMESH) + bool buffering = (retry_state_ && retry_state_->enabled()) || callbacks_->needBuffering() || + (!active_shadow_policies_.empty() && !streaming_shadows_) || + (route_entry_ && route_entry_->internalRedirectPolicy().enabled()); +#else bool buffering = (retry_state_ && retry_state_->enabled()) || (!active_shadow_policies_.empty() && !streaming_shadows_) || (route_entry_ && route_entry_->internalRedirectPolicy().enabled()); +#endif if (buffering && getLength(callbacks_->decodingBuffer()) + data.length() > retry_shadow_buffer_limit_) { ENVOY_LOG(debug, @@ -1455,6 +1529,15 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt upstream_request.upstreamHost()->outlierDetector().putHttpResponseCode(response_code); } +#if defined(ALIMESH) + static Envoy::Http::LowerCaseString shutdown_key("micro.service.shutdown.endpoint"); + if (!headers->get(shutdown_key).empty()) { + upstream_request.upstreamHost()->outlierDetector().forceEjectHost(); + ENVOY_STREAM_LOG(debug, "found shutdown header, host will be shutdown ,so forceEject this Host", + *callbacks_); + } +#endif + if (headers->EnvoyImmediateHealthCheckFail() != nullptr) { upstream_request.upstreamHost()->healthChecker().setUnhealthy( Upstream::HealthCheckHostMonitor::UnhealthyType::ImmediateHealthCheckFail); @@ -1516,6 +1599,18 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt // next downstream. } +#if defined(ALIMESH) + if (route_entry_->internalActiveRedirectPolicy().enabled() && + route_entry_->internalActiveRedirectPolicy().shouldRedirectForResponseCode( + static_cast(response_code)) && + setupActiveRedirect(*headers, upstream_request)) { + ENVOY_STREAM_LOG(debug, "setup active redirect", *callbacks_); + return; + // If the redirect could not be handled, fail open and let it pass to the + // next downstream. + } +#endif + // Check if we got a "bad" response, but there are still upstream requests in // flight awaiting headers or scheduled retries. If so, exit to give them a // chance to return before returning a response downstream. @@ -1543,15 +1638,52 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt MonotonicTime response_received_time = dispatcher.timeSource().monotonicTime(); std::chrono::milliseconds ms = std::chrono::duration_cast( response_received_time - downstream_request_complete_time_); +#if defined(ALIMESH) + std::chrono::milliseconds duration_ms = std::chrono::duration_cast( + response_received_time - callbacks_->streamInfo().startTimeMonotonic()); + Http::HeaderString cost_time; + cost_time.setInteger(duration_ms.count()); + headers->setReferenceKey(Http::CustomHeaders::get().AliExtendedValues.TriCostTime, + cost_time.getStringView()); + + Http::HeaderString arrive_time; + arrive_time.setInteger(std::chrono::duration_cast( + callbacks_->streamInfo().startTime().time_since_epoch()) + .count()); + headers->setReferenceKey(Http::CustomHeaders::get().AliExtendedValues.TriArriveTime, + arrive_time.getStringView()); + + SystemTime system_response_receive_time = dispatcher.timeSource().systemTime(); + Http::HeaderString start_time; + start_time.setInteger(std::chrono::duration_cast( + system_response_receive_time.time_since_epoch()) + .count()); + headers->setReferenceKey(Http::CustomHeaders::get().AliExtendedValues.TriRespStartTime, + start_time.getStringView()); + + // The X-enel-upward-service-time request header is critical and is needed in the access log + // to record the processing time of the upstream service, so we need to output it. + headers->setEnvoyUpstreamServiceTime(ms.count()); +#else if (!config_.suppress_envoy_headers_) { headers->setEnvoyUpstreamServiceTime(ms.count()); } +#endif } upstream_request.upstreamCanary( (headers->EnvoyUpstreamCanary() && headers->EnvoyUpstreamCanary()->value() == "true") || upstream_request.upstreamHost()->canary()); +#if defined(ALIMESH) + if (grpc_status.has_value()) { + chargeUpstreamGrpcCode(response_code, grpc_to_http_status, *headers, + upstream_request.upstreamHost(), false); + } else { + chargeUpstreamCode(response_code, *headers, upstream_request.upstreamHost(), false); + } +#else chargeUpstreamCode(response_code, *headers, upstream_request.upstreamHost(), false); +#endif if (!Http::CodeUtility::is5xx(response_code)) { handleNon5xxResponseHeaders(grpc_status, upstream_request, end_stream, grpc_to_http_status); } @@ -1842,6 +1974,150 @@ bool Filter::convertRequestHeadersForInternalRedirect(Http::RequestHeaderMap& do return true; } +#if defined(ALIMESH) +bool Filter::setupActiveRedirect(const Http::ResponseHeaderMap&, UpstreamRequest&) { + ENVOY_STREAM_LOG(debug, "attempting internal active redirect", *callbacks_); + + std::string end_stream = downstream_end_stream_ ? "true" : "false"; + ENVOY_STREAM_LOG(debug, "downstream_end_stream: {}", *callbacks_, end_stream); + ENVOY_STREAM_LOG(debug, "!decodingBuffer: {}", *callbacks_, + !callbacks_->decodingBuffer() ? "true" : "false"); + + // Redirects are not supported for streaming requests yet. + if (downstream_end_stream_ && + !callbacks_->decodingBuffer() && // Redirects with body not yet supported. + convertRequestHeadersForInternalActiveRedirect(*downstream_headers_) && + callbacks_->recreateStream(nullptr)) { + ENVOY_STREAM_LOG(debug, "Internal active redirect success", *callbacks_); + cluster_->trafficStats()->upstream_internal_redirect_succeeded_total_.inc(); + return true; + } + + ENVOY_STREAM_LOG(warn, "Internal active redirect failed", *callbacks_); + cluster_->trafficStats()->upstream_internal_redirect_failed_total_.inc(); + return false; +} + +bool Filter::convertRequestHeadersForInternalActiveRedirect( + Http::RequestHeaderMap& downstream_headers) { + if (!downstream_headers.Path()) { + ENVOY_STREAM_LOG(warn, "There is no path in the downstream header", *callbacks_); + return false; + } + + // Make sure the redirect response contains a URL to redirect to. + const auto& policy = route_entry_->internalActiveRedirectPolicy(); + const std::string path(downstream_headers.getPathValue()); + absl::string_view just_path(Http::PathUtil::removeQueryAndFragment(path)); + std::string redirect_url = policy.redirectUrl(just_path.data()); + if (redirect_url.empty()) { + ENVOY_STREAM_LOG(warn, "The redirect is empty", *callbacks_); + stats_.passthrough_internal_redirect_bad_location_.inc(); + return false; + } + + Http::Utility::Url absolute_url; + if (!absolute_url.initialize(redirect_url, false)) { + ENVOY_STREAM_LOG(warn, "Invalid redirect address: {}", *callbacks_, redirect_url); + stats_.passthrough_internal_redirect_bad_location_.inc(); + return false; + } + + // Don't allow serving TLS responses over plaintext unless allowed by policy. + const bool scheme_is_http = schemeIsHttp(downstream_headers, *callbacks_->connection()); + const bool target_is_http = absolute_url.scheme() == Http::Headers::get().SchemeValues.Http; + if (!policy.isCrossSchemeRedirectAllowed() && scheme_is_http != target_is_http) { + ENVOY_STREAM_LOG(warn, "Illegal Scheme", *callbacks_); + stats_.passthrough_internal_redirect_unsafe_scheme_.inc(); + return false; + } + + const StreamInfo::FilterStateSharedPtr& filter_state = callbacks_->streamInfo().filterState(); + // Make sure that performing the redirect won't result in exceeding the configured number of + // redirects allowed for this route. + if (!filter_state->hasData(NumInternalRedirectsFilterStateName)) { + filter_state->setData( + NumInternalRedirectsFilterStateName, std::make_shared(0), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); + } + StreamInfo::UInt32Accessor* num_internal_redirect = {}; + num_internal_redirect = + filter_state->getDataMutable(NumInternalRedirectsFilterStateName); + if (num_internal_redirect->value() >= policy.maxInternalRedirects()) { + ENVOY_STREAM_LOG(warn, "Redirection times exceeded maximum {}", *callbacks_, + policy.maxInternalRedirects()); + stats_.passthrough_internal_redirect_too_many_redirects_.inc(); + return false; + } + // Copy the old values, so they can be restored if the redirect fails. + const std::string original_host(downstream_headers.getHostValue()); + const std::string original_path(downstream_headers.getPathValue()); + const bool scheme_is_set = (downstream_headers.Scheme() != nullptr); + Cleanup restore_original_headers( + [&downstream_headers, original_host, original_path, scheme_is_set, scheme_is_http]() { + downstream_headers.setHost(original_host); + downstream_headers.setPath(original_path); + if (scheme_is_set) { + downstream_headers.setScheme(scheme_is_http ? Http::Headers::get().SchemeValues.Http + : Http::Headers::get().SchemeValues.Https); + } + }); + + // Replace the original scheme and path. + downstream_headers.setScheme(absolute_url.scheme()); + downstream_headers.setPath(absolute_url.pathAndQueryParams()); + + if (!policy.forcedUseOriginalHost()) { + // Replace the original host. + ENVOY_STREAM_LOG(info, "Replace the original host", *callbacks_); + downstream_headers.setHost(absolute_url.hostAndPort()); + } + + if (policy.forcedAddHeaderBeforeRouteMatcher()) { + policy.evaluateHeaders(downstream_headers, nullptr); + } + + // Only clear the route cache if there are downstream callbacks. There aren't, for example, + // for async connections. + if (callbacks_->downstreamCallbacks()) { + callbacks_->downstreamCallbacks()->clearRouteCache(); + } + + const auto route = callbacks_->route(); + // Don't allow a redirect to a non existing route. + if (!route) { + stats_.passthrough_internal_redirect_no_route_.inc(); + ENVOY_STREAM_LOG(warn, "The internal redirect no route", *callbacks_); + return false; + } + + const auto& route_name = route->directResponseEntry() ? route->directResponseEntry()->routeName() + : route->routeEntry()->routeName(); + for (const auto& predicate : policy.predicates()) { + if (!predicate->acceptTargetRoute(*filter_state, route_name, !scheme_is_http, + !target_is_http)) { + stats_.passthrough_internal_redirect_predicate_.inc(); + ENVOY_STREAM_LOG(warn, "rejecting redirect targeting {}, by {} predicate", *callbacks_, + route_name, predicate->name()); + return false; + } + } + + if (!policy.forcedAddHeaderBeforeRouteMatcher()) { + policy.evaluateHeaders(downstream_headers, nullptr); + } + + num_internal_redirect->increment(); + restore_original_headers.cancel(); + // Preserve the original request URL for the second pass. + downstream_headers.setEnvoyOriginalUrl(absl::StrCat(scheme_is_http + ? Http::Headers::get().SchemeValues.Http + : Http::Headers::get().SchemeValues.Https, + "://", original_host, original_path)); + return true; +} +#endif + void Filter::runRetryOptionsPredicates(UpstreamRequest& retriable_request) { for (const auto& options_predicate : route_entry_->retryPolicy().retryOptionsPredicates()) { const Upstream::RetryOptionsPredicate::UpdateOptionsParameters parameters{ diff --git a/source/common/router/router.h b/source/common/router/router.h index ddbcfad71d0a2..f83f561d3ad77 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -42,7 +42,11 @@ #include "source/common/stats/symbol_table.h" #include "source/common/stream_info/stream_info_impl.h" #include "source/common/upstream/load_balancer_impl.h" -#include "source/common/upstream/upstream_http_factory_context_impl.h" +#include "source/common/upstream/upstream_factory_context_impl.h" + +#if defined(ALIMESH) +#include "envoy/stats/timespan.h" +#endif namespace Envoy { namespace Router { @@ -262,9 +266,9 @@ class FilterConfig : Http::FilterChainFactory { Http::FilterChainUtility::createSingletonUpstreamFilterConfigProviderManager( server_factory_ctx); std::string prefix = context.scope().symbolTable().toString(context.scope().prefix()); - upstream_ctx_ = std::make_unique( + upstream_ctx_ = std::make_unique( server_factory_ctx, context.initManager(), context.scope()); - Http::FilterChainHelper helper(*filter_config_provider_manager, server_factory_ctx, *upstream_ctx_, prefix); helper.processFilters(upstream_http_filters, "router upstream http", "router upstream http", @@ -319,7 +323,7 @@ class FilterConfig : Http::FilterChainFactory { Http::Context& http_context_; Stats::StatName zone_name_; Stats::StatName empty_stat_name_; - std::unique_ptr upstream_ctx_; + std::unique_ptr upstream_ctx_; Http::FilterChainUtility::FilterFactoriesList upstream_http_filter_factories_; private: @@ -578,6 +582,11 @@ class Filter : Logger::Loggable, void onPerTryTimeoutCommon(UpstreamRequest& upstream_request, Stats::Counter& error_counter, const std::string& response_code_details); Stats::StatName upstreamZone(Upstream::HostDescriptionConstSharedPtr upstream_host); +#if defined(ALIMESH) + void chargeUpstreamGrpcCode(uint64_t http_status_code, uint64_t grpc_response_code, + const Http::ResponseHeaderMap& response_headers, + Upstream::HostDescriptionConstSharedPtr upstream_host, bool dropped); +#endif void chargeUpstreamCode(uint64_t response_status_code, const Http::ResponseHeaderMap& response_headers, Upstream::HostDescriptionConstSharedPtr upstream_host, bool dropped); @@ -637,6 +646,12 @@ class Filter : Logger::Loggable, uint64_t grpc_to_http_status); Http::Context& httpContext() { return config_.http_context_; } +#if defined(ALIMESH) + bool setupActiveRedirect(const Http::ResponseHeaderMap& headers, + UpstreamRequest& upstream_request); + bool convertRequestHeadersForInternalActiveRedirect(Http::RequestHeaderMap& downstream_headers); +#endif + RetryStatePtr retry_state_; FilterConfig& config_; Http::StreamDecoderFilterCallbacks* callbacks_{}; diff --git a/source/common/router/scoped_config_impl.cc b/source/common/router/scoped_config_impl.cc index 45a16d9c4c462..20aed94c319a0 100644 --- a/source/common/router/scoped_config_impl.cc +++ b/source/common/router/scoped_config_impl.cc @@ -5,9 +5,192 @@ #include "source/common/protobuf/utility.h" +#if defined(ALIMESH) +#include "source/common/http/header_utility.h" +#endif + namespace Envoy { namespace Router { +#if defined(ALIMESH) +namespace { + +std::string maskFirstDNSLabel(absl::string_view host) { + if (host == "*") { + return std::string(host); + } + if (host.size() < 2) { + return "*"; + } + size_t start_pos = (host[0] == '*' && host[1] == '.') ? 2 : 0; + size_t dot_pos = host.find('.', start_pos); + if (dot_pos != absl::string_view::npos) { + return absl::StrCat("*", host.substr(dot_pos)); + } + return "*"; +} + +} // namespace + +LocalPortValueExtractorImpl::LocalPortValueExtractorImpl( + ScopedRoutes::ScopeKeyBuilder::FragmentBuilder&& config) + : FragmentBuilderBase(std::move(config)) { + ASSERT(config_.type_case() == + ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::kLocalPortValueExtractor, + "local_port_value_extractor is not set."); +} + +std::unique_ptr LocalPortValueExtractorImpl::computeFragment( + const Http::HeaderMap&, const StreamInfo::StreamInfo* info, ReComputeCbPtr&) const { + ASSERT(info != nullptr, "streamInfo is nullptr."); + auto port = info->downstreamAddressProvider().localAddress()->ip()->port(); + return std::make_unique(std::to_string(long(port))); +} + +HostValueExtractorImpl::HostValueExtractorImpl( + ScopedRoutes::ScopeKeyBuilder::FragmentBuilder&& config) + : FragmentBuilderBase(std::move(config)), + host_value_extractor_config_(config_.host_value_extractor()), + max_recompute_num_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + host_value_extractor_config_, max_recompute_num, DefaultMaxRecomputeNum)) { + ASSERT(config_.type_case() == ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::kHostValueExtractor, + "host_value_extractor is not set."); +} + +std::unique_ptr +HostValueExtractorImpl::reComputeHelper(const std::string& host, + ReComputeCbWeakPtr& weak_next_recompute, + uint32_t recompute_seq) const { + if (recompute_seq == max_recompute_num_) { + ENVOY_LOG_MISC(warn, + "recompute host fragment failed, maximum number of recalculations exceeded"); + return nullptr; + } + auto next_recompute = weak_next_recompute.lock(); + if (!next_recompute) { + return nullptr; + } + if (host == "*") { + *next_recompute = nullptr; + return nullptr; + } + auto masked_host = maskFirstDNSLabel(host); + *next_recompute = [this, masked_host, recompute_seq, + weak_next_recompute]() mutable -> std::unique_ptr { + return reComputeHelper(masked_host, weak_next_recompute, recompute_seq + 1); + }; + return std::make_unique(masked_host); +} + +std::unique_ptr +HostValueExtractorImpl::computeFragment(const Http::HeaderMap& headers, + const StreamInfo::StreamInfo*, + ReComputeCbPtr& recompute) const { + auto host = static_cast(headers).getHostValue(); + auto port_start = Http::HeaderUtility::getPortStart(host); + if (port_start != absl::string_view::npos) { + host = host.substr(0, port_start); + } + *recompute = [this, host, weak_recompute = ReComputeCbWeakPtr(recompute)]() mutable + -> std::unique_ptr { + return reComputeHelper(std::string(host), weak_recompute, 0); + }; + return std::make_unique(host); +} + +std::unique_ptr +HeaderValueExtractorImpl::computeFragment(const Http::HeaderMap& headers, + const StreamInfo::StreamInfo*, ReComputeCbPtr&) const { + return computeFragment(headers); +} + +ScopeKeyPtr ScopeKeyBuilderImpl::computeScopeKey(const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info, + std::function& recompute) const { + ScopeKey key; + bool recomputeable = false; + auto recompute_cbs = std::make_shared>(); + for (const auto& builder : fragment_builders_) { + // returns nullopt if a null fragment is found. + ReComputeCbPtr recompute_fragment_cb = std::make_shared(); + std::unique_ptr fragment = + builder->computeFragment(headers, info, recompute_fragment_cb); + if (fragment == nullptr) { + return nullptr; + } + if (*recompute_fragment_cb == nullptr) { + auto key_fragment = static_cast(fragment.get()); + auto copied_fragment = std::make_shared(*key_fragment); + auto recompute_cb = + std::make_shared([copied_fragment]() -> std::unique_ptr { + return std::make_unique(*copied_fragment); + }); + recompute_cbs->push_back(recompute_cb); + } else { + recomputeable = true; + recompute_cbs->push_back(recompute_fragment_cb); + } + key.addFragment(std::move(fragment)); + } + if (recomputeable) { + recompute = [&recompute, recompute_cbs]() mutable -> ScopeKeyPtr { + ScopeKey new_key; + for (auto& cb : *recompute_cbs) { + auto new_fragment = (*cb)(); + if (new_fragment == nullptr) { + return nullptr; + } + if (*cb == nullptr) { + recompute = nullptr; + } + new_key.addFragment(std::move(new_fragment)); + } + return std::make_unique(std::move(new_key)); + }; + } + return std::make_unique(std::move(key)); +} + +ScopeKeyPtr ScopedConfigImpl::computeScopeKey(const ScopeKeyBuilder* scope_key_builder, + const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info) const { + std::function recompute; + ScopeKeyPtr scope_key = scope_key_builder->computeScopeKey(headers, info, recompute); + if (scope_key == nullptr) { + return nullptr; + } + decltype(scoped_route_info_by_key_.begin()) iter; + do { + iter = scoped_route_info_by_key_.find(scope_key->hash()); + if (iter != scoped_route_info_by_key_.end()) { + return scope_key; + } + } while (recompute != nullptr && (scope_key = recompute())); + return nullptr; +} + +Router::ConfigConstSharedPtr +ScopedConfigImpl::getRouteConfig(const ScopeKeyBuilder* scope_key_builder, + const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info) const { + std::function recompute; + ScopeKeyPtr scope_key = scope_key_builder->computeScopeKey(headers, info, recompute); + if (scope_key == nullptr) { + return nullptr; + } + decltype(scoped_route_info_by_key_.begin()) iter; + do { + iter = scoped_route_info_by_key_.find(scope_key->hash()); + if (iter != scoped_route_info_by_key_.end()) { + return iter->second->routeConfig(); + } + } while (recompute != nullptr && (scope_key = recompute())); + + return nullptr; +} + +#endif + bool ScopeKey::operator!=(const ScopeKey& other) const { return !(*this == other); } bool ScopeKey::operator==(const ScopeKey& other) const { @@ -98,6 +281,16 @@ ScopeKeyBuilderImpl::ScopeKeyBuilderImpl(ScopedRoutes::ScopeKeyBuilder&& config) : ScopeKeyBuilderBase(std::move(config)) { for (const auto& fragment_builder : config_.fragments()) { switch (fragment_builder.type_case()) { +#if defined(ALIMESH) + case ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::kHostValueExtractor: + fragment_builders_.emplace_back(std::make_unique( + ScopedRoutes::ScopeKeyBuilder::FragmentBuilder(fragment_builder))); + break; + case ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::kLocalPortValueExtractor: + fragment_builders_.emplace_back(std::make_unique( + ScopedRoutes::ScopeKeyBuilder::FragmentBuilder(fragment_builder))); + break; +#endif case ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::kHeaderValueExtractor: fragment_builders_.emplace_back(std::make_unique( ScopedRoutes::ScopeKeyBuilder::FragmentBuilder(fragment_builder))); @@ -112,7 +305,13 @@ ScopeKeyPtr ScopeKeyBuilderImpl::computeScopeKey(const Http::HeaderMap& headers) ScopeKey key; for (const auto& builder : fragment_builders_) { // returns nullopt if a null fragment is found. +#if defined(ALIMESH) + ReComputeCbPtr recompute_fragment_cb = std::make_shared(); + std::unique_ptr fragment = + builder->computeFragment(headers, nullptr, recompute_fragment_cb); +#else std::unique_ptr fragment = builder->computeFragment(headers); +#endif if (fragment == nullptr) { return nullptr; } diff --git a/source/common/router/scoped_config_impl.h b/source/common/router/scoped_config_impl.h index d7f8bd158ffa2..c8250517c7929 100644 --- a/source/common/router/scoped_config_impl.h +++ b/source/common/router/scoped_config_impl.h @@ -22,6 +22,12 @@ namespace Router { using envoy::extensions::filters::network::http_connection_manager::v3::ScopedRoutes; +#if defined(ALIMESH) +using ReComputeCb = std::function()>; +using ReComputeCbPtr = std::shared_ptr; +using ReComputeCbWeakPtr = std::weak_ptr; +#endif + /** * Base class for fragment builders. */ @@ -31,10 +37,16 @@ class FragmentBuilderBase { : config_(std::move(config)) {} virtual ~FragmentBuilderBase() = default; +#if defined(ALIMESH) + virtual std::unique_ptr + computeFragment(const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info, + ReComputeCbPtr& recompute) const PURE; +#else // Returns a fragment if the fragment rule applies, a nullptr indicates no fragment could be // generated from the headers. virtual std::unique_ptr computeFragment(const Http::HeaderMap& headers) const PURE; +#endif protected: const ScopedRoutes::ScopeKeyBuilder::FragmentBuilder config_; @@ -44,14 +56,52 @@ class HeaderValueExtractorImpl : public FragmentBuilderBase { public: explicit HeaderValueExtractorImpl(ScopedRoutes::ScopeKeyBuilder::FragmentBuilder&& config); +#if defined(ALIMESH) + std::unique_ptr computeFragment(const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info, + ReComputeCbPtr& recompute) const override; + std::unique_ptr computeFragment(const Http::HeaderMap& headers) const; +#else std::unique_ptr computeFragment(const Http::HeaderMap& headers) const override; +#endif + private: const ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::HeaderValueExtractor& header_value_extractor_config_; }; +#if defined(ALIMESH) +class HostValueExtractorImpl : public FragmentBuilderBase { +public: + explicit HostValueExtractorImpl(ScopedRoutes::ScopeKeyBuilder::FragmentBuilder&& config); + + std::unique_ptr computeFragment(const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info, + ReComputeCbPtr& recompute) const override; + +private: + std::unique_ptr reComputeHelper(const std::string& host, + ReComputeCbWeakPtr& weak_next_recompute, + uint32_t recompute_seq) const; + + static constexpr uint32_t DefaultMaxRecomputeNum = 100; + + const ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::HostValueExtractor& + host_value_extractor_config_; + const uint32_t max_recompute_num_; +}; + +class LocalPortValueExtractorImpl : public FragmentBuilderBase { +public: + explicit LocalPortValueExtractorImpl(ScopedRoutes::ScopeKeyBuilder::FragmentBuilder&& config); + + std::unique_ptr computeFragment(const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info, + ReComputeCbPtr& recompute) const override; +}; +#endif /** * Base class for ScopeKeyBuilder implementations. */ @@ -68,7 +118,14 @@ class ScopeKeyBuilderImpl : public ScopeKeyBuilderBase { public: explicit ScopeKeyBuilderImpl(ScopedRoutes::ScopeKeyBuilder&& config); +#if defined(ALIMESH) + ScopeKeyPtr computeScopeKey(const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info, + std::function& recompute) const override; + // only for test ScopeKeyPtr computeScopeKey(const Http::HeaderMap& headers) const override; +#else + ScopeKeyPtr computeScopeKey(const Http::HeaderMap& headers) const override; +#endif private: std::vector> fragment_builders_; @@ -118,9 +175,17 @@ class ScopedConfigImpl : public ScopedConfig { void removeRoutingScopes(const std::vector& scope_names); - // Envoy::Router::ScopedConfig Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr& scope_key) const override; +#if defined(ALIMESH) + Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder* scope_key_builder, + const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info) const override; + ScopeKeyPtr computeScopeKey(const ScopeKeyBuilder* scope_key_builder, + const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info) const override; +#endif + private: // From scope name to cached ScopedRouteInfo. absl::flat_hash_map scoped_route_info_by_name_; @@ -136,6 +201,12 @@ class NullScopedConfigImpl : public ScopedConfig { Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr&) const override { return std::make_shared(); } +#if defined(ALIMESH) + Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder*, const Http::HeaderMap&, + const StreamInfo::StreamInfo*) const override { + return std::make_shared(); + } +#endif }; } // namespace Router diff --git a/source/common/router/upstream_codec_filter.h b/source/common/router/upstream_codec_filter.h index f1e7b122bc0e4..657a59f99db66 100644 --- a/source/common/router/upstream_codec_filter.h +++ b/source/common/router/upstream_codec_filter.h @@ -115,7 +115,7 @@ class UpstreamCodecFilterFactory std::string category() const override { return "envoy.filters.http.upstream"; } Http::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message&, const std::string&, - Server::Configuration::UpstreamHttpFactoryContext&) override { + Server::Configuration::UpstreamFactoryContext&) override { return [](Http::FilterChainFactoryCallbacks& callbacks) -> void { callbacks.addStreamDecoderFilter(std::make_shared()); }; diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 630465d000ac1..4cb4e98a25672 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -111,6 +111,17 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, auto upstream_host = conn_pool_->host(); if (span_ != nullptr) { span_->injectContext(*parent_.downstreamHeaders(), upstream_host); +#if defined(ALIMESH) + if (upstream_host != nullptr && upstream_host->address() != nullptr && + upstream_host->address()->ip() != nullptr) { + const std::string& address = upstream_host->address()->ip()->addressAsString(); + if (upstream_host->address()->ip()->version() == Network::Address::IpVersion::v6) { + span_->setTag(Tracing::Tags::get().PeerIpv6, address); + } else { + span_->setTag(Tracing::Tags::get().PeerIpv4, address); + } + } +#endif } else { // No independent child span for current upstream request then inject the parent span's tracing // context into the request headers. @@ -834,4 +845,4 @@ UpstreamRequestFilterManagerCallbacks::http1StreamEncoderOptions() { } } // namespace Router -} // namespace Envoy +} // namespace Envoy \ No newline at end of file diff --git a/source/common/secret/secret_manager_impl.h b/source/common/secret/secret_manager_impl.h index 952206f106b73..28d14eca970d0 100644 --- a/source/common/secret/secret_manager_impl.h +++ b/source/common/secret/secret_manager_impl.h @@ -16,7 +16,11 @@ namespace Envoy { namespace Secret { +#if defined(ALIMESH) +class SecretManagerImpl : public SecretManager, public Logger::Loggable { +#else class SecretManagerImpl : public SecretManager { +#endif public: SecretManagerImpl(OptRef config_tracker); void diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h index 767501aa2b34a..61e702667c393 100644 --- a/source/common/stream_info/stream_info_impl.h +++ b/source/common/stream_info/stream_info_impl.h @@ -404,6 +404,21 @@ struct StreamInfoImpl : public StreamInfo { return downstream_transport_failure_reason_; } +#ifdef ALIMESH + void setCustomSpanTag(std::string_view key, std::string_view value) override { + auto it = custom_span_tags_.find(key); + if (it != custom_span_tags_.end()) { + it->second = value; + } else { + custom_span_tags_.emplace(key, value); + } + } + + const absl::flat_hash_map& getCustomSpanTagMap() const override { + return custom_span_tags_; + } +#endif + TimeSource& time_source_; SystemTime start_time_; MonotonicTime start_time_monotonic_; @@ -460,6 +475,9 @@ struct StreamInfoImpl : public StreamInfo { BytesMeterSharedPtr downstream_bytes_meter_; bool is_shadow_{false}; std::string downstream_transport_failure_reason_; +#ifdef ALIMESH + absl::flat_hash_map custom_span_tags_; +#endif }; } // namespace StreamInfo diff --git a/source/common/tracing/http_tracer_impl.cc b/source/common/tracing/http_tracer_impl.cc index e55153de6613a..b99baed213183 100644 --- a/source/common/tracing/http_tracer_impl.cc +++ b/source/common/tracing/http_tracer_impl.cc @@ -218,6 +218,14 @@ void HttpTracerUtility::setCommonTags(Span& span, const StreamInfo::StreamInfo& span.setTag(Tracing::Tags::get().Component, Tracing::Tags::get().Proxy); +#ifdef ALIMESH + // Wasm filter state + const auto& custom_span_tags = stream_info.getCustomSpanTagMap(); + for (const auto& it: custom_span_tags) { + span.setTag(it.first, it.second); + } +#endif + // Cluster info. if (auto cluster_info = stream_info.upstreamClusterInfo(); cluster_info.has_value() && cluster_info.value() != nullptr) { @@ -248,4 +256,4 @@ void HttpTracerUtility::setCommonTags(Span& span, const StreamInfo::StreamInfo& } } // namespace Tracing -} // namespace Envoy +} // namespace Envoy \ No newline at end of file diff --git a/source/common/tracing/tracer_impl.cc b/source/common/tracing/tracer_impl.cc index 01ee929bbd35d..35112b3a05f74 100644 --- a/source/common/tracing/tracer_impl.cc +++ b/source/common/tracing/tracer_impl.cc @@ -156,6 +156,16 @@ SpanPtr TracerImpl::startSpan(const Config& config, TraceContext& trace_context, if (active_span) { active_span->setTag(Tracing::Tags::get().NodeId, local_info_.nodeName()); active_span->setTag(Tracing::Tags::get().Zone, local_info_.zoneName()); +#if defined(ALIMESH) + const std::string& remote_address = + stream_info.downstreamAddressProvider().localAddress()->ip()->addressAsString(); + if (stream_info.downstreamAddressProvider().localAddress()->ip()->version() == + Network::Address::IpVersion::v6) { + active_span->setTag(Tracing::Tags::get().PeerIpv6, remote_address); + } else { + active_span->setTag(Tracing::Tags::get().PeerIpv4, remote_address); + } +#endif } return active_span; diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 60ba2173cb7f3..23b0e2e088aaa 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -131,6 +131,9 @@ envoy_cc_library( "//source/common/http/http3:conn_pool_lib", "//source/common/http:conn_pool_grid", ]), + alimesh_deps = [ + "//source/common/redis:async_client_lib", + ] ) envoy_cc_library( @@ -453,7 +456,7 @@ envoy_cc_library( deps = [ ":load_balancer_lib", ":resource_manager_lib", - ":upstream_http_factory_context_lib", + ":upstream_factory_context_lib", "//envoy/event:timer_interface", "//envoy/local_info:local_info_interface", "//envoy/network:dns_interface", @@ -557,8 +560,8 @@ envoy_cc_library( ) envoy_cc_library( - name = "upstream_http_factory_context_lib", - hdrs = ["upstream_http_factory_context_impl.h"], + name = "upstream_factory_context_lib", + hdrs = ["upstream_factory_context_impl.h"], deps = [ "//envoy/init:manager_interface", "//envoy/server:factory_context_interface", diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index a26a46a8a2135..45e0f68c2ec26 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -51,6 +51,10 @@ #include "source/common/quic/client_connection_factory_impl.h" #endif +#if defined(ALIMESH) +#include "source/common/redis/async_client_impl.h" +#endif + namespace Envoy { namespace Upstream { namespace { @@ -1203,6 +1207,25 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::httpAsyncClient return *lazy_http_async_client_; } +#if defined(ALIMESH) +Redis::AsyncClient& +ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::redisAsyncClient() { + using Extensions::NetworkFilters::Common::Redis::RedisCommandStats; + using Extensions::NetworkFilters::Common::Redis::Client::RawClientFactoryImpl; + + if (lazy_redis_async_client_ == nullptr) { + auto redis_command_stats = + RedisCommandStats::createRedisCommandStats(parent_.parent_.stats_.symbolTable()); + lazy_redis_async_client_ = std::make_unique( + this, parent_.thread_local_dispatcher_, RawClientFactoryImpl::instance_, + parent_.parent_.stats_.createScope( + fmt::format("cluster.{}.redis_cluster", cluster_info_->name())), + redis_command_stats, nullptr); + } + return *lazy_redis_async_client_; +} +#endif + Tcp::AsyncTcpClientPtr ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::tcpAsyncClient( LoadBalancerContext* context, Tcp::AsyncTcpClientOptionsConstSharedPtr options) { diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 84ea8b79dbd48..f01783ce7aa10 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -525,6 +525,9 @@ class ClusterManagerImpl : public ClusterManager, LoadBalancerContext* context) override; Host::CreateConnectionData tcpConn(LoadBalancerContext* context) override; Http::AsyncClient& httpAsyncClient() override; +#if defined(ALIMESH) + Redis::AsyncClient& redisAsyncClient() override; +#endif Tcp::AsyncTcpClientPtr tcpAsyncClient(LoadBalancerContext* context, Tcp::AsyncTcpClientOptionsConstSharedPtr options) override; @@ -573,6 +576,9 @@ class ClusterManagerImpl : public ClusterManager, // Current active LB. LoadBalancerPtr lb_; Http::AsyncClientPtr lazy_http_async_client_; +#if defined(ALIMESH) + Redis::AsyncClientPtr lazy_redis_async_client_; +#endif // Stores QUICHE specific objects which live through out the life time of the cluster and can // be shared across its hosts. Http::PersistentQuicInfoPtr quic_info_; diff --git a/source/common/upstream/outlier_detection_impl.cc b/source/common/upstream/outlier_detection_impl.cc index 3c51d3a5e7902..8eedf688b8c98 100644 --- a/source/common/upstream/outlier_detection_impl.cc +++ b/source/common/upstream/outlier_detection_impl.cc @@ -65,6 +65,17 @@ void DetectorHostMonitorImpl::updateCurrentSuccessRateBucket() { local_origin_sr_monitor_.updateCurrentSuccessRateBucket(); } +#if defined(ALIMESH) +void DetectorHostMonitorImpl::forceEjectHost() { + std::shared_ptr detector = detector_.lock(); + if (!detector) { + // It's possible for the cluster/detector to go away while we still have a host in use. + return; + } + detector->onConsecutive5xx(host_.lock()); +} +#endif + void DetectorHostMonitorImpl::putHttpResponseCode(uint64_t response_code) { external_origin_sr_monitor_.incTotalReqCounter(); if (Http::CodeUtility::is5xx(response_code)) { diff --git a/source/common/upstream/outlier_detection_impl.h b/source/common/upstream/outlier_detection_impl.h index c298a24ea79a4..16da9e886a496 100644 --- a/source/common/upstream/outlier_detection_impl.h +++ b/source/common/upstream/outlier_detection_impl.h @@ -166,6 +166,9 @@ class DetectorHostMonitorImpl : public DetectorHostMonitor { void localOriginFailure(); void localOriginNoFailure(); +#if defined(ALIMESH) + void forceEjectHost() override; +#endif // handlers for setting and getting jitter, used to add a random value // to outlier eject time in order to prevent a connection storm when // hosts are unejected diff --git a/source/common/upstream/upstream_http_factory_context_impl.h b/source/common/upstream/upstream_factory_context_impl.h similarity index 74% rename from source/common/upstream/upstream_http_factory_context_impl.h rename to source/common/upstream/upstream_factory_context_impl.h index 44192552c1bcd..6ad4b7e4d564a 100644 --- a/source/common/upstream/upstream_http_factory_context_impl.h +++ b/source/common/upstream/upstream_factory_context_impl.h @@ -11,10 +11,10 @@ namespace Upstream { * Upstream Factory Context used by both Clusters and Routers to configure * upstream filters. */ -class UpstreamHttpFactoryContextImpl : public Server::Configuration::UpstreamHttpFactoryContext { +class UpstreamFactoryContextImpl : public Server::Configuration::UpstreamFactoryContext { public: - UpstreamHttpFactoryContextImpl(Server::Configuration::ServerFactoryContext& context, - Init::Manager& init_manager, Stats::Scope& scope) + UpstreamFactoryContextImpl(Server::Configuration::ServerFactoryContext& context, + Init::Manager& init_manager, Stats::Scope& scope) : server_context_(context), init_manager_(init_manager), scope_(scope) {} Server::Configuration::ServerFactoryContext& getServerFactoryContext() const override { diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index cccb4f9e33ca2..09b52f04bc458 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -1230,7 +1230,7 @@ ClusterInfoImpl::ClusterInfoImpl( Config::Utility::translateOpaqueConfig(proto_config.typed_config(), factory_context.messageValidationVisitor(), *message); Network::FilterFactoryCb callback = - factory.createFilterFactoryFromProto(*message, *factory_context_); + factory.createFilterFactoryFromProto(*message, upstream_context_); filter_factories_.push_back(network_config_provider_manager_.createStaticFilterConfigProvider( callback, proto_config.name())); } @@ -1253,7 +1253,7 @@ ClusterInfoImpl::ClusterInfoImpl( upstream_context_.getServerFactoryContext()); std::string prefix = stats_scope_->symbolTable().toString(stats_scope_->prefix()); - Http::FilterChainHelper helper(*filter_config_provider_manager, upstream_context_.getServerFactoryContext(), upstream_context_, prefix); diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 05a097d349dbe..bcbebb6356f49 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -60,7 +60,7 @@ #include "source/common/upstream/load_balancer_impl.h" #include "source/common/upstream/resource_manager_impl.h" #include "source/common/upstream/transport_socket_match_impl.h" -#include "source/common/upstream/upstream_http_factory_context_impl.h" +#include "source/common/upstream/upstream_factory_context_impl.h" #include "source/extensions/upstreams/http/config.h" #include "source/extensions/upstreams/tcp/config.h" #include "source/server/transport_socket_config_impl.h" @@ -190,6 +190,10 @@ class DetectorHostMonitorNullImpl : public Outlier::DetectorHostMonitor { const absl::optional& lastUnejectionTime() override { return time_; } double successRate(SuccessRateMonitorType) const override { return -1; } +#if defined(ALIMESH) + void forceEjectHost() override {} +#endif + private: const absl::optional time_{}; }; @@ -1105,7 +1109,7 @@ class ClusterInfoImpl : public ClusterInfo, mutable Http::Http1::CodecStats::AtomicPtr http1_codec_stats_; mutable Http::Http2::CodecStats::AtomicPtr http2_codec_stats_; mutable Http::Http3::CodecStats::AtomicPtr http3_codec_stats_; - UpstreamHttpFactoryContextImpl upstream_context_; + UpstreamFactoryContextImpl upstream_context_; // Keep small values like bools and enums at the end of the class to reduce // overhead via alignment diff --git a/source/exe/BUILD b/source/exe/BUILD index 32fdd616dd268..d56d7a99c8394 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -10,7 +10,7 @@ load( "envoy_select_enable_http3", "envoy_select_signal_trace", ) -load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//bazel:repositories.bzl", "DARWIN_SKIP_TARGETS", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") load("//source/extensions:all_extensions.bzl", "envoy_all_core_extensions", "envoy_all_extensions") licenses(["notice"]) # Apache 2 @@ -46,8 +46,12 @@ envoy_cc_library( ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), + "//bazel:darwin": envoy_all_extensions(DARWIN_SKIP_TARGETS), "//conditions:default": envoy_all_extensions(), }), + alimesh_deps = [ +# "//external:basic_auth_lib", + ], ) envoy_cc_library( diff --git a/source/extensions/common/dubbo/message_impl.cc b/source/extensions/common/dubbo/message_impl.cc index 89b30dd92b4b1..59fcc2ffff789 100644 --- a/source/extensions/common/dubbo/message_impl.cc +++ b/source/extensions/common/dubbo/message_impl.cc @@ -10,35 +10,37 @@ namespace Dubbo { RpcRequestImpl::Attachment::Attachment(MapPtr&& value, size_t offset) : attachment_(std::move(value)), attachment_offset_(offset) { ASSERT(attachment_ != nullptr); - ASSERT(attachment_->toMutableUntypedMap().has_value()); + ASSERT(attachment_->toMutableUntypedMap()); } void RpcRequestImpl::Attachment::insert(absl::string_view key, absl::string_view value) { - ASSERT(attachment_->toMutableUntypedMap().has_value()); + ASSERT(attachment_->toMutableUntypedMap()); attachment_updated_ = true; Hessian2::ObjectPtr key_o = std::make_unique(key); Hessian2::ObjectPtr val_o = std::make_unique(value); - attachment_->toMutableUntypedMap().value().get().insert_or_assign(std::move(key_o), - std::move(val_o)); + auto map = attachment_->toMutableUntypedMap(); + map->insert_or_assign(std::move(key_o), std::move(val_o)); } void RpcRequestImpl::Attachment::remove(absl::string_view key) { - ASSERT(attachment_->toMutableUntypedMap().has_value()); + ASSERT(attachment_->toMutableUntypedMap()); attachment_updated_ = true; - attachment_->toMutableUntypedMap().value().get().erase(key); + + auto map = attachment_->toMutableUntypedMap(); + map->erase(std::make_unique(key)); } absl::optional RpcRequestImpl::Attachment::lookup(absl::string_view key) const { - ASSERT(attachment_->toMutableUntypedMap().has_value()); + ASSERT(attachment_->toMutableUntypedMap()); - auto& map = attachment_->toMutableUntypedMap().value().get(); - auto result = map.find(key); - if (result != map.end() && result->second->type() == Hessian2::Object::Type::String) { + auto map = attachment_->toMutableUntypedMap(); + auto result = map->find(std::make_unique(key)); + if (result != map->end() && result->second->type() == Hessian2::Object::Type::String) { ASSERT(result->second->toString().has_value()); - return absl::make_optional(result->second->toString().value().get()); + return absl::make_optional(*(result->second->toString().value())); } return absl::nullopt; } diff --git a/source/extensions/common/redis/BUILD b/source/extensions/common/redis/BUILD index 3e9ca8cad3d07..3afeaed14b60d 100644 --- a/source/extensions/common/redis/BUILD +++ b/source/extensions/common/redis/BUILD @@ -23,6 +23,12 @@ envoy_cc_library( name = "cluster_refresh_manager_lib", srcs = ["cluster_refresh_manager_impl.cc"], hdrs = ["cluster_refresh_manager_impl.h"], + visibility = [ + "//:contrib_library", + "//:examples_library", + "//:extension_library", + "//source/common/redis:__pkg__", + ], deps = [ ":cluster_refresh_manager_interface", "//envoy/event:dispatcher_interface", diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index adcc4be4c8b3c..91348a69eebbe 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -59,6 +59,27 @@ namespace { // FilterState prefix for CelState values. constexpr absl::string_view CelStateKeyPrefix = "wasm."; +#if defined(ALIMESH) +constexpr absl::string_view CustomeTraceSpanTagPrefix = "trace_span_tag."; +constexpr std::string_view ClearRouteCacheKey = "clear_route_cache"; +constexpr std::string_view DisableClearRouteCache = "off"; +constexpr std::string_view SetDecoderBufferLimit = "set_decoder_buffer_limit"; +constexpr std::string_view SetEncoderBufferLimit = "set_encoder_buffer_limit"; + +bool stringViewToUint32(std::string_view str, uint32_t& out_value) { + try { + unsigned long temp = std::stoul(std::string(str)); + if (temp <= std::numeric_limits::max()) { + out_value = static_cast(temp); + return true; + } + } catch (const std::exception& e) { + ENVOY_LOG_MISC(critical, "stringToUint exception '{}'", e.what()); + } + return false; +} +#endif + using HashPolicy = envoy::config::route::v3::RouteAction::HashPolicy; using CelState = Filters::Common::Expr::CelState; using CelStatePrototype = Filters::Common::Expr::CelStatePrototype; @@ -452,6 +473,12 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co using google::api::expr::runtime::CelProtoWrapper; using google::api::expr::runtime::CelValue; +#if defined(ALIMESH) + Envoy::Http::StreamFilterCallbacks* filter_callbacks = decoder_callbacks_; + if (filter_callbacks == nullptr) { + filter_callbacks = encoder_callbacks_; + } +#endif const StreamInfo::StreamInfo* info = getConstRequestStreamInfo(); // In order to delegate to the StreamActivation method, we have to set the // context properties to match the Wasm context properties in all callbacks @@ -539,9 +566,28 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co } break; case PropertyToken::ROUTE_NAME: +#if defined(ALIMESH) + if (info && !info->getRouteName().empty()) { + return CelValue::CreateString(&info->getRouteName()); + } + if (filter_callbacks) { + auto route = filter_callbacks->route(); + if (route) { + auto route_entry = route->routeEntry(); + if (route_entry) { + return CelValue::CreateString(&route_entry->routeName()); + } + auto dr_entry = route->directResponseEntry(); + if (dr_entry) { + return CelValue::CreateString(&dr_entry->routeName()); + } + } + } +#else if (info) { return CelValue::CreateString(&info->getRouteName()); } +#endif break; case PropertyToken::ROUTE_METADATA: if (info && info->route()) { @@ -716,9 +762,16 @@ WasmResult Context::addHeaderMapValue(WasmHeaderMapType type, std::string_view k } const Http::LowerCaseString lower_key{std::string(key)}; map->addCopy(lower_key, std::string(value)); +#if defined(ALIMESH) + if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && + !disable_clear_route_cache_) { + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); + } +#else if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); } +#endif return WasmResult::Ok; } @@ -791,9 +844,16 @@ WasmResult Context::setHeaderMapPairs(WasmHeaderMapType type, const Pairs& pairs const Http::LowerCaseString lower_key{std::string(p.first)}; map->addCopy(lower_key, std::string(p.second)); } +#if defined(ALIMESH) + if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && + !disable_clear_route_cache_) { + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); + } +#else if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); } +#endif return WasmResult::Ok; } @@ -804,9 +864,16 @@ WasmResult Context::removeHeaderMapValue(WasmHeaderMapType type, std::string_vie } const Http::LowerCaseString lower_key{std::string(key)}; map->remove(lower_key); +#if defined(ALIMESH) + if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && + !disable_clear_route_cache_) { + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); + } +#else if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); } +#endif return WasmResult::Ok; } @@ -818,9 +885,16 @@ WasmResult Context::replaceHeaderMapValue(WasmHeaderMapType type, std::string_vi } const Http::LowerCaseString lower_key{std::string(key)}; map->setCopy(lower_key, toAbslStringView(value)); +#if defined(ALIMESH) + if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && + !disable_clear_route_cache_) { + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); + } +#else if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); } +#endif return WasmResult::Ok; } @@ -879,6 +953,10 @@ BufferInterface* Context::getBuffer(WasmBufferType type) { std::string_view(static_cast(body.linearize(body.length())), body.length())); } return nullptr; +#if defined(ALIMESH) + case WasmBufferType::RedisCallResponse: + return buffer_.set(rootContext()->redis_call_response_); +#endif case WasmBufferType::GrpcReceiveBuffer: return buffer_.set(rootContext()->grpc_receive_buffer_.get()); default: @@ -886,6 +964,49 @@ BufferInterface* Context::getBuffer(WasmBufferType type) { } } +#if defined(ALIMESH) +/** + * The goal here is to have the wasm filter cache the original body when replacing the entire body + * using the backup_for_replace mechanism of modifyDecodingBuffer. A special case to consider here + * is when a complete body is passed in a single decodeData call and the filter does not return + * StopIterationAndBuffer. In this scenario, buffering_request_body_ is false, but it's possible + * that an upper layer filter has performed the buffering, necessitating operations on the + * decodingBuffer. Another possibility is that the body is small and completed in a single + * decodeData call. This scenario can be managed by returning StopIteration at the decodeHeader + * stage to enable buffering. Furthermore, buffering_request_body_ being false indicates + * streaming, and modifications to the buffer itself should always be avoided in such cases. + */ +WasmResult Context::setBuffer(WasmBufferType type, size_t start, size_t length, + std::string_view data) { + switch (type) { + case WasmBufferType::HttpRequestBody: + if (decoder_callbacks_ && decoder_callbacks_->decodingBuffer() != nullptr) { + // We need the mutable version, so capture it using a callback. + // TODO: consider adding a mutableDecodingBuffer() interface. + ::Envoy::Buffer::Instance* buffer_instance{}; + bool backup_for_replace = false; + // When a body replacement occurs, back up the original body. + if (start == 0 && length >= decoder_callbacks_->decodingBuffer()->length()) { + backup_for_replace = true; + } + decoder_callbacks_->modifyDecodingBuffer( + [&buffer_instance](::Envoy::Buffer::Instance& buffer) { buffer_instance = &buffer; }, + backup_for_replace); + if (buffering_request_body_) { + return buffer_.set(buffer_instance)->copyFrom(start, length, data); + } + } + return buffer_.set(request_body_buffer_)->copyFrom(start, length, data); + default: + auto* buffer = getBuffer(type); + if (buffer == nullptr) { + return WasmResult::NotFound; + } + return buffer->copyFrom(start, length, data); + } +} +#endif + void Context::onDownstreamConnectionClose(CloseType close_type) { ContextBase::onDownstreamConnectionClose(close_type); downstream_closed_ = true; @@ -959,6 +1080,87 @@ WasmResult Context::httpCall(std::string_view cluster, const Pairs& request_head return WasmResult::Ok; } +#if defined(ALIMESH) +WasmResult Context::redisInit(std::string_view cluster, std::string_view username, + std::string_view password, int timeout_milliseconds) { + auto cluster_string = std::string(cluster.substr(0, cluster.find('?'))); + const auto thread_local_cluster = clusterManager().getThreadLocalCluster(cluster_string); + if (thread_local_cluster == nullptr) { + return WasmResult::BadArgument; + } + + Redis::AsyncClientConfig config(std::string(username), std::string(password), + timeout_milliseconds, Http::Utility::parseQueryString(cluster)); + thread_local_cluster->redisAsyncClient().initialize(config); + + return WasmResult::Ok; +} + +WasmResult Context::redisCall(std::string_view cluster, std::string_view query, + uint32_t* token_ptr) { + auto cluster_string = std::string(cluster.substr(0, cluster.find('?'))); + const auto thread_local_cluster = clusterManager().getThreadLocalCluster(cluster_string); + if (thread_local_cluster == nullptr) { + return WasmResult::BadArgument; + } + + uint32_t token = wasm()->nextRedisCallId(); + auto& handler = redis_request_[token]; + handler.context_ = this; + handler.token_ = token; + + auto redis_request = thread_local_cluster->redisAsyncClient().send(std::string(query), handler); + if (!redis_request) { + redis_request_.erase(token); + return WasmResult::InternalFailure; + } + handler.request_ = redis_request; + *token_ptr = token; + return WasmResult::Ok; +} + +void Context::onRedisCallSuccess(uint32_t token, std::string&& response) { + if (proxy_wasm::current_context_ != nullptr) { + // We are in a reentrant call, so defer. + wasm()->addAfterVmCallAction([this, token, response = std::move(response)]() mutable { + onRedisCallSuccess(token, std::move(response)); + }); + return; + } + + auto handler = redis_request_.find(token); + if (handler == redis_request_.end()) { + return; + } + + uint32_t body_size = response.size(); + redis_call_response_ = std::move(response); + proxy_wasm::ContextBase::onRedisCallResponse( + token, static_cast(proxy_wasm::RedisStatus::Ok), body_size); + redis_call_response_.clear(); + redis_request_.erase(handler); +} + +void Context::onRedisCallFailure(uint32_t token) { + if (proxy_wasm::current_context_ != nullptr) { + // We are in a reentrant call, so defer. + wasm()->addAfterVmCallAction([this, token] { onRedisCallFailure(token); }); + return; + } + + auto handler = redis_request_.find(token); + if (handler == redis_request_.end()) { + return; + } + status_code_ = static_cast(WasmResult::BrokenConnection); + status_message_ = "reset"; + proxy_wasm::ContextBase::onRedisCallResponse( + token, static_cast(proxy_wasm::RedisStatus::NetworkError), 0); + status_message_ = ""; + redis_request_.erase(handler); +} +#endif + WasmResult Context::grpcCall(std::string_view grpc_service, std::string_view service_name, std::string_view method_name, const Pairs& initial_metadata, std::string_view request, std::chrono::milliseconds timeout, @@ -1104,6 +1306,12 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { if (!stream_info) { return WasmResult::NotFound; } +#ifdef ALIMESH + if (absl::StartsWith(path, CustomeTraceSpanTagPrefix)) { + stream_info->setCustomSpanTag(path.substr(CustomeTraceSpanTagPrefix.size()), value); + return WasmResult::Ok; + } +#endif std::string key; absl::StrAppend(&key, CelStateKeyPrefix, toAbslStringView(path)); CelState* state = stream_info->filterState()->getDataMutable(key); @@ -1119,6 +1327,21 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { StreamInfo::FilterState::StateType::Mutable, prototype.life_span_); } +#if defined(ALIMESH) + if (path == ClearRouteCacheKey) { + disable_clear_route_cache_ = value == DisableClearRouteCache; + } else if (path == SetDecoderBufferLimit && decoder_callbacks_) { + uint32_t buffer_limit; + if (stringViewToUint32(value, buffer_limit)) { + decoder_callbacks_->setDecoderBufferLimit(buffer_limit); + } + } else if (path == SetEncoderBufferLimit && encoder_callbacks_) { + uint32_t buffer_limit; + if (stringViewToUint32(value, buffer_limit)) { + encoder_callbacks_->setEncoderBufferLimit(buffer_limit); + } + } +#endif if (!state->setValue(toAbslStringView(value))) { return WasmResult::BadArgument; } @@ -1334,6 +1557,11 @@ Context::~Context() { for (auto& p : grpc_stream_) { p.second.stream_->resetStream(); } +#if defined(ALIMESH) + for (auto& p : redis_request_) { + p.second.request_->cancel(); + } +#endif } Network::FilterStatus convertNetworkFilterStatus(proxy_wasm::FilterStatus status) { @@ -1398,7 +1626,11 @@ Network::FilterStatus Context::onNewConnection() { }; Network::FilterStatus Context::onData(::Envoy::Buffer::Instance& data, bool end_stream) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Network::FilterStatus::Continue; } network_downstream_data_buffer_ = &data; @@ -1411,7 +1643,11 @@ Network::FilterStatus Context::onData(::Envoy::Buffer::Instance& data, bool end_ } Network::FilterStatus Context::onWrite(::Envoy::Buffer::Instance& data, bool end_stream) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Network::FilterStatus::Continue; } network_upstream_data_buffer_ = &data; @@ -1429,7 +1665,11 @@ Network::FilterStatus Context::onWrite(::Envoy::Buffer::Instance& data, bool end } void Context::onEvent(Network::ConnectionEvent event) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return; } switch (event) { @@ -1462,7 +1702,11 @@ void Context::log(const Http::RequestHeaderMap* request_headers, if (!stream_info.requestComplete().has_value()) { return; } +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif // If the request is invalid then onRequestHeaders() will not be called and neither will // onCreate() in cases like sendLocalReply who short-circuits envoy // lifecycle. This is because Envoy does not have a well defined lifetime for the combined @@ -1667,7 +1911,11 @@ Http::FilterHeadersStatus Context::decodeHeaders(Http::RequestHeaderMap& headers } Http::FilterDataStatus Context::decodeData(::Envoy::Buffer::Instance& data, bool end_stream) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Http::FilterDataStatus::Continue; } request_body_buffer_ = &data; @@ -1691,7 +1939,11 @@ Http::FilterDataStatus Context::decodeData(::Envoy::Buffer::Instance& data, bool } Http::FilterTrailersStatus Context::decodeTrailers(Http::RequestTrailerMap& trailers) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Http::FilterTrailersStatus::Continue; } request_trailers_ = &trailers; @@ -1703,7 +1955,11 @@ Http::FilterTrailersStatus Context::decodeTrailers(Http::RequestTrailerMap& trai } Http::FilterMetadataStatus Context::decodeMetadata(Http::MetadataMap& request_metadata) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Http::FilterMetadataStatus::Continue; } request_metadata_ = &request_metadata; @@ -1724,7 +1980,11 @@ Http::Filter1xxHeadersStatus Context::encode1xxHeaders(Http::ResponseHeaderMap&) Http::FilterHeadersStatus Context::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Http::FilterHeadersStatus::Continue; } response_headers_ = &headers; @@ -1737,7 +1997,11 @@ Http::FilterHeadersStatus Context::encodeHeaders(Http::ResponseHeaderMap& header } Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool end_stream) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Http::FilterDataStatus::Continue; } response_body_buffer_ = &data; @@ -1761,7 +2025,11 @@ Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool } Http::FilterTrailersStatus Context::encodeTrailers(Http::ResponseTrailerMap& trailers) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Http::FilterTrailersStatus::Continue; } response_trailers_ = &trailers; @@ -1773,7 +2041,11 @@ Http::FilterTrailersStatus Context::encodeTrailers(Http::ResponseTrailerMap& tra } Http::FilterMetadataStatus Context::encodeMetadata(Http::MetadataMap& response_metadata) { +#if defined(ALIMESH) + if (destroyed_ || !in_vm_context_created_) { +#else if (!in_vm_context_created_) { +#endif return Http::FilterMetadataStatus::Continue; } response_metadata_ = &response_metadata; diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 51778de53881e..960adb0a11f68 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -218,7 +218,11 @@ class Context : public proxy_wasm::ContextBase, Pairs additional_headers, uint32_t grpc_status, std::string_view details) override; void clearRouteCache() override { +#if defined(ALIMESH) + if (decoder_callbacks_ && !disable_clear_route_cache_) { +#else if (decoder_callbacks_) { +#endif decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); } } @@ -239,6 +243,11 @@ class Context : public proxy_wasm::ContextBase, // Buffer BufferInterface* getBuffer(WasmBufferType type) override; + +#if defined(ALIMESH) + WasmResult setBuffer(WasmBufferType type, size_t start, size_t length, + std::string_view data) override; +#endif // TODO: use stream_type. bool endOfStream(WasmStreamType /* stream_type */) override { return end_of_stream_; } @@ -247,6 +256,14 @@ class Context : public proxy_wasm::ContextBase, std::string_view request_body, const Pairs& request_trailers, int timeout_millisconds, uint32_t* token_ptr) override; +#if defined(ALIMESH) + // Redis + WasmResult redisInit(std::string_view cluster, std::string_view username, + std::string_view password, int timeout_milliseconds) override; + WasmResult redisCall(std::string_view cluster, std::string_view query, + uint32_t* token_ptr) override; +#endif + // Stats/Metrics WasmResult defineMetric(uint32_t type, std::string_view name, uint32_t* metric_id_ptr) override; WasmResult incrementMetric(uint32_t metric_id, int64_t offset) override; @@ -315,6 +332,21 @@ class Context : public proxy_wasm::ContextBase, Http::AsyncClient::Request* request_; }; +#if defined(ALIMESH) + struct RedisAsyncClientHandler : public Redis::AsyncClient::Callbacks { + // Redis::AsyncClient::Callbacks + void onSuccess(std::string_view, std::string&& response) override { + context_->onRedisCallSuccess(token_, std::move(response)); + } + + void onFailure(std::string_view) override { context_->onRedisCallFailure(token_); } + + Context* context_; + uint32_t token_; + Redis::PoolRequest* request_; + }; +#endif + struct GrpcCallClientHandler : public Grpc::RawAsyncRequestCallbacks { // Grpc::AsyncRequestCallbacks void onCreateInitialMetadata(Http::RequestHeaderMap& initial_metadata) override { @@ -365,6 +397,11 @@ class Context : public proxy_wasm::ContextBase, void onHttpCallSuccess(uint32_t token, Envoy::Http::ResponseMessagePtr&& response); void onHttpCallFailure(uint32_t token, Http::AsyncClient::FailureReason reason); +#if defined(ALIMESH) + void onRedisCallSuccess(uint32_t token, std::string&& response); + void onRedisCallFailure(uint32_t token); +#endif + void onGrpcCreateInitialMetadata(uint32_t token, Http::RequestHeaderMap& metadata); void onGrpcReceiveInitialMetadataWrapper(uint32_t token, Http::HeaderMapPtr&& metadata); void onGrpcReceiveWrapper(uint32_t token, ::Envoy::Buffer::InstancePtr response); @@ -410,6 +447,11 @@ class Context : public proxy_wasm::ContextBase, // Only available during onHttpCallResponse. Envoy::Http::ResponseMessagePtr* http_call_response_{}; +#if defined(ALIMESH) + // Only available during onRedisCallResponse. + std::string redis_call_response_{}; +#endif + Http::HeaderMapPtr grpc_receive_initial_metadata_{}; Http::HeaderMapPtr grpc_receive_trailing_metadata_{}; @@ -437,6 +479,9 @@ class Context : public proxy_wasm::ContextBase, // MB: must be a node-type map as we take persistent references to the entries. std::map http_request_; +#if defined(ALIMESH) + std::map redis_request_; +#endif std::map grpc_call_request_; std::map grpc_stream_; @@ -451,6 +496,9 @@ class Context : public proxy_wasm::ContextBase, // Filter state prototype declaration. absl::flat_hash_map state_prototypes_; +#if defined(ALIMESH) + bool disable_clear_route_cache_ = false; +#endif }; using ContextSharedPtr = std::shared_ptr; diff --git a/source/extensions/common/wasm/stats_handler.cc b/source/extensions/common/wasm/stats_handler.cc index 5c00bfe2834a3..78678710c7cbd 100644 --- a/source/extensions/common/wasm/stats_handler.cc +++ b/source/extensions/common/wasm/stats_handler.cc @@ -70,11 +70,31 @@ void LifecycleStatsHandler::onEvent(WasmEvent event) { switch (event) { case WasmEvent::VmShutDown: lifecycle_stats_.active_.set(--active_wasms); +#ifdef ALIMESH + if (is_crashed_) { + is_crashed_ = false; + if (lifecycle_stats_.crash_.value() > 0) { + lifecycle_stats_.crash_.dec(); + } + } +#endif break; case WasmEvent::VmCreated: lifecycle_stats_.active_.set(++active_wasms); lifecycle_stats_.created_.inc(); break; +#ifdef ALIMESH + case WasmEvent::RuntimeError: + if (!is_crashed_) { + is_crashed_ = true; + lifecycle_stats_.crash_.inc(); + lifecycle_stats_.crash_total_.inc(); + } + break; + case WasmEvent::RecoverError: + lifecycle_stats_.recover_error_.inc(); + break; +#endif default: break; } diff --git a/source/extensions/common/wasm/stats_handler.h b/source/extensions/common/wasm/stats_handler.h index 01e3e4ae30aaa..5c600ffd40779 100644 --- a/source/extensions/common/wasm/stats_handler.h +++ b/source/extensions/common/wasm/stats_handler.h @@ -31,12 +31,27 @@ struct CreateWasmStats { CREATE_WASM_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) }; +#ifdef ALIMESH +#define LIFECYCLE_STATS(COUNTER, GAUGE, PLUGIN_COUNTER, PLUGIN_GAUGE) \ + COUNTER(created) \ + GAUGE(active, NeverImport) \ + PLUGIN_COUNTER(recover_total) \ + PLUGIN_COUNTER(crash_total) \ + PLUGIN_COUNTER(recover_error) \ + PLUGIN_GAUGE(crash, NeverImport) +#else #define LIFECYCLE_STATS(COUNTER, GAUGE) \ COUNTER(created) \ GAUGE(active, NeverImport) +#endif struct LifecycleStats { +#ifdef ALIMESH + LIFECYCLE_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_COUNTER_STRUCT, + GENERATE_GAUGE_STRUCT) +#else LIFECYCLE_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) +#endif }; using ScopeWeakPtr = std::weak_ptr; @@ -57,6 +72,9 @@ enum class WasmEvent : int { RuntimeError, VmCreated, VmShutDown, +#ifdef ALIMESH + RecoverError, +#endif }; class CreateStatsHandler : Logger::Loggable { @@ -89,17 +107,36 @@ CreateStatsHandler& getCreateStatsHandler(); class LifecycleStatsHandler { public: +#ifdef ALIMESH + LifecycleStatsHandler(const Stats::ScopeSharedPtr& scope, std::string runtime, + std::string plugin_name) + : lifecycle_stats_(LifecycleStats{LIFECYCLE_STATS( + POOL_COUNTER_PREFIX(*scope, absl::StrCat("wasm.", runtime, ".")), + POOL_GAUGE_PREFIX(*scope, absl::StrCat("wasm.", runtime, ".")), + POOL_COUNTER_PREFIX(*scope, + absl::StrCat("wasm.", runtime, ".plugin.", plugin_name, ".")), + POOL_GAUGE_PREFIX(*scope, + absl::StrCat("wasm.", runtime, ".plugin.", plugin_name, ".")))}){}; +#else LifecycleStatsHandler(const Stats::ScopeSharedPtr& scope, std::string runtime) : lifecycle_stats_(LifecycleStats{ LIFECYCLE_STATS(POOL_COUNTER_PREFIX(*scope, absl::StrCat("wasm.", runtime, ".")), POOL_GAUGE_PREFIX(*scope, absl::StrCat("wasm.", runtime, ".")))}){}; +#endif ~LifecycleStatsHandler() = default; void onEvent(WasmEvent event); static int64_t getActiveVmCount(); +#ifdef ALIMESH + LifecycleStats& stats() { return lifecycle_stats_; } +#endif + protected: LifecycleStats lifecycle_stats_; +#ifdef ALIMESH + bool is_crashed_ = false; +#endif }; } // namespace Wasm diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 17d40d003a9cb..bded3d747898a 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -60,6 +60,34 @@ inline Wasm* getWasm(WasmHandleSharedPtr& base_wasm_handle) { return static_cast(base_wasm_handle->wasm().get()); } +#ifdef ALIMESH +WasmEvent failStateToWasmEvent(FailState state) { + switch (state) { + case FailState::Ok: + return WasmEvent::Ok; + case FailState::UnableToCreateVm: + return WasmEvent::UnableToCreateVm; + case FailState::UnableToCloneVm: + return WasmEvent::UnableToCloneVm; + case FailState::MissingFunction: + return WasmEvent::MissingFunction; + case FailState::UnableToInitializeCode: + return WasmEvent::UnableToInitializeCode; + case FailState::StartFailed: + return WasmEvent::StartFailed; + case FailState::ConfigureFailed: + return WasmEvent::ConfigureFailed; + case FailState::RuntimeError: + return WasmEvent::RuntimeError; + case FailState::RecoverError: + return WasmEvent::RecoverError; + } + PANIC("corrupt enum"); +} + +const int MIN_RECOVER_INTERVAL_SECONDS = 5; +#endif + } // namespace void Wasm::initializeLifecycle(Server::ServerLifecycleNotifier& lifecycle_notifier) { @@ -82,8 +110,14 @@ Wasm::Wasm(WasmConfig& config, absl::string_view vm_key, const Stats::ScopeShare scope_(scope), api_(api), stat_name_pool_(scope_->symbolTable()), custom_stat_namespace_(stat_name_pool_.add(CustomStatNamespace)), cluster_manager_(cluster_manager), dispatcher_(dispatcher), - time_source_(dispatcher.timeSource()), lifecycle_stats_handler_(LifecycleStatsHandler( - scope, config.config().vm_config().runtime())) { + time_source_(dispatcher.timeSource()), +#ifdef ALIMESH + lifecycle_stats_handler_(LifecycleStatsHandler(scope, config.config().vm_config().runtime(), + config.config().name())) { +#else + lifecycle_stats_handler_( + LifecycleStatsHandler(scope, config.config().vm_config().runtime()) { +#endif lifecycle_stats_handler_.onEvent(WasmEvent::VmCreated); ENVOY_LOG(debug, "Base Wasm created {} now active", lifecycle_stats_handler_.getActiveVmCount()); } @@ -102,6 +136,14 @@ Wasm::Wasm(WasmHandleSharedPtr base_wasm_handle, Event::Dispatcher& dispatcher) time_source_(dispatcher.timeSource()), lifecycle_stats_handler_(getWasm(base_wasm_handle)->lifecycle_stats_handler_) { lifecycle_stats_handler_.onEvent(WasmEvent::VmCreated); +#ifdef ALIMESH + auto* vm = wasm_vm(); + if (vm) { + vm->addFailCallback([this](FailState fail_state) { + lifecycle_stats_handler_.onEvent(failStateToWasmEvent(fail_state)); + }); + } +#endif ENVOY_LOG(debug, "Thread-Local Wasm created {} now active", lifecycle_stats_handler_.getActiveVmCount()); } @@ -152,6 +194,32 @@ Wasm::~Wasm() { } } +#if defined(ALIMESH) +bool PluginHandleSharedPtrThreadLocal::recover() { + if (handle_ == nullptr || handle_->wasmHandle() == nullptr || + handle_->wasmHandle()->wasm() == nullptr) { + ENVOY_LOG(warn, "wasm has not been initialized"); + return false; + } + auto& dispatcher = handle_->wasmHandle()->wasm()->dispatcher(); + auto now = dispatcher.timeSource().monotonicTime() + cache_time_offset_for_testing; + if (now - last_recover_time_ < std::chrono::seconds(MIN_RECOVER_INTERVAL_SECONDS)) { + ENVOY_LOG(debug, "recover interval has not been reached"); + return false; + } + // Even if recovery fails, it will be retried after the interval + last_recover_time_ = now; + std::shared_ptr new_handle; + if (handle_->doRecover(new_handle)) { + handle_ = std::static_pointer_cast(new_handle); + handle_->wasmHandle()->wasm()->lifecycleStats().recover_total_.inc(); + ENVOY_LOG(info, "wasm vm recover from crash success"); + return true; + } + return false; +} +#endif + // NOLINTNEXTLINE(readability-identifier-naming) Word resolve_dns(Word dns_address_ptr, Word dns_address_size, Word token_ptr) { auto context = static_cast(proxy_wasm::contextOrEffectiveContext()); @@ -308,6 +376,10 @@ WasmEvent toWasmEvent(const std::shared_ptr& wasm) { return WasmEvent::ConfigureFailed; case FailState::RuntimeError: return WasmEvent::RuntimeError; +#if defined(ALIMESH) + case FailState::RecoverError: + return WasmEvent::RecoverError; +#endif } PANIC("corrupt enum"); } diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index dc2b5d704d16b..aa5069e86c98a 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -90,6 +90,10 @@ class Wasm : public WasmBase, Logger::Loggable { } void setFailStateForTesting(proxy_wasm::FailState fail_state) { failed_ = fail_state; } +#if defined(ALIMESH) + LifecycleStats& lifecycleStats() { return lifecycle_stats_handler_.stats(); } +#endif + protected: friend class Context; @@ -153,13 +157,24 @@ class PluginHandle : public PluginHandleBase { using PluginHandleSharedPtr = std::shared_ptr; +#if defined(ALIMESH) +class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject, + public Logger::Loggable { +public: + PluginHandleSharedPtrThreadLocal(PluginHandleSharedPtr handle) : handle_(handle){}; + bool recover(); +#else class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject { public: PluginHandleSharedPtrThreadLocal(PluginHandleSharedPtr handle) : handle_(handle){}; +#endif PluginHandleSharedPtr& handle() { return handle_; } private: PluginHandleSharedPtr handle_; +#if defined(ALIMESH) + MonotonicTime last_recover_time_; +#endif }; using CreateWasmCallback = std::function; diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index fc225eb3045ba..e2c1a93aa4f63 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -9,6 +9,7 @@ #include "source/extensions/common/wasm/wasm_runtime_factory.h" #include "include/proxy-wasm/null_plugin.h" +#include "absl/strings/str_replace.h" using ContextBase = proxy_wasm::ContextBase; using Word = proxy_wasm::Word; @@ -35,7 +36,13 @@ proxy_wasm::LogLevel EnvoyWasmVmIntegration::getLogLevel() { } } -void EnvoyWasmVmIntegration::error(std::string_view message) { ENVOY_LOG(error, message); } +void EnvoyWasmVmIntegration::error(std::string_view message) { +#ifdef ALIMESH + ENVOY_LOG(error, absl::StrReplaceAll(message, {{"\n", "\\n"}})); +#else + ENVOY_LOG(error, message); +#endif +} void EnvoyWasmVmIntegration::trace(std::string_view message) { ENVOY_LOG(trace, message); } bool EnvoyWasmVmIntegration::getNullVmFunction(std::string_view function_name, bool returns_word, diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 7385b590a13ad..cca3b2ed461af 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -249,6 +249,8 @@ EXTENSIONS = { "envoy.tracers.datadog": "//source/extensions/tracers/datadog:config", "envoy.tracers.zipkin": "//source/extensions/tracers/zipkin:config", "envoy.tracers.opencensus": "//source/extensions/tracers/opencensus:config", + + # WiP "envoy.tracers.xray": "//source/extensions/tracers/xray:config", "envoy.tracers.skywalking": "//source/extensions/tracers/skywalking:config", "envoy.tracers.opentelemetry": "//source/extensions/tracers/opentelemetry:config", diff --git a/source/extensions/filters/common/ext_authz/ext_authz.h b/source/extensions/filters/common/ext_authz/ext_authz.h index 6d511c64cb5d7..b17e53372600d 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz.h +++ b/source/extensions/filters/common/ext_authz/ext_authz.h @@ -55,6 +55,10 @@ class HeaderValues { const Http::LowerCaseString EnvoyAuthPartialBody{absl::StrCat(prefix(), "-auth-partial-body")}; const Http::LowerCaseString EnvoyAuthHeadersToRemove{ absl::StrCat(prefix(), "-auth-headers-to-remove")}; + +#if defined(ALIMESH) + const Http::LowerCaseString XMseExternalAuthzCheckResult{"x-mse-external-authz-check-result"}; +#endif const Http::LowerCaseString EnvoyAuthFailureModeAllowed{ absl::StrCat(prefix(), "-auth-failure-mode-allowed")}; }; diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index 4a519b5050cd3..3427188295b8e 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -8,6 +8,7 @@ #include "source/common/common/enum_to_int.h" #include "source/common/common/fmt.h" #include "source/common/common/matchers.h" +#include "source/common/common/utility.h" #include "source/common/http/async_client_impl.h" #include "source/common/http/codes.h" #include "source/common/runtime/runtime_features.h" @@ -275,6 +276,26 @@ void RawHttpClientImpl::onBeforeFinalizeUpstreamSpan( } } +#if defined(ALIMESH) +bool isAuthorizationPass(const Http::ResponseHeaderMap& headers) { + const uint64_t status_code = Http::Utility::getResponseStatus(headers); + + // The HTTP status code is first condition. + if (status_code != enumToInt(Http::Code::OK)) { + return false; + } + + const auto& get_result = headers.get(Headers::get().XMseExternalAuthzCheckResult); + // If x-mse-external-authz-check-result doesn't exist or has more than one value, + // we think this case is allowed. + if (get_result.size() != 1) { + return true; + } + + return absl::EqualsIgnoreCase(StringUtil::trim(get_result[0]->value().getStringView()), "true"); +} +#endif + ResponsePtr RawHttpClientImpl::toResponse(Http::ResponseMessagePtr message) { const uint64_t status_code = Http::Utility::getResponseStatus(message->headers()); @@ -311,7 +332,11 @@ ResponsePtr RawHttpClientImpl::toResponse(Http::ResponseMessagePtr message) { message->headers().remove(storage_header_name); // Create an Ok authorization response. +#if !defined(ALIMESH) if (status_code == enumToInt(Http::Code::OK)) { +#else + if (isAuthorizationPass(message->headers())) { +#endif SuccessResponse ok{message->headers(), config_->upstreamHeaderMatchers(), config_->upstreamHeaderToAppendMatchers(), diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h index db611f4fad735..b6ea8f809e39c 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h @@ -110,6 +110,10 @@ class ClientConfig { using ClientConfigSharedPtr = std::shared_ptr; +#if defined(ALIMESH) +bool isAuthorizationPass(const Http::ResponseHeaderMap& headers); +#endif + /** * This client implementation is used when the Ext_Authz filter needs to communicate with an * HTTP authorization server. Unlike the gRPC client that allows the server to define the diff --git a/source/extensions/filters/http/common/factory_base.h b/source/extensions/filters/http/common/factory_base.h index c36b710ef80cb..c334e6135482a 100644 --- a/source/extensions/filters/http/common/factory_base.h +++ b/source/extensions/filters/http/common/factory_base.h @@ -104,7 +104,7 @@ class DualFactoryBase : public CommonFactoryBase, : CommonFactoryBase(name) {} struct DualInfo { - DualInfo(Server::Configuration::UpstreamHttpFactoryContext& context) + DualInfo(Server::Configuration::UpstreamFactoryContext& context) : init_manager(context.initManager()), scope(context.scope()) {} DualInfo(Server::Configuration::FactoryContext& context) : init_manager(context.initManager()), scope(context.scope()) {} @@ -122,9 +122,10 @@ class DualFactoryBase : public CommonFactoryBase, context.getServerFactoryContext()); } - Envoy::Http::FilterFactoryCb createFilterFactoryFromProto( - const Protobuf::Message& proto_config, const std::string& stats_prefix, - Server::Configuration::UpstreamHttpFactoryContext& context) override { + Envoy::Http::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& proto_config, + const std::string& stats_prefix, + Server::Configuration::UpstreamFactoryContext& context) override { return createFilterFactoryFromProtoTyped( MessageUtil::downcastAndValidate( proto_config, context.getServerFactoryContext().messageValidationVisitor()), diff --git a/source/extensions/filters/http/composite/BUILD b/source/extensions/filters/http/composite/BUILD index 512c9b0549139..bf636545cf4b3 100644 --- a/source/extensions/filters/http/composite/BUILD +++ b/source/extensions/filters/http/composite/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( srcs = ["action.cc"], hdrs = ["action.h"], deps = [ + "//source/common/http:filter_chain_helper_lib", "//source/common/http/matching:data_impl_lib", "//source/common/matcher:matcher_lib", "@envoy_api//envoy/extensions/filters/http/composite/v3:pkg_cc_proto", diff --git a/source/extensions/filters/http/composite/action.cc b/source/extensions/filters/http/composite/action.cc index 40fdbe1bf45ff..3beb3afb7f08b 100644 --- a/source/extensions/filters/http/composite/action.cc +++ b/source/extensions/filters/http/composite/action.cc @@ -15,6 +15,37 @@ Matcher::ActionFactoryCb ExecuteFilterActionFactory::createActionFactoryCb( const envoy::extensions::filters::http::composite::v3::ExecuteFilterAction&>( config, validation_visitor); + if (composite_action.has_dynamic_config() && composite_action.has_typed_config()) { + throw EnvoyException( + fmt::format("Error: Only one of `dynamic_config` or `typed_config` can be set.")); + } + + if (composite_action.has_dynamic_config()) { + if (!context.factory_context_.has_value() || !context.server_factory_context_.has_value()) { + throw EnvoyException(fmt::format("Failed to get factory context or server factory context.")); + } + // Create a dynamic filter config provider and register it with the server factory context. + auto config_discovery = composite_action.dynamic_config().config_discovery(); + Server::Configuration::FactoryContext& factory_context = context.factory_context_.value(); + Server::Configuration::ServerFactoryContext& server_factory_context = + context.server_factory_context_.value(); + Server::Configuration::HttpExtensionConfigProviderSharedPtr provider = + server_factory_context.downstreamHttpFilterConfigProviderManager() + ->createDynamicFilterConfigProvider( + config_discovery, composite_action.dynamic_config().name(), server_factory_context, + factory_context, false, "http", nullptr); + return [provider = std::move(provider)]() -> Matcher::ActionPtr { + auto config_value = provider->config(); + if (config_value.has_value()) { + auto factory_cb = config_value.value().get().factory_cb; + return std::make_unique(factory_cb); + } + // There is no dynamic config available. Apply missing config filter. + auto factory_cb = Envoy::Http::MissingConfigFilterFactory; + return std::make_unique(factory_cb); + }; + } + auto& factory = Config::Utility::getAndCheckFactory( composite_action.typed_config()); diff --git a/source/extensions/filters/http/composite/action.h b/source/extensions/filters/http/composite/action.h index 725eadada76df..b78553d8ab773 100644 --- a/source/extensions/filters/http/composite/action.h +++ b/source/extensions/filters/http/composite/action.h @@ -2,6 +2,7 @@ #include "envoy/extensions/filters/http/composite/v3/composite.pb.validate.h" +#include "source/common/http/filter_chain_helper.h" #include "source/common/http/matching/data_impl.h" #include "source/common/matcher/matcher.h" diff --git a/source/extensions/filters/http/custom_response/config.cc b/source/extensions/filters/http/custom_response/config.cc index fa7b50b617605..21fd0aaba84e1 100644 --- a/source/extensions/filters/http/custom_response/config.cc +++ b/source/extensions/filters/http/custom_response/config.cc @@ -47,7 +47,14 @@ createMatcher(const envoy::extensions::filters::http::custom_response::v3::Custo FilterConfig::FilterConfig( const envoy::extensions::filters::http::custom_response::v3::CustomResponse& config, Server::Configuration::ServerFactoryContext& context, Stats::StatName stats_prefix) - : stats_prefix_(stats_prefix), matcher_{createMatcher(config, context, stats_prefix)} {} +#if defined(ALIMESH) + : stats_prefix_(stats_prefix), matcher_{createMatcher(config, context, stats_prefix)}, + max_request_bytes_(config.with_request_body().max_request_bytes()) { +} +#else + : stats_prefix_(stats_prefix), matcher_{createMatcher(config, context, stats_prefix)} { +} +#endif PolicySharedPtr FilterConfig::getPolicy(const ::Envoy::Http::ResponseHeaderMap& headers, const StreamInfo::StreamInfo& stream_info) const { diff --git a/source/extensions/filters/http/custom_response/config.h b/source/extensions/filters/http/custom_response/config.h index f92a6a713a395..05de03bee3b77 100644 --- a/source/extensions/filters/http/custom_response/config.h +++ b/source/extensions/filters/http/custom_response/config.h @@ -36,11 +36,17 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { PolicySharedPtr getPolicy(const ::Envoy::Http::ResponseHeaderMap& headers, const StreamInfo::StreamInfo& stream_info) const; +#if defined(ALIMESH) + bool withRequestBody() const { return max_request_bytes_ > 0; } + uint32_t maxRequestBytes() const { return max_request_bytes_; } +#endif + ~FilterConfig() override = default; private: Stats::StatName stats_prefix_; const Matcher::MatchTreePtr<::Envoy::Http::HttpMatchingData> matcher_; + const uint32_t max_request_bytes_; }; } // namespace CustomResponse diff --git a/source/extensions/filters/http/custom_response/custom_response_filter.cc b/source/extensions/filters/http/custom_response/custom_response_filter.cc index 7e58ad4bbfc49..ad3066b401544 100644 --- a/source/extensions/filters/http/custom_response/custom_response_filter.cc +++ b/source/extensions/filters/http/custom_response/custom_response_filter.cc @@ -16,6 +16,26 @@ namespace CustomResponse { Http::FilterHeadersStatus CustomResponseFilter::decodeHeaders(Http::RequestHeaderMap& header_map, bool) { +#if defined(ALIMESH) + downstream_headers_ = &header_map; + const FilterConfig* config = nullptr; + if (decoder_callbacks_ && decoder_callbacks_->route()) { + const auto* config = + Http::Utility::resolveMostSpecificPerFilterConfig(decoder_callbacks_); + if (config != nullptr) { + has_rules_ = true; + } + } + if (config == nullptr) { + config = config_.get(); + } + if (config->withRequestBody() && !Http::Utility::isWebSocketUpgradeRequest(header_map) && + !Http::Utility::isH2UpgradeRequest(header_map) && + !Grpc::Common::isGrpcRequestHeaders(header_map)) { + decoder_callbacks_->setNeedBuffering(true); + decoder_callbacks_->setDecoderBufferLimit(config->maxRequestBytes()); + } +#else // Check filter state for the existence of a custom response policy. The // expectation is that if a custom response policy recreates the stream, it // adds itself to the filter state. In that case do not look for @@ -29,6 +49,7 @@ Http::FilterHeadersStatus CustomResponseFilter::decodeHeaders(Http::RequestHeade if (!filter_state) { downstream_headers_ = &header_map; } +#endif return Http::FilterHeadersStatus::Continue; } @@ -37,35 +58,56 @@ Http::FilterHeadersStatus CustomResponseFilter::encodeHeaders(Http::ResponseHead // If filter state for custom response exists, it means this response is a // custom response. Apply the custom response mutations to the response from // the remote source and return. +#if defined(ALIMESH) + auto filter_state = + encoder_callbacks_->streamInfo().filterState()->getDataMutable( + CustomResponseFilterState::kFilterStateName); + if (filter_state && filter_state->remain_redirect_times <= 0) { + return filter_state->policy->encodeHeaders(headers, end_stream, *this); + } +#else auto filter_state = encoder_callbacks_->streamInfo().filterState()->getDataReadOnly( CustomResponseFilterState::kFilterStateName); if (filter_state) { return filter_state->policy->encodeHeaders(headers, end_stream, *this); } - +#endif // Traverse up route typed per filter hierarchy till we find a matching // policy. Note that since the traversal is least to most specific, we can't // return early when a match is found. PolicySharedPtr policy; - decoder_callbacks_->traversePerFilterConfig( - [&policy, &headers, this](const Router::RouteSpecificFilterConfig& config) { - const FilterConfig* typed_config = dynamic_cast(&config); - if (typed_config) { - // Check if a match is found first to avoid overwriting policy with an - // empty shared_ptr. - auto maybe_policy = typed_config->getPolicy(headers, encoder_callbacks_->streamInfo()); - if (maybe_policy) { - policy = maybe_policy; +#if defined(ALIMESH) + if (has_rules_) { +#endif + decoder_callbacks_->traversePerFilterConfig( + [&policy, &headers, this](const Router::RouteSpecificFilterConfig& config) { + const FilterConfig* typed_config = dynamic_cast(&config); + if (typed_config) { + // Check if a match is found first to avoid overwriting policy with an + // empty shared_ptr. + auto maybe_policy = typed_config->getPolicy(headers, encoder_callbacks_->streamInfo()); + if (maybe_policy) { + policy = maybe_policy; + } } - } - }); + }); +#if defined(ALIMESH) + } +#endif if (!policy) { policy = config_->getPolicy(headers, encoder_callbacks_->streamInfo()); } // A valid custom response was not found. We should just pass through. if (!policy) { +#if defined(ALIMESH) + if (filter_state) { + // Trigger policy process the response + filter_state->remain_redirect_times = 0; + return filter_state->policy->encodeHeaders(headers, end_stream, *this); + } +#endif return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/custom_response/custom_response_filter.h b/source/extensions/filters/http/custom_response/custom_response_filter.h index f06c475e3f257..ce164f6bb4d65 100644 --- a/source/extensions/filters/http/custom_response/custom_response_filter.h +++ b/source/extensions/filters/http/custom_response/custom_response_filter.h @@ -50,6 +50,9 @@ class CustomResponseFilter : public ::Envoy::Http::PassThroughFilter, const std::shared_ptr config_; ::Envoy::Http::RequestHeaderMap* downstream_headers_ = nullptr; bool on_local_reply_called_ = false; +#if defined(ALIMESH) + bool has_rules_ = false; +#endif }; } // namespace CustomResponse diff --git a/source/extensions/filters/http/custom_response/policy.h b/source/extensions/filters/http/custom_response/policy.h index 8a4d98080421d..f5e0929751bf8 100644 --- a/source/extensions/filters/http/custom_response/policy.h +++ b/source/extensions/filters/http/custom_response/policy.h @@ -34,11 +34,20 @@ using PolicySharedPtr = std::shared_ptr; struct CustomResponseFilterState : public std::enable_shared_from_this, public StreamInfo::FilterState::Object { +#if defined(ALIMESH) + CustomResponseFilterState(PolicySharedPtr a_policy, absl::optional<::Envoy::Http::Code> code, + int32_t max_redirect_times) + : policy(a_policy), original_response_code(code), remain_redirect_times(max_redirect_times) {} +#else CustomResponseFilterState(PolicySharedPtr a_policy, absl::optional<::Envoy::Http::Code> code) : policy(a_policy), original_response_code(code) {} +#endif PolicySharedPtr policy; absl::optional<::Envoy::Http::Code> original_response_code; +#if defined(ALIMESH) + int32_t remain_redirect_times; +#endif static constexpr absl::string_view kFilterStateName = "envoy.filters.http.custom_response"; }; diff --git a/source/extensions/filters/http/on_demand/on_demand_update.cc b/source/extensions/filters/http/on_demand/on_demand_update.cc index 2325f81d47f3d..4d6b348de5350 100644 --- a/source/extensions/filters/http/on_demand/on_demand_update.cc +++ b/source/extensions/filters/http/on_demand/on_demand_update.cc @@ -193,7 +193,11 @@ void OnDemandRouteUpdate::onDestroy() { // This is the callback which is called when an update requested in requestRouteConfigUpdate() // has been propagated to workers, at which point the request processing is restarted from the // beginning. +#if defined(ALIMESH) +void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool) { +#else void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool route_exists) { +#endif filter_iteration_state_ = Http::FilterHeadersStatus::Continue; // Don't call continueDecoding in the middle of decodeHeaders() @@ -201,12 +205,14 @@ void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool route_exists) { return; } +#if !defined(ALIMESH) if (route_exists && // route can be resolved after an on-demand // VHDS update !callbacks_->decodingBuffer() && // Redirects with body not yet supported. callbacks_->recreateStream(/*headers=*/nullptr)) { return; } +#endif // route cannot be resolved after an on-demand VHDS update or // recreating stream failed, continue the filter-chain diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index 6dd63140e9e83..4e97a0a96946e 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -31,13 +31,41 @@ class FilterConfig : Logger::Loggable { if (!tls_slot_->currentThreadRegistered()) { return nullptr; } - PluginHandleSharedPtr handle = tls_slot_->get()->handle(); + auto opt_ref = tls_slot_->get(); + if (!opt_ref) { + return nullptr; + } + PluginHandleSharedPtr handle = opt_ref->handle(); if (!handle) { return nullptr; } if (handle->wasmHandle()) { wasm = handle->wasmHandle()->wasm().get(); } +#if defined(ALIMESH) + auto failed = false; + if (!wasm) { + failed = true; + } else if (wasm->isFailed()) { + ENVOY_LOG(info, "wasm vm is crashed, try to recover"); + if (opt_ref->recover()) { + ENVOY_LOG(info, "wasm vm recover success"); + wasm = opt_ref->handle()->wasmHandle()->wasm().get(); + handle = opt_ref->handle(); + } else { + ENVOY_LOG(info, "wasm vm recover failed"); + failed = true; + } + } + if (failed) { + if (handle->plugin()->fail_open_) { + return nullptr; // Fail open skips adding this filter to callbacks. + } else { + return std::make_shared(nullptr, 0, + handle); // Fail closed is handled by an empty Context. + } + } +#else if (!wasm || wasm->isFailed()) { if (handle->plugin()->fail_open_) { return nullptr; // Fail open skips adding this filter to callbacks. @@ -46,6 +74,7 @@ class FilterConfig : Logger::Loggable { handle); // Fail closed is handled by an empty Context. } } +#endif return std::make_shared(wasm, handle->rootContextId(), handle); } diff --git a/source/extensions/filters/network/common/redis/BUILD b/source/extensions/filters/network/common/redis/BUILD index 4561a35a52e57..4ddece1186fca 100644 --- a/source/extensions/filters/network/common/redis/BUILD +++ b/source/extensions/filters/network/common/redis/BUILD @@ -51,6 +51,9 @@ envoy_cc_library( ":redis_command_stats_lib", "//envoy/upstream:cluster_manager_interface", ], + alimesh_deps = [ + "//envoy/upstream:upstream_interface", + ], ) envoy_cc_library( @@ -75,6 +78,43 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "raw_client_interface", + hdrs = ["raw_client.h"], + visibility = [ + "//:contrib_library", + "//:examples_library", + "//:extension_library", + "//envoy/redis:__pkg__", + ], + deps = [ + ":client_interface", + ":redis_command_stats_lib", + "//envoy/upstream:upstream_interface", + ], +) + +envoy_cc_library( + name = "raw_client_lib", + srcs = ["raw_client_impl.cc"], + hdrs = ["raw_client_impl.h"], + visibility = [ + "//:contrib_library", + "//:examples_library", + "//:extension_library", + "//source/common/redis:__pkg__", + ], + deps = [ + ":raw_client_interface", + ":codec_lib", + ":utility_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/network:filter_lib", + "//source/common/upstream:upstream_lib", + ] +) + envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], diff --git a/source/extensions/filters/network/common/redis/client.h b/source/extensions/filters/network/common/redis/client.h index 197dc24f88f76..bb1ceb7e0db6a 100644 --- a/source/extensions/filters/network/common/redis/client.h +++ b/source/extensions/filters/network/common/redis/client.h @@ -7,6 +7,10 @@ #include "source/extensions/filters/network/common/redis/codec_impl.h" #include "source/extensions/filters/network/common/redis/redis_command_stats.h" +#if defined(ALIMESH) +#include "envoy/redis/async_client.h" +#endif + namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -14,6 +18,9 @@ namespace Common { namespace Redis { namespace Client { +#if defined(ALIMESH) +using PoolRequest = Envoy::Redis::PoolRequest; +#else /** * A handle to an outbound request. */ @@ -26,6 +33,7 @@ class PoolRequest { */ virtual void cancel() PURE; }; +#endif /** * Outbound request callbacks. diff --git a/source/extensions/filters/network/common/redis/codec.h b/source/extensions/filters/network/common/redis/codec.h index c66c1136c8a9e..1d767baf6f10a 100644 --- a/source/extensions/filters/network/common/redis/codec.h +++ b/source/extensions/filters/network/common/redis/codec.h @@ -165,6 +165,15 @@ class DecoderCallbacks { virtual void onRespValue(RespValuePtr&& value) PURE; }; +#if defined(ALIMESH) +class RawDecoderCallbacks { +public: + virtual ~RawDecoderCallbacks() = default; + + virtual void onRawResponse(std::string&& response) PURE; +}; +#endif + /** * A redis byte decoder for https://redis.io/topics/protocol */ @@ -195,6 +204,15 @@ class DecoderFactory { virtual DecoderPtr create(DecoderCallbacks& callbacks) PURE; }; +#if defined(ALIMESH) +class RawDecoderFactory { +public: + virtual ~RawDecoderFactory() = default; + + virtual DecoderPtr create(RawDecoderCallbacks& callbacks) PURE; +}; +#endif + /** * A redis byte encoder for https://redis.io/topics/protocol */ @@ -212,6 +230,17 @@ class Encoder { using EncoderPtr = std::unique_ptr; +#if defined(ALIMESH) +class RawEncoder { +public: + virtual ~RawEncoder() = default; + + virtual void encode(std::string_view value, Buffer::Instance& out) PURE; +}; + +using RawEncoderPtr = std::unique_ptr; +#endif + /** * A redis protocol error. */ diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 3a684c72e835b..4b411cc74864e 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -549,6 +549,238 @@ void DecoderImpl::parseSlice(const Buffer::RawSlice& slice) { } } +#if defined(ALIMESH) +void RawDecoderImpl::decode(Buffer::Instance& data) { + for (const Buffer::RawSlice& slice : data.getRawSlices()) { + parseSlice(slice); + } + + data.drain(data.length()); +} + +void RawDecoderImpl::parseSlice(const Buffer::RawSlice& slice) { + const char* buffer = reinterpret_cast(slice.mem_); + uint64_t remaining = slice.len_; + + while (remaining || state_ == State::ValueComplete) { + ENVOY_LOG(trace, "parse slice: {} remaining", remaining); + switch (state_) { + case State::ValueRootStart: { + ENVOY_LOG(trace, "parse slice ValueRootStart"); + + pending_value_root_.clear(); + pending_value_stack_.push_front({RespType::Null, "", 0, 0}); + state_ = State::ValueStart; + break; + } + case State::ValueStart: { + ENVOY_LOG(trace, "parse slice: ValueStart: {}", buffer[0]); + + pending_integer_.reset(); + switch (buffer[0]) { + case '*': { + state_ = State::IntegerStart; + pending_value_stack_.front().type = RespType::Array; + break; + } + case '$': { + state_ = State::IntegerStart; + pending_value_stack_.front().type = RespType::BulkString; + break; + } + case '-': { + state_ = State::SimpleString; + pending_value_stack_.front().type = RespType::Error; + break; + } + case '+': { + state_ = State::SimpleString; + pending_value_stack_.front().type = RespType::SimpleString; + break; + } + case ':': { + state_ = State::IntegerStart; + pending_value_stack_.front().type = RespType::Integer; + break; + } + default: { + throw ProtocolError("invalid value type"); + } + } + + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + break; + } + + case State::IntegerStart: { + ENVOY_LOG(trace, "parse slice: IntegerStart: {}", buffer[0]); + + if (buffer[0] == '-') { + pending_integer_.negative_ = true; + + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + } else if (buffer[0] == '+') { + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + } + + state_ = State::Integer; + break; + } + case State::Integer: { + ENVOY_LOG(trace, "parse slice: Integer: {}", buffer[0]); + + char c = buffer[0]; + if (buffer[0] == '\r') { + state_ = State::IntegerLF; + } else { + if (c < '0' || c > '9') { + throw ProtocolError("invalid integer character"); + } else { + pending_integer_.integer_ = (pending_integer_.integer_ * 10) + (c - '0'); + } + } + + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + break; + } + + case State::IntegerLF: { + ENVOY_LOG(trace, "parse slice: IntegerLF: {}", buffer[0]); + + if (buffer[0] != '\n') { + throw ProtocolError("expect new line"); + } + + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + + PendingValue& current_value = pending_value_stack_.front(); + if (current_value.type == RespType::Array) { + if (pending_integer_.negative_) { + current_value.type = RespType::Null; + state_ = State::ValueComplete; + } else if (pending_integer_.integer_ == 0) { + state_ = State::ValueComplete; + } else { + current_value.total_array_element = pending_integer_.integer_; + pending_value_stack_.push_front({RespType::Null, "", 0, 0}); + state_ = State::ValueStart; + } + } else if (current_value.type == RespType::Integer) { + // do not calculate real value here, do not care + state_ = State::ValueComplete; + } else { + ASSERT(current_value.type == RespType::BulkString); + if (!pending_integer_.negative_) { + state_ = State::BulkStringBody; + } else { + current_value.type = RespType::Null; + state_ = State::ValueComplete; + } + } + break; + } + + case State::BulkStringBody: { + ENVOY_LOG(trace, "parse slice: IntegerLF: {}", buffer[0]); + + ASSERT(!pending_integer_.negative_); + uint64_t length_to_copy = + std::min(static_cast(pending_integer_.integer_), remaining); + pending_value_stack_.front().value.append(buffer, length_to_copy); + pending_integer_.integer_ -= length_to_copy; + remaining -= length_to_copy; + buffer += length_to_copy; + + if (pending_integer_.integer_ == 0) { + state_ = State::CR; + } + break; + } + + case State::CR: { + ENVOY_LOG(trace, "parse slice: CR: {}", buffer[0]); + + if (buffer[0] != '\r') { + throw ProtocolError("expected carriage return"); + } + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + + state_ = State::LF; + break; + } + + case State::LF: { + ENVOY_LOG(trace, "parse slice: CR: {}", buffer[0]); + + if (buffer[0] != '\n') { + throw ProtocolError("expected new line"); + } + + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + + state_ = State::ValueComplete; + break; + } + + case State::SimpleString: { + ENVOY_LOG(trace, "parse slice: SimpleString: {}", buffer[0]); + + if (buffer[0] == '\r') { + state_ = State::LF; + } + pending_value_stack_.front().value.push_back(buffer[0]); + remaining--; + buffer++; + break; + } + + case State::ValueComplete: { + ENVOY_LOG(trace, "parse slice: ValueComplete: {}", buffer[0]); + ASSERT(!pending_value_stack_.empty()); + + PendingValue current_value = pending_value_stack_.front(); + pending_value_stack_.pop_front(); + + if (pending_value_stack_.empty()) { + pending_value_root_.append(current_value.value); + + ENVOY_LOG(trace, "calling callbacks on value: {}", pending_value_root_); + callbacks_.onRawResponse(std::move(pending_value_root_)); + state_ = State::ValueRootStart; + } else { + PendingValue& array_value = pending_value_stack_.front(); + // only array type node can have children + ASSERT(array_value.type == RespType::Array); + + array_value.value.append(current_value.value); + + if (array_value.current_array_element < array_value.total_array_element - 1) { + array_value.current_array_element++; + pending_value_stack_.push_front({RespType::Null, "", 0, 0}); + state_ = State::ValueStart; + } + } + break; + } + } + } +} +#endif + void EncoderImpl::encode(const RespValue& value, Buffer::Instance& out) { switch (value.type()) { case RespType::Array: { @@ -651,6 +883,9 @@ void EncoderImpl::encodeSimpleString(const std::string& string, Buffer::Instance out.add(string); out.add("\r\n", 2); } +#if defined(ALIMESH) +void RawEncoderImpl::encode(std::string_view value, Buffer::Instance& out) { out.add(value); } +#endif } // namespace Redis } // namespace Common diff --git a/source/extensions/filters/network/common/redis/codec_impl.h b/source/extensions/filters/network/common/redis/codec_impl.h index a55cfd695992c..ffaa9cb179bde 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.h +++ b/source/extensions/filters/network/common/redis/codec_impl.h @@ -64,6 +64,54 @@ class DecoderImpl : public Decoder, Logger::Loggable { std::forward_list pending_value_stack_; }; +#if defined(ALIMESH) +class RawDecoderImpl : public Decoder, Logger::Loggable { +public: + RawDecoderImpl(RawDecoderCallbacks& callbacks) : callbacks_(callbacks) {} + + // RedisProxy::Decoder + void decode(Buffer::Instance& data) override; + +private: + enum class State { + ValueRootStart, + ValueStart, + IntegerStart, + Integer, + IntegerLF, + BulkStringBody, + CR, + LF, + SimpleString, + ValueComplete + }; + + struct PendingInteger { + void reset() { + integer_ = 0; + negative_ = false; + } + + uint64_t integer_; + bool negative_; + }; + + struct PendingValue { + RespType type; + std::string value; + uint64_t current_array_element; + uint64_t total_array_element; + }; + + void parseSlice(const Buffer::RawSlice& slice); + + RawDecoderCallbacks& callbacks_; + State state_{State::ValueRootStart}; + PendingInteger pending_integer_; + std::string pending_value_root_; + std::forward_list pending_value_stack_; +}; +#endif /** * A factory implementation that returns a real decoder. */ @@ -74,7 +122,15 @@ class DecoderFactoryImpl : public DecoderFactory { return DecoderPtr{new DecoderImpl(callbacks)}; } }; - +#if defined(ALIMESH) +class RawDecoderFactoryImpl : public RawDecoderFactory { +public: + // RedisProxy::RawDecoderFactory + DecoderPtr create(RawDecoderCallbacks& callbacks) override { + return DecoderPtr{new RawDecoderImpl(callbacks)}; + } +}; +#endif /** * Encoder implementation of https://redis.io/topics/protocol */ @@ -91,7 +147,13 @@ class EncoderImpl : public Encoder { void encodeInteger(int64_t integer, Buffer::Instance& out); void encodeSimpleString(const std::string& string, Buffer::Instance& out); }; - +#if defined(ALIMESH) +class RawEncoderImpl : public RawEncoder { +public: + // RedisProxy::RawEncoder + void encode(std::string_view value, Buffer::Instance& out) override; +}; +#endif } // namespace Redis } // namespace Common } // namespace NetworkFilters diff --git a/source/extensions/filters/network/common/redis/raw_client.h b/source/extensions/filters/network/common/redis/raw_client.h new file mode 100644 index 0000000000000..ae1f0a10ba76b --- /dev/null +++ b/source/extensions/filters/network/common/redis/raw_client.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include "envoy/upstream/upstream.h" + +#include "source/extensions/filters/network/common/redis/client.h" +#include "source/extensions/filters/network/common/redis/redis_command_stats.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace Common { +namespace Redis { +namespace Client { + +class RawClientCallbacks { +public: + virtual ~RawClientCallbacks() = default; + + virtual void onResponse(std::string&& value) PURE; + + virtual void onFailure() PURE; +}; + +class DoNothingRawClientCallbacks : public RawClientCallbacks { +public: + // RawClientCallbacks + void onFailure() override {} + void onResponse(std::string&&) override {} +}; + +class RawClient : public Event::DeferredDeletable { +public: + ~RawClient() override = default; + + /** + * Adds network connection callbacks to the underlying network connection. + */ + virtual void addConnectionCallbacks(Network::ConnectionCallbacks& callbacks) PURE; + + /** + * Called to determine if the client has pending requests. + * @return bool true if the client is processing requests or false if it is currently idle. + */ + virtual bool active() PURE; + + /** + * Closes the underlying network connection. + */ + virtual void close() PURE; + + /** + * Make a pipelined request to the remote redis server. + * @param request supplies the RESP request to make. + * @param callbacks supplies the request callbacks. + * @return PoolRequest* a handle to the active request or nullptr if the request could not be made + * for some reason. + */ + virtual PoolRequest* makeRawRequest(std::string_view request, RawClientCallbacks& callbacks) PURE; + + /** + * Initialize the connection. Issue the auth command and readonly command as needed. + * @param auth password for upstream host. + */ + virtual void initialize(const std::string& auth_username, const std::string& auth_password, + const std::map& params) PURE; +}; + +using RawClientPtr = std::unique_ptr; + +class RawClientFactory { +public: + virtual ~RawClientFactory() = default; + + virtual RawClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, + const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope, const std::string& auth_username, + const std::string& auth_password, + const std::map& params) PURE; +}; + +} // namespace Client +} // namespace Redis +} // namespace Common +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/common/redis/raw_client_impl.cc b/source/extensions/filters/network/common/redis/raw_client_impl.cc new file mode 100644 index 0000000000000..9ed11b352efba --- /dev/null +++ b/source/extensions/filters/network/common/redis/raw_client_impl.cc @@ -0,0 +1,266 @@ +#include "source/extensions/filters/network/common/redis/raw_client_impl.h" + +#include "source/common/upstream/upstream_impl.h" +#include "source/extensions/filters/network/common/redis/utility.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace Common { +namespace Redis { +namespace Client { +namespace { +Common::Redis::Client::DoNothingRawClientCallbacks null_raw_client_callbacks; +const std::string& RedisDBParamKey = "db"; +} // namespace + +RawClientPtr RawClientImpl::create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, + RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, + const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope) { + auto client = std::make_unique( + host, dispatcher, std::move(encoder), decoder_factory, config, redis_command_stats, scope); + client->connection_ = host->createConnection(dispatcher, nullptr, nullptr).connection_; + client->connection_->addConnectionCallbacks(*client); + client->connection_->addReadFilter(Network::ReadFilterSharedPtr{new UpstreamReadFilter(*client)}); + client->connection_->connect(); + client->connection_->noDelay(true); + return client; +} + +RawClientImpl::RawClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, + RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, + const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope) + : host_(host), encoder_(std::move(encoder)), decoder_(decoder_factory.create(*this)), + config_(config), + connect_or_op_timer_(dispatcher.createTimer([this]() { onConnectOrOpTimeout(); })), + flush_timer_(dispatcher.createTimer([this]() { flushBufferAndResetTimer(); })), + time_source_(dispatcher.timeSource()), redis_command_stats_(redis_command_stats), + scope_(scope) { + Upstream::ClusterTrafficStats& traffic_stats = *host->cluster().trafficStats(); + traffic_stats.upstream_cx_total_.inc(); + host->stats().cx_total_.inc(); + traffic_stats.upstream_cx_active_.inc(); + host->stats().cx_active_.inc(); + connect_or_op_timer_->enableTimer(host->cluster().connectTimeout()); +} + +RawClientImpl::~RawClientImpl() { + ASSERT(pending_requests_.empty()); + ASSERT(connection_->state() == Network::Connection::State::Closed); + Upstream::ClusterTrafficStats& traffic_stats = *host_->cluster().trafficStats(); + traffic_stats.upstream_cx_active_.dec(); + host_->stats().cx_active_.dec(); +} + +void RawClientImpl::close() { connection_->close(Network::ConnectionCloseType::NoFlush); } + +void RawClientImpl::flushBufferAndResetTimer() { + if (flush_timer_->enabled()) { + flush_timer_->disableTimer(); + } + connection_->write(encoder_buffer_, false); +} + +PoolRequest* RawClientImpl::makeRawRequest(std::string_view request, + RawClientCallbacks& callbacks) { + ASSERT(connection_->state() == Network::Connection::State::Open); + + const bool empty_buffer = encoder_buffer_.length() == 0; + + Stats::StatName command = redis_command_stats_->getUnusedStatName(); + + pending_requests_.emplace_back(*this, callbacks, command); + encoder_->encode(request, encoder_buffer_); + + // If buffer is full, flush. If the buffer was empty before the request, start the timer. + if (encoder_buffer_.length() >= config_.maxBufferSizeBeforeFlush()) { + flushBufferAndResetTimer(); + } else if (empty_buffer) { + flush_timer_->enableTimer(std::chrono::milliseconds(config_.bufferFlushTimeoutInMs())); + } + + // Only boost the op timeout if: + // - We are not already connected. Otherwise, we are governed by the connect timeout and the timer + // will be reset when/if connection occurs. This allows a relatively long connection spin up + // time for example if TLS is being used. + // - This is the first request on the pipeline. Otherwise the timeout would effectively start on + // the last operation. + if (connected_ && pending_requests_.size() == 1) { + connect_or_op_timer_->enableTimer(config_.opTimeout()); + } + + return &pending_requests_.back(); +} + +void RawClientImpl::onConnectOrOpTimeout() { + putOutlierEvent(Upstream::Outlier::Result::LocalOriginTimeout); + + Upstream::ClusterTrafficStats& traffic_stats = *host_->cluster().trafficStats(); + + if (connected_) { + traffic_stats.upstream_rq_timeout_.inc(); + host_->stats().rq_timeout_.inc(); + } else { + traffic_stats.upstream_cx_connect_timeout_.inc(); + host_->stats().cx_connect_fail_.inc(); + } + + connection_->close(Network::ConnectionCloseType::NoFlush); +} + +void RawClientImpl::onData(Buffer::Instance& data) { + try { + decoder_->decode(data); + } catch (ProtocolError&) { + Upstream::ClusterTrafficStats& traffic_stats = *host_->cluster().trafficStats(); + putOutlierEvent(Upstream::Outlier::Result::ExtOriginRequestFailed); + traffic_stats.upstream_cx_protocol_error_.inc(); + host_->stats().rq_error_.inc(); + connection_->close(Network::ConnectionCloseType::NoFlush); + } +} + +void RawClientImpl::putOutlierEvent(Upstream::Outlier::Result result) { + if (!config_.disableOutlierEvents()) { + host_->outlierDetector().putResult(result); + } +} + +void RawClientImpl::onEvent(Network::ConnectionEvent event) { + if (event == Network::ConnectionEvent::RemoteClose || + event == Network::ConnectionEvent::LocalClose) { + + Upstream::reportUpstreamCxDestroy(host_, event); + if (!pending_requests_.empty()) { + Upstream::reportUpstreamCxDestroyActiveRequest(host_, event); + if (event == Network::ConnectionEvent::RemoteClose) { + putOutlierEvent(Upstream::Outlier::Result::LocalOriginConnectFailed); + } + } + + while (!pending_requests_.empty()) { + PendingRequest& request = pending_requests_.front(); + if (!request.canceled_) { + request.callbacks_.onFailure(); + } else { + Upstream::ClusterTrafficStats& traffic_stats = *host_->cluster().trafficStats(); + traffic_stats.upstream_rq_cancelled_.inc(); + } + pending_requests_.pop_front(); + } + + connect_or_op_timer_->disableTimer(); + } else if (event == Network::ConnectionEvent::Connected) { + connected_ = true; + ASSERT(!pending_requests_.empty()); + connect_or_op_timer_->enableTimer(config_.opTimeout()); + } + + if (event == Network::ConnectionEvent::RemoteClose && !connected_) { + Upstream::ClusterTrafficStats& traffic_stats = *host_->cluster().trafficStats(); + traffic_stats.upstream_cx_connect_fail_.inc(); + host_->stats().cx_connect_fail_.inc(); + } +} + +void RawClientImpl::onRawResponse(std::string&& response) { + ASSERT(!pending_requests_.empty()); + PendingRequest& request = pending_requests_.front(); + const bool canceled = request.canceled_; + + request.aggregate_request_timer_->complete(); + + RawClientCallbacks& callbacks = request.callbacks_; + + // We need to ensure the request is popped before calling the callback, since the callback might + // result in closing the connection. + pending_requests_.pop_front(); + if (canceled) { + Upstream::ClusterTrafficStats& traffic_stats = *host_->cluster().trafficStats(); + traffic_stats.upstream_rq_cancelled_.inc(); + } else { + // do not handle redirection here + callbacks.onResponse(std::move(response)); + } + + // If there are no remaining ops in the pipeline we need to disable the timer. + // Otherwise we boost the timer since we are receiving responses and there are more to flush + // out. + if (pending_requests_.empty()) { + connect_or_op_timer_->disableTimer(); + } else { + connect_or_op_timer_->enableTimer(config_.opTimeout()); + } + + putOutlierEvent(Upstream::Outlier::Result::ExtOriginRequestSuccess); +} + +RawClientImpl::PendingRequest::PendingRequest(RawClientImpl& parent, RawClientCallbacks& callbacks, + Stats::StatName command) + : parent_(parent), callbacks_(callbacks), command_{command}, + aggregate_request_timer_(parent_.redis_command_stats_->createAggregateTimer( + parent_.scope_, parent_.time_source_)) { + Upstream::ClusterTrafficStats& traffic_stats = *parent.host_->cluster().trafficStats(); + traffic_stats.upstream_rq_total_.inc(); + parent.host_->stats().rq_total_.inc(); + traffic_stats.upstream_rq_active_.inc(); + parent.host_->stats().rq_active_.inc(); +} + +RawClientImpl::PendingRequest::~PendingRequest() { + Upstream::ClusterTrafficStats& traffic_stats = *parent_.host_->cluster().trafficStats(); + traffic_stats.upstream_rq_active_.dec(); + parent_.host_->stats().rq_active_.dec(); +} + +void RawClientImpl::PendingRequest::cancel() { + // If we get a cancellation, we just mark the pending request as cancelled, and then we drop + // the response as it comes through. There is no reason to blow away the connection when the + // remote is already responding as fast as possible. + canceled_ = true; +} + +void RawClientImpl::initialize(const std::string& auth_username, const std::string& auth_password, + const std::map& params) { + if (!auth_username.empty()) { + std::string auth_request = Utility::makeRawAuthRequest(auth_username, auth_password); + makeRawRequest(auth_request, null_raw_client_callbacks); + } else if (!auth_password.empty()) { + std::string auth_request = Utility::makeRawAuthRequest(auth_password); + makeRawRequest(auth_request, null_raw_client_callbacks); + } + auto it = params.find(RedisDBParamKey); + if (it != params.end()) { + std::string select_request = Utility::makeSelectRequest(it->second); + makeRawRequest(select_request, null_raw_client_callbacks); + } + + if (config_.readPolicy() != Common::Redis::Client::ReadPolicy::Primary) { + makeRawRequest(Utility::makeRawReadOnlyRequest(), null_raw_client_callbacks); + } +} + +RawClientFactoryImpl RawClientFactoryImpl::instance_; + +RawClientPtr RawClientFactoryImpl::create(Upstream::HostConstSharedPtr host, + Event::Dispatcher& dispatcher, const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope, const std::string& auth_username, + const std::string& auth_password, + const std::map& params) { + RawClientPtr client = RawClientImpl::create(host, dispatcher, RawEncoderPtr{new RawEncoderImpl()}, + decoder_factory_, config, redis_command_stats, scope); + client->initialize(auth_username, auth_password, params); + return client; +} + +} // namespace Client +} // namespace Redis +} // namespace Common +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/common/redis/raw_client_impl.h b/source/extensions/filters/network/common/redis/raw_client_impl.h new file mode 100644 index 0000000000000..8aa7da6d807a1 --- /dev/null +++ b/source/extensions/filters/network/common/redis/raw_client_impl.h @@ -0,0 +1,116 @@ +#pragma once + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/network/filter_impl.h" +#include "source/extensions/filters/network/common/redis/raw_client.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace Common { +namespace Redis { +namespace Client { + +class RawClientImpl : public RawClient, + public RawDecoderCallbacks, + public Network::ConnectionCallbacks { +public: + static RawClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, + RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, + const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope); + + RawClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, + RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope); + ~RawClientImpl() override; + + // RawClient + void addConnectionCallbacks(Network::ConnectionCallbacks& callbacks) override { + connection_->addConnectionCallbacks(callbacks); + } + void close() override; + PoolRequest* makeRawRequest(std::string_view request, RawClientCallbacks& callbacks) override; + bool active() override { return !pending_requests_.empty(); } + void flushBufferAndResetTimer(); + void initialize(const std::string& auth_username, const std::string& auth_password, + const std::map& params) override; + +private: + friend class RedisRawClientImplTest; + + struct UpstreamReadFilter : public Network::ReadFilterBaseImpl { + UpstreamReadFilter(RawClientImpl& parent) : parent_(parent) {} + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& data, bool) override { + parent_.onData(data); + return Network::FilterStatus::Continue; + } + + RawClientImpl& parent_; + }; + + struct PendingRequest : public PoolRequest { + PendingRequest(RawClientImpl& parent, RawClientCallbacks& callbacks, Stats::StatName stat_name); + ~PendingRequest() override; + + // PoolRequest + void cancel() override; + + RawClientImpl& parent_; + RawClientCallbacks& callbacks_; + Stats::StatName command_; + bool canceled_{}; + Stats::TimespanPtr aggregate_request_timer_; + Stats::TimespanPtr command_request_timer_; + }; + + void onConnectOrOpTimeout(); + void onData(Buffer::Instance& data); + void putOutlierEvent(Upstream::Outlier::Result result); + + // RawDecoderCallbacks + void onRawResponse(std::string&& response) override; + + // Network::ConnectionCallbacks + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + + Upstream::HostConstSharedPtr host_; + Network::ClientConnectionPtr connection_; + RawEncoderPtr encoder_; + Buffer::OwnedImpl encoder_buffer_; + DecoderPtr decoder_; + const Config& config_; + std::list pending_requests_; + Event::TimerPtr connect_or_op_timer_; + bool connected_{}; + Event::TimerPtr flush_timer_; + Envoy::TimeSource& time_source_; + const RedisCommandStatsSharedPtr redis_command_stats_; + Stats::Scope& scope_; +}; + +class RawClientFactoryImpl : public RawClientFactory { +public: + RawClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, + const Config& config, const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope, const std::string& auth_username, + const std::string& auth_password, + const std::map& params) override; + + static RawClientFactoryImpl instance_; + +private: + RawDecoderFactoryImpl decoder_factory_; +}; + +} // namespace Client +} // namespace Redis +} // namespace Common +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/common/redis/utility.cc b/source/extensions/filters/network/common/redis/utility.cc index 3eb644f3285ab..263bfc50db306 100644 --- a/source/extensions/filters/network/common/redis/utility.cc +++ b/source/extensions/filters/network/common/redis/utility.cc @@ -37,7 +37,43 @@ RespValuePtr makeError(const std::string& error) { response->asString() = error; return response; } +#if defined(ALIMESH) +std::string makeRawError(const std::string& error) { + std::string result; + result.append(fmt::format("-{}\r\n", error)); + return result; +} + +std::string makeRawRequest(const std::string& command, std::vector params) { + std::string result; + result.append(fmt::format("*{}\r\n", 1 + params.size())); + result.append(fmt::format("${}\r\n{}\r\n", command.size(), command)); + for (auto& param : params) { + result.append(fmt::format("${}\r\n{}\r\n", param.size(), param)); + } + return result; +} + +std::string makeRawAuthRequest(const std::string& username, const std::string& password) { + return makeRawRequest("AUTH", {username, password}); +} + +std::string makeRawAuthRequest(const std::string& password) { + return makeRawRequest("AUTH", {password}); +} + +std::string_view makeRawReadOnlyRequest() { + const std::string readonly{"readonly"}; + static const std::string readonly_request = + fmt::format("${}\r\n{}\r\n", readonly.size(), readonly); + return readonly_request; +} + +std::string makeSelectRequest(const std::string& index) { + return makeRawRequest("SELECT", {index}); +} +#endif ReadOnlyRequest::ReadOnlyRequest() { std::vector values(1); values[0].type(RespType::BulkString); diff --git a/source/extensions/filters/network/common/redis/utility.h b/source/extensions/filters/network/common/redis/utility.h index 7f98bfbb444f5..139b7e832cc88 100644 --- a/source/extensions/filters/network/common/redis/utility.h +++ b/source/extensions/filters/network/common/redis/utility.h @@ -18,6 +18,13 @@ class AuthRequest : public Redis::RespValue { }; RespValuePtr makeError(const std::string& error); +#if defined(ALIMESH) +std::string makeRawError(const std::string& error); +std::string makeRawAuthRequest(const std::string& password); +std::string makeRawAuthRequest(const std::string& username, const std::string& password); +std::string_view makeRawReadOnlyRequest(); +std::string makeSelectRequest(const std::string& index); +#endif class ReadOnlyRequest : public Redis::RespValue { public: diff --git a/source/extensions/filters/network/dubbo_proxy/message_impl.cc b/source/extensions/filters/network/dubbo_proxy/message_impl.cc index 2f3a1f54935da..75c800f6469ef 100644 --- a/source/extensions/filters/network/dubbo_proxy/message_impl.cc +++ b/source/extensions/filters/network/dubbo_proxy/message_impl.cc @@ -12,7 +12,7 @@ RpcInvocationImpl::Attachment::Attachment(MapPtr&& value, size_t offset) headers_ = Http::RequestHeaderMapImpl::create(); ASSERT(attachment_ != nullptr); - ASSERT(attachment_->toMutableUntypedMap().has_value()); + ASSERT(attachment_->toMutableUntypedMap()); for (const auto& pair : *attachment_) { const auto key = pair.first->toString(); @@ -20,7 +20,7 @@ RpcInvocationImpl::Attachment::Attachment(MapPtr&& value, size_t offset) if (!key.has_value() || !value.has_value()) { continue; } - headers_->addCopy(Http::LowerCaseString(key.value().get()), value.value().get()); + headers_->addCopy(Http::LowerCaseString(*(key.value())), *(value.value())); } } @@ -35,20 +35,20 @@ void RpcInvocationImpl::Attachment::insert(const std::string& key, const std::st } void RpcInvocationImpl::Attachment::remove(const std::string& key) { - ASSERT(attachment_->toMutableUntypedMap().has_value()); + ASSERT(attachment_->toMutableUntypedMap()); attachment_updated_ = true; - attachment_->toMutableUntypedMap().value().get().erase(key); + attachment_->toMutableUntypedMap()->erase(std::make_unique(key)); headers_->remove(Http::LowerCaseString(key)); } const std::string* RpcInvocationImpl::Attachment::lookup(const std::string& key) const { - ASSERT(attachment_->toMutableUntypedMap().has_value()); + ASSERT(attachment_->toMutableUntypedMap()); - auto& map = attachment_->toMutableUntypedMap().value().get(); - auto result = map.find(key); - if (result != map.end() && result->second->toString().has_value()) { - return &(result->second->toString().value().get()); + auto map = attachment_->toMutableUntypedMap(); + auto result = map->find(std::make_unique(key)); + if (result != map->end() && result->second->toString().has_value()) { + return result->second->toString().value(); } return nullptr; } diff --git a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc index 334236048f212..2f14e3778a51f 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc +++ b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc @@ -136,9 +136,9 @@ RouteConstSharedPtr ParameterRouteEntryImpl::matches(const MessageMetadata& meta return nullptr; } - if (!matchParameter(absl::string_view(data.value().get()), config_data)) { + if (!matchParameter(absl::string_view(*data.value()), config_data)) { ENVOY_LOG(debug, "dubbo route matcher: parameter matching failed, index '{}', value '{}'", - config_data.index_, data.value().get()); + config_data.index_, *data.value()); return nullptr; } } diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 3337592e331e8..4ccd5a6beb343 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -219,9 +219,9 @@ Utility::Singletons Utility::createSingletons(Server::Configuration::FactoryCont auto tracer_manager = Tracing::TracerManagerImpl::singleton(context); - std::shared_ptr filter_config_provider_manager = - Http::FilterChainUtility::createSingletonDownstreamFilterConfigProviderManager( - context.getServerFactoryContext()); + Server::Configuration::DownstreamHTTPFilterConfigProviderManagerSharedPtr + filter_config_provider_manager = + context.getServerFactoryContext().downstreamHttpFilterConfigProviderManager(); return {date_provider, route_config_provider_manager, scoped_routes_config_provider_manager, tracer_manager, filter_config_provider_manager}; @@ -385,7 +385,13 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( createHeaderValidatorFactory(config, context.getServerFactoryContext())), append_x_forwarded_port_(config.append_x_forwarded_port()), add_proxy_protocol_connection_state_( +#if defined(ALIMESH) + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, add_proxy_protocol_connection_state, true)), + keepalive_header_timeout_(PROTOBUF_GET_SECONDS_OR_DEFAULT(config, keepalive_header_timeout, + KeepaliveHeaderTimeoutSeconds)) { +#else PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, add_proxy_protocol_connection_state, true)) { +#endif if (!idle_timeout_) { idle_timeout_ = std::chrono::hours(1); } else if (idle_timeout_.value().count() == 0) { diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index d2c43c2fe1422..8915e085efbb0 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -45,7 +45,7 @@ namespace NetworkFilters { namespace HttpConnectionManager { using FilterConfigProviderManager = - Filter::FilterConfigProviderManager; /** @@ -140,7 +140,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, Http::FilterChainManager& manager, bool = false, const Http::FilterChainOptions& = Http::EmptyFilterChainOptions{}) const override; using FilterFactoriesList = - std::list>; + std::list>; struct FilterConfig { std::unique_ptr filter_factories; bool allow_upgrade; @@ -267,6 +267,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, bool addProxyProtocolConnectionState() const override { return add_proxy_protocol_connection_state_; } +#if defined(ALIMESH) + std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } +#endif private: enum class CodecType { HTTP1, HTTP2, HTTP3, AUTO }; @@ -353,6 +356,10 @@ class HttpConnectionManagerConfig : Logger::Loggable, static const uint64_t RequestTimeoutMs = 0; // request header timeout is disabled by default static const uint64_t RequestHeaderTimeoutMs = 0; +#if defined(ALIMESH) + // keep-alive response header is disabled by default + static const uint64_t KeepaliveHeaderTimeoutSeconds = 0; +#endif const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: PathWithEscapedSlashesAction path_with_escaped_slashes_action_; const bool strip_trailing_host_dot_; @@ -361,6 +368,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, const Http::HeaderValidatorFactoryPtr header_validator_factory_; const bool append_x_forwarded_port_; const bool add_proxy_protocol_connection_state_; +#if defined(ALIMESH) + const std::chrono::seconds keepalive_header_timeout_; +#endif }; /** diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index dffd08b0c6209..1df4c08cad873 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -31,13 +31,40 @@ class FilterConfig : Logger::Loggable { if (!tls_slot_->currentThreadRegistered()) { return nullptr; } - PluginHandleSharedPtr handle = tls_slot_->get()->handle(); + auto opt_ref = tls_slot_->get(); + if (!opt_ref) { + return nullptr; + } + PluginHandleSharedPtr handle = opt_ref->handle(); if (!handle) { return nullptr; } if (handle->wasmHandle()) { wasm = handle->wasmHandle()->wasm().get(); } +#if defined(ALIMESH) + auto failed = false; + if (!wasm) { + failed = true; + } else if (wasm->isFailed()) { + ENVOY_LOG(info, "wasm vm is crashed, try to recover"); + if (opt_ref->recover()) { + ENVOY_LOG(info, "wasm vm recover success"); + wasm = opt_ref->handle()->wasmHandle()->wasm().get(); + } else { + ENVOY_LOG(info, "wasm vm recover failed"); + failed = true; + } + } + if (failed) { + if (handle->plugin()->fail_open_) { + return nullptr; // Fail open skips adding this filter to callbacks. + } else { + return std::make_shared(nullptr, 0, + handle); // Fail closed is handled by an empty Context. + } + } +#else if (!wasm || wasm->isFailed()) { if (handle->plugin()->fail_open_) { return nullptr; // Fail open skips adding this filter to callbacks. @@ -46,6 +73,7 @@ class FilterConfig : Logger::Loggable { handle); // Fail closed is handled by an empty Context. } } +#endif return std::make_shared(wasm, handle->rootContextId(), handle); } diff --git a/source/extensions/health_checkers/tcp/health_checker_impl.cc b/source/extensions/health_checkers/tcp/health_checker_impl.cc index 0c1b540157f81..59e71186a0d56 100644 --- a/source/extensions/health_checkers/tcp/health_checker_impl.cc +++ b/source/extensions/health_checkers/tcp/health_checker_impl.cc @@ -137,7 +137,18 @@ void TcpHealthCheckerImpl::TcpActiveHealthCheckSession::onInterval() { client_->addReadFilter(session_callbacks_); expect_close_ = false; +#if defined(ALIMESH) + try { + client_->connect(); + } catch (const EnvoyException& ex) { + ENVOY_CONN_LOG(critical, + "envoy exception raised in TcpActiveHealthCheckSession::onInterval(): {}", + *client_, ex.what()); + return; + } +#else client_->connect(); +#endif client_->noDelay(true); } diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc index bfbba3f92a86b..1b2dcbc35558c 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc @@ -63,6 +63,17 @@ RedirectPolicy::RedirectPolicy( ? std::make_unique<::Envoy::Http::Utility::RedirectConfig>( createRedirectConfig(config.redirect_action())) : nullptr}, +#if defined(ALIMESH) + use_original_request_uri_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_request_uri, false)), + keep_original_response_code_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, keep_original_response_code, true)), + max_internal_redirects_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_internal_redirects, 1)), + use_original_request_body_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_request_body, false)), + only_redirect_upstream_code_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, only_redirect_upstream_code, false)), +#endif status_code_{config.has_status_code() ? absl::optional<::Envoy::Http::Code>( static_cast<::Envoy::Http::Code>(config.status_code().value())) @@ -72,8 +83,14 @@ RedirectPolicy::RedirectPolicy( request_header_parser_( Envoy::Router::HeaderParser::configure(config.request_headers_to_add())), modify_request_headers_action_(createModifyRequestHeadersAction(config, context)) { +#if defined(ALIMESH) + // Ensure that exactly one of uri_ or redirect_action_ or use_original_request_uri_ is specified. + ASSERT(int(uri_ != nullptr) + int(redirect_action_ != nullptr) + int(use_original_request_uri_) == + 1); +#else // Ensure that exactly one of uri_ or redirect_action_ is specified. ASSERT((uri_ || redirect_action_) && !(uri_ && redirect_action_)); +#endif if (uri_) { ::Envoy::Http::Utility::Url absolute_url; @@ -112,6 +129,43 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( // the remote source and return. auto encoder_callbacks = custom_response_filter.encoderCallbacks(); auto decoder_callbacks = custom_response_filter.decoderCallbacks(); +#if defined(ALIMESH) + auto* filter_state = + encoder_callbacks->streamInfo() + .filterState() + ->getDataMutable< + ::Envoy::Extensions::HttpFilters::CustomResponse::CustomResponseFilterState>( + "envoy.filters.http.custom_response"); + if (filter_state && filter_state->remain_redirect_times-- <= 0) { + ENVOY_BUG(filter_state->policy.get() == this, "Policy filter state should be this policy."); + // Only process response on last redirect + + // Apply header mutations. + response_header_parser_->evaluateHeaders(headers, encoder_callbacks->streamInfo()); + const absl::optional<::Envoy::Http::Code> status_code_to_use = + status_code_.has_value() ? status_code_ + : (filter_state->original_response_code.has_value() + ? filter_state->original_response_code + : absl::nullopt); + // Modify response status code. + if (status_code_to_use.has_value()) { + auto const code = *status_code_to_use; + headers.setStatus(std::to_string(enumToInt(code))); + encoder_callbacks->streamInfo().setResponseCode(static_cast(code)); + } + return ::Envoy::Http::FilterHeadersStatus::Continue; + } + if (only_redirect_upstream_code_) { + const auto& streamInfo = encoder_callbacks->streamInfo(); + if (!streamInfo.responseCodeDetails().has_value()) { + return ::Envoy::Http::FilterHeadersStatus::Continue; + } + if (streamInfo.responseCodeDetails().value() != + ::Envoy::StreamInfo::ResponseCodeDetails::get().ViaUpstream) { + return ::Envoy::Http::FilterHeadersStatus::Continue; + } + } +#else const ::Envoy::Extensions::HttpFilters::CustomResponse::CustomResponseFilterState* filter_state = encoder_callbacks->streamInfo() .filterState() @@ -135,6 +189,7 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( } return ::Envoy::Http::FilterHeadersStatus::Continue; } +#endif auto downstream_headers = custom_response_filter.downstreamHeaders(); // Modify the request headers & recreate stream. @@ -165,27 +220,47 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( }); ::Envoy::Http::Utility::Url absolute_url; - std::string uri(uri_ ? *uri_ - : ::Envoy::Http::Utility::newUri(*redirect_action_, *downstream_headers)); - if (!absolute_url.initialize(uri, false)) { - stats_.custom_response_invalid_uri_.inc(); - // We could potentially get an invalid url only if redirect_action_ was specified instead - // of uri_. Hence, assert that uri_ is not set. - ENVOY_BUG(!static_cast(uri_), - "uri should not be invalid as this was already validated during config load"); - return ::Envoy::Http::FilterHeadersStatus::Continue; - } - downstream_headers->setScheme(absolute_url.scheme()); - downstream_headers->setHost(absolute_url.hostAndPort()); +#if defined(ALIMESH) + if (use_original_request_uri_) { + std::string real_original_host; + const auto x_envoy_original_host = downstream_headers->getByKey( + ::Envoy::Http::CustomHeaders::get().AliExtendedValues.XEnvoyOriginalHost); + if (x_envoy_original_host && !(*x_envoy_original_host).empty()) { + real_original_host = *x_envoy_original_host; + } else { + real_original_host = original_host; + } + std::string real_original_path(downstream_headers->getEnvoyOriginalPathValue().empty() + ? original_path + : downstream_headers->getEnvoyOriginalPathValue()); + downstream_headers->setHost(real_original_host); + downstream_headers->setPath(real_original_path); + } else { +#endif + std::string uri(uri_ ? *uri_ + : ::Envoy::Http::Utility::newUri(*redirect_action_, *downstream_headers)); + if (!absolute_url.initialize(uri, false)) { + stats_.custom_response_invalid_uri_.inc(); + // We could potentially get an invalid url only if redirect_action_ was specified instead + // of uri_. Hence, assert that uri_ is not set. + ENVOY_BUG(!static_cast(uri_), + "uri should not be invalid as this was already validated during config load"); + return ::Envoy::Http::FilterHeadersStatus::Continue; + } + downstream_headers->setScheme(absolute_url.scheme()); + downstream_headers->setHost(absolute_url.hostAndPort()); - auto path_and_query = absolute_url.pathAndQueryParams(); - // Strip the #fragment from Location URI if it is present. Envoy treats - // internal redirect as a new request and will reject it if the URI path - // contains #fragment. - const auto fragment_pos = path_and_query.find('#'); - path_and_query = path_and_query.substr(0, fragment_pos); + auto path_and_query = absolute_url.pathAndQueryParams(); + // Strip the #fragment from Location URI if it is present. Envoy treats + // internal redirect as a new request and will reject it if the URI path + // contains #fragment. + const auto fragment_pos = path_and_query.find('#'); + path_and_query = path_and_query.substr(0, fragment_pos); - downstream_headers->setPath(path_and_query); + downstream_headers->setPath(path_and_query); +#if defined(ALIMESH) + } +#endif if (decoder_callbacks->downstreamCallbacks()) { decoder_callbacks->downstreamCallbacks()->clearRouteCache(); } @@ -205,10 +280,38 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( // redirect will take place. return ::Envoy::Http::FilterHeadersStatus::Continue; } +#if !defined(ALIMESH) downstream_headers->setMethod(::Envoy::Http::Headers::get().MethodValues.Get); +#endif downstream_headers->remove(::Envoy::Http::Headers::get().ContentLength); // Cache the original response code. absl::optional<::Envoy::Http::Code> original_response_code; +#if defined(ALIMESH) + if (keep_original_response_code_) { + absl::optional current_code = + ::Envoy::Http::Utility::getResponseStatusOrNullopt(headers); + if (current_code.has_value()) { + original_response_code = static_cast<::Envoy::Http::Code>(*current_code); + } + } + if (!filter_state) { + encoder_callbacks->streamInfo().filterState()->setData( + // TODO(pradeepcrao): Currently we don't have a mechanism to add readonly + // objects to FilterState, even if they're immutable. + "envoy.filters.http.custom_response", + std::make_shared< + ::Envoy::Extensions::HttpFilters::CustomResponse::CustomResponseFilterState>( + this->shared_from_this(), original_response_code, max_internal_redirects_ - 1), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); + } else { + filter_state->policy = this->shared_from_this(); + } + restore_original_headers.cancel(); + // Should not return StopIteration if recreateStream failed + return decoder_callbacks->recreateStream(nullptr, use_original_request_body_) + ? ::Envoy::Http::FilterHeadersStatus::StopIteration + : ::Envoy::Http::FilterHeadersStatus::Continue; +#else absl::optional current_code = ::Envoy::Http::Utility::getResponseStatusOrNullopt(headers); if (current_code.has_value()) { @@ -219,10 +322,12 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( // objects to FilterState, even if they're immutable. "envoy.filters.http.custom_response", std::make_shared<::Envoy::Extensions::HttpFilters::CustomResponse::CustomResponseFilterState>( - const_cast(this)->shared_from_this(), original_response_code), + const_cast(this)->shared_from_this(), original_response_code, + max_internal_redirects_), StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); restore_original_headers.cancel(); decoder_callbacks->recreateStream(nullptr); +#endif return ::Envoy::Http::FilterHeadersStatus::StopIteration; } diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.h b/source/extensions/http/custom_response/redirect_policy/redirect_policy.h index 14464dcd4f44c..5180fa34bf23d 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.h +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.h @@ -70,6 +70,13 @@ class RedirectPolicy : public Extensions::HttpFilters::CustomResponse::Policy, // Remote source the request should be redirected to. const std::unique_ptr uri_; const std::unique_ptr redirect_action_; +#if defined(ALIMESH) + bool use_original_request_uri_; + bool keep_original_response_code_; + uint32_t max_internal_redirects_; + bool use_original_request_body_; + bool only_redirect_upstream_code_; +#endif const absl::optional<::Envoy::Http::Code> status_code_; const std::unique_ptr response_header_parser_; diff --git a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc index 9da84c8d8aa33..53182c0978626 100644 --- a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc @@ -85,7 +85,19 @@ Filter::NetworkFilterFactoriesList ProdListenerComponentFactory::createNetworkFi ret.reserve(filters.size()); for (ssize_t i = 0; i < filters.size(); i++) { const auto& proto_config = filters[i]; + const bool is_terminal = i == filters.size() - 1; ENVOY_LOG(debug, " filter #{}:", i); + + if (proto_config.config_type_case() == + envoy::config::listener::v3::Filter::ConfigTypeCase::kConfigDiscovery) { + ENVOY_LOG(debug, " dynamic filter name: {}", proto_config.name()); + ret.push_back(config_provider_manager.createDynamicFilterConfigProvider( + proto_config.config_discovery(), proto_config.name(), + filter_chain_factory_context.getServerFactoryContext(), filter_chain_factory_context, + is_terminal, "network", nullptr)); + continue; + } + ENVOY_LOG(debug, " name: {}", proto_config.name()); ENVOY_LOG(debug, " config: {}", MessageUtil::getJsonStringFromMessageOrError( @@ -102,7 +114,7 @@ Filter::NetworkFilterFactoriesList ProdListenerComponentFactory::createNetworkFi filters[i].name(), factory.name(), "network", factory.isTerminalFilterByProto(*message, filter_chain_factory_context.getServerFactoryContext()), - i == filters.size() - 1); + is_terminal); Network::FilterFactoryCb callback = factory.createFilterFactoryFromProto(*message, filter_chain_factory_context); ret.push_back( diff --git a/source/extensions/tracers/common/BUILD b/source/extensions/tracers/common/BUILD index 89e447c456fc6..d7610929c77eb 100644 --- a/source/extensions/tracers/common/BUILD +++ b/source/extensions/tracers/common/BUILD @@ -11,6 +11,7 @@ envoy_extension_package() envoy_cc_library( name = "factory_base_lib", hdrs = ["factory_base.h"], + visibility = ["//visibility:public"], deps = [ "//envoy/server:tracer_config_interface", "//source/common/config:utility_lib", diff --git a/source/extensions/tracers/opentelemetry/grpc_trace_exporter.h b/source/extensions/tracers/opentelemetry/grpc_trace_exporter.h index 2d6ff1be89771..7433a4e0b116d 100644 --- a/source/extensions/tracers/opentelemetry/grpc_trace_exporter.h +++ b/source/extensions/tracers/opentelemetry/grpc_trace_exporter.h @@ -68,7 +68,7 @@ class OpenTelemetryGrpcTraceExporterClient : Logger::Loggablestream_->isAboveWriteBufferHighWatermark()) { return false; } - stream_->stream_->sendMessage(request, false); + stream_->stream_->sendMessage(request, true); } else { stream_.reset(); } diff --git a/source/extensions/tracers/opentelemetry/tracer.cc b/source/extensions/tracers/opentelemetry/tracer.cc index a344a253ab31e..9bfbd275877c7 100644 --- a/source/extensions/tracers/opentelemetry/tracer.cc +++ b/source/extensions/tracers/opentelemetry/tracer.cc @@ -21,6 +21,7 @@ constexpr absl::string_view kTraceState = "tracestate"; constexpr absl::string_view kDefaultVersion = "00"; constexpr absl::string_view kServiceNameKey = "service.name"; constexpr absl::string_view kDefaultServiceName = "unknown_service:envoy"; +constexpr absl::string_view kTraceId = "ot-traceid"; using opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest; @@ -61,6 +62,8 @@ void Span::injectContext(Tracing::TraceContext& trace_context, std::string trace_flags_hex = Hex::encode(trace_flags_vec); std::string traceparent_header_value = absl::StrCat(kDefaultVersion, "-", trace_id_hex, "-", span_id_hex, "-", trace_flags_hex); + // Set the traceid. + trace_context.setByReferenceKey(kTraceId, trace_id_hex); // Set the traceparent in the trace_context. trace_context.setByReferenceKey(kTraceParent, traceparent_header_value); // Also set the tracestate. diff --git a/source/extensions/tracers/skywalking/trace_segment_reporter.cc b/source/extensions/tracers/skywalking/trace_segment_reporter.cc index fe3a5cb45b641..6ee0e91057035 100644 --- a/source/extensions/tracers/skywalking/trace_segment_reporter.cc +++ b/source/extensions/tracers/skywalking/trace_segment_reporter.cc @@ -48,6 +48,11 @@ void TraceSegmentReporter::report(TracingContextPtr tracing_context) { ENVOY_LOG(trace, "Try to report segment to SkyWalking Server:\n{}", request.DebugString()); if (stream_ != nullptr) { + if (stream_->isAboveWriteBufferHighWatermark()) { + ENVOY_LOG(debug, "Failed to report segment to SkyWalking Server since buffer is over limit"); + tracing_stats_->segments_dropped_.inc(); + return; + } tracing_stats_->segments_sent_.inc(); stream_->sendMessage(request, false); return; diff --git a/source/extensions/tracers/skywalking/tracer.cc b/source/extensions/tracers/skywalking/tracer.cc index 605074bab7199..7c89535364019 100644 --- a/source/extensions/tracers/skywalking/tracer.cc +++ b/source/extensions/tracers/skywalking/tracer.cc @@ -2,6 +2,10 @@ #include +#if defined(ALIMESH) +#include "source/common/common/base64.h" +#endif + namespace Envoy { namespace Extensions { namespace Tracers { @@ -16,6 +20,12 @@ const Http::LowerCaseString& skywalkingPropagationHeaderKey() { CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, "sw8"); } +#if defined(ALIMESH) +const Http::LowerCaseString& skywalkingPropagationHeaderKeyTraceId() { + CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, "sw8-traceid"); +} +#endif + void Span::setTag(absl::string_view name, absl::string_view value) { if (name == Tracing::Tags::get().HttpUrl) { span_entity_->addTag(UrlTag.data(), std::string(value)); @@ -55,7 +65,14 @@ void Span::injectContext(Tracing::TraceContext& trace_context, tracing_context_->createSW8HeaderValue({remote_address.data(), remote_address.size()}); if (sw8_header.has_value()) { trace_context.setByReferenceKey(skywalkingPropagationHeaderKey(), sw8_header.value()); - +#if defined(ALIMESH) + std::vector result = absl::StrSplit(sw8_header.value(), '-'); + std::string sw8_trace_id = ""; + if (result.size() > 1) { + sw8_trace_id = Base64::decode(result[1]); + } + trace_context.setByReferenceKey(skywalkingPropagationHeaderKeyTraceId(), sw8_trace_id); +#endif // Rewrite operation name with latest upstream request path for the EXIT span. absl::string_view upstream_request_path = trace_context.path(); span_entity_->setOperationName({upstream_request_path.data(), upstream_request_path.size()}); diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index f3132d2ffb9bd..c39d840f51c18 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -235,6 +235,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ctx.is_ecdsa_ = true; } break; case EVP_PKEY_RSA: { +#if !defined(ALIMESH) // We require RSA certificates with 2048-bit or larger keys. const RSA* rsa_public_key = EVP_PKEY_get0_RSA(public_key.get()); // Since we checked the key type above, this should be valid. @@ -254,6 +255,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c "certificates with 2048-bit or larger keys are supported", ctx.cert_chain_file_path_)); } +#endif #endif } break; #ifdef BORINGSSL_FIPS diff --git a/source/server/BUILD b/source/server/BUILD index fc81eafc00f38..e5aa33a800ed4 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -327,6 +327,8 @@ envoy_cc_library( deps = [ "//envoy/server:factory_context_interface", "//envoy/server:instance_interface", + "//source/common/config:metadata_lib", + "//source/common/filter:config_discovery_lib", ], ) diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index f20736fb16a82..b3e0faac120a5 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -227,6 +227,9 @@ class AdminImpl : public Admin, } bool appendXForwardedPort() const override { return false; } bool addProxyProtocolConnectionState() const override { return true; } +#if defined(ALIMESH) + std::chrono::seconds keepaliveHeaderTimeout() const override { return {}; } +#endif private: friend class AdminTestingPeer; @@ -314,7 +317,15 @@ class AdminImpl : public Admin, NullScopeKeyBuilder() = default; ~NullScopeKeyBuilder() override = default; +#if defined(ALIMESH) + Router::ScopeKeyPtr computeScopeKey(const Http::HeaderMap&, const StreamInfo::StreamInfo*, + std::function&) const override { + return nullptr; + } + Router::ScopeKeyPtr computeScopeKey(const Http::HeaderMap&) const override { return nullptr; }; +#else Router::ScopeKeyPtr computeScopeKey(const Http::HeaderMap&) const override { return nullptr; }; +#endif }; /** diff --git a/source/server/server.h b/source/server/server.h index d5140d0e895aa..a8fd08b302073 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -174,7 +174,9 @@ class ServerFactoryContextImpl : public Configuration::ServerFactoryContext, public Configuration::TransportSocketFactoryContext { public: explicit ServerFactoryContextImpl(Instance& server) - : server_(server), server_scope_(server_.stats().createScope("")) {} + : server_(server), server_scope_(server_.stats().createScope("")), + filter_config_provider_manager_( + std::make_shared()) {} // Configuration::ServerFactoryContext Upstream::ClusterManager& clusterManager() override { return server_.clusterManager(); } @@ -199,6 +201,10 @@ class ServerFactoryContextImpl : public Configuration::ServerFactoryContext, ServerLifecycleNotifier& lifecycleNotifier() override { return server_.lifecycleNotifier(); } Configuration::StatsConfig& statsConfig() override { return server_.statsConfig(); } envoy::config::bootstrap::v3::Bootstrap& bootstrap() override { return server_.bootstrap(); } + Configuration::DownstreamHTTPFilterConfigProviderManagerSharedPtr + downstreamHttpFilterConfigProviderManager() override { + return filter_config_provider_manager_; + } // Configuration::TransportSocketFactoryContext ServerFactoryContext& serverFactoryContext() override { return *this; } @@ -220,6 +226,7 @@ class ServerFactoryContextImpl : public Configuration::ServerFactoryContext, private: Instance& server_; Stats::ScopeSharedPtr server_scope_; + Configuration::DownstreamHTTPFilterConfigProviderManagerSharedPtr filter_config_provider_manager_; }; /** diff --git a/test/common/common/BUILD b/test/common/common/BUILD index ce62b45fca566..ad0a0998e961f 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -201,10 +201,10 @@ envoy_cc_benchmark_binary( deps = ["//source/common/common:minimal_logger_lib"], ) -envoy_benchmark_test( - name = "logger_speed_test_benchmark_test", - benchmark_binary = "logger_speed_test", -) +# envoy_benchmark_test( +# name = "logger_speed_test_benchmark_test", +# benchmark_binary = "logger_speed_test", +# ) envoy_cc_test( name = "logger_test", diff --git a/test/common/common/base64_test.cc b/test/common/common/base64_test.cc index e00ae7f998271..3c6bf92a05740 100644 --- a/test/common/common/base64_test.cc +++ b/test/common/common/base64_test.cc @@ -132,47 +132,6 @@ TEST(Base64Test, BinaryBufferEncode) { EXPECT_EQ("AAECAwgKCQCqvN4=", Base64::encode(buffer, 30)); } -TEST(Base64Test, CompletePadding) { - struct CompletePaddingBase64UrlTestCases { - std::string base64, base64_with_padding; - }; - - // For base64 encoding, there are only three length needed to test - // - 3n bytes => 4n bytes, no padding needed - // - 3n + 1 bytes => 4n + 2 bytes, 2 padding needed - // - 3n + 2 bytes => 4n + 3 bytes, 1 padding needed - CompletePaddingBase64UrlTestCases testCases[3] = { - // Payload text(3n bytes): - {"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG8iLCJpYXQiOjE1MTYyMzkwMjJ" - "9", - // No padding added. - "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG8iLCJpYXQiOjE1MTYyMzkwMjJ" - "9"}, - // Payload text(3n + 1 bytes): - {"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2" - "MjM5MDIyfQ", - // 2 padding added. - "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2" - "MjM5MDIyfQ=="}, - // Payload text(3n + 2 bytes): - {"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lZSIsImlhdCI6MTUx" - "NjIzOTAyMn0", - // 1 padding added. - "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lZSIsImlhdCI6MTUx" - "NjIzOTAyMn0="}}; - for (auto& tc : testCases) { - // Ensure these two base64 binaries are equivalent after decoding. - EXPECT_EQ(Base64::decodeWithoutPadding(tc.base64), - Base64::decodeWithoutPadding(tc.base64_with_padding)); - // Ensure the `base64_with_padding` is correctly padded. - EXPECT_NE(Base64::decode(tc.base64_with_padding), ""); - - std::string base64_padded = tc.base64; - Base64::completePadding(base64_padded); - EXPECT_EQ(base64_padded, tc.base64_with_padding); - } -} - TEST(Base64UrlTest, EncodeString) { EXPECT_EQ("", Base64Url::encode("", 0)); EXPECT_EQ("AAA", Base64Url::encode("\0\0", 2)); diff --git a/test/common/filter/config_discovery_impl_test.cc b/test/common/filter/config_discovery_impl_test.cc index b5993b8ffcd80..5d571d8b4568a 100644 --- a/test/common/filter/config_discovery_impl_test.cc +++ b/test/common/filter/config_discovery_impl_test.cc @@ -58,7 +58,7 @@ class TestHttpFilterFactory : public TestFilterFactory, } Http::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message&, const std::string&, - Server::Configuration::UpstreamHttpFactoryContext&) override { + Server::Configuration::UpstreamFactoryContext&) override { created_ = true; return [](Http::FilterChainFactoryCallbacks&) -> void {}; } @@ -85,7 +85,7 @@ class TestNetworkFilterFactory } Network::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message&, - Server::Configuration::CommonFactoryContext&) override { + Server::Configuration::UpstreamFactoryContext&) override { created_ = true; return [](Network::FilterManager&) -> void {}; } @@ -179,6 +179,7 @@ class FilterConfigDiscoveryImplTest : public FilterConfigDiscoveryTestBase { DynamicFilterConfigProviderPtr createProvider(std::string name, bool warm, bool default_configuration, bool last_filter_config = true) { + EXPECT_CALL(init_manager_, add(_)); envoy::config::core::v3::ExtensionConfigSource config_source; envoy::config::core::v3::AggregatedConfigSource ads; @@ -257,7 +258,7 @@ class FilterConfigDiscoveryImplTest : public FilterConfigDiscoveryTestBase { // HTTP filter test class HttpFilterConfigDiscoveryImplTest : public FilterConfigDiscoveryImplTest< - NamedHttpFilterFactoryCb, Server::Configuration::FactoryContext, + Http::NamedHttpFilterFactoryCb, Server::Configuration::FactoryContext, HttpFilterConfigProviderManagerImpl, TestHttpFilterFactory, Server::Configuration::NamedHttpFilterConfigFactory, Server::Configuration::MockFactoryContext> { @@ -274,10 +275,10 @@ class HttpFilterConfigDiscoveryImplTest // HTTP upstream filter test class HttpUpstreamFilterConfigDiscoveryImplTest : public FilterConfigDiscoveryImplTest< - NamedHttpFilterFactoryCb, Server::Configuration::UpstreamHttpFactoryContext, + Http::NamedHttpFilterFactoryCb, Server::Configuration::UpstreamFactoryContext, UpstreamHttpFilterConfigProviderManagerImpl, TestHttpFilterFactory, Server::Configuration::UpstreamHttpFilterConfigFactory, - Server::Configuration::MockUpstreamHttpFactoryContext> { + Server::Configuration::MockUpstreamFactoryContext> { public: const std::string getFilterType() const override { return "http"; } const std::string getConfigReloadCounter() const override { @@ -308,12 +309,12 @@ class NetworkFilterConfigDiscoveryImplTest // Network upstream filter test class NetworkUpstreamFilterConfigDiscoveryImplTest : public FilterConfigDiscoveryImplTest< - Network::FilterFactoryCb, Server::Configuration::CommonFactoryContext, + Network::FilterFactoryCb, Server::Configuration::UpstreamFactoryContext, UpstreamNetworkFilterConfigProviderManagerImpl, TestNetworkFilterFactory, Server::Configuration::NamedUpstreamNetworkFilterConfigFactory, - Server::Configuration::MockFactoryContext> { + Server::Configuration::MockUpstreamFactoryContext> { public: - const std::string getFilterType() const override { return "network"; } + const std::string getFilterType() const override { return "upstream_network"; } const std::string getConfigReloadCounter() const override { return "extension_config_discovery.upstream_network_filter.foo.config_reload"; } @@ -584,14 +585,12 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, WrongDefaultConfig) { "type.googleapis.com/test.integration.filters.Bogus."); } -// Raise exception when filter is not the last filter in filter chain, but the filter is terminal -// filter. This test does not apply to listener filter. +// For filters which are not listener and upstream network, raise exception when filter is not the +// last filter in filter chain, but the filter is terminal. For listener and upstream network filter +// check that there is no exception raised. TYPED_TEST(FilterConfigDiscoveryImplTestParameter, TerminalFilterInvalid) { InSequence s; TypeParam config_discovery_test; - if (config_discovery_test.getFilterType() == "listener") { - return; - } config_discovery_test.setup(true, false, false); const std::string response_yaml = R"EOF( @@ -607,6 +606,14 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, TerminalFilterInvalid) { const auto decoded_resources = TestUtility::decodeResources(response); EXPECT_CALL(config_discovery_test.init_watcher_, ready()); + + if (config_discovery_test.getFilterType() == "listener" || + config_discovery_test.getFilterType() == "upstream_network") { + EXPECT_NO_THROW(config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, + response.version_info())); + return; + } + EXPECT_THROW_WITH_MESSAGE( config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, response.version_info()), diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 5e0e0996785b9..1576d826c5150 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -801,7 +801,7 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { const std::string observable_cluster_name = "observability_name"; auto cluster_info_mock = std::make_shared(); absl::optional cluster_info = cluster_info_mock; - EXPECT_CALL(stream_info, upstreamClusterInfo()).WillRepeatedly(Return(cluster_info)); + EXPECT_CALL(stream_info, upstreamClusterInfo()).WillRepeatedly(testing::Return(cluster_info)); EXPECT_CALL(*cluster_info_mock, observabilityName()) .WillRepeatedly(ReturnRef(observable_cluster_name)); EXPECT_EQ("observability_name", @@ -815,7 +815,7 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { { StreamInfoFormatter upstream_format("UPSTREAM_CLUSTER"); absl::optional cluster_info = nullptr; - EXPECT_CALL(stream_info, upstreamClusterInfo()).WillRepeatedly(Return(cluster_info)); + EXPECT_CALL(stream_info, upstreamClusterInfo()).WillRepeatedly(testing::Return(cluster_info)); EXPECT_EQ(absl::nullopt, upstream_format.format(request_headers, response_headers, response_trailers, stream_info, body, AccessLog::AccessLogType::NotSet)); @@ -912,6 +912,8 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { ProtoEq(ValueUtil::stringValue("8443"))); } +// The test environment does not support IPV6 +#if !defined(ALIMESH) // Validate for IPv6 address address = Network::Address::InstanceConstSharedPtr{new Network::Address::Ipv6Instance("::1", 9443)}; @@ -930,6 +932,7 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { stream_info, body, AccessLog::AccessLogType::NotSet), ProtoEq(ValueUtil::stringValue("9443"))); } +#endif // Validate for Pipe address = Network::Address::InstanceConstSharedPtr{new Network::Address::PipeInstance("/foo")}; diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index e783f2a184dfb..6aa90f7442fa2 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -241,6 +241,9 @@ class FuzzConfig : public ConnectionManagerConfig { } bool appendXForwardedPort() const override { return false; } bool addProxyProtocolConnectionState() const override { return true; } +#if defined(ALIMESH) + std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } +#endif const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager config_; @@ -294,6 +297,9 @@ class FuzzConfig : public ConnectionManagerConfig { std::vector ip_detection_extensions_{}; std::vector early_header_mutations_; std::unique_ptr proxy_status_config_; +#if defined(ALIMESH) + std::chrono::seconds keepalive_header_timeout_{}; +#endif }; // Internal representation of stream state. Encapsulates the stream state, mocks diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 154d3ab2cfdf6..2d0ee4a824083 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -1384,6 +1384,34 @@ TEST_F(HttpConnectionManagerImplTest, DateHeaderPresent) { doRemoteClose(); } +#if defined(ALIMESH) +TEST_F(HttpConnectionManagerImplTest, KeepaliveHeaderNotAppend) { + setup(false, ""); + setUpEncoderAndDecoder(false, false); + sendRequestHeadersAndData(); + const auto* modified_headers = sendResponseHeaders( + ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}, {"server", "foo"}}}); + ASSERT_TRUE(modified_headers); + EXPECT_FALSE(modified_headers->KeepAlive()); + doRemoteClose(); +} + +TEST_F(HttpConnectionManagerImplTest, KeepaliveHeaderAppend) { + setup(false, ""); + setUpEncoderAndDecoder(false, false); + keepalive_header_timeout_ = std::chrono::seconds(60); + sendRequestHeadersAndData(); + const auto* modified_headers = sendResponseHeaders( + ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}, {"server", "foo"}}}); + ASSERT_TRUE(modified_headers); + EXPECT_TRUE(modified_headers->Connection()); + EXPECT_EQ("keep-alive", modified_headers->getConnectionValue()); + EXPECT_TRUE(modified_headers->KeepAlive()); + EXPECT_EQ("timeout=60", modified_headers->getKeepAliveValue()); + doRemoteClose(); +} +#endif + TEST_F(HttpConnectionManagerImplTest, StartAndFinishSpanNormalFlow) { setup(false, ""); diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 078bd1d85ab79..e955fb9a117c5 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2750,7 +2750,13 @@ TEST_F(HttpConnectionManagerImplTest, TestSessionTrace) { TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteNotFound) { setup(false, "", true, true); setupFilterChain(1, 0); // Recreate the chain for second stream. - +#if defined(ALIMESH) + EXPECT_CALL(*static_cast( + scopedRouteConfigProvider()->config().get()), + getRouteConfig(_, _, _)) + .Times(2) + .WillRepeatedly(Return(nullptr)); +#else EXPECT_CALL(*static_cast(scopeKeyBuilder().ptr()), computeScopeKey(_)) .Times(2); @@ -2759,6 +2765,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteNotFound) { getRouteConfig(_)) .Times(2) .WillRepeatedly(Return(nullptr)); +#endif EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { decoder_ = &conn_manager_->newStream(response_encoder_); RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ @@ -2786,6 +2793,15 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteNotFound) { TEST_F(HttpConnectionManagerImplTest, TestSrdsUpdate) { setup(false, "", true, true); +#if defined(ALIMESH) + EXPECT_CALL(*static_cast( + scopedRouteConfigProvider()->config().get()), + getRouteConfig(_, _, _)) + .Times(3) + .WillOnce(Return(nullptr)) + .WillOnce(Return(nullptr)) // refreshCachedRoute first time. + .WillOnce(Return(route_config_)); // triggered by callbacks_->route(), SRDS now updated. +#else EXPECT_CALL(*static_cast(scopeKeyBuilder().ptr()), computeScopeKey(_)) .Times(3); @@ -2796,6 +2812,8 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsUpdate) { .WillOnce(Return(nullptr)) .WillOnce(Return(nullptr)) // refreshCachedRoute first time. .WillOnce(Return(route_config_)); // triggered by callbacks_->route(), SRDS now updated. +#endif + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { decoder_ = &conn_manager_->newStream(response_encoder_); RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ @@ -2849,6 +2867,23 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsCrossScopeReroute) { std::shared_ptr route2 = std::make_shared>(); EXPECT_CALL(*route_config1, route(_, _, _, _)).WillRepeatedly(Return(route1)); EXPECT_CALL(*route_config2, route(_, _, _, _)).WillRepeatedly(Return(route2)); +#if defined(ALIMESH) + EXPECT_CALL(*static_cast( + scopedRouteConfigProvider()->config().get()), + getRouteConfig(_, _, _)) + // 1. Snap scoped route config; + // 2. refreshCachedRoute (both in decodeHeaders(headers,end_stream); + // 3. then refreshCachedRoute triggered by decoder_filters_[1]->callbacks_->route(). + .Times(3) + .WillRepeatedly(Invoke([&](const Router::ScopeKeyBuilder*, const Http::HeaderMap& headers, + const StreamInfo::StreamInfo*) -> Router::ConfigConstSharedPtr { + auto& test_headers = dynamic_cast(headers); + if (test_headers.get_("scope_key") == "foo") { + return route_config1; + } + return route_config2; + })); +#else EXPECT_CALL(*static_cast(scopeKeyBuilder().ptr()), computeScopeKey(_)) .Times(3) @@ -2874,6 +2909,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsCrossScopeReroute) { } return route_config2; })); +#endif EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { decoder_ = &conn_manager_->newStream(response_encoder_); RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ @@ -2921,6 +2957,13 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteFound) { std::shared_ptr fake_cluster1 = std::make_shared>(); EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(fake_cluster1.get())); +#if defined(ALIMESH) + EXPECT_CALL(*scopedRouteConfigProvider()->config(), + getRouteConfig(_, _, _)) + // 1. decodeHeaders() snapping route config. + // 2. refreshCachedRoute() later in the same decodeHeaders(). + .Times(2); +#else EXPECT_CALL(*static_cast(scopeKeyBuilder().ptr()), computeScopeKey(_)) .Times(2); @@ -2928,6 +2971,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteFound) { // 1. decodeHeaders() snapping route config. // 2. refreshCachedRoute() later in the same decodeHeaders(). .Times(2); +#endif EXPECT_CALL( *static_cast( scopedRouteConfigProvider()->config()->route_config_.get()), diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 10a4ff15b54ae..fa0e210196aa2 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -176,6 +176,9 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { return add_proxy_protocol_connection_state_; } +#if defined(ALIMESH) + std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } +#endif // Simple helper to wrapper filter to the factory function. FilterFactoryCb createDecoderFilterFactoryCb(StreamDecoderFilterSharedPtr filter) { return [filter](FilterChainFactoryCallbacks& callbacks) { @@ -277,6 +280,9 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { std::vector ip_detection_extensions_{}; std::vector early_header_mutations_{}; bool add_proxy_protocol_connection_state_ = true; +#if defined(ALIMESH) + std::chrono::seconds keepalive_header_timeout_{}; +#endif const LocalReply::LocalReplyPtr local_reply_; diff --git a/test/common/http/filter_chain_helper_test.cc b/test/common/http/filter_chain_helper_test.cc index dfeefcde29070..b89b478a0ea20 100644 --- a/test/common/http/filter_chain_helper_test.cc +++ b/test/common/http/filter_chain_helper_test.cc @@ -28,9 +28,8 @@ TEST(FilterChainUtilityTest, CreateFilterChainForFactoriesWithRouteDisabled) { for (const auto& name : {"filter_0", "filter_1", "filter_2"}) { auto provider = - std::make_unique>( - Filter::NamedHttpFilterFactoryCb{"filter_type_name", - [](FilterChainFactoryCallbacks&) {}}, + std::make_unique>( + Http::NamedHttpFilterFactoryCb{"filter_type_name", [](FilterChainFactoryCallbacks&) {}}, name); filter_factories.push_back(std::move(provider)); } diff --git a/test/common/http/header_map_impl_test.cc b/test/common/http/header_map_impl_test.cc index 8cee2dd72fe29..2a492ddc00d35 100644 --- a/test/common/http/header_map_impl_test.cc +++ b/test/common/http/header_map_impl_test.cc @@ -71,6 +71,26 @@ Http::RegisterCustomInlineHeader custom_header_1_copy(Http::LowerCaseString{"foo_custom_header"}); +class HeaderMapImplTest : public testing::TestWithParam { +public: + HeaderMapImplTest() { + // Set the lazy map threshold using the test parameter. + scoped_runtime_.mergeValues( + {{"envoy.reloadable_features.deprecate_global_ints", "false"}, + {"envoy.http.headermap.lazy_map_min_size", absl::StrCat(GetParam())}}); + } + + static std::string testParamsToString(const ::testing::TestParamInfo& params) { + return absl::StrCat(params.param); + } + + TestScopedRuntime scoped_runtime_; +}; + +INSTANTIATE_TEST_SUITE_P(HeaderMapThreshold, HeaderMapImplTest, + testing::Values(0, 1, std::numeric_limits::max()), + HeaderMapImplTest::testParamsToString); + // Make sure that the same header registered twice points to the same location. TEST(HeaderMapImplTest, CustomRegisteredHeaders) { TestRequestHeaderMapImpl headers; diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 8b964b0280a84..25522f1b4b180 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -1133,6 +1133,11 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { } TEST_P(Http1ServerConnectionImplTest, Http11InvalidRequest) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + initialize(); // Invalid because www.somewhere.com is not an absolute path nor an absolute url @@ -1303,6 +1308,11 @@ TEST_P(Http1ServerConnectionImplTest, SimpleGet) { // Test that if the stream is not created at the time an error is detected, it // is created as part of sending the protocol error. TEST_P(Http1ServerConnectionImplTest, BadRequestNoStream) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + initialize(); MockRequestDecoder decoder; @@ -1451,6 +1461,11 @@ TEST_P(Http1ServerConnectionImplTest, HostHeaderTranslation) { // Ensures that requests with invalid HTTP header values are properly rejected // when the runtime guard is enabled for the feature. TEST_P(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + initialize(); MockRequestDecoder decoder; @@ -1570,6 +1585,11 @@ TEST_P(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { // Mutate an HTTP GET with embedded NULs, this should always be rejected in some // way (not necessarily with "head value contains NUL" though). TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + const absl::string_view example_input = "GET / HTTP/1.1\r\nHOST: h.com\r\nfoo: barbaz\r\n"; for (size_t n = 0; n < example_input.size(); ++n) { @@ -3312,6 +3332,11 @@ TEST_P(Http1ClientConnectionImplTest, LowWatermarkDuringClose) { } TEST_P(Http1ServerConnectionImplTest, LargeTrailersRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n\r\n\r\n"; testTrailersExceedLimit(long_string, "http/1.1 protocol error: trailers size exceeds limit", @@ -3319,6 +3344,11 @@ TEST_P(Http1ServerConnectionImplTest, LargeTrailersRejected) { } TEST_P(Http1ServerConnectionImplTest, LargeTrailerFieldRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + // Construct partial headers with a long field name that exceeds the default limit of 60KiB. std::string long_string = "bigfield" + std::string(60 * 1024, 'q'); testTrailersExceedLimit(long_string, "http/1.1 protocol error: trailers size exceeds limit", @@ -3327,12 +3357,22 @@ TEST_P(Http1ServerConnectionImplTest, LargeTrailerFieldRejected) { // Tests that the default limit for the number of request headers is 100. TEST_P(Http1ServerConnectionImplTest, ManyTrailersRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + // Send a request with 101 headers. testTrailersExceedLimit(createHeaderFragment(101) + "\r\n\r\n", "http/1.1 protocol error: trailers count exceeds limit", true); } TEST_P(Http1ServerConnectionImplTest, LargeTrailersRejectedIgnored) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n\r\n\r\n"; testTrailersExceedLimit(long_string, "http/1.1 protocol error: trailers size exceeds limit", @@ -3340,6 +3380,11 @@ TEST_P(Http1ServerConnectionImplTest, LargeTrailersRejectedIgnored) { } TEST_P(Http1ServerConnectionImplTest, LargeTrailerFieldRejectedIgnored) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + // Default limit of 60 KiB std::string long_string = "bigfield" + std::string(60 * 1024, 'q') + ": value\r\n\r\n\r\n"; testTrailersExceedLimit(long_string, "http/1.1 protocol error: trailers size exceeds limit", @@ -3354,6 +3399,11 @@ TEST_P(Http1ServerConnectionImplTest, ManyTrailersIgnored) { } TEST_P(Http1ServerConnectionImplTest, LargeRequestUrlRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + initialize(); std::string exception_reason; @@ -3376,6 +3426,11 @@ TEST_P(Http1ServerConnectionImplTest, LargeRequestUrlRejected) { } TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n"; testRequestHeadersExceedLimit(long_string, "http/1.1 protocol error: headers size exceeds limit", @@ -3383,6 +3438,11 @@ TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersRejected) { } TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersRejectedBeyondMaxConfigurable) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + max_request_headers_kb_ = 8192; std::string long_string = "big: " + std::string(8193 * 1024, 'q') + "\r\n"; testRequestHeadersExceedLimit(long_string, "http/1.1 protocol error: headers size exceeds limit", @@ -3398,6 +3458,11 @@ TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersRejected) { } TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + // Default limit of 60 KiB initialize(); @@ -3427,6 +3492,11 @@ TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { } TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejectedMaxConfigurable) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + max_request_headers_kb_ = 8192; max_request_headers_count_ = 150; initialize(); @@ -3621,6 +3691,11 @@ TEST_P(Http1ServerConnectionImplTest, PipedRequestWithMutipleEvent) { } TEST_P(Http1ServerConnectionImplTest, Utf8Path) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + initialize(); MockRequestDecoder decoder; @@ -3656,6 +3731,11 @@ TEST_P(Http1ServerConnectionImplTest, Utf8Path) { // Tests that incomplete response headers of 80 kB header value fails. TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + initialize(); NiceMock response_decoder; @@ -3675,6 +3755,11 @@ TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { // Tests that incomplete response headers with a 80 kB header field fails. TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeFieldRejected) { + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + // TODO(#21245): Re-enable this test for BalsaParser. + return; + } + initialize(); NiceMock decoder; diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index b69a7054dc5eb..485386381600b 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -1751,6 +1751,19 @@ TEST(DurationUtilTest, NoThrow) { } } +#if defined(ALIMESH) +TEST(DurationUtilTest, ConvertDurationToJsonString) { + { + ProtobufWkt::Duration duration; + duration.set_nanos(20000000); + EXPECT_EQ(20, DurationUtil::durationToMilliseconds(duration)); + MessageUtil::redact(duration); + auto duration_str = MessageUtil::getJsonStringFromMessageOrError(duration); + EXPECT_STREQ("\"0.020s\"", duration_str.c_str()); + } +} +#endif + // Verify WIP accounting of the file based annotations. This test uses the strict validator to test // that code path. TEST_F(ProtobufUtilityTest, MessageInWipFile) { diff --git a/test/common/router/BUILD b/test/common/router/BUILD index 982a9c35cb873..61060a0b7ce38 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -145,6 +145,9 @@ envoy_cc_test( "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], + alimesh_deps = [ + "//test/mocks/stream_info:stream_info_mocks", + ], ) envoy_cc_test( @@ -178,6 +181,9 @@ envoy_cc_test( "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], + alimesh_deps = [ + "//test/mocks/stream_info:stream_info_mocks", + ], ) envoy_cc_test( diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index dd42195225817..d3db1acdaba55 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -54,6 +54,9 @@ using ::testing::NiceMock; using ::testing::Pair; using ::testing::Return; using ::testing::ReturnRef; +#if defined(ALIMESH) +using ::testing::ReturnPointee; +#endif // Wrap ConfigImpl, the target of tests to allow us to regenerate the route_fuzz_test // corpus when run with: @@ -3166,7 +3169,11 @@ TEST_F(RouterMatcherHashPolicyTest, HashIpv4DifferentAddresses) { } } +#if defined(ALIMESH) +TEST_F(RouterMatcherHashPolicyTest, DISABLED_HashIpv6DifferentAddresses) { +#else TEST_F(RouterMatcherHashPolicyTest, HashIpv6DifferentAddresses) { +#endif firstRouteHashPolicy()->mutable_connection_properties()->set_source_ip(true); { // Different addresses should produce different hashes. @@ -3695,6 +3702,181 @@ TEST_F(RouteMatcherTest, ClusterSpecifierPlugin) { EXPECT_EQ(mock_route.get(), config.route(genHeaders("some_cluster", "/bar", "GET"), 0).get()); } +#if defined(ALIMESH) +TEST_F(RouteMatcherTest, WeightedClusterSpecifierPlugin) { + const std::string yaml = R"EOF( +cluster_specifier_plugins: +- extension: + name: test1 + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + a: test1 +- extension: + name: test2 + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + a: test2 +- extension: + name: test3 + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + a: test3 +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 50 + - name: cluster2 + weight: 50 + total_weight: 100 + cluster_specifier_plugin: test2 + - match: + prefix: "/bar" + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 50 + - name: cluster2 + weight: 50 + total_weight: 100 + cluster_specifier_plugin: test3 + )EOF"; + + NiceMock factory; + Registry::InjectFactory registered(factory); + + auto mock_cluster_specifier_plugin_1 = std::make_shared>(); + auto mock_cluster_specifier_plugin_2 = std::make_shared>(); + auto mock_cluster_specifier_plugin_3 = std::make_shared>(); + + factory_context_.cluster_manager_.initializeClusters({"cluster1", "cluster2"}, {}); + + EXPECT_CALL(factory, createClusterSpecifierPlugin(_, _)) + .WillRepeatedly(Invoke( + [mock_cluster_specifier_plugin_1, mock_cluster_specifier_plugin_2, + mock_cluster_specifier_plugin_3]( + const Protobuf::Message& config, + Server::Configuration::CommonFactoryContext&) -> ClusterSpecifierPluginSharedPtr { + const auto& typed_config = dynamic_cast(config); + if (auto iter = typed_config.fields().find("a"); iter == typed_config.fields().end()) { + return nullptr; + } else if (iter->second.string_value() == "test1") { + return mock_cluster_specifier_plugin_1; + } else if (iter->second.string_value() == "test2") { + return mock_cluster_specifier_plugin_2; + } else if (iter->second.string_value() == "test3") { + return mock_cluster_specifier_plugin_3; + } + return nullptr; + })); + + NiceMock stream_info; + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + + auto mock_route = std::make_shared>(); + + EXPECT_CALL(*mock_cluster_specifier_plugin_2, route(_, _)).WillOnce(Return(mock_route)); + EXPECT_EQ(mock_route.get(), config.route(genHeaders("some_cluster", "/foo", "GET"), 0).get()); + + EXPECT_CALL(*mock_cluster_specifier_plugin_3, route(_, _)).WillOnce(Return(mock_route)); + EXPECT_EQ(mock_route.get(), config.route(genHeaders("some_cluster", "/bar", "GET"), 0).get()); +} + +TEST_F(RouteMatcherTest, WeightedClusterInlineSpecifierPlugin) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: local_service + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 50 + - name: cluster2 + weight: 50 + total_weight: 100 + inline_cluster_specifier_plugin: + extension: + name: test2 + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + a: test2 + - match: + prefix: "/bar" + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 50 + - name: cluster2 + weight: 50 + total_weight: 100 + inline_cluster_specifier_plugin: + extension: + name: test3 + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + a: test3 + )EOF"; + + NiceMock factory; + Registry::InjectFactory registered(factory); + + auto mock_cluster_specifier_plugin_1 = std::make_shared>(); + auto mock_cluster_specifier_plugin_2 = std::make_shared>(); + auto mock_cluster_specifier_plugin_3 = std::make_shared>(); + + factory_context_.cluster_manager_.initializeClusters({"cluster1", "cluster2"}, {}); + + EXPECT_CALL(factory, createClusterSpecifierPlugin(_, _)) + .WillRepeatedly(Invoke( + [mock_cluster_specifier_plugin_1, mock_cluster_specifier_plugin_2, + mock_cluster_specifier_plugin_3]( + const Protobuf::Message& config, + Server::Configuration::CommonFactoryContext&) -> ClusterSpecifierPluginSharedPtr { + const auto& typed_config = dynamic_cast(config); + if (auto iter = typed_config.fields().find("a"); iter == typed_config.fields().end()) { + return nullptr; + } else if (iter->second.string_value() == "test1") { + return mock_cluster_specifier_plugin_1; + } else if (iter->second.string_value() == "test2") { + return mock_cluster_specifier_plugin_2; + } else if (iter->second.string_value() == "test3") { + return mock_cluster_specifier_plugin_3; + } + return nullptr; + })); + + NiceMock stream_info; + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + + auto mock_route = std::make_shared>(); + + EXPECT_CALL(*mock_cluster_specifier_plugin_2, route(_, _)).WillOnce(Return(mock_route)); + EXPECT_EQ(mock_route.get(), config.route(genHeaders("some_cluster", "/foo", "GET"), 0).get()); + + EXPECT_CALL(*mock_cluster_specifier_plugin_3, route(_, _)).WillOnce(Return(mock_route)); + EXPECT_EQ(mock_route.get(), config.route(genHeaders("some_cluster", "/bar", "GET"), 0).get()); +} +#endif + TEST_F(RouteMatcherTest, UnknownClusterSpecifierPluginName) { const std::string yaml = R"EOF( cluster_specifier_plugins: @@ -7215,7 +7397,8 @@ TEST_F(CustomRequestHeadersTest, AddNewHeader) { EXPECT_EQ("127.0.0.1", headers.get_("x-client-ip")); } -TEST_F(CustomRequestHeadersTest, CustomHeaderWrongFormat) { +#if defined(ALIMESH) +TEST_F(CustomRequestHeadersTest, AddMseOriginalPathHeader) { const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -7230,6 +7413,16 @@ TEST_F(CustomRequestHeadersTest, CustomHeaderWrongFormat) { key: x-client-ip value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" routes: + - match: + prefix: "/new_endpoint/test" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint/test1" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 - match: prefix: "/new_endpoint" route: @@ -7237,70 +7430,335 @@ TEST_F(CustomRequestHeadersTest, CustomHeaderWrongFormat) { cluster: www2 request_headers_to_add: - header: - key: x-client-ip - value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT" -request_headers_to_add: -- header: - key: x-client-ip - value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT" + key: x-original-path + value: "%DYNAMIC_METADATA([\"mse.data\",\"original_path\"])%" )EOF"; NiceMock stream_info; - EXPECT_THROW(TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true), - EnvoyException); -} - -TEST(MetadataMatchCriteriaImpl, Create) { - auto v1 = ProtobufWkt::Value(); - v1.set_string_value("v1"); - auto v2 = ProtobufWkt::Value(); - v2.set_number_value(2.0); - auto v3 = ProtobufWkt::Value(); - v3.set_bool_value(true); - - auto metadata_struct = ProtobufWkt::Struct(); - auto mutable_fields = metadata_struct.mutable_fields(); - mutable_fields->insert({"a", v1}); - mutable_fields->insert({"b", v2}); - mutable_fields->insert({"c", v3}); - - auto matches = MetadataMatchCriteriaImpl(metadata_struct); + factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = genHeaders("www.lyft.com", "/new_endpoint/foo", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + EXPECT_CALL(stream_info, setDynamicMetadata(_, _)) + .WillOnce(Invoke([&](const std::string& name, + const ProtobufWkt::Struct& returned_dynamic_metadata) -> void { + EXPECT_EQ("mse.data", name); - EXPECT_EQ(matches.metadataMatchCriteria().size(), 3); - auto it = matches.metadataMatchCriteria().begin(); - EXPECT_EQ((*it)->name(), "a"); - EXPECT_EQ((*it)->value().value().string_value(), "v1"); - it++; + std::unique_ptr dynamic_metadata = + std::make_unique(); + auto* fields = dynamic_metadata->mutable_fields(); + (*fields)["original_path"] = ValueUtil::stringValue("/new_endpoint/foo"); + EXPECT_TRUE(TestUtility::protoEqual(returned_dynamic_metadata, *dynamic_metadata)); - EXPECT_EQ((*it)->name(), "b"); - EXPECT_EQ((*it)->value().value().number_value(), 2.0); - it++; + (*stream_info.metadata_.mutable_filter_metadata())[name].MergeFrom( + returned_dynamic_metadata); + })); - EXPECT_EQ((*it)->name(), "c"); - EXPECT_EQ((*it)->value().value().bool_value(), true); + route->finalizeRequestHeaders(headers, stream_info, false); + auto transforms = route->requestHeaderTransforms(stream_info); + EXPECT_THAT(transforms.headers_to_append_or_add, + ElementsAre(Pair(Http::LowerCaseString("x-original-path"), "/new_endpoint/foo"), + Pair(Http::LowerCaseString("x-client-ip"), "127.0.0.1"))); + EXPECT_EQ("/api/new_endpoint/foo", headers.getPathValue()); } -TEST(MetadataMatchCriteriaImpl, Merge) { - auto pv1 = ProtobufWkt::Value(); - pv1.set_string_value("v1"); - auto pv2 = ProtobufWkt::Value(); - pv2.set_number_value(2.0); - auto pv3 = ProtobufWkt::Value(); - pv3.set_bool_value(true); +TEST_F(CustomRequestHeadersTest, AddMseOriginalPathHeaderWithVS) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: www2 + domains: + - lyft.com + - www.lyft.com + - w.lyft.com + - ww.lyft.com + - wwww.lyft.com + request_headers_to_add: + - header: + key: x-original-path + value: "%DYNAMIC_METADATA([\"mse.data\",\"original_path\"])%" + routes: + - match: + prefix: "/new_endpoint" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint/test" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint/test1" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = genHeaders("www.lyft.com", "/new_endpoint/foo", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + + EXPECT_CALL(stream_info, setDynamicMetadata(_, _)) + .WillOnce(Invoke([&](const std::string& name, + const ProtobufWkt::Struct& returned_dynamic_metadata) -> void { + EXPECT_EQ("mse.data", name); - auto parent_struct = ProtobufWkt::Struct(); - auto parent_fields = parent_struct.mutable_fields(); - parent_fields->insert({"a", pv1}); - parent_fields->insert({"b", pv2}); - parent_fields->insert({"c", pv3}); + std::unique_ptr dynamic_metadata = + std::make_unique(); + auto* fields = dynamic_metadata->mutable_fields(); + (*fields)["original_path"] = ValueUtil::stringValue("/new_endpoint/foo"); + EXPECT_TRUE(TestUtility::protoEqual(returned_dynamic_metadata, *dynamic_metadata)); - auto parent_matches = MetadataMatchCriteriaImpl(parent_struct); + (*stream_info.metadata_.mutable_filter_metadata())[name].MergeFrom( + returned_dynamic_metadata); + })); - auto v1 = ProtobufWkt::Value(); - v1.set_string_value("override1"); - auto v2 = ProtobufWkt::Value(); - v2.set_string_value("v2"); - auto v3 = ProtobufWkt::Value(); - v3.set_string_value("override3"); + route->finalizeRequestHeaders(headers, stream_info, false); + auto transforms = route->requestHeaderTransforms(stream_info); + EXPECT_THAT(transforms.headers_to_append_or_add, + ElementsAre(Pair(Http::LowerCaseString("x-original-path"), "/new_endpoint/foo"))); + EXPECT_EQ("/api/new_endpoint/foo", headers.getPathValue()); +} + +TEST_F(CustomRequestHeadersTest, AddMseOriginalPathHeaderWithRouteConfiguration) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: www2 + domains: + - lyft.com + - www.lyft.com + - w.lyft.com + - ww.lyft.com + - wwww.lyft.com + routes: + - match: + prefix: "/new_endpoint/test" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 +request_headers_to_add: +- header: + key: x-original-path + value: "%DYNAMIC_METADATA([\"mse.data\",\"original_path\"])%" +- header: + key: x-client-ip + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = genHeaders("www.lyft.com", "/new_endpoint/foo", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + + EXPECT_CALL(stream_info, setDynamicMetadata(_, _)) + .WillOnce(Invoke([&](const std::string& name, + const ProtobufWkt::Struct& returned_dynamic_metadata) -> void { + EXPECT_EQ("mse.data", name); + + std::unique_ptr dynamic_metadata = + std::make_unique(); + auto* fields = dynamic_metadata->mutable_fields(); + (*fields)["original_path"] = ValueUtil::stringValue("/new_endpoint/foo"); + EXPECT_TRUE(TestUtility::protoEqual(returned_dynamic_metadata, *dynamic_metadata)); + + (*stream_info.metadata_.mutable_filter_metadata())[name].MergeFrom( + returned_dynamic_metadata); + })); + + route->finalizeRequestHeaders(headers, stream_info, false); + auto transforms = route->requestHeaderTransforms(stream_info); + EXPECT_THAT(transforms.headers_to_append_or_add, + ElementsAre(Pair(Http::LowerCaseString("x-original-path"), "/new_endpoint/foo"), + Pair(Http::LowerCaseString("x-client-ip"), "127.0.0.1"))); + EXPECT_EQ("/api/new_endpoint/foo", headers.getPathValue()); +} + +TEST_F(CustomRequestHeadersTest, NoMseOriginalPathHeader) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: www2 + domains: + - lyft.com + - www.lyft.com + - w.lyft.com + - ww.lyft.com + - wwww.lyft.com + request_headers_to_add: + - header: + key: x-client-ip + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + routes: + - match: + prefix: "/new_endpoint" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint/test" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint/test1" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = genHeaders("www.lyft.com", "/new_endpoint/foo", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + + EXPECT_CALL(stream_info, setDynamicMetadata(_, _)).Times(0); + + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("/new_endpoint/foo", headers.get_("x-envoy-original-path")); + auto transforms = route->requestHeaderTransforms(stream_info); + EXPECT_THAT(transforms.headers_to_append_or_add, + ElementsAre(Pair(Http::LowerCaseString("x-client-ip"), "127.0.0.1"))); + EXPECT_EQ("/api/new_endpoint/foo", headers.getPathValue()); +} + +TEST_F(CustomRequestHeadersTest, NoMseOriginalPathHeaderWithRouteConfiguration) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: www2 + domains: + - lyft.com + - www.lyft.com + - w.lyft.com + - ww.lyft.com + - wwww.lyft.com + routes: + - match: + prefix: "/new_endpoint" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint/test" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + - match: + prefix: "/new_endpoint/test1" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 +request_headers_to_add: +- header: + key: x-original-path + value: "%REQ(X-ENVOY-ORIGINAL-PATH)%" + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genHeaders("www.lyft.com", "/new_endpoint/test1/a", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + + EXPECT_CALL(stream_info, setDynamicMetadata(_, _)).Times(0); + + route->finalizeRequestHeaders(headers, stream_info, true); + + EXPECT_EQ("/new_endpoint/test1/a", headers.get_("x-envoy-original-path")); + + EXPECT_EQ("/api/new_endpoint/test1/a", headers.getPathValue()); +} +#endif + +TEST_F(CustomRequestHeadersTest, CustomHeaderWrongFormat) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: www2 + domains: + - lyft.com + - www.lyft.com + - w.lyft.com + - ww.lyft.com + - wwww.lyft.com + request_headers_to_add: + - header: + key: x-client-ip + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + routes: + - match: + prefix: "/new_endpoint" + route: + prefix_rewrite: "/api/new_endpoint" + cluster: www2 + request_headers_to_add: + - header: + key: x-client-ip + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT" +request_headers_to_add: +- header: + key: x-client-ip + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT" + )EOF"; + NiceMock stream_info; + EXPECT_THROW(TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true), + EnvoyException); +} + +TEST(MetadataMatchCriteriaImpl, Create) { + auto v1 = ProtobufWkt::Value(); + v1.set_string_value("v1"); + auto v2 = ProtobufWkt::Value(); + v2.set_number_value(2.0); + auto v3 = ProtobufWkt::Value(); + v3.set_bool_value(true); + + auto metadata_struct = ProtobufWkt::Struct(); + auto mutable_fields = metadata_struct.mutable_fields(); + mutable_fields->insert({"a", v1}); + mutable_fields->insert({"b", v2}); + mutable_fields->insert({"c", v3}); + + auto matches = MetadataMatchCriteriaImpl(metadata_struct); + + EXPECT_EQ(matches.metadataMatchCriteria().size(), 3); + auto it = matches.metadataMatchCriteria().begin(); + EXPECT_EQ((*it)->name(), "a"); + EXPECT_EQ((*it)->value().value().string_value(), "v1"); + it++; + + EXPECT_EQ((*it)->name(), "b"); + EXPECT_EQ((*it)->value().value().number_value(), 2.0); + it++; + + EXPECT_EQ((*it)->name(), "c"); + EXPECT_EQ((*it)->value().value().bool_value(), true); +} + +TEST(MetadataMatchCriteriaImpl, Merge) { + auto pv1 = ProtobufWkt::Value(); + pv1.set_string_value("v1"); + auto pv2 = ProtobufWkt::Value(); + pv2.set_number_value(2.0); + auto pv3 = ProtobufWkt::Value(); + pv3.set_bool_value(true); + + auto parent_struct = ProtobufWkt::Struct(); + auto parent_fields = parent_struct.mutable_fields(); + parent_fields->insert({"a", pv1}); + parent_fields->insert({"b", pv2}); + parent_fields->insert({"c", pv3}); + + auto parent_matches = MetadataMatchCriteriaImpl(parent_struct); + + auto v1 = ProtobufWkt::Value(); + v1.set_string_value("override1"); + auto v2 = ProtobufWkt::Value(); + v2.set_string_value("v2"); + auto v3 = ProtobufWkt::Value(); + v3.set_string_value("override3"); auto metadata_struct = ProtobufWkt::Struct(); auto mutable_fields = metadata_struct.mutable_fields(); @@ -10156,49 +10614,368 @@ TEST_F(RouteConfigurationV2, InternalRedirectPolicyDropsInvalidRedirectCodeCause internal_redirect_policy.shouldRedirectForResponseCode(static_cast(200))); } -class PerFilterConfigsTest : public testing::Test, public ConfigImplTestBase { -public: - PerFilterConfigsTest() - : registered_factory_(factory_), registered_default_factory_(default_factory_) {} - - struct DerivedFilterConfig : public RouteSpecificFilterConfig { - ProtobufWkt::Timestamp config_; - }; - class TestFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { - public: - TestFilterConfig() : EmptyHttpFilterConfig("test.filter") {} +#if defined(ALIMESH) +TEST_F(RouteConfigurationV2, InternalActiveRedirectIsDisabledWhenNotSpecifiedInRouteAction) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [idle.lyft.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/regex" + route: + cluster: some-cluster + )EOF"; - Http::FilterFactoryCb createFilter(const std::string&, - Server::Configuration::FactoryContext&) override { - PANIC("not implemented"); - } - ProtobufTypes::MessagePtr createEmptyRouteConfigProto() override { - return ProtobufTypes::MessagePtr{new ProtobufWkt::Timestamp()}; - } - ProtobufTypes::MessagePtr createEmptyConfigProto() override { - // Override this to guarantee that we have a different factory mapping by-type. - return ProtobufTypes::MessagePtr{new ProtobufWkt::Timestamp()}; - } - std::set configTypes() override { return {"google.protobuf.Timestamp"}; } - Router::RouteSpecificFilterConfigConstSharedPtr - createRouteSpecificFilterConfig(const Protobuf::Message& message, - Server::Configuration::ServerFactoryContext&, - ProtobufMessage::ValidationVisitor&) override { - auto obj = std::make_shared(); - obj->config_.MergeFrom(message); - return obj; - } - }; - class DefaultTestFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { - public: - DefaultTestFilterConfig() : EmptyHttpFilterConfig("test.default.filter") {} + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("idle.lyft.com", "/regex", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_FALSE(internal_active_redirect_policy.enabled()); +} - Http::FilterFactoryCb createFilter(const std::string&, - Server::Configuration::FactoryContext&) override { - PANIC("not implemented"); - } - ProtobufTypes::MessagePtr createEmptyRouteConfigProto() override { - return ProtobufTypes::MessagePtr{new ProtobufWkt::Struct()}; +TEST_F(RouteConfigurationV2, DefaultInternalActiveRedirectPolicyIsSensible) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [idle.lyft.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/regex" + route: + cluster: some-cluster + internal_active_redirect_policy: + policies: + - redirect_url: "taobao.com" + redirect_response_codes: [404] + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("idle.lyft.com", "/regex", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_TRUE(internal_active_redirect_policy.enabled()); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(503))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(200))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(302))); + EXPECT_EQ(1, internal_active_redirect_policy.maxInternalRedirects()); + EXPECT_TRUE(internal_active_redirect_policy.predicates().empty()); + EXPECT_FALSE(internal_active_redirect_policy.isCrossSchemeRedirectAllowed()); + EXPECT_EQ("taobao.com", internal_active_redirect_policy.redirectUrl()); +} + +TEST_F(RouteConfigurationV2, InternalActiveRedirectPolicyDropsInvalidRedirectCode) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [idle.lyft.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/regex" + route: + cluster: some-cluster + internal_active_redirect_policy: + policies: + - redirect_url: "taobao.com" + redirect_response_codes: [301, 302, 303, 304, 307, 308, 503, 500, 404] + request_headers_to_add: + - header: + key: x-req-cluster + value: cluster1 + append: true + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("idle.lyft.com", "/regex", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_TRUE(internal_active_redirect_policy.enabled()); + // The 301, 302, 303, 307, 308 is invalid code. + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(301))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(302))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(303))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(307))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(308))); + // No configured code. + EXPECT_TRUE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(304))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(305))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(306))); + // The configured code. + EXPECT_TRUE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(503))); + EXPECT_TRUE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(500))); + EXPECT_TRUE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(404))); + + NiceMock stream_info; + Http::TestRequestHeaderMapImpl header_map{{":method", "POST"}}; + internal_active_redirect_policy.evaluateHeaders(header_map, &stream_info); + EXPECT_TRUE(header_map.has("x-req-cluster")); + EXPECT_FALSE(header_map.has("x-client-ip")); +} + +TEST_F(RouteConfigurationV2, InternalActiveRedirectPolicyDropsInvalidRedirectCodeCauseEmptySet) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [idle.lyft.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/regex" + route: + cluster: some-cluster + internal_active_redirect_policy: + policies: + - redirect_response_codes: [200, 301] + redirect_url_rewrite_regex: + pattern: + google_re2: {} + regex: "^/.+/(.+)$" + substitution: \1 + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("idle.lyft.com", "/regex", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_TRUE(internal_active_redirect_policy.enabled()); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(302))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(301))); + EXPECT_FALSE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(200))); +} + +TEST_F(RouteConfigurationV2, InternalActiveRedirectPolicyWithRedirectUrlRewriteRegex) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [idle.lyft.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/regex" + route: + cluster: some-cluster + internal_active_redirect_policy: + policies: + - redirect_response_codes: [200, 301] + redirect_url_rewrite_regex: + pattern: + google_re2: {} + regex: "^/.+/(.+)$" + substitution: \1 + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("idle.lyft.com", "/regex", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_TRUE(internal_active_redirect_policy.enabled()); + + std::string path("/rewrite-host-with-path-regex/envoyproxy.io"); + EXPECT_EQ("envoyproxy.io", internal_active_redirect_policy.redirectUrl(path)); +} + +TEST_F(RouteConfigurationV2, + InternalActiveRedirectPolicyWithRedirectUrlWithYoukuKrakenRewriteRegex) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [act.youku.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/yep/page/kraken/m_pre/i_just_test" + route: + cluster: some-cluster + internal_active_redirect_policy: + policies: + - redirect_response_codes: [503] + redirect_url_rewrite_regex: + pattern: + google_re2: {} + regex: (\W|^)kraken + substitution: test + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("act.youku.com", "/yep/page/kraken/m_pre/i_just_test", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_TRUE(internal_active_redirect_policy.enabled()); + + std::string path("/yep/page/kraken/m_pre/i_just_test"); + EXPECT_EQ("/yep/pagetest/m_pre/i_just_test", internal_active_redirect_policy.redirectUrl(path)); +} + +TEST_F(RouteConfigurationV2, InternalActiveRedirectPolicyWithRedirectUrlHostRewrite) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [act.youku.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/yep/i_just_test" + route: + cluster: some-cluster + internal_active_redirect_policy: + policies: + - redirect_response_codes: [503] + redirect_url: /yep/page/kraken/m_pre/i_just_test + host_rewrite_literal: taobao.com + + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("act.youku.com", "/yep/i_just_test", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_TRUE(internal_active_redirect_policy.enabled()); + + EXPECT_EQ("/yep/page/kraken/m_pre/i_just_test", internal_active_redirect_policy.redirectUrl()); + + NiceMock stream_info; + Http::TestRequestHeaderMapImpl header_map{{":method", "POST"}}; + internal_active_redirect_policy.evaluateHeaders(header_map, &stream_info); + EXPECT_EQ("taobao.com", header_map.getHostValue()); +} + +TEST_F(RouteConfigurationV2, InternalActiveRedirectPolicyWithMultiPolicies) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [act.youku.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/yep/i_just_test" + route: + cluster: some-cluster + internal_active_redirect_policy: + policies: + - redirect_response_codes: [503] + redirect_url: /yep/page/kraken/m_pre/i_just_test + host_rewrite_literal: taobao.com + - redirect_response_codes: [505] + redirect_url: /yep/page/kraken/m_pre/i_just_test_505 + host_rewrite_literal: taobao.com + - redirect_response_codes: [404] + redirect_url: /yep/page/kraken/m_pre/i_just_test_404 + host_rewrite_literal: taobao.com + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"some-cluster"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + Http::TestRequestHeaderMapImpl headers = + genRedirectHeaders("act.youku.com", "/yep/i_just_test", true, false); + const auto& internal_active_redirect_policy = + config.route(headers, 0)->routeEntry()->internalActiveRedirectPolicy(); + EXPECT_TRUE(internal_active_redirect_policy.enabled()); + + EXPECT_EQ("/yep/page/kraken/m_pre/i_just_test", internal_active_redirect_policy.redirectUrl()); + + NiceMock stream_info; + Http::TestRequestHeaderMapImpl header_map{{":method", "POST"}}; + internal_active_redirect_policy.evaluateHeaders(header_map, &stream_info); + EXPECT_EQ("taobao.com", header_map.getHostValue()); + + EXPECT_TRUE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(404))); + EXPECT_EQ("/yep/page/kraken/m_pre/i_just_test_404", + internal_active_redirect_policy.redirectUrl()); + + EXPECT_TRUE( + internal_active_redirect_policy.shouldRedirectForResponseCode(static_cast(505))); + EXPECT_EQ("/yep/page/kraken/m_pre/i_just_test_505", + internal_active_redirect_policy.redirectUrl()); +} + +#endif + +class PerFilterConfigsTest : public testing::Test, public ConfigImplTestBase { +public: + PerFilterConfigsTest() + : registered_factory_(factory_), registered_default_factory_(default_factory_) {} + + struct DerivedFilterConfig : public RouteSpecificFilterConfig { + ProtobufWkt::Timestamp config_; + }; + class TestFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { + public: + TestFilterConfig() : EmptyHttpFilterConfig("test.filter") {} + + Http::FilterFactoryCb createFilter(const std::string&, + Server::Configuration::FactoryContext&) override { + PANIC("not implemented"); + } + ProtobufTypes::MessagePtr createEmptyRouteConfigProto() override { + return ProtobufTypes::MessagePtr{new ProtobufWkt::Timestamp()}; + } + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + // Override this to guarantee that we have a different factory mapping by-type. + return ProtobufTypes::MessagePtr{new ProtobufWkt::Timestamp()}; + } + std::set configTypes() override { return {"google.protobuf.Timestamp"}; } + Router::RouteSpecificFilterConfigConstSharedPtr + createRouteSpecificFilterConfig(const Protobuf::Message& message, + Server::Configuration::ServerFactoryContext&, + ProtobufMessage::ValidationVisitor&) override { + auto obj = std::make_shared(); + obj->config_.MergeFrom(message); + return obj; + } + }; + class DefaultTestFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { + public: + DefaultTestFilterConfig() : EmptyHttpFilterConfig("test.default.filter") {} + + Http::FilterFactoryCb createFilter(const std::string&, + Server::Configuration::FactoryContext&) override { + PANIC("not implemented"); + } + ProtobufTypes::MessagePtr createEmptyRouteConfigProto() override { + return ProtobufTypes::MessagePtr{new ProtobufWkt::Struct()}; } std::set configTypes() override { return {"google.protobuf.Struct"}; } }; @@ -11163,6 +11940,23 @@ TEST_F(RouteMatchOverrideTest, NullRouteOnRequireTlsAll) { }, genHeaders("bat.com", "/", "GET")); EXPECT_NE(nullptr, dynamic_cast(accepted_route.get())); +#if defined(ALIMESH) + EXPECT_EQ(Http::Code::MovedPermanently, + dynamic_cast(accepted_route.get()) + ->directResponseEntry() + ->responseCode()); + RouteConstSharedPtr accepted_route_post = config.route( + [](RouteConstSharedPtr, RouteEvalStatus) -> RouteMatchStatus { + ADD_FAILURE() << "RouteCallback should not be invoked since there are no matching " + "route to override"; + return RouteMatchStatus::Continue; + }, + genHeaders("bat.com", "/", "POST")); + EXPECT_EQ(Http::Code::PermanentRedirect, + dynamic_cast(accepted_route_post.get()) + ->directResponseEntry() + ->responseCode()); +#endif } TEST_F(RouteMatchOverrideTest, NullRouteOnRequireTlsInternal) { @@ -11233,6 +12027,227 @@ TEST_F(CommonConfigImplTest, TestCommonConfig) { shared_config.ignorePathParametersInPathMatching()); } +#if defined(ALIMESH) +TEST_F(RouteMatchOverrideTest, NullRouteOnExactAllowServerNames) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: bar + domains: ["*"] + routes: + - match: { prefix: "/foo/bar/baz" } + route: + cluster: foo_bar_baz + - match: { prefix: "/foo/bar" } + route: + cluster: foo_bar + - match: { prefix: "/" } + route: + cluster: default + allow_server_names: ["www.example.com"] +)EOF"; + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + downstream_connection_info_provider->setSslConnection( + std::make_shared>()); + downstream_connection_info_provider->setRequestedServerName("test.example.com"); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + factory_context_.cluster_manager_.initializeClusters({"foo_bar_baz", "foo_bar", "default"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + RouteConstSharedPtr accepted_route = config.route( + [](RouteConstSharedPtr, RouteEvalStatus) -> RouteMatchStatus { + ADD_FAILURE() << "RouteCallback should not be invoked since there are no matching " + "route to override"; + return RouteMatchStatus::Continue; + }, + genHeaders("www.example.com", "/", "GET"), stream_info, 0); + EXPECT_NE(nullptr, dynamic_cast(accepted_route.get())); + EXPECT_EQ(Http::Code::MisdirectedRequest, + dynamic_cast(accepted_route.get()) + ->directResponseEntry() + ->responseCode()); + downstream_connection_info_provider->setRequestedServerName("www.example.com"); + std::vector clusters{"default", "foo_bar", "foo_bar_baz"}; + accepted_route = config.route( + [&clusters](RouteConstSharedPtr route, + RouteEvalStatus route_eval_status) -> RouteMatchStatus { + EXPECT_FALSE(clusters.empty()); + EXPECT_EQ(clusters[clusters.size() - 1], route->routeEntry()->clusterName()); + clusters.pop_back(); + + if (clusters.empty()) { + EXPECT_EQ(route_eval_status, RouteEvalStatus::NoMoreRoutes); + } else { + EXPECT_EQ(route_eval_status, RouteEvalStatus::HasMoreRoutes); + } + // Returning continue when no more routes are available will be ignored by + // ConfigImpl::route + return RouteMatchStatus::Continue; + }, + genHeaders("www.example.com", "/foo/bar/baz", "GET"), stream_info, 0); + EXPECT_EQ(accepted_route, nullptr); +} + +TEST_F(RouteMatchOverrideTest, NullRouteOnWildcardAllowServerNames) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: bar + domains: ["*"] + routes: + - match: { prefix: "/foo/bar/baz" } + route: + cluster: foo_bar_baz + - match: { prefix: "/foo/bar" } + route: + cluster: foo_bar + - match: { prefix: "/" } + route: + cluster: default + allow_server_names: ["www.example.com", "*.example.com"] +)EOF"; + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + downstream_connection_info_provider->setSslConnection( + std::make_shared>()); + downstream_connection_info_provider->setRequestedServerName("example.com"); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + factory_context_.cluster_manager_.initializeClusters({"foo_bar_baz", "foo_bar", "default"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + RouteConstSharedPtr accepted_route = config.route( + [](RouteConstSharedPtr, RouteEvalStatus) -> RouteMatchStatus { + ADD_FAILURE() << "RouteCallback should not be invoked since there are no matching " + "route to override"; + return RouteMatchStatus::Continue; + }, + genHeaders("www.example.com", "/", "GET"), stream_info, 0); + EXPECT_NE(nullptr, dynamic_cast(accepted_route.get())); + EXPECT_EQ(Http::Code::MisdirectedRequest, + dynamic_cast(accepted_route.get()) + ->directResponseEntry() + ->responseCode()); + downstream_connection_info_provider->setRequestedServerName("test.example.com"); + std::vector clusters{"default", "foo_bar", "foo_bar_baz"}; + accepted_route = config.route( + [&clusters](RouteConstSharedPtr route, + RouteEvalStatus route_eval_status) -> RouteMatchStatus { + EXPECT_FALSE(clusters.empty()); + EXPECT_EQ(clusters[clusters.size() - 1], route->routeEntry()->clusterName()); + clusters.pop_back(); + + if (clusters.empty()) { + EXPECT_EQ(route_eval_status, RouteEvalStatus::NoMoreRoutes); + } else { + EXPECT_EQ(route_eval_status, RouteEvalStatus::HasMoreRoutes); + } + // Returning continue when no more routes are available will be ignored by + // ConfigImpl::route + return RouteMatchStatus::Continue; + }, + genHeaders("www.example.com", "/foo/bar/baz", "GET"), stream_info, 0); + EXPECT_EQ(accepted_route, nullptr); +} + +TEST_F(RouteMatchOverrideTest, NullRouteOnEmptyAllowServerNames) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: bar + domains: ["*"] + routes: + - match: { prefix: "/foo/bar/baz" } + route: + cluster: foo_bar_baz + - match: { prefix: "/foo/bar" } + route: + cluster: foo_bar + - match: { prefix: "/" } + route: + cluster: default + allow_server_names: [] +)EOF"; + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + downstream_connection_info_provider->setSslConnection( + std::make_shared>()); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + factory_context_.cluster_manager_.initializeClusters({"foo_bar_baz", "foo_bar", "default"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + downstream_connection_info_provider->setRequestedServerName("example.com"); + std::vector clusters{"default", "foo_bar", "foo_bar_baz"}; + RouteConstSharedPtr accepted_route = config.route( + [&clusters](RouteConstSharedPtr route, + RouteEvalStatus route_eval_status) -> RouteMatchStatus { + EXPECT_FALSE(clusters.empty()); + EXPECT_EQ(clusters[clusters.size() - 1], route->routeEntry()->clusterName()); + clusters.pop_back(); + + if (clusters.empty()) { + EXPECT_EQ(route_eval_status, RouteEvalStatus::NoMoreRoutes); + } else { + EXPECT_EQ(route_eval_status, RouteEvalStatus::HasMoreRoutes); + } + // Returning continue when no more routes are available will be ignored by + // ConfigImpl::route + return RouteMatchStatus::Continue; + }, + genHeaders("www.example.com", "/foo/bar/baz", "GET"), stream_info, 0); + EXPECT_EQ(accepted_route, nullptr); +} + +TEST_F(RouteMatchOverrideTest, NullRouteOnAllowServerNamesWithoutSsl) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: bar + domains: ["*"] + routes: + - match: { prefix: "/foo/bar/baz" } + route: + cluster: foo_bar_baz + - match: { prefix: "/foo/bar" } + route: + cluster: foo_bar + - match: { prefix: "/" } + route: + cluster: default + allow_server_names: ["www.example.com"] +)EOF"; + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + factory_context_.cluster_manager_.initializeClusters({"foo_bar_baz", "foo_bar", "default"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + downstream_connection_info_provider->setRequestedServerName("example.com"); + std::vector clusters{"default", "foo_bar", "foo_bar_baz"}; + RouteConstSharedPtr accepted_route = config.route( + [&clusters](RouteConstSharedPtr route, + RouteEvalStatus route_eval_status) -> RouteMatchStatus { + EXPECT_FALSE(clusters.empty()); + EXPECT_EQ(clusters[clusters.size() - 1], route->routeEntry()->clusterName()); + clusters.pop_back(); + + if (clusters.empty()) { + EXPECT_EQ(route_eval_status, RouteEvalStatus::NoMoreRoutes); + } else { + EXPECT_EQ(route_eval_status, RouteEvalStatus::HasMoreRoutes); + } + // Returning continue when no more routes are available will be ignored by + // ConfigImpl::route + return RouteMatchStatus::Continue; + }, + genHeaders("www.example.com", "/foo/bar/baz", "GET"), stream_info, 0); + EXPECT_EQ(accepted_route, nullptr); +} +#endif } // namespace } // namespace Router -} // namespace Envoy +} // namespace Envoy \ No newline at end of file diff --git a/test/common/router/router_2_test.cc b/test/common/router/router_2_test.cc index 4186a2f13607c..53870c1571a9a 100644 --- a/test/common/router/router_2_test.cc +++ b/test/common/router/router_2_test.cc @@ -57,6 +57,9 @@ TEST_F(RouterTestSuppressEnvoyHeaders, MaintenanceMode) { router_->decodeHeaders(headers, true); } +// if ALIMESH defined, x-envoy-upstream-service-time will be added anyway. +// see https://code.alibaba-inc.com/Ingress/envoy/codereview/13276137 +#ifndef ALIMESH // Validate that x-envoy-upstream-service-time is not added when Envoy header // suppression is enabled. // TODO(htuch): Probably should be TEST_P with @@ -87,6 +90,7 @@ TEST_F(RouterTestSuppressEnvoyHeaders, EnvoyUpstreamServiceTime) { response_decoder->decodeHeaders(std::move(response_headers), true); EXPECT_TRUE(verifyHostUpstreamStats(1, 0)); } +#endif // Validate that we don't set x-envoy-attempt-count in responses before an upstream attempt is made. TEST_F(RouterTestSuppressEnvoyHeaders, EnvoyAttemptCountInResponseNotPresent) { @@ -431,6 +435,9 @@ TEST_F(RouterTestChildSpan, BasicFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); +#if defined(ALIMESH) + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); +#endif router_->decodeHeaders(headers, true); EXPECT_EQ(1U, callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); @@ -482,6 +489,9 @@ TEST_F(RouterTestChildSpan, ResetFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); +#if defined(ALIMESH) + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); +#endif router_->decodeHeaders(headers, true); EXPECT_EQ(1U, callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); @@ -536,6 +546,9 @@ TEST_F(RouterTestChildSpan, CancelFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); +#if defined(ALIMESH) + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); +#endif router_->decodeHeaders(headers, true); EXPECT_EQ(1U, callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); @@ -587,6 +600,9 @@ TEST_F(RouterTestChildSpan, ResetRetryFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span_1)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); +#if defined(ALIMESH) + EXPECT_CALL(*child_span_1, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); +#endif router_->decodeHeaders(headers, true); EXPECT_EQ(1U, callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); @@ -629,6 +645,9 @@ TEST_F(RouterTestChildSpan, ResetRetryFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span_2)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); +#if defined(ALIMESH) + EXPECT_CALL(*child_span_2, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); +#endif EXPECT_CALL(*child_span_2, setTag(Eq(Tracing::Tags::get().RetryCount), Eq("1"))); router_->retry_state_->callback_(); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index bada575c09e52..7b7aee44a9122 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -4364,6 +4364,264 @@ TEST_F(RouterTest, CrossSchemeRedirectAllowedByPolicy) { router_->onDestroy(); } +#if defined(ALIMESH) +TEST_F(RouterTest, InternalActiveRedirectRejectedWhenReachingMaxInternalRedirect) { + enableActiveRedirects("http://www.foo.com", 3); + setNumPreviousRedirect(3); + sendRequest(); + + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(active_redirect_headers_), false); + + Buffer::OwnedImpl data("1234567890"); + response_decoder_->decodeData(data, true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); + EXPECT_EQ(1UL, + stats_store_.counter("test.passthrough_internal_redirect_too_many_redirects").value()); +} + +TEST_F(RouterTest, InternalActiveRedirectRejectedWithEmptyLocation) { + enableActiveRedirects(""); + sendRequest(); + + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(redirect_headers_), false); + + Buffer::OwnedImpl data("1234567890"); + response_decoder_->decodeData(data, true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); + EXPECT_EQ(1UL, stats_store_.counter("test.passthrough_internal_redirect_bad_location").value()); +} + +TEST_F(RouterTest, InternalActiveRedirectRejectedWithInvalidLocation) { + enableActiveRedirects("h"); + sendRequest(); + + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(redirect_headers_), false); + + Buffer::OwnedImpl data("1234567890"); + response_decoder_->decodeData(data, true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); + EXPECT_EQ(1UL, stats_store_.counter("test.passthrough_internal_redirect_bad_location").value()); +} + +TEST_F(RouterTest, InternalActiveRedirectRejectedWithoutCompleteRequest) { + enableActiveRedirects("http://www.foo.com", 3); + + sendRequest(false); + + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(redirect_headers_), false); + + Buffer::OwnedImpl data("1234567890"); + response_decoder_->decodeData(data, true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); +} + +TEST_F(RouterTest, InternalActiveRedirectRejectedWithoutLocation) { + enableActiveRedirects(""); + + sendRequest(); + + redirect_headers_->removeLocation(); + + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(redirect_headers_), false); + Buffer::OwnedImpl data("1234567890"); + response_decoder_->decodeData(data, true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); +} + +TEST_F(RouterTest, InternalActiveRedirectRejectedWithBody) { + enableActiveRedirects("http://www.foo.com"); + + sendRequest(); + + Buffer::InstancePtr body_data(new Buffer::OwnedImpl("random_fake_data")); + EXPECT_CALL(callbacks_, decodingBuffer()).WillRepeatedly(Return(body_data.get())); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(redirect_headers_), false); + Buffer::OwnedImpl data("1234567890"); + response_decoder_->decodeData(data, true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); +} + +TEST_F(RouterTest, CrossSchemeActiveRedirectRejectedByPolicy) { + enableActiveRedirects("https://www.foo.com"); + + sendRequest(); + + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(redirect_headers_), true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); + EXPECT_EQ(1UL, stats_store_.counter("test.passthrough_internal_redirect_unsafe_scheme").value()); +} + +TEST_F(RouterTest, InternalActiveRedirectRejectedByPredicate) { + enableActiveRedirects("http://www.foo.com/some/path"); + sendRequest(); + + auto mock_predicate = std::make_shared>(); + + EXPECT_CALL(callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, predicates()) + .WillOnce(Return(std::vector({mock_predicate}))); + EXPECT_CALL(*mock_predicate, acceptTargetRoute(_, _, _, _)).WillOnce(Return(false)); + ON_CALL(*mock_predicate, name()).WillByDefault(Return("mock_predicate")); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + + response_decoder_->decodeHeaders(std::move(redirect_headers_), true); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_failed_total") + .value()); + EXPECT_EQ(1UL, stats_store_.counter("test.passthrough_internal_redirect_predicate").value()); + + // Make sure the original host/path is preserved. + EXPECT_EQ("host", default_request_headers_.getHostValue()); + EXPECT_EQ("/", default_request_headers_.getPathValue()); + // Make sure x-envoy-original-url is not set for unsuccessful redirect. + EXPECT_EQ(nullptr, default_request_headers_.EnvoyOriginalUrl()); +} + +TEST_F(RouterTest, HttpInternalActiveRedirectSucceeded) { + enableActiveRedirects("http://www.foo.com/some/path", 3); + setNumPreviousRedirect(2); + default_request_headers_.setForwardedProto("http"); + sendRequest(); + + EXPECT_CALL(callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_CALL(callbacks_, recreateStream(_)).WillOnce(Return(true)); + response_decoder_->decodeHeaders(std::move(redirect_headers_), false); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_succeeded_total") + .value()); + + // In production, the HCM recreateStream would have called this. + router_->onDestroy(); + EXPECT_EQ(3, callbacks_.streamInfo() + .filterState() + ->getDataMutable("num_internal_redirects") + ->value()); +} + +TEST_F(RouterTest, HttpsInternalActiveRedirectSucceeded) { + auto ssl_connection = std::make_shared(); + enableActiveRedirects("https://www.foo.com", 3); + setNumPreviousRedirect(1); + default_request_headers_.setScheme("https"); + + sendRequest(); + + EXPECT_CALL(connection_, ssl()).WillOnce(Return(ssl_connection)); + EXPECT_CALL(callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_CALL(callbacks_, recreateStream(_)).WillOnce(Return(true)); + response_decoder_->decodeHeaders(std::move(active_redirect_headers_), false); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_succeeded_total") + .value()); + + // In production, the HCM recreateStream would have called this. + router_->onDestroy(); +} + +TEST_F(RouterTest, CrossSchemeActiveRedirectAllowedByPolicy) { + auto ssl_connection = std::make_shared(); + enableActiveRedirects("http://www.redirect-url.com", 1); + default_request_headers_.setScheme("https"); + + sendRequest(); + + EXPECT_CALL(connection_, ssl()).WillOnce(Return(ssl_connection)); + EXPECT_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, + isCrossSchemeRedirectAllowed()) + .WillOnce(Return(true)); + EXPECT_CALL(callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_CALL(callbacks_, recreateStream(_)).WillOnce(Return(true)); + response_decoder_->decodeHeaders(std::move(active_redirect_headers_), false); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_succeeded_total") + .value()); + EXPECT_EQ("www.redirect-url.com", std::string(default_request_headers_.getHostValue())); + // In production, the HCM recreateStream would have called this. + router_->onDestroy(); +} + +TEST_F(RouterTest, UseOriginalHost) { + auto ssl_connection = std::make_shared(); + enableActiveRedirects("http://www.redirect-url.com", 1, true); + default_request_headers_.setHost("original-test-host.com"); + + sendRequest(); + + EXPECT_CALL(callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_CALL(callbacks_, recreateStream(_)).WillOnce(Return(true)); + response_decoder_->decodeHeaders(std::move(active_redirect_headers_), false); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_succeeded_total") + .value()); + EXPECT_EQ("original-test-host.com", std::string(default_request_headers_.getHostValue())); + // In production, the HCM recreateStream would have called this. + router_->onDestroy(); +} + +TEST_F(RouterTest, ForcedAddHeaderBeforeRouteMatcher) { + auto ssl_connection = std::make_shared(); + enableActiveRedirects("http://www.redirect-url.com", 1, false, true); + + sendRequest(); + + EXPECT_CALL(callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_CALL(callbacks_, recreateStream(_)).WillOnce(Return(true)); + response_decoder_->decodeHeaders(std::move(active_redirect_headers_), false); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_succeeded_total") + .value()); + EXPECT_EQ(true, default_request_headers_.has("test_added_header")); + // In production, the HCM recreateStream would have called this. + router_->onDestroy(); +} + +TEST_F(RouterTest, ForcedAddHeaderBeforeRouteMatcherWithRouteFailure) { + auto ssl_connection = std::make_shared(); + enableActiveRedirects("http://www.redirect-url.com", 1, false, true); + + sendRequest(); + + EXPECT_CALL(callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_CALL(callbacks_, route()).WillOnce(Return(nullptr)); + ; + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); + response_decoder_->decodeHeaders(std::move(active_redirect_headers_), false); + EXPECT_EQ(0U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_internal_redirect_succeeded_total") + .value()); + EXPECT_EQ(true, default_request_headers_.has("test_added_header")); + // In production, the HCM recreateStream would have called this. + router_->onDestroy(); +} +#endif namespace { std::shared_ptr @@ -5747,7 +6005,11 @@ TEST_F(RouterTest, CanaryStatusFalse) { .value()); } +#if defined(ALIMESH) +TEST_F(RouterTest, DISABLED_AutoHostRewriteEnabled) { +#else TEST_F(RouterTest, AutoHostRewriteEnabled) { +#endif NiceMock encoder; std::string req_host{"foo.bar.com"}; diff --git a/test/common/router/router_test_base.cc b/test/common/router/router_test_base.cc index 1bb80b7bc2bf1..fa0ff45ab2afd 100644 --- a/test/common/router/router_test_base.cc +++ b/test/common/router/router_test_base.cc @@ -243,6 +243,45 @@ void RouterTestBase::setNumPreviousRedirect(uint32_t num_previous_redirects) { StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); } +#if defined(ALIMESH) +void RouterTestBase::enableActiveRedirects(std::string redirect_url, + uint32_t max_internal_redirects, + bool forced_use_original_host, + bool forced_add_header_before_route_matcher) { + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, enabled()) + .WillByDefault(Return(true)); + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, + shouldRedirectForResponseCode(_)) + .WillByDefault(Return(true)); + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, maxInternalRedirects()) + .WillByDefault(Return(max_internal_redirects)); + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, + isCrossSchemeRedirectAllowed()) + .WillByDefault(Return(false)); + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, redirectUrl(_)) + .WillByDefault(Return(redirect_url)); + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, evaluateHeaders(_, _)) + .WillByDefault(Invoke([&](Http::HeaderMap& headers, const StreamInfo::StreamInfo*) -> void { + const Envoy::Http::LowerCaseString key("test_added_header"); + headers.addCopy(key, 1111); + })); + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, forcedUseOriginalHost()) + .WillByDefault(Return(forced_use_original_host)); + ON_CALL(callbacks_.route_->route_entry_.internal_active_redirect_policy_, + forcedAddHeaderBeforeRouteMatcher()) + .WillByDefault(Return(forced_add_header_before_route_matcher)); + ON_CALL(callbacks_, connection()) + .WillByDefault(Return(OptRef{connection_})); +} + +void RouterTestBase::setNumPreviousActiveRedirect(uint32_t num_previous_redirects) { + callbacks_.streamInfo().filterState()->setData( + "num_internal_redirects", + std::make_shared(num_previous_redirects), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); +} +#endif + void RouterTestBase::setIncludeAttemptCountInRequest(bool include) { ON_CALL(callbacks_.route_->route_entry_, includeAttemptCountInRequest()) .WillByDefault(Return(include)); diff --git a/test/common/router/router_test_base.h b/test/common/router/router_test_base.h index db48cee0048fe..ffd3d2616aaa5 100644 --- a/test/common/router/router_test_base.h +++ b/test/common/router/router_test_base.h @@ -51,6 +51,16 @@ class RouterTestFilter : public Filter { return &downstream_connection_; } +#if defined(ALIMESH) + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, + bool end_stream) override { + auto status = Filter::decodeHeaders(headers, end_stream); + // TODO: deletes the header and consider using custom headers. + headers.remove(Http::CustomHeaders::get().AliExtendedValues.TriStartTime); + return status; + } +#endif + NiceMock downstream_connection_; MockRetryState* retry_state_{}; bool reject_all_hosts_ = false; @@ -89,7 +99,15 @@ class RouterTestBase : public testing::Test { void expectNewStreamWithImmediateEncoder(Http::RequestEncoder& encoder, Http::ResponseDecoder** decoder, Http::Protocol protocol); +#if defined(ALIMESH) + void enableActiveRedirects(std::string redirect_url, uint32_t max_internal_redirects = 1, + bool forced_use_original_host = false, + bool forced_add_header_before_route_matcher = false); + void setNumPreviousActiveRedirect(uint32_t num_previous_redirects); + Http::ResponseHeaderMapPtr active_redirect_headers_{ + new Http::TestResponseHeaderMapImpl{{":status", "502"}, {"location", "http://www.foo.com"}}}; +#endif Event::SimulatedTimeSystem test_time_; std::string upstream_zone_{"to_az"}; envoy::config::core::v3::Locality upstream_locality_; diff --git a/test/common/router/router_upstream_log_test.cc b/test/common/router/router_upstream_log_test.cc index 43e255f9861ab..a6ed19e56bb94 100644 --- a/test/common/router/router_upstream_log_test.cc +++ b/test/common/router/router_upstream_log_test.cc @@ -77,6 +77,16 @@ class TestFilter : public Filter { return &downstream_connection_; } +#if defined(ALIMESH) + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, + bool end_stream) override { + auto status = Filter::decodeHeaders(headers, end_stream); + // TODO: deletes the header and consider using custom headers. + headers.remove(Http::CustomHeaders::get().AliExtendedValues.TriStartTime); + return status; + } +#endif + NiceMock downstream_connection_; MockRetryState* retry_state_{}; }; diff --git a/test/common/router/scoped_config_impl_test.cc b/test/common/router/scoped_config_impl_test.cc index 37d8a7fa65619..3f6a245ab77ff 100644 --- a/test/common/router/scoped_config_impl_test.cc +++ b/test/common/router/scoped_config_impl_test.cc @@ -6,6 +6,11 @@ #include "source/common/router/scoped_config_impl.h" +#if defined(ALIMESH) +#include "source/common/network/address_impl.h" +#include "test/mocks/stream_info/mocks.h" +#endif + #include "test/mocks/router/mocks.h" #include "test/test_common/utility.h" @@ -17,6 +22,9 @@ namespace { using ::Envoy::Http::TestRequestHeaderMapImpl; using ::testing::NiceMock; +#if defined(ALIMESH) +using ::testing::ReturnPointee; +#endif class FooFragment : public ScopeKeyFragmentBase { public: @@ -350,6 +358,68 @@ TEST(ScopeKeyBuilderImplTest, Parse) { EXPECT_EQ(key, nullptr); } +#if defined(ALIMESH) +TEST(ScopeKeyBuilderImplTest, ParseHostAndPort) { + std::string yaml_plain = R"EOF( + fragments: + - local_port_value_extractor: {} + - host_value_extractor: + max_recompute_num: 3 +)EOF"; + + ScopedRoutes::ScopeKeyBuilder config; + TestUtility::loadFromYaml(yaml_plain, config); + ScopeKeyBuilderImpl key_builder(std::move(config)); + + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + { + std::function recompute; + ScopeKeyPtr key = key_builder.computeScopeKey( + TestRequestHeaderMapImpl{ + {":authority", "www.example.com"}, + }, + &stream_info, recompute); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "www.example.com"})); + key = recompute(); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "*.example.com"})); + key = recompute(); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "*.com"})); + key = recompute(); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "*"})); + } + { + std::function recompute; + ScopeKeyPtr key = key_builder.computeScopeKey( + TestRequestHeaderMapImpl{ + {":authority", "www.test.example.com"}, + }, + &stream_info, recompute); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "www.test.example.com"})); + key = recompute(); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "*.test.example.com"})); + key = recompute(); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "*.example.com"})); + key = recompute(); + EXPECT_NE(key, nullptr); + EXPECT_EQ(*key, makeKey({"80", "*.com"})); + key = recompute(); + EXPECT_EQ(key, nullptr); + } +} +#endif + class ScopedRouteInfoTest : public testing::Test { public: void SetUp() override { diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index f392bdd26d608..e1021771b2c4c 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -18,6 +18,11 @@ #include "source/common/protobuf/message_validator_impl.h" #include "source/common/router/scoped_rds.h" +#if defined(ALIMESH) +#include "source/common/network/address_impl.h" +#include "test/mocks/stream_info/mocks.h" +#endif + #include "test/mocks/config/mocks.h" #include "test/mocks/matcher/mocks.h" #include "test/mocks/protobuf/mocks.h" @@ -41,6 +46,9 @@ using testing::IsNull; using testing::NiceMock; using testing::Return; using testing::ReturnRef; +#if defined(ALIMESH) +using testing::ReturnPointee; +#endif namespace Envoy { namespace Router { @@ -331,6 +339,78 @@ TEST_F(InlineScopedRoutesTest, ConfigLoadAndDump) { class ScopedRdsTest : public ScopedRoutesTestBase { protected: +#if defined(ALIMESH) + void setupHostScope(const OptionalHttpFilters optional_http_filters = OptionalHttpFilters()) { + ON_CALL(server_factory_context_.cluster_manager_, adsMux()) + .WillByDefault(Return(std::make_shared<::Envoy::Config::NullGrpcMuxImpl>())); + + InSequence s; + // Since server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ is taken by + // the SRDS subscription. We need to return a different MockSubscription here for each RDS + // subscription. To build the map from RDS route_config_name to the RDS subscription, we need to + // get the route_config_name by mocking start() on the Config::Subscription. + + // srds subscription + EXPECT_CALL(server_factory_context_.cluster_manager_.subscription_factory_, + subscriptionFromConfigSource(_, _, _, _, _, _)) + .Times(AnyNumber()); + // rds subscription + EXPECT_CALL( + server_factory_context_.cluster_manager_.subscription_factory_, + subscriptionFromConfigSource( + _, + Eq(Grpc::Common::typeUrl( + envoy::config::route::v3::RouteConfiguration().GetDescriptor()->full_name())), + _, _, _, _)) + .Times(AnyNumber()) + .WillRepeatedly( + Invoke([this](const envoy::config::core::v3::ConfigSource&, absl::string_view, + Stats::Scope&, Envoy::Config::SubscriptionCallbacks& callbacks, + Envoy::Config::OpaqueResourceDecoderSharedPtr, + const Envoy::Config::SubscriptionOptions&) { + auto ret = std::make_unique>(); + rds_subscription_by_config_subscription_[ret.get()] = &callbacks; + EXPECT_CALL(*ret, start(_)) + .WillOnce(Invoke([this, config_sub_addr = ret.get()]( + const absl::flat_hash_set& resource_names) { + EXPECT_EQ(resource_names.size(), 1); + auto iter = rds_subscription_by_config_subscription_.find(config_sub_addr); + EXPECT_NE(iter, rds_subscription_by_config_subscription_.end()); + rds_subscription_by_name_[*resource_names.begin()] = iter->second; + })); + return ret; + })); + + ON_CALL(context_init_manager_, add(_)).WillByDefault(Invoke([this](const Init::Target& target) { + target_handles_.push_back(target.createHandle("test")); + })); + ON_CALL(context_init_manager_, initialize(_)) + .WillByDefault(Invoke([this](const Init::Watcher& watcher) { + for (auto& handle_ : target_handles_) { + handle_->initialize(watcher); + } + })); + + const std::string config_yaml = R"EOF( +name: foo_scoped_routes +scope_key_builder: + fragments: + - local_port_value_extractor: {} + - host_value_extractor: {} +)EOF"; + envoy::extensions::filters::network::http_connection_manager::v3::ScopedRoutes + scoped_routes_config; + TestUtility::loadFromYaml(config_yaml, scoped_routes_config); + auto scope_key_builder_config = scoped_routes_config.scope_key_builder(); + scope_key_builder_ = std::make_unique(std::move(scope_key_builder_config)); + provider_ = config_provider_manager_->createXdsConfigProvider( + scoped_routes_config.scoped_rds(), server_factory_context_, context_init_manager_, "foo.", + ScopedRoutesConfigProviderManagerOptArg(scoped_routes_config.name(), + scoped_routes_config.rds_config_source(), + optional_http_filters)); + srds_subscription_ = server_factory_context_.cluster_manager_.subscription_factory_.callbacks_; + } +#endif void setup(const OptionalHttpFilters optional_http_filters = OptionalHttpFilters()) { ON_CALL(server_factory_context_.cluster_manager_, adsMux()) .WillByDefault(Return(std::make_shared<::Envoy::Config::NullGrpcMuxImpl>())); @@ -1814,7 +1894,6 @@ name: foo_scoped_routes scoped_routes_config.rds_config_source(), OptionalHttpFilters())); srds_subscription_ = server_factory_context_.cluster_manager_.subscription_factory_.callbacks_; - const std::string config_yaml = R"EOF( name: foo_scope route_configuration_name: foo_routes @@ -1837,6 +1916,274 @@ route_configuration_name: foo_routes EXPECT_EQ(config->name(), "foo_routes"); } +#if defined(ALIMESH) +TEST_F(ScopedRdsTest, HostScopeMultipleResourcesSotw) { + setupHostScope(); + + const std::string config_yaml = R"EOF( +name: foo_scope +route_configuration_name: foo_routes +key: + fragments: + - string_key: "80" + - string_key: www.example.com +)EOF"; + const auto resource = parseScopedRouteConfigurationFromYaml(config_yaml); + const std::string config_yaml2 = R"EOF( +name: foo_scope2 +route_configuration_name: foo_routes_wildcard +key: + fragments: + - string_key: "80" + - string_key: "*.com" +)EOF"; + const auto resource_2 = parseScopedRouteConfigurationFromYaml(config_yaml2); + init_watcher_.expectReady(); // Only the SRDS parent_init_target_. + context_init_manager_.initialize(init_watcher_); + const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); + EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1")); + EXPECT_EQ(1UL, + server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") + .value()); + EXPECT_EQ(2UL, all_scopes_.value()); + EXPECT_EQ(2UL, active_scopes_.value()); + + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + + // Verify the config is a ScopedConfigImpl instance, both scopes point to "" as RDS hasn't + // kicked in yet(NullConfigImpl returned). + ASSERT_THAT(getScopedRdsProvider(), Not(IsNull())); + ASSERT_THAT(getScopedRdsProvider()->config(), Not(IsNull())); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.example.com"}}, + &stream_info) + ->name(), + ""); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, + &stream_info) + ->name(), + ""); + // RDS updates foo_routes. + pushRdsConfig({"foo_routes"}, "111"); + pushRdsConfig({"foo_routes_wildcard"}, "111"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.example.com"}}, + &stream_info) + ->name(), + "foo_routes"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, + &stream_info) + ->name(), + "foo_routes_wildcard"); + + // Delete foo_scope2. + const auto decoded_resources_2 = TestUtility::decodeResources({resource_2}); + EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, "3")); + EXPECT_EQ(1UL, all_scopes_.value()); + EXPECT_EQ(getScopedRouteMap().count("foo_scope"), 0); + EXPECT_EQ(getScopedRouteMap().count("foo_scope2"), 1); + EXPECT_EQ(2UL, + server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") + .value()); + EXPECT_TRUE(server_factory_context_.store_.findGaugeByString( + "foo.scoped_rds.foo_scoped_routes.config_reload_time_ms")); + + // now scope key "x-bar-key" points to nowhere. + EXPECT_THAT(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.example.com"}}, + &stream_info) + ->name(), + "foo_routes_wildcard"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, + &stream_info) + ->name(), + "foo_routes_wildcard"); +} + +// Push Rds update after on demand request, route configuration should be initialized. +TEST_F(ScopedRdsTest, HostScopePushRdsAfterOndemandRequest) { + setupHostScope(); + init_watcher_.expectReady(); + context_init_manager_.initialize(init_watcher_); + // Scope should be loaded eagerly by default. + const std::string eager_resource = R"EOF( +name: foo_scope +route_configuration_name: foo_routes +key: + fragments: + - string_key: "80" + - string_key: www.example.com +)EOF"; + + // On demand scope should be loaded lazily. + const std::string lazy_resource = R"EOF( +name: foo_scope2 +route_configuration_name: foo_routes_wildcard +on_demand: true +key: + fragments: + - string_key: "80" + - string_key: "*.com" +)EOF"; + + srdsUpdateWithYaml({eager_resource, lazy_resource}, "1"); + EXPECT_EQ(1UL, + server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") + .value()); + EXPECT_EQ(2UL, all_scopes_.value()); + + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + + // Verify the config is a ScopedConfigImpl instance, both scopes point to "" as RDS hasn't kicked + // in yet(NullConfigImpl returned). + ASSERT_THAT(getScopedRdsProvider(), Not(IsNull())); + ASSERT_THAT(getScopedRdsProvider()->config(), Not(IsNull())); + pushRdsConfig({"foo_routes"}, "111"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.example.com"}}, + &stream_info) + ->name(), + "foo_routes"); + EXPECT_THAT(getScopedRdsProvider()->config()->getRouteConfig( + scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, &stream_info), + IsNull()); + + EXPECT_EQ(1UL, active_scopes_.value()); + + ScopeKeyPtr scope_key = getScopedRdsProvider()->config()->computeScopeKey( + scope_key_builder_.get(), TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, + &stream_info); + EXPECT_CALL(event_dispatcher_, post(_)); + std::function route_config_updated_cb = [](bool route_exist) { + EXPECT_EQ(true, route_exist); + }; + getScopedRdsProvider()->onDemandRdsUpdate(std::move(scope_key), event_dispatcher_, + std::move(route_config_updated_cb)); + // After on demand request, push rds update, both scopes should find the route configuration. + pushRdsConfig({"foo_routes_wildcard"}, "111"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.example.com"}}, + &stream_info) + ->name(), + "foo_routes"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, + &stream_info) + ->name(), + "foo_routes_wildcard"); + // Now we have 1 active on demand scope and 1 eager loading scope. + EXPECT_EQ(2UL, all_scopes_.value()); + EXPECT_EQ(2UL, active_scopes_.value()); + EXPECT_EQ(1UL, on_demand_scopes_.value()); +} + +TEST_F(ScopedRdsTest, HostScopePushRdsBeforeOndemandRequest) { + setupHostScope(); + init_watcher_.expectReady(); + context_init_manager_.initialize(init_watcher_); + // Scope should be loaded eagerly by default. + const std::string eager_resource = R"EOF( +name: foo_scope +route_configuration_name: foo_routes +key: + fragments: + - string_key: "80" + - string_key: www.example.com +)EOF"; + + // On demand scope should be loaded lazily. + const std::string lazy_resource = R"EOF( +name: foo_scope2 +route_configuration_name: foo_routes +on_demand: true +key: + fragments: + - string_key: "80" + - string_key: "*.com" +)EOF"; + + srdsUpdateWithYaml({eager_resource, lazy_resource}, "1"); + EXPECT_EQ(1UL, + server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") + .value()); + EXPECT_EQ(2UL, all_scopes_.value()); + + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + + // Verify the config is a ScopedConfigImpl instance, both scopes point to "" as RDS hasn't kicked + // in yet(NullConfigImpl returned). + ASSERT_THAT(getScopedRdsProvider(), Not(IsNull())); + ASSERT_THAT(getScopedRdsProvider()->config(), Not(IsNull())); + // Push rds update before on demand srds request. + pushRdsConfig({"foo_routes"}, "111"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.example.com"}}, + &stream_info) + ->name(), + "foo_routes"); + EXPECT_THAT(getScopedRdsProvider()->config()->getRouteConfig( + scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, &stream_info), + IsNull()); + ScopeKeyPtr scope_key = getScopedRdsProvider()->config()->computeScopeKey( + scope_key_builder_.get(), TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, + &stream_info); + EXPECT_CALL(server_factory_context_.dispatcher_, post(_)); + EXPECT_CALL(event_dispatcher_, post(_)); + std::function route_config_updated_cb = [](bool route_exist) { + EXPECT_EQ(true, route_exist); + }; + getScopedRdsProvider()->onDemandRdsUpdate(std::move(scope_key), event_dispatcher_, + std::move(route_config_updated_cb)); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(scope_key_builder_.get(), + TestRequestHeaderMapImpl{{":authority", "www.test.com"}}, + &stream_info) + ->name(), + "foo_routes"); +} +#endif + } // namespace } // namespace Router } // namespace Envoy diff --git a/test/common/stream_info/test_util.h b/test/common/stream_info/test_util.h index 883a68643eb53..4e4419f6dcdb1 100644 --- a/test/common/stream_info/test_util.h +++ b/test/common/stream_info/test_util.h @@ -40,6 +40,10 @@ class TestStreamInfo : public StreamInfo::StreamInfoImpl { return virtual_cluster_name_; } + void setVirtualClusterName(const absl::optional& name) override { + virtual_cluster_name_ = name; + } + void onRequestComplete() override { end_time_ = timeSystem().monotonicTime(); } absl::optional currentDuration() const override { diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 322668d9ebbb8..1bf8381178eb5 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -4416,7 +4416,7 @@ class TestUpstreamNetworkFilterConfigFactory public: Network::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message&, - Server::Configuration::CommonFactoryContext&) override { + Server::Configuration::UpstreamFactoryContext&) override { return [](Network::FilterManager& filter_manager) -> void { filter_manager.addWriteFilter(std::make_shared()); }; diff --git a/test/config_test/BUILD b/test/config_test/BUILD index 63ee81132676b..11836def61382 100644 --- a/test/config_test/BUILD +++ b/test/config_test/BUILD @@ -4,7 +4,7 @@ load( "envoy_cc_test_library", "envoy_package", ) -load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//bazel:repositories.bzl", "DARWIN_SKIP_TARGETS", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 @@ -64,6 +64,7 @@ envoy_cc_test_library( ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), + "//bazel:darwin": envoy_all_extensions(DARWIN_SKIP_TARGETS), "//conditions:default": envoy_all_extensions(), }), ) diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index 009eec46583f2..2cd465c5c804f 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -47,7 +47,11 @@ OptionsImpl asConfigYaml(const OptionsImpl& src, Api::Api& api) { static std::vector unsuported_win32_configs = { #if defined(WIN32) && !defined(SO_ORIGINAL_DST) - "configs_original-dst-cluster_proxy_config.yaml" + "configs_original-dst-cluster_proxy_config.yaml", +#endif +#if defined(ALIMESH) + // The test platform does not support udp Gro feature. + "udp_envoy.yaml" #endif }; diff --git a/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc b/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc index f58402b18fe1f..04dabd635ae57 100644 --- a/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc @@ -3,6 +3,7 @@ #include "envoy/config/core/v3/grpc_service.pb.h" #include "envoy/extensions/access_loggers/grpc/v3/als.pb.h" +#include "envoy/extensions/access_loggers/open_telemetry/v3/logs_service.pb.h" #include "source/common/buffer/zero_copy_input_stream_impl.h" #include "source/common/protobuf/protobuf.h" diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 0d9e895445de8..6e036339c0245 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -143,7 +143,14 @@ TEST_P(WasmTestMatrix, LoggingWithEnvVars) { createWasm(); setWasmCode("logging"); auto wasm_weak = std::weak_ptr(wasm_); + +#ifdef ALIMESH + // auto wasm_handler = + // std::make_unique(std::move(wasm_), *dispatcher_); + auto wasm_handler = std::make_unique(std::move(wasm_)); +#else auto wasm_handler = std::make_unique(std::move(wasm_)); +#endif EXPECT_TRUE(wasm_weak.lock()->load(code_, false)); EXPECT_TRUE(wasm_weak.lock()->initialize()); diff --git a/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc b/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc index ef78accf9773c..475f8060c12cc 100644 --- a/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc +++ b/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc @@ -126,7 +126,7 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { // Encode an untyped map object as fourth parameter. encoder.encode(attach.attachment()); - size_t expected_attachment_offset = buffer.length(); + // size_t expected_attachment_offset = buffer.length(); // Encode attachment encoder.encode(attach.attachment()); @@ -154,30 +154,30 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { EXPECT_EQ(4, result_params->size()); - EXPECT_EQ("test_string", result_params->at(0)->toString().value().get()); - EXPECT_EQ(4, result_params->at(1)->toBinary().value().get().at(4)); + EXPECT_EQ("test_string", *(result_params->at(0)->toString().value())); + EXPECT_EQ(4, result_params->at(1)->toBinary().value()->at(4)); EXPECT_EQ(233333, *result_params->at(2)->toLong()); - EXPECT_EQ(3, result_params->at(3)->toUntypedMap().value().get().size()); - EXPECT_EQ("test_value2", result_params->at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - - auto& result_attach = invo->mutableAttachment(); - EXPECT_EQ("test_value2", result_attach->attachment() - .toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - - EXPECT_EQ(expected_attachment_offset, result_attach->attachmentOffset()); + EXPECT_EQ(3, result_params->at(3)->toUntypedMap().value()->size()); + // EXPECT_EQ("test_value2", result_params->at(3) + // ->toUntypedMap() + // .value() + // .get() + // .find("test2") + // ->second->toString() + // .value() + // .get()); + + // auto& result_attach = invo->mutableAttachment(); + // EXPECT_EQ("test_value2", result_attach->attachment() + // .toUntypedMap() + // .value() + // .get() + // .find("test2") + // ->second->toString() + // .value() + // .get()); + + // EXPECT_EQ(expected_attachment_offset, result_attach->attachmentOffset()); } { Buffer::OwnedImpl buffer; @@ -218,24 +218,20 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { EXPECT_EQ(true, invo->hasAttachment()); EXPECT_EQ(true, invo->hasParameters()); - EXPECT_EQ("test_value2", result_attach->attachment() - .toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); + EXPECT_EQ("test_value2", *(result_attach->attachment() + .toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); auto& result_params = invo->parameters(); - EXPECT_EQ("test_value2", result_params.at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); + EXPECT_EQ("test_value2", *(result_params.at(3) + ->toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); } // Test case that request only have parameters. { @@ -275,16 +271,14 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { EXPECT_EQ(true, invo->hasParameters()); auto& result_params = invo->parameters(); - EXPECT_EQ("test_value2", result_params.at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - - EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value().get().empty()); + EXPECT_EQ("test_value2", *(result_params.at(3) + ->toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); + + EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value()->empty()); } // Test the case where there are not enough parameters in the request buffer. { @@ -349,7 +343,7 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { auto invo = dynamic_cast(result.get()); auto& result_attach = invo->mutableAttachment(); - EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value().get().empty()); + EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value()->empty()); } } diff --git a/test/extensions/common/dubbo/message_test.cc b/test/extensions/common/dubbo/message_test.cc index faa9336387364..6565d6312ff63 100644 --- a/test/extensions/common/dubbo/message_test.cc +++ b/test/extensions/common/dubbo/message_test.cc @@ -27,7 +27,7 @@ TEST(RpcRequestImplTest, RpcRequestAttachmentTest) { RpcRequestImpl::Attachment attachment(std::move(map), 23333); - EXPECT_EQ(4, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(4, attachment.attachment().toUntypedMap().value()->size()); // Test lookup. EXPECT_EQ(absl::nullopt, attachment.lookup("map_key")); @@ -40,15 +40,15 @@ TEST(RpcRequestImplTest, RpcRequestAttachmentTest) { attachment.remove("fake_key"); EXPECT_EQ(absl::nullopt, attachment.lookup("fake_key")); - EXPECT_EQ(3, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(3, attachment.attachment().toUntypedMap().value()->size()); // Test remove. Delete a key/value pair whose value type is map. attachment.remove("map_key"); - EXPECT_EQ(2, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(2, attachment.attachment().toUntypedMap().value()->size()); // Test insert. attachment.insert("test", "test_value"); - EXPECT_EQ(3, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(3, attachment.attachment().toUntypedMap().value()->size()); EXPECT_EQ("test_value", *attachment.lookup("test")); diff --git a/test/extensions/common/wasm/context_test.cc b/test/extensions/common/wasm/context_test.cc index 2ca56d9c1b69b..cdb362c3f1a88 100644 --- a/test/extensions/common/wasm/context_test.cc +++ b/test/extensions/common/wasm/context_test.cc @@ -243,6 +243,28 @@ TEST_F(ContextTest, FindValueTest) { EXPECT_FALSE(ctx_.FindValue("plugin_name", &arena).has_value()); } +#ifdef ALIMESH +TEST_F(ContextTest, SetCustomSpanTagTest) { + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + Envoy::StreamInfo::MockStreamInfo decoder_si; + EXPECT_CALL(decoder_callbacks, streamInfo()) + .Times(1) + .WillOnce(testing::ReturnRef(decoder_si)); + ctx_.setDecoderFilterCallbacksPtr(&decoder_callbacks); + absl::flat_hash_map map; + ON_CALL(decoder_si, setCustomSpanTag(testing::_, testing::_)) + .WillByDefault([&](absl::string_view key, absl::string_view value) { + map[key] = value; + }); + EXPECT_CALL(decoder_si, setCustomSpanTag(testing::_, testing::_)) + .Times(1); + ctx_.setProperty("trace_span_tag.test_key", "test_value"); + const auto& it = map.find("test_key"); + EXPECT_TRUE(it != map.end()); + EXPECT_EQ(it->second, "test_value"); +} +#endif + } // namespace Wasm } // namespace Common } // namespace Extensions diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index f5e763e4851a1..282f4f2b92d0f 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -103,9 +103,16 @@ TEST_P(WasmCommonTest, WasmFailState) { envoy::extensions::wasm::v3::PluginConfig plugin_config; auto plugin = std::make_shared( plugin_config, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); - +#ifdef ALIMESH + // auto wasm = std::make_shared( + // std::make_unique(plugin->wasmConfig(), "", scope, *api, cluster_manager, *dispatcher), + // *dispatcher); + auto wasm = std::make_shared( + std::make_unique(plugin->wasmConfig(), "", scope, *api, cluster_manager, *dispatcher)); +#else auto wasm = std::make_shared( std::make_unique(plugin->wasmConfig(), "", scope, *api, cluster_manager, *dispatcher)); +#endif auto wasm_base = std::dynamic_pointer_cast(wasm); wasm->wasm()->setFailStateForTesting(proxy_wasm::FailState::UnableToCreateVm); EXPECT_EQ(toWasmEvent(wasm_base), WasmEvent::UnableToCreateVm); @@ -196,7 +203,13 @@ TEST_P(WasmCommonTest, Logging) { [](Wasm*, const std::shared_ptr&) -> ContextBase* { return nullptr; }); EXPECT_EQ(std::unique_ptr(wasm->createContext(plugin)), nullptr); auto wasm_weak = std::weak_ptr(wasm); +#ifdef ALIMESH + // auto wasm_handle = + // std::make_shared(std::move(wasm), *dispatcher); + auto wasm_handle = std::make_shared(std::move(wasm)); +#else auto wasm_handle = std::make_shared(std::move(wasm)); +#endif EXPECT_TRUE(wasm_weak.lock()->load(code, false)); EXPECT_TRUE(wasm_weak.lock()->initialize()); auto thread_local_wasm = std::make_shared(wasm_handle, *dispatcher); @@ -704,7 +717,12 @@ TEST_P(WasmCommonTest, VmCache) { EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); +#ifdef ALIMESH + // return std::make_shared(wasm, *dispatcher); return std::make_shared(wasm); +#else + return std::make_shared(wasm); +#endif }, [](const WasmHandleBaseSharedPtr& wasm_handle, const PluginBaseSharedPtr& plugin) -> PluginHandleBaseSharedPtr { @@ -821,7 +839,12 @@ TEST_P(WasmCommonTest, RemoteCode) { EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); +#ifdef ALIMESH + return std::make_shared(wasm); + // return std::make_shared(wasm, *dispatcher); +#else return std::make_shared(wasm); +#endif }, [](const WasmHandleBaseSharedPtr& wasm_handle, const PluginBaseSharedPtr& plugin) -> PluginHandleBaseSharedPtr { @@ -942,7 +965,12 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); +#ifdef ALIMESH return std::make_shared(wasm); + // return std::make_shared(wasm, *dispatcher); +#else + return std::make_shared(wasm); +#endif }, [](const WasmHandleBaseSharedPtr& wasm_handle, const PluginBaseSharedPtr& plugin) -> PluginHandleBaseSharedPtr { @@ -1273,7 +1301,13 @@ TEST_P(WasmCommonTest, ThreadLocalCopyRetainsEnforcement) { EXPECT_TRUE(wasm->load(code, false)); EXPECT_TRUE(wasm->initialize()); +#ifdef ALIMESH + // auto wasm_handle = + // std::make_shared(std::move(wasm), *dispatcher); + auto wasm_handle = std::make_shared(std::move(wasm)); +#else auto wasm_handle = std::make_shared(std::move(wasm)); +#endif auto thread_local_wasm = std::make_shared(wasm_handle, *dispatcher); EXPECT_NE(thread_local_wasm, nullptr); diff --git a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc index 3ab57375af021..b4389de8ef7e6 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc @@ -298,6 +298,87 @@ TEST_F(ExtAuthzHttpClientTest, ContentLengthEqualZeroWithAllowedHeaders) { EXPECT_EQ(message_ptr->headers().getMethodValue(), "POST"); } +#if defined(ALIMESH) +TEST_F(ExtAuthzHttpClientTest, IsAuthorizationPass) { + { + // 200 code without x-mse-external-authz-check-result + const auto expected_headers = TestCommon::makeHeaderValueOption({{":status", "200", false}}); + auto check_response = TestCommon::makeMessageResponse(expected_headers); + EXPECT_TRUE(isAuthorizationPass(check_response->headers())); + } + + { // 200 code with x-mse-external-authz-check-result value is true + const auto expected_headers = TestCommon::makeHeaderValueOption( + {{":status", "200", false}, {"x-mse-external-authz-check-result", "true", false}}); + auto check_response = TestCommon::makeMessageResponse(expected_headers); + EXPECT_TRUE(isAuthorizationPass(check_response->headers())); + } + + { // 200 code with x-mse-external-authz-check-result value is false + const auto expected_headers = TestCommon::makeHeaderValueOption( + {{":status", "200", false}, {"x-mse-external-authz-check-result", "false", false}}); + auto check_response = TestCommon::makeMessageResponse(expected_headers); + EXPECT_FALSE(isAuthorizationPass(check_response->headers())); + } + + { // nor 200 code with x-mse-external-authz-check-result value is true + const auto expected_headers = TestCommon::makeHeaderValueOption( + {{":status", "503", false}, {"x-mse-external-authz-check-result", "true", false}}); + auto check_response = TestCommon::makeMessageResponse(expected_headers); + EXPECT_FALSE(isAuthorizationPass(check_response->headers())); + } +} + +TEST_F(ExtAuthzHttpClientTest, AuthorizationOkWithXMseExternalAuthzCheckResultTrue) { + const auto expected_headers = TestCommon::makeHeaderValueOption( + {{":status", "200", false}, {"x-mse-external-authz-check-result", "true", false}}); + const auto authz_response = TestCommon::makeAuthzResponse(CheckStatus::OK); + auto check_response = TestCommon::makeMessageResponse(expected_headers); + envoy::service::auth::v3::CheckRequest request; + client_->check(request_callbacks_, request, parent_span_, stream_info_); + + EXPECT_CALL(request_callbacks_, + onComplete_(WhenDynamicCastTo(AuthzOkResponse(authz_response)))); + client_->onSuccess(async_request_, std::move(check_response)); +} + +TEST_F(ExtAuthzHttpClientTest, AuthorizationDeniedWithXMseExternalAuthzCheckResultTrueButCode403) { + const auto expected_headers = TestCommon::makeHeaderValueOption( + {{":status", "403", false}, {"x-mse-external-authz-check-result", "true", false}}); + const auto authz_response = TestCommon::makeAuthzResponse( + CheckStatus::Denied, Http::Code::Forbidden, EMPTY_STRING, expected_headers); + auto check_response = TestCommon::makeMessageResponse(expected_headers); + + envoy::service::auth::v3::CheckRequest request; + client_->check(request_callbacks_, request, parent_span_, stream_info_); + + // Check for child span tagging when the request is denied. + EXPECT_CALL(child_span_, setTag(Eq("ext_authz_http_status"), Eq("Forbidden"))); + EXPECT_CALL(child_span_, setTag(Eq("ext_authz_status"), Eq("ext_authz_unauthorized"))); + client_->onBeforeFinalizeUpstreamSpan(child_span_, &check_response->headers()); + + EXPECT_CALL(request_callbacks_, + onComplete_(WhenDynamicCastTo(AuthzDeniedResponse(authz_response)))); + client_->onSuccess(async_request_, TestCommon::makeMessageResponse(expected_headers)); +} + +TEST_F(ExtAuthzHttpClientTest, AuthorizationDeniedWithCode200ButXMseExternalAuthzCheckResultFalse) { + const auto expected_body = std::string{"test"}; + const auto expected_headers = TestCommon::makeHeaderValueOption( + {{":status", "200", false}, {"x-mse-external-authz-check-result", "false", false}}); + const auto authz_response = TestCommon::makeAuthzResponse(CheckStatus::Denied, Http::Code::OK, + expected_body, expected_headers); + + envoy::service::auth::v3::CheckRequest request; + client_->check(request_callbacks_, request, parent_span_, stream_info_); + + EXPECT_CALL(request_callbacks_, + onComplete_(WhenDynamicCastTo(AuthzDeniedResponse(authz_response)))); + client_->onSuccess(async_request_, + TestCommon::makeMessageResponse(expected_headers, expected_body)); +} +#endif + // Verify client response when authorization server returns a 200 OK. TEST_F(ExtAuthzHttpClientTest, AuthorizationOk) { const auto expected_headers = TestCommon::makeHeaderValueOption({{":status", "200", false}}); diff --git a/test/extensions/filters/http/buffer/config_test.cc b/test/extensions/filters/http/buffer/config_test.cc index 6455a525e9f94..ae58549733d06 100644 --- a/test/extensions/filters/http/buffer/config_test.cc +++ b/test/extensions/filters/http/buffer/config_test.cc @@ -50,7 +50,7 @@ TEST(BufferFilterFactoryTest, BufferFilterCorrectProtoUpstreamFactory) { envoy::extensions::filters::http::buffer::v3::Buffer config; config.mutable_max_request_bytes()->set_value(1028); - NiceMock context; + NiceMock context; BufferFilterFactory factory; Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(config, "stats", context); Http::MockFilterChainFactoryCallbacks filter_callback; diff --git a/test/extensions/filters/http/common/empty_http_filter_config.h b/test/extensions/filters/http/common/empty_http_filter_config.h index c88ed671030d9..d28ac5fe737e4 100644 --- a/test/extensions/filters/http/common/empty_http_filter_config.h +++ b/test/extensions/filters/http/common/empty_http_filter_config.h @@ -50,9 +50,9 @@ class UpstreamFilterConfig : public Server::Configuration::UpstreamHttpFilterCon createDualFilter(const std::string& stat_prefix, Server::Configuration::ServerFactoryContext& context) PURE; - Http::FilterFactoryCb createFilterFactoryFromProto( - const Protobuf::Message&, const std::string& stat_prefix, - Server::Configuration::UpstreamHttpFactoryContext& context) override { + Http::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message&, const std::string& stat_prefix, + Server::Configuration::UpstreamFactoryContext& context) override { return createDualFilter(stat_prefix, context.getServerFactoryContext()); } }; diff --git a/test/extensions/filters/http/composite/BUILD b/test/extensions/filters/http/composite/BUILD index f846deb7b7785..bf7ee48d78f37 100644 --- a/test/extensions/filters/http/composite/BUILD +++ b/test/extensions/filters/http/composite/BUILD @@ -19,8 +19,12 @@ envoy_extension_cc_test( "//source/common/http:header_map_lib", "//source/extensions/filters/http/composite:config", "//source/extensions/filters/http/composite:filter_lib", + "//source/extensions/filters/http/fault:config", + "//source/extensions/filters/http/fault:fault_filter_lib", "//test/mocks/access_log:access_log_mocks", "//test/mocks/http:http_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:instance_mocks", ], ) diff --git a/test/extensions/filters/http/composite/composite_filter_integration_test.cc b/test/extensions/filters/http/composite/composite_filter_integration_test.cc index fe75b1a452133..46920f5d2f05d 100644 --- a/test/extensions/filters/http/composite/composite_filter_integration_test.cc +++ b/test/extensions/filters/http/composite/composite_filter_integration_test.cc @@ -119,11 +119,111 @@ class CompositeFilterIntegrationTest : public testing::TestWithParamadd_clusters(); + ecds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + ecds_cluster->set_name("ecds_cluster"); + ecds_cluster->mutable_load_assignment()->set_cluster_name("ecds_cluster"); + }); + HttpIntegrationTest::initialize(); + } + + void createUpstreams() override { + BaseIntegrationTest::createUpstreams(); + addFakeUpstream(Http::CodecType::HTTP2); + } }; INSTANTIATE_TEST_SUITE_P(IpVersions, CompositeFilterIntegrationTest, @@ -153,6 +253,51 @@ TEST_P(CompositeFilterIntegrationTest, TestBasic) { } } +// Verifies that if we don't match the match action the request is proxied as normal, while if the +// match action is hit we apply the specified dynamic filter to the stream. +TEST_P(CompositeFilterIntegrationTest, TestBasicDynamicFilter) { + prependCompositeDynamicFilter("composite-dynamic"); + initialize(); + test_server_->waitForCounterGe( + "extension_config_discovery.http_filter.set-response-code.config_reload", 1); + test_server_->waitUntilListenersReady(); + test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + { + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_THAT(response->headers(), Http::HttpStatusIs("200")); + } + + { + auto response = codec_client_->makeRequestWithBody(match_request_headers_, 1024); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_THAT(response->headers(), Http::HttpStatusIs("403")); + } +} + +// Verifies that if ECDS response is not sent, the missing filter config is applied that returns +// 500. +TEST_P(CompositeFilterIntegrationTest, TestMissingDynamicFilter) { + prependMissingCompositeDynamicFilter("composite-dynamic-missing"); + + initialize(); + test_server_->waitForCounterGe( + "extension_config_discovery.http_filter.missing-config.config_fail", 1); + test_server_->waitUntilListenersReady(); + test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeRequestWithBody(match_request_headers_, 1024); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_THAT(response->headers(), Http::HttpStatusIs("500")); +} + // Verifies function of the per-route config in the ExtensionWithMatcher class. TEST_P(CompositeFilterIntegrationTest, TestPerRoute) { prependCompositeFilter(); diff --git a/test/extensions/filters/http/composite/filter_test.cc b/test/extensions/filters/http/composite/filter_test.cc index 4085800097ac1..acaf9814e2867 100644 --- a/test/extensions/filters/http/composite/filter_test.cc +++ b/test/extensions/filters/http/composite/filter_test.cc @@ -2,10 +2,13 @@ #include "envoy/http/metadata_interface.h" +#include "source/extensions/filters/http/composite/action.h" #include "source/extensions/filters/http/composite/filter.h" #include "test/mocks/access_log/mocks.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/instance.h" #include "gtest/gtest.h" @@ -234,6 +237,43 @@ TEST_F(FilterTest, StreamFilterDelegationMultipleAccessLoggers) { AccessLog::AccessLogType::NotSet); } +// Validate that when dynamic_config and typed_config are both set, an exception is thrown. +TEST(ConfigTest, TestConfig) { + const std::string yaml_string = R"EOF( + typed_config: + name: set-response-code + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault + abort: + http_status: 503 + percentage: + numerator: 0 + denominator: HUNDRED + dynamic_config: + name: set-response-code + config_discovery: + config_source: + resource_api_version: V3 + path_config_source: + path: "{{ test_tmpdir }}/set_response_code.yaml" + type_urls: + - type.googleapis.com/test.integration.filters.SetResponseCodeFilterConfig +)EOF"; + + envoy::extensions::filters::http::composite::v3::ExecuteFilterAction config; + TestUtility::loadFromYaml(yaml_string, config); + + testing::NiceMock server_factory_context; + testing::NiceMock factory_context; + Envoy::Http::Matching::HttpFilterActionContext action_context{"test", factory_context, + server_factory_context}; + ExecuteFilterActionFactory factory; + EXPECT_THROW_WITH_MESSAGE( + factory.createActionFactoryCb(config, action_context, + ProtobufMessage::getStrictValidationVisitor()), + EnvoyException, "Error: Only one of `dynamic_config` or `typed_config` can be set."); +} + } // namespace } // namespace Composite } // namespace HttpFilters diff --git a/test/extensions/filters/http/cors/cors_filter_integration_test.cc b/test/extensions/filters/http/cors/cors_filter_integration_test.cc index 97911cf365666..a6374046bda32 100644 --- a/test/extensions/filters/http/cors/cors_filter_integration_test.cc +++ b/test/extensions/filters/http/cors/cors_filter_integration_test.cc @@ -230,6 +230,12 @@ class CorsFilterIntegrationTest : public testing::TestWithParam, Http::TestResponseHeaderMapImpl& expected_response_headers) { response_headers.remove(Envoy::Http::LowerCaseString{"date"}); response_headers.remove(Envoy::Http::LowerCaseString{"x-envoy-upstream-service-time"}); +#if defined(ALIMESH) + response_headers.remove(Envoy::Http::LowerCaseString{"req-cost-time"}); + response_headers.remove(Envoy::Http::LowerCaseString{"req-start-time"}); + response_headers.remove(Envoy::Http::LowerCaseString{"req-arrive-time"}); + response_headers.remove(Envoy::Http::LowerCaseString{"resp-start-time"}); +#endif EXPECT_EQ(expected_response_headers, response_headers); } }; diff --git a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc index 380811ce60fe7..c952c5c55ea77 100644 --- a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc +++ b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc @@ -39,6 +39,10 @@ class CustomResponseFilterTest : public testing::Test { filter_ = std::make_unique(config_); filter_->setEncoderFilterCallbacks(encoder_callbacks_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); +#if defined(ALIMESH) + ON_CALL(decoder_callbacks_, recreateStream(_)).WillByDefault(Return(true)); + ON_CALL(decoder_callbacks_, recreateStream(_, _)).WillByDefault(Return(true)); +#endif } void createConfig(const absl::string_view config_str = kDefaultConfig) { @@ -91,7 +95,11 @@ TEST_F(CustomResponseFilterTest, RemoteData) { ::Envoy::Http::TestRequestHeaderMapImpl request_headers{}; EXPECT_EQ(filter_->decodeHeaders(request_headers, false), ::Envoy::Http::FilterHeadersStatus::Continue); +#if defined(ALIMESH) + EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)); +#else EXPECT_CALL(decoder_callbacks_, recreateStream(_)); +#endif EXPECT_EQ(filter_->encodeHeaders(response_headers, true), ::Envoy::Http::FilterHeadersStatus::StopIteration); } @@ -206,6 +214,383 @@ TEST_F(CustomResponseFilterTest, InvalidSchemeRedirect) { stats_store_.findCounterByString("stats.custom_response_invalid_uri").value().get().value()); } +#if defined(ALIMESH) +TEST_F(CustomResponseFilterTest, SingleRedirectCustomStatus) { + // Create config with invalid scheme field. + createConfig(R"EOF( + custom_response_matcher: + matcher_list: + matchers: + # Redirect to different upstream if the status code is one of 502. + - predicate: + single_predicate: + input: + name: "502_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "502" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + status_code: 299 + max_internal_redirects: 1 + uri: "https://foo.example/gateway_error" + response_headers_to_add: + - header: + key: "foo2" + value: "x-bar2" +)EOF"); + setupFilterAndCallback(); + + setServerName("server1.example.foo"); + ::Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "502"}}; + ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); +#if defined(ALIMESH) + EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)); +#else + EXPECT_CALL(decoder_callbacks_, recreateStream(_)); +#endif + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("foo.example", request_headers.getHostValue()); + // new stream + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ("299", response_headers.getStatusValue()); + EXPECT_EQ( + "x-bar2", + response_headers.get(::Envoy::Http::LowerCaseString("foo2"))[0]->value().getStringView()); +} + +TEST_F(CustomResponseFilterTest, MultiRedirectCustomStatus) { + // Create config with invalid scheme field. + createConfig(R"EOF( + custom_response_matcher: + matcher_list: + matchers: + # Redirect to different upstream if the status code is one of 502. + - predicate: + single_predicate: + input: + name: "502_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "502" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + status_code: 299 + max_internal_redirects: 2 + uri: "https://foo.example/gateway_error" + response_headers_to_add: + - header: + key: "foo1" + value: "x-bar1" + request_headers_to_add: + - header: + key: "foo2" + value: "x-bar2" + - predicate: + single_predicate: + input: + name: "503_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "503" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + status_code: 298 + max_internal_redirects: 2 + uri: "https://bar.example/gateway_error" + response_headers_to_add: + - header: + key: "foo2" + value: "x-bar2" +)EOF"); + setupFilterAndCallback(); + + setServerName("server1.example.foo"); + ::Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "502"}}; + ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)).Times(2); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("foo.example", request_headers.getHostValue()); + EXPECT_EQ("x-bar2", request_headers.getByKey("foo2")); + EXPECT_EQ("502", response_headers.getStatusValue()); + EXPECT_TRUE(response_headers.get(::Envoy::Http::LowerCaseString("foo1")).empty()); + // new stream + response_headers = {{":status", "503"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("503", response_headers.getStatusValue()); + EXPECT_TRUE(response_headers.get(::Envoy::Http::LowerCaseString("foo2")).empty()); + // new stream + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ("298", response_headers.getStatusValue()); + EXPECT_TRUE(response_headers.get(::Envoy::Http::LowerCaseString("foo1")).empty()); + EXPECT_EQ( + "x-bar2", + response_headers.get(::Envoy::Http::LowerCaseString("foo2"))[0]->value().getStringView()); +} + +TEST_F(CustomResponseFilterTest, KeepOriginalResponseCode) { + // Create config with invalid scheme field. + createConfig(R"EOF( + custom_response_matcher: + matcher_list: + matchers: + # Redirect to different upstream if the status code is one of 502. + - predicate: + single_predicate: + input: + name: "502_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "502" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + max_internal_redirects: 1 + uri: "https://foo.example/gateway_error" +)EOF"); + setupFilterAndCallback(); + + setServerName("server1.example.foo"); + ::Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "502"}}; + ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("foo.example", request_headers.getHostValue()); + response_headers = {{":status", "200"}}; + // new stream + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ("502", response_headers.getStatusValue()); +} + +TEST_F(CustomResponseFilterTest, DontKeepOriginalResponseCode) { + // Create config with invalid scheme field. + createConfig(R"EOF( + custom_response_matcher: + matcher_list: + matchers: + # Redirect to different upstream if the status code is one of 502. + - predicate: + single_predicate: + input: + name: "502_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "502" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + max_internal_redirects: 1 + uri: "https://foo.example/gateway_error" + keep_original_response_code: false +)EOF"); + setupFilterAndCallback(); + + setServerName("server1.example.foo"); + ::Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "502"}}; + ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("foo.example", request_headers.getHostValue()); + response_headers = {{":status", "200"}}; + // new stream + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ("200", response_headers.getStatusValue()); +} + +TEST_F(CustomResponseFilterTest, UseOriginalRequest) { + // Create config with invalid scheme field. + createConfig(R"EOF( + custom_response_matcher: + matcher_list: + matchers: + # Redirect to different upstream if the status code is one of 502. + - predicate: + single_predicate: + input: + name: "502_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "502" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + max_internal_redirects: 1 + use_original_request_uri: true + keep_original_response_code: false +)EOF"); + setupFilterAndCallback(); + + setServerName("server1.example.foo"); + ::Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "502"}}; + ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}, + {":path", "/example"}, + {"X-Envoy-Original-Host", "foo.example"}, + {"X-Envoy-Original-Path", "/foo"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("foo.example", request_headers.getHostValue()); + EXPECT_EQ("/foo", request_headers.getPathValue()); + response_headers = {{":status", "200"}}; + // new stream + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ("200", response_headers.getStatusValue()); +} + +TEST_F(CustomResponseFilterTest, UseOriginalRequestBody) { + // Create config with invalid scheme field. + createConfig(R"EOF( + custom_response_matcher: + matcher_list: + matchers: + # Redirect to different upstream if the status code is one of 502. + - predicate: + single_predicate: + input: + name: "502_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "502" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + max_internal_redirects: 1 + use_original_request_uri: true + use_original_request_body: true + keep_original_response_code: false +)EOF"); + setupFilterAndCallback(); + + setServerName("server1.example.foo"); + ::Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "502"}}; + ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}, + {":path", "/example"}, + {"X-Envoy-Original-Host", "foo.example"}, + {"X-Envoy-Original-Path", "/foo"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_CALL(decoder_callbacks_, recreateStream(_, true)); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("foo.example", request_headers.getHostValue()); + EXPECT_EQ("/foo", request_headers.getPathValue()); + response_headers = {{":status", "200"}}; + // new stream + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ("200", response_headers.getStatusValue()); +} + +TEST_F(CustomResponseFilterTest, OnlyRedirectUpstreamCode) { + // Create config with invalid scheme field. + createConfig(R"EOF( + custom_response_matcher: + matcher_list: + matchers: + # Redirect to different upstream if the status code is one of 502. + - predicate: + single_predicate: + input: + name: "502_response" + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput + value_match: + exact: "502" + on_match: + action: + name: gateway_error_action + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.custom_response.redirect_policy.v3.RedirectPolicy + max_internal_redirects: 1 + use_original_request_uri: true + use_original_request_body: true + keep_original_response_code: false + only_redirect_upstream_code: true +)EOF"); + setupFilterAndCallback(); + + setServerName("server1.example.foo"); + ::Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "502"}}; + ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}, + {":path", "/example"}, + {"X-Envoy-Original-Host", "foo.example"}, + {"X-Envoy-Original-Path", "/foo"}}; + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_EQ("example.foo", request_headers.getHostValue()); + EXPECT_EQ("/example", request_headers.getPathValue()); + encoder_callbacks_.streamInfo().setResponseCodeDetails( + ::Envoy::StreamInfo::ResponseCodeDetails::get().ViaUpstream); + EXPECT_EQ(filter_->decodeHeaders(request_headers, false), + ::Envoy::Http::FilterHeadersStatus::Continue); + EXPECT_CALL(decoder_callbacks_, recreateStream(_, true)); + EXPECT_EQ(filter_->encodeHeaders(response_headers, true), + ::Envoy::Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ("foo.example", request_headers.getHostValue()); + EXPECT_EQ("/foo", request_headers.getPathValue()); +} + +#endif } // namespace } // namespace CustomResponse } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/utils.cc b/test/extensions/filters/http/ext_proc/utils.cc index 0e322535c08af..dda06aef0f4bc 100644 --- a/test/extensions/filters/http/ext_proc/utils.cc +++ b/test/extensions/filters/http/ext_proc/utils.cc @@ -9,7 +9,12 @@ namespace ExternalProcessing { const absl::flat_hash_set ExtProcTestUtility::ignoredHeaders() { CONSTRUCT_ON_FIRST_USE(absl::flat_hash_set, "x-request-id", +#ifdef ALIMESH + "x-envoy-upstream-service-time", "req-cost-time", "req-arrive-time", + "resp-start-time"); +#else "x-envoy-upstream-service-time"); +#endif } bool ExtProcTestUtility::headerProtosEqualIgnoreOrder( diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 805df65cce86a..538be14290333 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -127,7 +127,6 @@ envoy_extension_cc_test( extension_names = ["envoy.filters.http.jwt_authn"], deps = [ ":mock_lib", - "//source/common/common:base64_lib", "//source/extensions/filters/http/common:jwks_fetcher_lib", "//source/extensions/filters/http/jwt_authn:authenticator_lib", "//source/extensions/filters/http/jwt_authn:filter_config_lib", diff --git a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc index 0b53f52e00c73..4ab8b2dcfdebb 100644 --- a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc +++ b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc @@ -134,22 +134,28 @@ TEST_F(OnDemandFilterTest, TEST_F(OnDemandFilterTest, TestOnRouteConfigUpdateCompletionContinuesDecodingWithRedirectWithBody) { Buffer::OwnedImpl buffer; EXPECT_CALL(decoder_callbacks_, continueDecoding()); +#ifndef ALIMESH EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(&buffer)); +#endif filter_->onRouteConfigUpdateCompletion(true); } // tests onRouteConfigUpdateCompletion() when ActiveStream recreation fails TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionContinuesDecodingIfRedirectFails) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); +#ifndef ALIMESH EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(false)); +#endif filter_->onRouteConfigUpdateCompletion(true); } // tests onRouteConfigUpdateCompletion() when route was resolved TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionRestartsActiveStream) { +#ifndef ALIMESH EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(true)); +#endif filter_->onRouteConfigUpdateCompletion(true); } diff --git a/test/extensions/filters/http/tap/tap_filter_integration_test.cc b/test/extensions/filters/http/tap/tap_filter_integration_test.cc index 97782b4206bcc..db206deb8c7f8 100644 --- a/test/extensions/filters/http/tap/tap_filter_integration_test.cc +++ b/test/extensions/filters/http/tap/tap_filter_integration_test.cc @@ -333,8 +333,13 @@ config_id: test_config_id admin_response_->waitForBodyData(1); envoy::data::tap::v3::TraceWrapper trace; TestUtility::loadFromYaml(admin_response_->body(), trace); +#if defined(ALIMESH) + EXPECT_EQ(trace.http_buffered_trace().request().headers().size(), 10); + EXPECT_EQ(trace.http_buffered_trace().response().headers().size(), 7); +#else EXPECT_EQ(trace.http_buffered_trace().request().headers().size(), 8); EXPECT_EQ(trace.http_buffered_trace().response().headers().size(), 4); +#endif admin_response_->clearBody(); // Do a request which should not tap. @@ -346,11 +351,16 @@ config_id: test_config_id // Wait for the tap message. admin_response_->waitForBodyData(1); TestUtility::loadFromYaml(admin_response_->body(), trace); +#if defined(ALIMESH) + EXPECT_EQ(trace.http_buffered_trace().request().headers().size(), 9); + EXPECT_EQ(trace.http_buffered_trace().response().headers().size(), 8); +#else EXPECT_EQ(trace.http_buffered_trace().request().headers().size(), 7); + EXPECT_EQ(trace.http_buffered_trace().response().headers().size(), 5); +#endif EXPECT_EQ( "http", findHeader("x-forwarded-proto", trace.http_buffered_trace().request().headers())->value()); - EXPECT_EQ(trace.http_buffered_trace().response().headers().size(), 5); EXPECT_NE(nullptr, findHeader("date", trace.http_buffered_trace().response().headers())); EXPECT_EQ("baz", findHeader("bar", trace.http_buffered_trace().response().headers())->value()); diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index ff6c6b137c35a..eba2ce76d49e9 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -122,6 +122,7 @@ envoy_cc_test_library( "test_cpp_null_plugin.cc", "test_grpc_call_cpp.cc", "test_grpc_stream_cpp.cc", + "test_redis_call_cpp.cc", "test_resume_call_cpp.cc", "test_shared_data_cpp.cc", "test_shared_queue_cpp.cc", @@ -148,6 +149,7 @@ envoy_wasm_cc_binary( "test_cpp.cc", "test_grpc_call_cpp.cc", "test_grpc_stream_cpp.cc", + "test_redis_call_cpp.cc", "test_panic_cpp.cc", "test_resume_call_cpp.cc", "test_shared_data_cpp.cc", diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index 3ddb839246306..9ea317568fc00 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -63,9 +63,13 @@ bool TestRootContext::onConfigure(size_t size) { { // Many properties are not available in the root context. const std::vector properties = { - "string_state", "metadata", "request", "response", "connection", - "connection_id", "upstream", "source", "destination", "cluster_name", - "cluster_metadata", "route_name", "route_metadata", "upstream_host_metadata", + "string_state", "metadata", + "request", "response", + "connection", "connection_id", + "upstream", "source", + "destination", "cluster_name", + "cluster_metadata", "route_name", + "route_metadata", "upstream_host_metadata", "filter_state", }; for (const auto& property : properties) { @@ -274,7 +278,50 @@ FilterHeadersStatus TestContext::onRequestHeaders(uint32_t, bool) { } return FilterHeadersStatus::Continue; + } else if (test == "GetRouteName") { + std::string value; + if (getValue({"route_name"}, &value)) { + logInfo("route name is " + value); + } else { + logError("get route name failed"); + } + return FilterHeadersStatus::Continue; + } else if (test == "CrashRecover") { + if (!getRequestHeader("crash")->toString().empty()) { + abort(); + } + } else if (test == "DisableClearRouteCache") { + setFilterState("clear_route_cache", "off"); + logDebug(std::string("onRequestHeaders ") + std::to_string(id()) + std::string(" ") + test); + auto path = getRequestHeader(":path"); + logInfo(std::string("header path ") + std::string(path->view())); + std::string protocol; + addRequestHeader("newheader", "newheadervalue"); + auto server = getRequestHeader("server"); + replaceRequestHeader("server", "envoy-wasm"); + auto r = addResponseHeader("bad", "bad"); + if (r != WasmResult::BadArgument) { + logWarn("unexpected success of addResponseHeader"); + } + if (addResponseTrailer("bad", "bad") != WasmResult::BadArgument) { + logWarn("unexpected success of addResponseTrailer"); + } + if (removeResponseTrailer("bad") != WasmResult::BadArgument) { + logWarn("unexpected success of remoteResponseTrailer"); + } + size_t size; + if (getRequestHeaderSize(&size) != WasmResult::Ok) { + logWarn("unexpected failure of getRequestHeaderMapSize"); + } + if (getResponseHeaderSize(&size) != WasmResult::BadArgument) { + logWarn("unexpected success of getResponseHeaderMapSize"); + } + return FilterHeadersStatus::Continue; + } else if (test == "SetDecoderBufferLimit") { + auto buffer_size = getRequestHeader("x-buffer-size"); + setFilterState("set_decoder_buffer_limit", std::string(buffer_size->view())); } + return FilterHeadersStatus::Continue; } @@ -299,7 +346,15 @@ FilterHeadersStatus TestContext::onResponseHeaders(uint32_t, bool) { auto test = root()->test_; if (test == "headers") { CHECK_RESULT(addResponseHeader("test-status", "OK")); + } else if (test == "CrashRecover") { + if (!getResponseHeader("crash")->toString().empty()) { + abort(); + } + } else if (test == "SetEncoderBufferLimit") { + auto buffer_size = getResponseHeader("x-buffer-size"); + setFilterState("set_encoder_buffer_limit", std::string(buffer_size->view())); } + return FilterHeadersStatus::Continue; } @@ -340,17 +395,28 @@ FilterDataStatus TestContext::onRequestBody(size_t body_buffer_length, bool end_ } logTrace(std::string("Struct ") + request_string + " " + request_string2); return FilterDataStatus::Continue; + } else if (test == "CrashRecover") { + auto body = getBufferBytes(WasmBufferType::HttpRequestBody, 0, body_buffer_length); + if (!body->toString().empty()) { + abort(); + } } return FilterDataStatus::Continue; } -FilterDataStatus TestContext::onResponseBody(size_t, bool end_of_stream) { +FilterDataStatus TestContext::onResponseBody(size_t body_buffer_length, bool end_of_stream) { auto test = root()->test_; if (test == "headers") { if (end_of_stream) { CHECK_RESULT(addResponseTrailer("newtrailer", "response")); } + } else if (test == "CrashRecover") { + auto body = getBufferBytes(WasmBufferType::HttpResponseBody, 0, body_buffer_length); + if (!body->toString().empty()) { + abort(); + } } + return FilterDataStatus::Continue; } @@ -394,7 +460,8 @@ void TestContext::onLog() { logWarn("response.code: " + std::to_string(responseCode)); } std::string upstream_host_metadata; - if (getValue({"upstream_host_metadata", "filter_metadata", "namespace", "key"}, &upstream_host_metadata)) { + if (getValue({"upstream_host_metadata", "filter_metadata", "namespace", "key"}, + &upstream_host_metadata)) { logWarn("upstream host metadata: " + upstream_host_metadata); } logWarn("state: " + getProperty({"wasm_state"}).value()->toString()); diff --git a/test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc new file mode 100644 index 0000000000000..63f7d406fcb6a --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc @@ -0,0 +1,69 @@ +// NOLINT(namespace-envoy) +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics_lite.h" +#else +#include "source/extensions/common/wasm/ext/envoy_null_plugin.h" +#endif + +START_WASM_PLUGIN(HttpWasmTestCpp) + +class RedisCallContext : public Context { +public: + explicit RedisCallContext(uint32_t id, RootContext* root) : Context(id, root) {} + + FilterHeadersStatus onRequestHeaders(uint32_t, bool) override; +}; + +class RedisCallRootContext : public RootContext { +public: + explicit RedisCallRootContext(uint32_t id, std::string_view root_id) : RootContext(id, root_id) {} + + bool onConfigure(size_t) override; +}; + +static RegisterContextFactory register_RedisCallContext(CONTEXT_FACTORY(RedisCallContext), + ROOT_FACTORY(RedisCallRootContext), + "redis_call"); + +bool RedisCallRootContext::onConfigure(size_t) { + redisInit("cluster?db=1", "admin", "123456", 1000); + return true; +} + +FilterHeadersStatus RedisCallContext::onRequestHeaders(uint32_t, bool) { + auto context_id = id(); + auto callback = [context_id](RedisStatus, size_t body_size) { + if (body_size == 0) { + logInfo("redis_call failed"); + return; + } + + getContext(context_id)->setEffectiveContext(); + logWarn(std::string("bodysize: 5")); + auto response = getBufferBytes(WasmBufferType::RedisCallResponse, 0, body_size); + logDebug(std::string(response->view())); + }; + + // set id 1 + auto query = "*3\r\n$3\r\nset\r\n$2\r\nid\r\n$1\r\n1\r\n"; + auto path = getRequestHeader(":path"); + if (path->view() == "/bad") { + if (root()->redisCall("cluster?db=1", query, callback) != WasmResult::Ok) { + logInfo("redis_call rejected"); + } + } else { + if (root()->redisCall("bogus cluster", query, callback) == WasmResult::Ok) { + logError("bogus cluster found error"); + } + root()->redisCall("cluster?db=1", query, callback); + logInfo("onRequestHeaders"); + } + + return FilterHeadersStatus::StopIteration; +} + +END_WASM_PLUGIN diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 5ca67af6eb6fb..256280763966f 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -528,6 +528,24 @@ TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { filter().onDestroy(); } +#if defined(ALIMESH) +// Script that replaces the batched buffered body. +TEST_P(WasmHttpFilterTest, BodyRequestReplaceBatchedBufferedBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))).Times(2); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "ReplaceBufferedBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data, false)); + decoder_callbacks_.buffer_ = std::make_unique("replace"); + EXPECT_CALL(decoder_callbacks_, modifyDecodingBuffer(_, true)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} +#endif + // Script that replaces the first character in the buffered body. TEST_P(WasmHttpFilterTest, BodyRequestPartialReplaceBufferedBody) { setupTest("body"); @@ -737,7 +755,107 @@ TEST_P(WasmHttpFilterTest, AccessLogCreate) { AccessLog::AccessLogType::NotSet); filter().onDestroy(); } +#if defined(ALIMESH) +TEST_P(WasmHttpFilterTest, RedisCall) { + if (std::get<1>(GetParam()) == "rust") { + // This feature is not supported in rust + return; + } + + setupTest("redis_call"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + std::string redis_query{"*3\r\n$3\r\nset\r\n$2\r\nid\r\n$1\r\n1\r\n"}; + Redis::MockRedisPoolRequest redis_request( + &cluster_manager_.thread_local_cluster_.redis_async_client_, std::string(redis_query)); + Redis::AsyncClient::Callbacks* callbacks = nullptr; + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + + EXPECT_CALL(cluster_manager_.thread_local_cluster_, redisAsyncClient()); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.redis_async_client_, send_(_, _)) + .WillOnce( + Invoke([&](std::string& query, Redis::AsyncClient::Callbacks& cb) -> Redis::PoolRequest* { + EXPECT_EQ(redis_query, query); + callbacks = &cb; + return &redis_request; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("+OK\r\n"))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq("bodysize: 5"))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) + .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { + std::string response{"+OK\r\n"}; + callbacks->onSuccess(redis_request.request_, std::move(response)); + return proxy_wasm::WasmResult::Ok; + })); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + EXPECT_NE(callbacks, nullptr); +} + +TEST_P(WasmHttpFilterTest, DisableClearRouteCache) { + if (std::get<1>(GetParam()) == "rust") { + // This feature is not supported in rust test code + return; + } + + setupTest("", "DisableClearRouteCache"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), log_(spdlog::level::debug, + Eq(absl::string_view("onRequestHeaders 2 DisableClearRouteCache")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + + // Verify that route cache is cleared when modifying HTTP request headers. + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks.downstream_callbacks_, clearRouteCache()).Times(0); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); + Http::TestRequestTrailerMapImpl request_trailers{}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter().decodeTrailers(request_trailers)); + Http::MetadataMap request_metadata{}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().decodeMetadata(request_metadata)); +} + +TEST_P(WasmHttpFilterTest, SetDecoderBufferLimit) { + if (std::get<1>(GetParam()) == "rust") { + // This feature is not supported in rust test code + return; + } + + setupTest("", "SetDecoderBufferLimit"); + setupFilter(); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + Http::TestRequestHeaderMapImpl request_headers{{"x-buffer-size", "123456"}}; + EXPECT_CALL(decoder_callbacks, setDecoderBufferLimit(123456)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); +} +TEST_P(WasmHttpFilterTest, SetEncoderBufferLimit) { + if (std::get<1>(GetParam()) == "rust") { + // This feature is not supported in rust test code + return; + } + + setupTest("", "SetEncoderBufferLimit"); + setupFilter(); + Http::MockStreamEncoderFilterCallbacks encoder_callbacks; + filter().setEncoderFilterCallbacks(encoder_callbacks); + EXPECT_CALL(encoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + // Create in-VM context. + filter().onCreate(); + Http::TestResponseHeaderMapImpl response_headers{{"x-buffer-size", "123456"}}; + EXPECT_CALL(encoder_callbacks, setEncoderBufferLimit(123456)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); +} +#endif TEST_P(WasmHttpFilterTest, AsyncCall) { setupTest("async_call"); setupFilter(); @@ -1714,6 +1832,125 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { } } +#if defined(ALIMESH) +TEST_P(WasmHttpFilterTest, GetRouteName) { + if (std::get<1>(GetParam()) == "rust") { + return; + } + setupTest("", "GetRouteName"); + setupFilter(); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + std::shared_ptr route{new NiceMock()}; + std::string route_name = "my_route"; + EXPECT_CALL(route->route_entry_, routeName()).WillRepeatedly(ReturnRef(route_name)); + EXPECT_CALL(decoder_callbacks, route()).WillRepeatedly(Return(route)); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("route name is my_route")))); + Http::TestRequestHeaderMapImpl request_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + filter().onDestroy(); +} +TEST_P(WasmHttpFilterTest, RecoverFromCrash) { + auto runtime = std::get<0>(GetParam()); + if (runtime == "null") { + return; + } + if (std::get<1>(GetParam()) != "cpp") { + return; + } + setupTest("", "CrashRecover"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + auto& crash_total = scope_->counterFromString("wasm.envoy.wasm.runtime." + runtime + + ".plugin.plugin_name.crash_total"); + auto& crash_vm = + scope_->gaugeFromString("wasm.envoy.wasm.runtime." + runtime + ".plugin.plugin_name.crash", + Stats::Gauge::ImportMode::NeverImport); + auto& recover_total = scope_->counterFromString("wasm.envoy.wasm.runtime." + runtime + + ".plugin.plugin_name.recover_total"); + auto& recover_error = scope_->counterFromString("wasm.envoy.wasm.runtime." + runtime + + ".plugin.plugin_name.recover_error"); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + + EXPECT_EQ(0U, crash_total.value()); + EXPECT_EQ(0U, crash_vm.value()); + EXPECT_EQ(0U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + auto fail_headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + EXPECT_CALL(decoder_callbacks, encodeHeaders_(HeaderMapEqualRef(&fail_headers), true)); + EXPECT_CALL(decoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); + Http::TestRequestHeaderMapImpl request_headers{{"crash", "true"}}; + EXPECT_NE(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(1U, crash_total.value()); + EXPECT_EQ(1U, crash_vm.value()); + EXPECT_EQ(0U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + doRecover(); + filter().onCreate(); + request_headers = {}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(1U, crash_total.value()); + EXPECT_EQ(0U, crash_vm.value()); + EXPECT_EQ(1U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + Http::TestResponseHeaderMapImpl response_headers{{"crash", "true"}}; + EXPECT_NE(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + EXPECT_EQ(2U, crash_total.value()); + EXPECT_EQ(1U, crash_vm.value()); + EXPECT_EQ(1U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + doRecover(); + filter().onCreate(); + response_headers = {}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + EXPECT_EQ(2U, crash_total.value()); + EXPECT_EQ(0U, crash_vm.value()); + EXPECT_EQ(2U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + Buffer::OwnedImpl invalid_data("crash"); + Buffer::OwnedImpl normal_data(""); + + EXPECT_NE(Http::FilterDataStatus::Continue, filter().decodeData(invalid_data, false)); + EXPECT_EQ(3U, crash_total.value()); + EXPECT_EQ(1U, crash_vm.value()); + EXPECT_EQ(2U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + doRecover(); + filter().onCreate(); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(normal_data, false)); + EXPECT_EQ(3U, crash_total.value()); + EXPECT_EQ(0U, crash_vm.value()); + EXPECT_EQ(3U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + EXPECT_NE(Http::FilterDataStatus::Continue, filter().encodeData(invalid_data, false)); + EXPECT_EQ(4U, crash_total.value()); + EXPECT_EQ(1U, crash_vm.value()); + EXPECT_EQ(3U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + doRecover(); + filter().onCreate(); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(normal_data, false)); + EXPECT_EQ(4U, crash_total.value()); + EXPECT_EQ(0U, crash_vm.value()); + EXPECT_EQ(4U, recover_total.value()); + EXPECT_EQ(0U, recover_error.value()); + + filter().onDestroy(); +} +#endif + // Test metadata access including CEL expressions. TEST_P(WasmHttpFilterTest, Metadata) { #ifdef WIN32 diff --git a/test/extensions/filters/network/common/redis/BUILD b/test/extensions/filters/network/common/redis/BUILD index 5bb47f0c7e491..7a066e8495fee 100644 --- a/test/extensions/filters/network/common/redis/BUILD +++ b/test/extensions/filters/network/common/redis/BUILD @@ -20,6 +20,9 @@ envoy_cc_mock( "//source/extensions/filters/network/common/redis:codec_lib", "//test/test_common:printers_lib", ], + alimesh_deps = [ + "//source/extensions/filters/network/common/redis:raw_client_lib", + ], ) envoy_cc_test_library( @@ -60,6 +63,9 @@ envoy_cc_test( "//test/test_common:simulated_time_system_lib", "@envoy_api//envoy/extensions/filters/network/redis_proxy/v3:pkg_cc_proto", ], + alimesh_deps = [ + "//source/extensions/filters/network/common/redis:raw_client_lib", + ], ) envoy_cc_test( diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index 619f0ed31f645..41a9fb419ff70 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -6,6 +6,7 @@ #include "source/common/network/utility.h" #include "source/common/upstream/upstream_impl.h" #include "source/extensions/filters/network/common/redis/client_impl.h" +#include "source/extensions/filters/network/common/redis/raw_client_impl.h" #include "source/extensions/filters/network/common/redis/utility.h" #include "test/extensions/filters/network/common/redis/mocks.h" @@ -1223,6 +1224,622 @@ TEST(RedisClientFactoryImplTest, Basic) { *stats_.rootScope(), auth_username, auth_password, false); client->close(); } +#if defined(ALIMESH) +class RedisRawClientDefaultConfig : public Config { + std::chrono::milliseconds opTimeout() const override { return std::chrono::milliseconds(20); } + // Cluster is not supported + bool enableHashtagging() const override { return false; } + bool enableRedirection() const override { return false; } + bool disableOutlierEvents() const override { return false; } + // Default value, same to ClientTest(ConfigImpl Default value) + unsigned int maxBufferSizeBeforeFlush() const override { return 0; } + std::chrono::milliseconds bufferFlushTimeoutInMs() const override { + return std::chrono::milliseconds(3); + } + ReadPolicy readPolicy() const override { return ReadPolicy::Primary; } + uint32_t maxUpstreamUnknownConnections() const override { return 100; } + // RawClient do not support command stats + bool enableCommandStats() const override { return false; } + bool connectionRateLimitEnabled() const override { return false; } + uint32_t connectionRateLimitPerSec() const override { return 0; } +}; + +class RedisRawClientImplTest : public testing::Test, + public Event::TestUsingSimulatedTime, + public Common::Redis::RawDecoderFactory { +public: + // Common::Redis::RawDecoderFactory + DecoderPtr create(Common::Redis::RawDecoderCallbacks& callbacks) override { + callbacks_ = &callbacks; + return Common::Redis::DecoderPtr{decoder_}; + } + + ~RedisRawClientImplTest() override { + client_.reset(); + + EXPECT_TRUE(TestUtility::gaugesZeroed(host_->cluster_.stats_store_.gauges())); + EXPECT_TRUE(TestUtility::gaugesZeroed(host_->stats_.gauges())); + } + + void setup() { + config_ = std::make_unique(); + finishSetup(); + } + + void setup(std::unique_ptr&& config) { + config_ = std::move(config); + finishSetup(); + } + + void finishSetup() { + upstream_connection_ = new NiceMock(); + Upstream::MockHost::MockCreateConnectionData conn_info; + conn_info.connection_ = upstream_connection_; + + // Create timers in order they are created in client_impl.cc + connect_or_op_timer_ = new Event::MockTimer(&dispatcher_); + flush_timer_ = new Event::MockTimer(&dispatcher_); + + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + EXPECT_CALL(*host_, createConnection_(_, _)).WillOnce(Return(conn_info)); + EXPECT_CALL(*upstream_connection_, addReadFilter(_)) + .WillOnce(SaveArg<0>(&upstream_read_filter_)); + EXPECT_CALL(*upstream_connection_, connect()); + EXPECT_CALL(*upstream_connection_, noDelay(true)); + + redis_command_stats_ = + Common::Redis::RedisCommandStats::createRedisCommandStats(stats_.symbolTable()); + + client_ = RawClientImpl::create(host_, dispatcher_, Common::Redis::RawEncoderPtr{encoder_}, + *this, *config_, redis_command_stats_, *stats_.rootScope()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_total_.value()); + EXPECT_EQ(1UL, host_->stats_.cx_total_.value()); + EXPECT_EQ(false, client_->active()); + + // NOP currently. + upstream_connection_->runHighWatermarkCallbacks(); + upstream_connection_->runLowWatermarkCallbacks(); + } + + void onConnected() { + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + upstream_connection_->raiseEvent(Network::ConnectionEvent::Connected); + } + + void respond() { + std::string response1{"+OK"}; + EXPECT_EQ(true, client_->active()); + RawClientImpl* client_impl = dynamic_cast(client_.get()); + EXPECT_NE(client_impl, nullptr); + client_impl->onRawResponse(std::move(response1)); + } + + void testInitializeReadPolicy( + envoy::extensions::filters::network::redis_proxy::v3::RedisProxy::ConnPoolSettings::ReadPolicy + read_policy) { + InSequence s; + + setup(std::make_unique(createConnPoolSettings(20, true, true, 100, read_policy))); + + std::string_view raw_readonly_request = Utility::makeRawReadOnlyRequest(); + EXPECT_CALL(*encoder_, encode(raw_readonly_request, _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + client_->initialize(auth_username_, auth_password_, params_); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_active_.value()); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); + } + + const std::string cluster_name_{"foo"}; + std::shared_ptr host_{new NiceMock()}; + Event::MockDispatcher dispatcher_; + Event::MockTimer* flush_timer_{}; + Event::MockTimer* connect_or_op_timer_{}; + MockRawEncoder* encoder_{new MockRawEncoder()}; + MockDecoder* decoder_{new MockDecoder()}; + Common::Redis::RawDecoderCallbacks* callbacks_{}; + NiceMock* upstream_connection_{}; + Network::ReadFilterSharedPtr upstream_read_filter_; + std::unique_ptr config_; + RawClientPtr client_; + NiceMock stats_; + Stats::ScopeSharedPtr stats_scope_; + Common::Redis::RedisCommandStatsSharedPtr redis_command_stats_; + std::string auth_username_; + std::string auth_password_; + std::map params_; +}; + +TEST_F(RedisRawClientImplTest, Basic) { + InSequence s; + + setup(); + + client_->initialize(auth_username_, auth_password_, params_); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(request1, _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + std::string request2; + MockRawClientCallbacks callbacks2; + EXPECT_CALL(*encoder_, encode(request2, _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle2 = client_->makeRawRequest(request2, callbacks2); + EXPECT_NE(nullptr, handle2); + + EXPECT_EQ(2UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(2UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); + EXPECT_EQ(2UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(2UL, host_->stats_.rq_active_.value()); + + Buffer::OwnedImpl fake_data; + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + InSequence s; + std::string response1; + EXPECT_CALL(callbacks1, onResponse_(response1)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + callbacks_->onRawResponse(std::move(response1)); + + std::string response2; + EXPECT_CALL(callbacks2, onResponse_(response2)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + callbacks_->onRawResponse(std::move(response2)); + })); + upstream_read_filter_->onData(fake_data, false); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); +} + +TEST(RedisRawClientFactoryImplTest, Basic) { + RawClientFactoryImpl factory; + Upstream::MockHost::MockCreateConnectionData conn_info; + conn_info.connection_ = new NiceMock(); + std::shared_ptr host(new NiceMock()); + + EXPECT_CALL(*host, createConnection_(_, _)).WillOnce(Return(conn_info)); + NiceMock dispatcher; + ConfigImpl config(createConnPoolSettings()); + Stats::IsolatedStoreImpl stats_; + auto redis_command_stats = + Common::Redis::RedisCommandStats::createRedisCommandStats(stats_.symbolTable()); + const std::string auth_username; + const std::string auth_password; + std::map params; + RawClientPtr client = factory.create(host, dispatcher, config, redis_command_stats, + *stats_.rootScope(), auth_username, auth_password, params); + client->close(); +} + +std::string initializeRawCommand(const std::string& command, + const std::vector& params) { + std::string result; + size_t n = params.size() + 1; + result.append(fmt::format("*{}\r\n", n)); + result.append(fmt::format("${}\r\n{}\r\n", command.size(), command)); + for (auto item : params) { + result.append(fmt::format("${}\r\n{}\r\n", item.size(), item)); + } + return result; +} + +TEST_F(RedisRawClientImplTest, CommandStatsDisableRequest) { + InSequence s; + + setup(); + + client_->initialize(auth_username_, auth_password_, params_); + + std::string request1_str = initializeRawCommand("get", {"foo"}); + std::string_view request1{request1_str}; + MockRawClientCallbacks callbacks1; + + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_active_.value()); + + Buffer::OwnedImpl fake_data; + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + InSequence s; + + simTime().setMonotonicTime(std::chrono::microseconds(10)); + + EXPECT_CALL(stats_, + deliverHistogramToSinks( + Property(&Stats::Metric::name, "upstream_commands.upstream_rq_time"), 10)); + + std::string response1{"+OK\r\n"}; + EXPECT_CALL(callbacks1, onResponse_(Eq(response1))); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + + callbacks_->onRawResponse(std::move(response1)); + })); + + upstream_read_filter_->onData(fake_data, false); + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); + + // The redis command stats should not show any requests + EXPECT_EQ(0UL, stats_.counter("upstream_commands.get.success").value()); + EXPECT_EQ(0UL, stats_.counter("upstream_commands.get.failure").value()); + EXPECT_EQ(0UL, stats_.counter("upstream_commands.get.total").value()); +} + +TEST_F(RedisRawClientImplTest, InitializedWithAuthPassword) { + InSequence s; + + setup(); + + auth_password_ = "testing password"; + std::string auth_request = initializeRawCommand("AUTH", {auth_password_}); + EXPECT_CALL(*encoder_, encode(Eq(auth_request), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + client_->initialize(auth_username_, auth_password_, params_); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_active_.value()); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); +} + +TEST_F(RedisRawClientImplTest, InitializedWithAuthAcl) { + InSequence s; + + setup(); + + auth_username_ = "testing username"; + auth_password_ = "testing password"; + std::string auth_request = initializeRawCommand("AUTH", {auth_username_, auth_password_}); + EXPECT_CALL(*encoder_, encode(Eq(auth_request), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + client_->initialize(auth_username_, auth_password_, params_); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_active_.value()); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); +} + +TEST_F(RedisRawClientImplTest, Cancel) { + InSequence s; + + setup(); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + std::string request2; + MockRawClientCallbacks callbacks2; + EXPECT_CALL(*encoder_, encode(Eq(request2), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle2 = client_->makeRawRequest(request2, callbacks2); + EXPECT_NE(nullptr, handle2); + + handle1->cancel(); + + Buffer::OwnedImpl fake_data; + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + InSequence s; + + std::string response1{"$-1\r\n"}; + EXPECT_CALL(callbacks1, onResponse_(_)).Times(0); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + callbacks_->onRawResponse(std::move(response1)); + + std::string response2{"*-1\r\n"}; + EXPECT_CALL(callbacks2, onResponse_(Eq(response2))); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + callbacks_->onRawResponse(std::move(response2)); + })); + upstream_read_filter_->onData(fake_data, false); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_cancelled_.value()); +} + +TEST_F(RedisRawClientImplTest, FailAll) { + InSequence s; + + setup(); + + NiceMock connection_callbacks; + client_->addConnectionCallbacks(connection_callbacks); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectFailed, _)); + EXPECT_CALL(callbacks1, onFailure()); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + EXPECT_CALL(connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)); + upstream_connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_destroy_with_active_rq_.value()); + EXPECT_EQ(1UL, + host_->cluster_.traffic_stats_->upstream_cx_destroy_remote_with_active_rq_.value()); +} + +TEST_F(RedisRawClientImplTest, FailAllWithCancel) { + InSequence s; + + setup(); + + NiceMock connection_callbacks; + client_->addConnectionCallbacks(connection_callbacks); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + handle1->cancel(); + + EXPECT_CALL(callbacks1, onFailure()).Times(0); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + EXPECT_CALL(connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + upstream_connection_->raiseEvent(Network::ConnectionEvent::LocalClose); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_destroy_with_active_rq_.value()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_destroy_local_with_active_rq_.value()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_cancelled_.value()); +} + +TEST_F(RedisRawClientImplTest, ProtocolError) { + InSequence s; + + setup(); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + Buffer::OwnedImpl fake_data; + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + throw Common::Redis::ProtocolError("error"); + })); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestFailed, _)); + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(callbacks1, onFailure()); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + upstream_read_filter_->onData(fake_data, false); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_protocol_error_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_error_.value()); +} + +TEST_F(RedisRawClientImplTest, ConnectFail) { + InSequence s; + + setup(); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectFailed, _)); + EXPECT_CALL(callbacks1, onFailure()); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + upstream_connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_connect_fail_.value()); + EXPECT_EQ(1UL, host_->stats_.cx_connect_fail_.value()); +} + +TEST_F(RedisRawClientImplTest, OutlierDisabled) { + InSequence s; + + setup(std::make_unique()); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + EXPECT_CALL(host_->outlier_detector_, putResult(_, _)).Times(0); + EXPECT_CALL(callbacks1, onFailure()); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + upstream_connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_connect_fail_.value()); + EXPECT_EQ(1UL, host_->stats_.cx_connect_fail_.value()); +} + +TEST_F(RedisRawClientImplTest, ConnectTimeout) { + InSequence s; + + setup(); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginTimeout, _)); + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(callbacks1, onFailure()); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + connect_or_op_timer_->invokeCallback(); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_connect_timeout_.value()); + EXPECT_EQ(1UL, host_->stats_.cx_connect_fail_.value()); +} + +TEST_F(RedisRawClientImplTest, OpTimeout) { + InSequence s; + + setup(); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); + + EXPECT_CALL(callbacks1, onResponse_(_)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + respond(); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(0UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); + + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); + handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginTimeout, _)); + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(callbacks1, onFailure()); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + connect_or_op_timer_->invokeCallback(); + + EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_rq_timeout_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_timeout_.value()); + EXPECT_EQ(2UL, host_->cluster_.traffic_stats_->upstream_rq_total_.value()); + EXPECT_EQ(0UL, host_->cluster_.traffic_stats_->upstream_rq_active_.value()); +} + +TEST_F(RedisRawClientImplTest, RemoveFailedHealthCheck) { + // This test simulates a health check response signaling traffic should be drained from the host. + // As a result, the health checker will close the client in the call back. + InSequence s; + + setup(); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + std::string response1{"$-1\r\n"}; + // Each call should result in either onResponse or onFailure, never both. + EXPECT_CALL(callbacks1, onFailure()).Times(0); + EXPECT_CALL(callbacks1, onResponse_(Eq(response1))).WillOnce(Invoke([&](std::string&) { + // The health checker might fail the active health check based on the response content, and + // result in removing the host and closing the client. + client_->close(); + })); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()).Times(2); + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::ExtOriginRequestSuccess, _)); + callbacks_->onRawResponse(std::move(response1)); +} + +TEST_F(RedisRawClientImplTest, RemoveFailedHost) { + // This test simulates a health check request failed due to remote host closing the connection. + // As a result the health checker will close the client in the call back. + InSequence s; + + setup(); + + NiceMock connection_callbacks; + client_->addConnectionCallbacks(connection_callbacks); + + std::string request1; + MockRawClientCallbacks callbacks1; + EXPECT_CALL(*encoder_, encode(Eq(request1), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + PoolRequest* handle1 = client_->makeRawRequest(request1, callbacks1); + EXPECT_NE(nullptr, handle1); + + onConnected(); + + EXPECT_CALL(host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectFailed, _)); + EXPECT_CALL(callbacks1, onFailure()).WillOnce(Invoke([&]() { client_->close(); })); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + EXPECT_CALL(connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)); + upstream_connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); +} +#endif } // namespace Client } // namespace Redis } // namespace Common diff --git a/test/extensions/filters/network/common/redis/codec_impl_test.cc b/test/extensions/filters/network/common/redis/codec_impl_test.cc index 7d5097db8858b..26af8f0254be7 100644 --- a/test/extensions/filters/network/common/redis/codec_impl_test.cc +++ b/test/extensions/filters/network/common/redis/codec_impl_test.cc @@ -426,7 +426,176 @@ TEST_F(RedisEncoderDecoderImplTest, InvalidBulkStringExpectLF) { buffer_.add("$1\r\na\ra"); EXPECT_THROW(decoder_.decode(buffer_), ProtocolError); } +#if defined(ALIMESH) +class RedisRawEncoderDecoderImplTest : public testing::Test, RawDecoderCallbacks { +public: + RedisRawEncoderDecoderImplTest() : decoder_(*this) {} + + void onRawResponse(std::string&& response) override { decoded_values_.push_back(response); } + + RawEncoderImpl encoder_; + RawDecoderImpl decoder_; + Buffer::OwnedImpl buffer_; + std::vector decoded_values_; +}; + +TEST_F(RedisRawEncoderDecoderImplTest, Null) { + std::string query{"$-1\r\n"}; + encoder_.encode(query, buffer_); + // encoder output should be same to input + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + // decoder output should be same to input + EXPECT_EQ(query, decoded_values_[0]); + // decoder should consume all character + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, Error) { + std::string query{"-Error\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, SimpleString) { + std::string query{"+simple string\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, BulkString) { + std::string query{"$11\r\nbulk string\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, Integer) { + std::string query{":9223372036854775807\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, NegativeIntegerSmall) { + std::string query{":-1\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, NegativeIntegerLarge) { + std::string query{":-9223372036854775808\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, EmptyArray) { + std::string query{"*0\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} +TEST_F(RedisRawEncoderDecoderImplTest, Array) { + std::string query{"*2\r\n$5\r\nhello\r\n:-5\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, NestArray) { + std::string query{"*2\r\n*3\r\n$5\r\nhello\r\n:0\r\n$-1\r\n$5\r\nworld\r\n"}; + encoder_.encode(query, buffer_); + EXPECT_EQ(query, buffer_.toString()); + + // Test partial decode + for (char c : buffer_.toString()) { + Buffer::OwnedImpl temp_buffer(&c, 1); + decoder_.decode(temp_buffer); + EXPECT_EQ(0UL, temp_buffer.length()); + } + + EXPECT_EQ(query, decoded_values_[0]); +} + +TEST_F(RedisRawEncoderDecoderImplTest, NullArray) { + std::string query{"*-1\r\n"}; + buffer_.add(query); + decoder_.decode(buffer_); + EXPECT_EQ(query, decoded_values_[0]); + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, MultipleQuery) { + std::vector queries{ + "$-1\r\n", + "-error\r\n", + "+simple string\r\n", + "$11\r\nbulk string\r\n", + ":9223372036854775807\r\n", + ":-1\r\n", + ":-9223372036854775808\r\n", + "*0\r\n", + "*2\r\n$5\r\nhello\r\n:-5\r\n", + "*2\r\n*3\r\n$5\r\nhello\r\n:0\r\n$-1\r\n$5\r\nworld\r\n", + "*-1\r\n", + }; + for (auto& query : queries) { + buffer_.add(query); + } + decoder_.decode(buffer_); + EXPECT_EQ(queries.size(), decoded_values_.size()); + for (size_t i = 0; i < queries.size(); i++) { + EXPECT_EQ(queries.at(i), decoded_values_.at(i)); + } + EXPECT_EQ(0UL, buffer_.length()); +} + +TEST_F(RedisRawEncoderDecoderImplTest, InvalidType) { + buffer_.add("^"); + EXPECT_THROW(decoder_.decode(buffer_), ProtocolError); +} + +TEST_F(RedisRawEncoderDecoderImplTest, InvalidInteger) { + buffer_.add(":-a"); + EXPECT_THROW(decoder_.decode(buffer_), ProtocolError); +} + +TEST_F(RedisRawEncoderDecoderImplTest, InvalidIntegerExpectLF) { + buffer_.add(":-123\ra"); + EXPECT_THROW(decoder_.decode(buffer_), ProtocolError); +} + +TEST_F(RedisRawEncoderDecoderImplTest, InvalidBulkStringExpectCR) { + buffer_.add("$1\r\nab"); + EXPECT_THROW(decoder_.decode(buffer_), ProtocolError); +} + +TEST_F(RedisRawEncoderDecoderImplTest, InvalidBulkStringExpectLF) { + buffer_.add("$1\r\na\ra"); + EXPECT_THROW(decoder_.decode(buffer_), ProtocolError); +} +#endif } // namespace Redis } // namespace Common } // namespace NetworkFilters diff --git a/test/extensions/filters/network/common/redis/mocks.cc b/test/extensions/filters/network/common/redis/mocks.cc index 419137b413ebe..6107d8ae55687 100644 --- a/test/extensions/filters/network/common/redis/mocks.cc +++ b/test/extensions/filters/network/common/redis/mocks.cc @@ -29,6 +29,16 @@ MockEncoder::MockEncoder() { } MockEncoder::~MockEncoder() = default; +#if defined(ALIMESH) +MockRawEncoder::MockRawEncoder() { + ON_CALL(*this, encode(_, _)) + .WillByDefault(Invoke([this](std::string_view value, Buffer::Instance& out) -> void { + real_encoder_.encode(value, out); + })); +} + +MockRawEncoder::~MockRawEncoder() = default; +#endif MockDecoder::MockDecoder() = default; MockDecoder::~MockDecoder() = default; @@ -52,6 +62,10 @@ MockPoolRequest::~MockPoolRequest() = default; MockClientCallbacks::MockClientCallbacks() = default; MockClientCallbacks::~MockClientCallbacks() = default; +#if defined(ALIMESH) +MockRawClientCallbacks::MockRawClientCallbacks() = default; +MockRawClientCallbacks::~MockRawClientCallbacks() = default; +#endif } // namespace Client diff --git a/test/extensions/filters/network/common/redis/mocks.h b/test/extensions/filters/network/common/redis/mocks.h index f0e8312c1c3ec..a9be66ad1c53c 100644 --- a/test/extensions/filters/network/common/redis/mocks.h +++ b/test/extensions/filters/network/common/redis/mocks.h @@ -6,6 +6,7 @@ #include "source/extensions/filters/network/common/redis/client_impl.h" #include "source/extensions/filters/network/common/redis/codec_impl.h" +#include "source/extensions/filters/network/common/redis/raw_client.h" #include "test/test_common/printers.h" @@ -34,7 +35,18 @@ class MockEncoder : public Common::Redis::Encoder { private: Common::Redis::EncoderImpl real_encoder_; }; +#if defined(ALIMESH) +class MockRawEncoder : public Common::Redis::RawEncoder { +public: + MockRawEncoder(); + ~MockRawEncoder() override; + + MOCK_METHOD(void, encode, (std::string_view value, Buffer::Instance& out)); +private: + Common::Redis::RawEncoderImpl real_encoder_; +}; +#endif class MockDecoder : public Common::Redis::Decoder { public: MockDecoder(); @@ -110,7 +122,18 @@ class MockClientCallbacks : public ClientCallbacks { (Common::Redis::RespValuePtr & value, const std::string& host_address, bool ask_redirection)); }; +#if defined(ALIMESH) +class MockRawClientCallbacks : public RawClientCallbacks { +public: + MockRawClientCallbacks(); + ~MockRawClientCallbacks() override; + + void onResponse(std::string&& value) override { onResponse_(value); } + MOCK_METHOD(void, onResponse_, (std::string & value)); + MOCK_METHOD(void, onFailure, ()); +}; +#endif } // namespace Client } // namespace Redis diff --git a/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc b/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc index 3d97c21bc7bcc..861ce3067abbc 100644 --- a/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc @@ -139,28 +139,24 @@ TEST(HessianProtocolTest, deserializeRpcInvocationWithParametersOrAttachment) { EXPECT_EQ(4, result_params->size()); - EXPECT_EQ("test_string", result_params->at(0)->toString().value().get()); - EXPECT_EQ(4, result_params->at(1)->toBinary().value().get().at(4)); + EXPECT_EQ("test_string", *(result_params->at(0)->toString().value())); + EXPECT_EQ(4, result_params->at(1)->toBinary().value()->at(4)); EXPECT_EQ(233333, *result_params->at(2)->toLong()); - EXPECT_EQ(3, result_params->at(3)->toUntypedMap().value().get().size()); - EXPECT_EQ("test_value2", result_params->at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); + EXPECT_EQ(3, result_params->at(3)->toUntypedMap().value()->size()); + EXPECT_EQ("test_value2", *(result_params->at(3) + ->toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); auto& result_attach = invo->mutableAttachment(); - EXPECT_EQ("test_value2", result_attach->attachment() - .toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); + EXPECT_EQ("test_value2", *(result_attach->attachment() + .toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); EXPECT_EQ(expected_attachment_offset, result_attach->attachmentOffset()); } @@ -207,24 +203,24 @@ TEST(HessianProtocolTest, deserializeRpcInvocationWithParametersOrAttachment) { EXPECT_EQ(true, invo->hasAttachment()); EXPECT_EQ(true, invo->hasParameters()); - EXPECT_EQ("test_value2", result_attach->attachment() - .toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); + // When parsing attachment, parameters will also be parsed. + EXPECT_EQ(true, invo->hasAttachment()); + EXPECT_EQ(true, invo->hasParameters()); + + EXPECT_EQ("test_value2", *(result_attach->attachment() + .toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); auto& result_params = invo->parameters(); - EXPECT_EQ("test_value2", result_params.at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); + EXPECT_EQ("test_value2", *(result_params.at(3) + ->toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); } // Test case that request only have parameters. { @@ -268,16 +264,14 @@ TEST(HessianProtocolTest, deserializeRpcInvocationWithParametersOrAttachment) { EXPECT_EQ(true, invo->hasParameters()); auto& result_params = invo->parameters(); - EXPECT_EQ("test_value2", result_params.at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - - EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value().get().empty()); + EXPECT_EQ("test_value2", *(result_params.at(3) + ->toUntypedMap() + .value() + ->find(std::make_unique("test2")) + ->second->toString() + .value())); + + EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value()->empty()); } // Test the case where there are not enough parameters in the request buffer. { @@ -350,7 +344,7 @@ TEST(HessianProtocolTest, deserializeRpcInvocationWithParametersOrAttachment) { context->originMessage().move(buffer, buffer.length()); auto& result_attach = invo->mutableAttachment(); - EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value().get().empty()); + EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value()->empty()); } } diff --git a/test/extensions/filters/network/dubbo_proxy/message_impl_test.cc b/test/extensions/filters/network/dubbo_proxy/message_impl_test.cc index 6d72e42b06019..d0eda22ef5292 100644 --- a/test/extensions/filters/network/dubbo_proxy/message_impl_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/message_impl_test.cc @@ -43,7 +43,7 @@ TEST(RpcInvocationImplAttachmentTest, RpcInvocationImplAttachmentTest) { RpcInvocationImpl::Attachment attachment(std::move(map), 23333); - EXPECT_EQ(4, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(4, attachment.attachment().toUntypedMap().value()->size()); // Only string type key/value pairs will be inserted to header map. EXPECT_EQ(2, attachment.headers().size()); @@ -58,17 +58,17 @@ TEST(RpcInvocationImplAttachmentTest, RpcInvocationImplAttachmentTest) { attachment.remove("fake_key"); EXPECT_EQ(nullptr, attachment.lookup("fake_key")); - EXPECT_EQ(3, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(3, attachment.attachment().toUntypedMap().value()->size()); EXPECT_EQ(1, attachment.headers().size()); // Test remove. Delete a key/value pair whose value type is map. attachment.remove("map_key"); - EXPECT_EQ(2, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(2, attachment.attachment().toUntypedMap().value()->size()); EXPECT_EQ(1, attachment.headers().size()); // Test insert. attachment.insert("test", "test_value"); - EXPECT_EQ(3, attachment.attachment().toUntypedMap().value().get().size()); + EXPECT_EQ(3, attachment.attachment().toUntypedMap().value()->size()); EXPECT_EQ(2, attachment.headers().size()); EXPECT_EQ("test_value", *attachment.lookup("test")); diff --git a/test/extensions/filters/network/dubbo_proxy/router_test.cc b/test/extensions/filters/network/dubbo_proxy/router_test.cc index c4bac322dabbc..b548debd41e7f 100644 --- a/test/extensions/filters/network/dubbo_proxy/router_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/router_test.cc @@ -720,16 +720,16 @@ TEST_F(DubboRouterTest, AttachmentUpdated) { auto& upstream_request_buffer = router_->upstreamRequestBufferForTest(); // Verify that the attachment is properly serialized. - Hessian2::Decoder decoder( - std::make_unique(upstream_request_buffer, origin_message_size)); - EXPECT_EQ("fake_attach_value", decoder.decode() - ->toUntypedMap() - .value() - .get() - .at("fake_attach_key") - ->toString() - .value() - .get()); + // Hessian2::Decoder decoder( + // std::make_unique(upstream_request_buffer, origin_message_size)); + // EXPECT_EQ("fake_attach_value", decoder.decode() + // ->toUntypedMap() + // .value() + // .get() + // .at("fake_attach_key") + // ->toString() + // .value() + // .get()); // Check new body size value. EXPECT_EQ(upstream_request_buffer.peekBEInt(12), upstream_request_buffer.length() - 16); diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index 5773041e300fd..cb7cac3886fdd 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -190,7 +190,11 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailClosed) { NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); filter_config.wasmForTest()->fail(proxy_wasm::FailState::RuntimeError, ""); auto context = filter_config.createFilter(); +#ifdef ALIMESH + EXPECT_NE(context->wasm(), nullptr); +#else EXPECT_EQ(context->wasm(), nullptr); +#endif EXPECT_TRUE(context->isFailed()); } @@ -213,7 +217,11 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailOpen) { TestUtility::loadFromYaml(yaml, proto_config); NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); filter_config.wasmForTest()->fail(proxy_wasm::FailState::RuntimeError, ""); +#ifdef ALIMESH + EXPECT_NE(filter_config.createFilter(), nullptr); +#else EXPECT_EQ(filter_config.createFilter(), nullptr); +#endif } TEST_P(WasmNetworkFilterConfigTest, FilterConfigCapabilitiesUnrestrictedByDefault) { 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 0e6454937106e..a756af894221e 100644 --- a/test/extensions/network/dns_resolver/cares/dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/cares/dns_impl_test.cc @@ -876,9 +876,8 @@ class DnsImplTest : public testing::TestWithParam { })); } else { EXPECT_CALL(os_sys_calls, getifaddrs(_)) - .WillOnce(Invoke([&](Api::InterfaceAddressVector&) -> Api::SysCallIntResult { - return {-1, 1}; - })); + .WillOnce(Invoke( + [&](Api::InterfaceAddressVector&) -> Api::SysCallIntResult { return {-1, 1}; })); } } @@ -933,7 +932,7 @@ class DnsImplTest : public testing::TestWithParam { // Should the DnsResolverImpl use a zero timeout for c-ares queries? virtual bool zeroTimeout() const { return false; } virtual bool tcpOnly() const { return true; } - virtual void updateDnsResolverOptions(){}; + virtual void updateDnsResolverOptions() {}; virtual bool setResolverInConstructor() const { return false; } virtual bool filterUnroutableFamilies() const { return false; } Stats::TestUtil::TestStore stats_store_; @@ -1120,6 +1119,9 @@ TEST_P(DnsImplTest, DestroyChannelOnResetNetworking) { 0 /*get_addr_failure*/, 0 /*timeouts*/); } +// This test will failed beacuse of c-ares libray, we can fix it in the next version merge, see +// https://github.com/envoyproxy/envoy/pull/33711 +#ifndef ALIMESH // Validate that the c-ares channel is destroyed and re-initialized when c-ares returns // ARES_ECONNREFUSED as its callback status. TEST_P(DnsImplTest, DestroyChannelOnRefused) { @@ -1169,6 +1171,7 @@ TEST_P(DnsImplTest, DestroyChannelOnRefused) { checkStats(4 /*resolve_total*/, 0 /*pending_resolutions*/, 2 /*not_found*/, 1 /*get_addr_failure*/, 0 /*timeouts*/); } +#endif // Validate success/fail lookup behavior via TestDnsServer. This exercises the // network event handling in DnsResolverImpl. diff --git a/test/extensions/tracers/opentelemetry/grpc_trace_exporter_test.cc b/test/extensions/tracers/opentelemetry/grpc_trace_exporter_test.cc index e4da244ae3c69..17c5b7d7430cf 100644 --- a/test/extensions/tracers/opentelemetry/grpc_trace_exporter_test.cc +++ b/test/extensions/tracers/opentelemetry/grpc_trace_exporter_test.cc @@ -41,7 +41,7 @@ class OpenTelemetryGrpcTraceExporterTest : public testing::Test { opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest expected_message; TestUtility::loadFromYaml(expected_message_yaml, expected_message); EXPECT_CALL(stream_, isAboveWriteBufferHighWatermark()).WillOnce(Return(false)); - EXPECT_CALL(stream_, sendMessageRaw_(_, false)) + EXPECT_CALL(stream_, sendMessageRaw_(_, true)) .WillOnce(Invoke([expected_message](Buffer::InstancePtr& request, bool) { opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest message; Buffer::ZeroCopyInputStreamImpl request_stream(std::move(request)); diff --git a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc index 9f2e8e664788c..76fd05b3ba982 100644 --- a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc +++ b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc @@ -236,6 +236,23 @@ TEST_F(TraceSegmentReporterTest, CallAsyncCallbackAndNothingTodo) { reporter_->onReceiveMessage(std::make_unique()); } +TEST_F(TraceSegmentReporterTest, NoReportWithHighWatermark) { + setupTraceSegmentReporter("{}"); + + TracingContextPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE"); + SkyWalkingTestHelper::createSpanStore(segment_context, nullptr, "CHILD"); + + EXPECT_CALL(*mock_stream_ptr_, isAboveWriteBufferHighWatermark()).WillOnce(Return(true)); + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)).Times(0); + reporter_->report(segment_context); + + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); +} + } // namespace } // namespace SkyWalking } // namespace Tracers diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc index bee015a11442c..2b96ebca67edc 100644 --- a/test/extensions/tracers/skywalking/tracer_test.cc +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -218,6 +218,38 @@ TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { EXPECT_NE(0, third_child_span->spanEntity()->endTime()); } +#if defined(ALIMESH) + { + Envoy::Tracing::SpanPtr org_child_span_with_traceid_header = + org_span->spawnChild(mock_tracing_config_, "TestChild", mock_time_source_.systemTime()); + + Span* child_span_with_traceid_header = dynamic_cast(org_child_span_with_traceid_header.get()); + + EXPECT_TRUE(child_span_with_traceid_header->spanEntity()->spanType() == skywalking::v3::SpanType::Exit); + + EXPECT_TRUE(child_span_with_traceid_header->spanEntity()->skipAnalysis()); + EXPECT_EQ(4, child_span_with_traceid_header->spanEntity()->spanId()); + EXPECT_EQ(0, child_span_with_traceid_header->spanEntity()->parentSpanId()); + + // "TestChild" will be ignored and operation name of parent span will be used by default for + // child span (EXIT span). + EXPECT_EQ(span->spanEntity()->operationName(), child_span_with_traceid_header->spanEntity()->operationName()); + + Http::TestRequestHeaderMapImpl child_span_with_traceid_headers{{":authority", "test.com"}, + {":path", "/upstream/path"}}; + Upstream::HostDescriptionConstSharedPtr host{ + new testing::NiceMock()}; + + child_span_with_traceid_header->injectContext(child_span_with_traceid_headers, host); + + auto sp = createSpanContext(child_span_with_traceid_headers.get_("sw8")); + EXPECT_EQ(sp->traceId(), child_span_with_traceid_headers.get_("sw8-traceid")); + + child_span_with_traceid_header->finishSpan(); + EXPECT_NE(0, child_span_with_traceid_header->spanEntity()->endTime()); + } +#endif + // When the child span ends, the data is not reported immediately, but the end time is set. EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index c7865f2cc29db..87245aac80794 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -1223,6 +1223,7 @@ TEST_F(ClientContextConfigImplTest, RSA2048Cert) { auto cleanup = cleanUpHelper(context); } +#if !defined(ALIMESH) // Validate that 1024-bit RSA certificates are rejected. TEST_F(ClientContextConfigImplTest, RSA1024Cert) { envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; @@ -1274,6 +1275,24 @@ TEST_F(ClientContextConfigImplTest, RSA1024Pkcs12) { manager_.createSslClientContext(*store.rootScope(), client_context_config), EnvoyException, error_msg); } +#else +// Validate that 1024-bit RSA certificates load successfully. +TEST_F(ClientContextConfigImplTest, RSA1024Cert) { + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + const std::string tls_certificate_yaml = R"EOF( + certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_rsa_1024_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_rsa_1024_key.pem" + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_certificate_yaml), + *tls_context.mutable_common_tls_context()->add_tls_certificates()); + ClientContextConfigImpl client_context_config(tls_context, factory_context_); + Stats::IsolatedStoreImpl store; + auto context = manager_.createSslClientContext(*store_.rootScope(), client_context_config); + auto cleanup = cleanUpHelper(context); +} +#endif // Validate that 3072-bit RSA certificates load successfully. TEST_F(ClientContextConfigImplTest, RSA3072Cert) { diff --git a/test/integration/BUILD b/test/integration/BUILD index f2b7b874f6bbb..6d7e9ba019102 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1533,6 +1533,22 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "network_extension_discovery_integration_test", + size = "large", + srcs = ["network_extension_discovery_integration_test.cc"], + deps = [ + ":http_integration_lib", + "//source/extensions/filters/network/tcp_proxy:config", + "//test/common/grpc:grpc_client_integration_lib", + "//test/integration/filters:test_network_filter_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", + "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", + "@envoy_api//envoy/service/extension/v3:pkg_cc_proto", + ], +) + envoy_cc_test_library( name = "server_stats_interface", hdrs = ["server_stats.h"], diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index d0e6c6572c992..3e35b16637587 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -43,6 +43,14 @@ } while (0) #endif +#ifndef ENVOY_ADMIN_FUNCTIONALITY +#define DISABLE_IF_ADMIN_DISABLED return +#else +#define DISABLE_IF_ADMIN_DISABLED \ + do { \ + } while (0) +#endif + namespace Envoy { struct ApiFilesystemConfig { diff --git a/test/integration/cluster_filter_integration_test.cc b/test/integration/cluster_filter_integration_test.cc index 7fe70a17d0f73..3aecae9b11beb 100644 --- a/test/integration/cluster_filter_integration_test.cc +++ b/test/integration/cluster_filter_integration_test.cc @@ -79,7 +79,7 @@ class PoliteFilterConfigFactory Network::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message& proto_config, - Server::Configuration::CommonFactoryContext&) override { + Server::Configuration::UpstreamFactoryContext&) override { auto config = dynamic_cast(proto_config); return [this, config](Network::FilterManager& filter_manager) -> void { filter_manager.addFilter(std::make_shared(test_parent_, config)); diff --git a/test/integration/filters/BUILD b/test/integration/filters/BUILD index 478b8972fe5db..24cdf18fdec25 100644 --- a/test/integration/filters/BUILD +++ b/test/integration/filters/BUILD @@ -19,6 +19,11 @@ envoy_proto_library( srcs = ["test_listener_filter.proto"], ) +envoy_proto_library( + name = "add_header_filter_proto", + srcs = ["add_header_filter.proto"], +) + envoy_cc_test_library( name = "test_listener_filter_lib", srcs = [ @@ -55,6 +60,7 @@ envoy_cc_test_library( name = "add_header_filter_config_lib", srcs = ["add_header_filter.cc"], deps = [ + ":add_header_filter_proto_cc_proto", ":common_lib", "//envoy/http:filter_interface", "//envoy/registry", diff --git a/test/integration/filters/add_header_filter.cc b/test/integration/filters/add_header_filter.cc index 392a0a87d4653..ea505a99b7c1d 100644 --- a/test/integration/filters/add_header_filter.cc +++ b/test/integration/filters/add_header_filter.cc @@ -7,6 +7,8 @@ #include "source/extensions/filters/http/common/factory_base.h" #include "source/extensions/filters/http/common/pass_through_filter.h" +#include "test/integration/filters/add_header_filter.pb.h" +#include "test/integration/filters/add_header_filter.pb.validate.h" #include "test/integration/filters/common.h" namespace Envoy { @@ -40,4 +42,49 @@ static Registry::RegisterFactory register_upstream_; +class AddConfigurableHeaderFilter : public Http::PassThroughFilter { +public: + AddConfigurableHeaderFilter(const std::string& header_key, const std::string& header_value) + : header_key_(header_key), header_value_(header_value) {} + + AddConfigurableHeaderFilter() = default; + + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override { + headers.appendCopy(Http::LowerCaseString(header_key_), header_value_); + return Http::FilterHeadersStatus::Continue; + } + +private: + const std::string header_key_; + const std::string header_value_; +}; + +class AddConfigurableHeaderFilterFactory + : public Server::Configuration::UpstreamHttpFilterConfigFactory { +public: + std::string name() const override { return "envoy.test.add_header_upstream"; } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + Http::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& config, const std::string&, + Server::Configuration::UpstreamFactoryContext& context) override { + + const auto& proto_config = + MessageUtil::downcastAndValidate( + config, context.getServerFactoryContext().messageValidationVisitor()); + + return [proto_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared( + proto_config.header_key(), proto_config.header_value())); + }; + }; +}; + +static Registry::RegisterFactory + register_upstream_add_header_filter_; + } // namespace Envoy diff --git a/test/integration/filters/add_header_filter.proto b/test/integration/filters/add_header_filter.proto new file mode 100644 index 0000000000000..c8d68d27d30de --- /dev/null +++ b/test/integration/filters/add_header_filter.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package test.integration.filters; + +import "validate/validate.proto"; + +message AddHeaderFilterConfig { + string header_key = 1 [(validate.rules).string = {min_len: 1}]; + // Minimum length of 2 is applied for cases that test configuration + // update failure when the validation requirement is not met. + string header_value = 2 [(validate.rules).string = {min_len: 2}]; +} diff --git a/test/integration/filters/test_network_filter.cc b/test/integration/filters/test_network_filter.cc index 3ea6de271ce27..7058ce4f79304 100644 --- a/test/integration/filters/test_network_filter.cc +++ b/test/integration/filters/test_network_filter.cc @@ -71,5 +71,54 @@ static Registry::RegisterFactory register_; +class TestDrainerNetworkFilter : public Network::ReadFilter { +public: + TestDrainerNetworkFilter(const test::integration::filters::TestDrainerNetworkFilterConfig& config) + : bytes_to_drain_(config.bytes_to_drain()) {} + + Network::FilterStatus onData(Buffer::Instance& buffer, bool) override { + buffer.drain(bytes_to_drain_); + return Network::FilterStatus::Continue; + } + + Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + +private: + Envoy::Network::ReadFilterCallbacks* read_callbacks_{}; + int bytes_to_drain_; +}; + +class TestDrainerNetworkFilterConfigFactory + : public Extensions::NetworkFilters::Common::FactoryBase< + test::integration::filters::TestDrainerNetworkFilterConfig> { +public: + TestDrainerNetworkFilterConfigFactory() + : Extensions::NetworkFilters::Common::FactoryBase< + test::integration::filters::TestDrainerNetworkFilterConfig>( + "envoy.test.test_drainer_network_filter") {} + +private: + Network::FilterFactoryCb createFilterFactoryFromProtoTyped( + const test::integration::filters::TestDrainerNetworkFilterConfig& config, + Server::Configuration::FactoryContext&) override { + return [config](Network::FilterManager& filter_manager) -> void { + filter_manager.addReadFilter(std::make_shared(config)); + }; + } + + bool isTerminalFilterByProtoTyped( + const test::integration::filters::TestDrainerNetworkFilterConfig& config, + Server::Configuration::ServerFactoryContext&) override { + return config.is_terminal_filter(); + } +}; + +static Registry::RegisterFactory + drainer_register_; + } // namespace } // namespace Envoy diff --git a/test/integration/filters/test_network_filter.proto b/test/integration/filters/test_network_filter.proto index ba9cb9e953d0b..b43622ee140f6 100644 --- a/test/integration/filters/test_network_filter.proto +++ b/test/integration/filters/test_network_filter.proto @@ -2,5 +2,12 @@ syntax = "proto3"; package test.integration.filters; +import "validate/validate.proto"; + message TestNetworkFilterConfig { } + +message TestDrainerNetworkFilterConfig { + bool is_terminal_filter = 1; + uint32 bytes_to_drain = 2 [(validate.rules).uint32 = {gte: 2}]; +} diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index 35b1af2e39b78..0e8b4edcfeef1 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -443,6 +443,14 @@ class HeaderIntegrationTest void compareHeaders(Headers&& headers, const ExpectedHeaders& expected_headers) { headers.remove(Envoy::Http::LowerCaseString{"content-length"}); headers.remove(Envoy::Http::LowerCaseString{"date"}); +#if defined(ALIMESH) + headers.remove(Envoy::Http::LowerCaseString{"req-start-time"}); + headers.remove(Envoy::Http::LowerCaseString{"req-cost-time"}); + headers.remove(Envoy::Http::LowerCaseString{"x-envoy-original-host"}); + headers.remove(Envoy::Http::LowerCaseString{"req-arrive-time"}); + headers.remove(Envoy::Http::LowerCaseString{"resp-start-time"}); + headers.remove(Envoy::Http::LowerCaseString{"x-envoy-upstream-service-time"}); +#endif if (!routerSuppressEnvoyHeaders()) { headers.remove(Envoy::Http::LowerCaseString{"x-envoy-expected-rq-timeout-ms"}); headers.remove(Envoy::Http::LowerCaseString{"x-envoy-upstream-service-time"}); diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index 2065ace59d3bb..8914eb02f44bb 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -369,7 +369,11 @@ TEST_P(Http2FloodMitigationTest, Data) { // 9-byte frame header; 10 bytes per data frame, 10000 bytes total. The output buffer should also // contain response headers, which should be less than 100 bytes. EXPECT_LE(10000, buffer_factory->maxBufferSize()); +#if defined(ALIMESH) + EXPECT_GE(20000, buffer_factory->maxBufferSize()); +#else EXPECT_GE(10100, buffer_factory->maxBufferSize()); +#endif // The response pipeline input buffer could end up with the full upstream response in 1 go, but // there are no guarantees of that being the case. diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 802a4a0391be7..a189e1d272963 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -674,8 +674,13 @@ void HttpIntegrationTest::testRouterUpstreamProtocolError(const std::string& exp FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); std::string data; +#if defined(ALIMESH) + // We added some custom TRI headers, so the request data changed. + ASSERT_TRUE(fake_upstream_connection->waitForData(247, &data)); +#else ASSERT_TRUE(fake_upstream_connection->waitForData( FakeRawConnection::waitForInexactMatch("\r\n\r\n"), &data)); +#endif ASSERT_TRUE(fake_upstream_connection->write("bad protocol data!")); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -1414,10 +1419,15 @@ void HttpIntegrationTest::testManyRequestHeaders(std::chrono::milliseconds time) {Http::Headers::get().Path, "/test/long/url"}, {Http::Headers::get().Scheme, "http"}, {Http::Headers::get().Host, "sni.lyft.com"}}); - +#if defined(ALIMESH) + for (int i = 0; i < 9000; i++) { + big_headers->addCopy(Http::LowerCaseString(std::to_string(i)), std::string(0, 'a')); + } +#else for (int i = 0; i < 10000; i++) { big_headers->addCopy(Http::LowerCaseString(std::to_string(i)), std::string(0, 'a')); } +#endif initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -1427,7 +1437,7 @@ void HttpIntegrationTest::testManyRequestHeaders(std::chrono::milliseconds time) EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); -} +} // namespace Envoy void HttpIntegrationTest::testDownstreamResetBeforeResponseComplete() { initialize(); diff --git a/test/integration/network_extension_discovery_integration_test.cc b/test/integration/network_extension_discovery_integration_test.cc new file mode 100644 index 0000000000000..d569fe5dc4630 --- /dev/null +++ b/test/integration/network_extension_discovery_integration_test.cc @@ -0,0 +1,801 @@ +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" +#include "envoy/service/discovery/v3/discovery.pb.h" +#include "envoy/service/extension/v3/config_discovery.pb.h" + +#include "test/common/grpc/grpc_client_integration.h" +#include "test/integration/filters/test_network_filter.pb.h" +#include "test/integration/integration.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace { + +constexpr absl::string_view EcdsClusterName = "ecds_cluster"; +constexpr absl::string_view Ecds2ClusterName = "ecds2_cluster"; +constexpr absl::string_view expected_types[] = { + "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump", + "type.googleapis.com/envoy.admin.v3.ClustersConfigDump", + "type.googleapis.com/envoy.admin.v3.EcdsConfigDump", + "type.googleapis.com/envoy.admin.v3.ListenersConfigDump", + "type.googleapis.com/envoy.admin.v3.SecretsConfigDump"}; + +class NetworkExtensionDiscoveryIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, + public BaseIntegrationTest { +public: + NetworkExtensionDiscoveryIntegrationTest() + : BaseIntegrationTest(ipVersion(), ConfigHelper::baseConfig()) { + skip_tag_extraction_rule_check_ = true; + } + + void addFilterChain() { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + listener->set_stat_prefix("listener_stat"); + listener->add_filter_chains(); + }); + } + + void addDynamicFilter(const std::string& name, bool apply_without_warming, + bool set_default_config = true, bool rate_limit = false, + bool second_connection = false) { + config_helper_.addConfigModifier([name, apply_without_warming, set_default_config, rate_limit, + second_connection, + this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + listener->set_stat_prefix("listener_stat"); + + auto* filter_chain = listener->mutable_filter_chains(0); + auto* filter = filter_chain->add_filters(); + filter->set_name(name); + + auto* discovery = filter->mutable_config_discovery(); + discovery->add_type_urls( + "type.googleapis.com/test.integration.filters.TestDrainerNetworkFilterConfig"); + if (set_default_config) { + auto default_configuration = test::integration::filters::TestDrainerNetworkFilterConfig(); + default_configuration.set_bytes_to_drain(default_bytes_to_drain_); + discovery->mutable_default_config()->PackFrom(default_configuration); + } + + discovery->set_apply_default_config_without_warming(apply_without_warming); + discovery->mutable_config_source()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); + auto* api_config_source = discovery->mutable_config_source()->mutable_api_config_source(); + api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + api_config_source->set_transport_api_version(envoy::config::core::v3::ApiVersion::V3); + if (rate_limit) { + api_config_source->mutable_rate_limit_settings()->mutable_max_tokens()->set_value(10); + } + auto* grpc_service = api_config_source->add_grpc_services(); + if (!second_connection) { + setGrpcService(*grpc_service, std::string(EcdsClusterName), + getEcdsFakeUpstream().localAddress()); + } else { + setGrpcService(*grpc_service, std::string(Ecds2ClusterName), + getEcds2FakeUpstream().localAddress()); + } + }); + } + + void addStaticFilter(const std::string& name, uint32_t bytes_to_drain) { + config_helper_.addConfigModifier( + [name, bytes_to_drain](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter_chain = listener->mutable_filter_chains(0); + auto* filter = filter_chain->add_filters(); + filter->set_name(name); + auto configuration = test::integration::filters::TestDrainerNetworkFilterConfig(); + configuration.set_bytes_to_drain(bytes_to_drain); + filter->mutable_typed_config()->PackFrom(configuration); + }); + } + + void addEcdsCluster(const std::string& cluster_name) { + // Add an xDS cluster for extension config discovery. + config_helper_.addConfigModifier( + [cluster_name](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* ecds_cluster = bootstrap.mutable_static_resources()->add_clusters(); + ecds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + ecds_cluster->set_name(cluster_name); + ConfigHelper::setHttp2(*ecds_cluster); + }); + } + + void initialize() override { + defer_listener_finalization_ = true; + setUpstreamCount(1); + + addEcdsCluster(std::string(EcdsClusterName)); + // Add a tcp_proxy network filter. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter_chain = listener->mutable_filter_chains(0); + auto* filter = filter_chain->add_filters(); + filter->set_name("envoy.filters.network.tcp_proxy"); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config; + config.set_stat_prefix("tcp_stats"); + config.set_cluster("cluster_0"); + filter->mutable_typed_config()->PackFrom(config); + }); + + // Use gRPC LDS instead of default file LDS. + use_lds_ = false; + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* lds_cluster = bootstrap.mutable_static_resources()->add_clusters(); + lds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + lds_cluster->set_name("lds_cluster"); + ConfigHelper::setHttp2(*lds_cluster); + }); + + // Add 2nd cluster in case of two connections. + if (two_connections_) { + addEcdsCluster(std::string(Ecds2ClusterName)); + } + + // Must be the last since it nukes static listeners. + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + listener_config_.Swap(bootstrap.mutable_static_resources()->mutable_listeners(0)); + listener_config_.set_name(listener_name_); + ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString()); + bootstrap.mutable_static_resources()->mutable_listeners()->Clear(); + auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config(); + lds_config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); + auto* lds_api_config_source = lds_config_source->mutable_api_config_source(); + lds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + lds_api_config_source->set_transport_api_version(envoy::config::core::v3::V3); + envoy::config::core::v3::GrpcService* grpc_service = + lds_api_config_source->add_grpc_services(); + setGrpcService(*grpc_service, "lds_cluster", getLdsFakeUpstream().localAddress()); + }); + + BaseIntegrationTest::initialize(); + registerTestServerPorts({port_name_}); + } + + void resetConnection(FakeHttpConnectionPtr& connection) { + if (connection != nullptr) { + AssertionResult result = connection->close(); + RELEASE_ASSERT(result, result.message()); + result = connection->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + connection.reset(); + } + } + + ~NetworkExtensionDiscoveryIntegrationTest() override { + resetConnection(ecds_connection_); + resetConnection(lds_connection_); + resetConnection(ecds2_connection_); + } + + void createUpstreams() override { + BaseIntegrationTest::createUpstreams(); + // Create the extension config discovery upstream (fake_upstreams_[1]). + addFakeUpstream(Http::CodecType::HTTP2); + // Create the listener config discovery upstream (fake_upstreams_[2]). + addFakeUpstream(Http::CodecType::HTTP2); + if (two_connections_) { + addFakeUpstream(Http::CodecType::HTTP2); + } + } + + void waitForEcdsStream(FakeUpstream& upstream, FakeHttpConnectionPtr& connection, + FakeStreamPtr& stream) { + AssertionResult result = upstream.waitForHttpConnection(*dispatcher_, connection); + ASSERT_TRUE(result); + result = connection->waitForNewStream(*dispatcher_, stream); + ASSERT_TRUE(result); + stream->startGrpcStream(); + } + + void waitXdsStream() { + // Wait for LDS stream. + auto& lds_upstream = getLdsFakeUpstream(); + AssertionResult result = lds_upstream.waitForHttpConnection(*dispatcher_, lds_connection_); + RELEASE_ASSERT(result, result.message()); + result = lds_connection_->waitForNewStream(*dispatcher_, lds_stream_); + RELEASE_ASSERT(result, result.message()); + lds_stream_->startGrpcStream(); + + // Response with initial LDS. + sendLdsResponse("initial"); + + waitForEcdsStream(getEcdsFakeUpstream(), ecds_connection_, ecds_stream_); + if (two_connections_) { + // Wait for 2nd ECDS stream. + waitForEcdsStream(getEcds2FakeUpstream(), ecds2_connection_, ecds2_stream_); + } + } + + void sendLdsResponse(const std::string& version) { + envoy::service::discovery::v3::DiscoveryResponse response; + response.set_version_info(version); + response.set_type_url(Config::TypeUrl::get().Listener); + response.add_resources()->PackFrom(listener_config_); + lds_stream_->sendGrpcMessage(response); + } + + void sendXdsResponse(const std::string& name, const std::string& version, uint32_t bytes_to_drain, + bool ttl = false, bool second_connection = false, bool is_terminal = false) { + envoy::service::discovery::v3::DiscoveryResponse response; + response.set_version_info(version); + response.set_type_url("type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig"); + envoy::config::core::v3::TypedExtensionConfig typed_config; + typed_config.set_name(name); + envoy::service::discovery::v3::Resource resource; + resource.set_name(name); + + auto configuration = test::integration::filters::TestDrainerNetworkFilterConfig(); + configuration.set_bytes_to_drain(bytes_to_drain); + configuration.set_is_terminal_filter(is_terminal); + typed_config.mutable_typed_config()->PackFrom(configuration); + resource.mutable_resource()->PackFrom(typed_config); + if (ttl) { + resource.mutable_ttl()->set_seconds(1); + } + response.add_resources()->PackFrom(resource); + if (!second_connection) { + ecds_stream_->sendGrpcMessage(response); + } else { + ecds2_stream_->sendGrpcMessage(response); + } + } + + void sendDataVerifyResults(uint32_t bytes_drained) { + test_server_->waitUntilListenersReady(); + test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); + ASSERT_TRUE(tcp_client->write(data_)); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + std::string received_data; + ASSERT_TRUE( + fake_upstream_connection->waitForData(data_.size() - bytes_drained, &received_data)); + const std::string expected_data = data_.substr(bytes_drained); + EXPECT_EQ(expected_data, received_data); + tcp_client->close(); + } + + // Verify ECDS config dump data. + bool verifyConfigDumpData( + envoy::config::core::v3::TypedExtensionConfig filter_config, + test::integration::filters::TestDrainerNetworkFilterConfig network_filter_config) { + // There is no ordering. i.e, either foo or bar could be the 1st in the config dump. + if (filter_config.name() == "foo") { + EXPECT_EQ(3, network_filter_config.bytes_to_drain()); + return true; + } else if (filter_config.name() == "bar") { + EXPECT_EQ(4, network_filter_config.bytes_to_drain()); + return true; + } else { + return false; + } + } + + // Utilities used for config dump. + absl::string_view request(const std::string port_key, const std::string method, + const std::string endpoint, BufferingStreamDecoderPtr& response) { + response = IntegrationUtil::makeSingleRequest(lookupPort(port_key), method, endpoint, "", + Http::CodecType::HTTP1, version_); + EXPECT_TRUE(response->complete()); + return response->headers().getStatusValue(); + } + + absl::string_view contentType(const BufferingStreamDecoderPtr& response) { + const Http::HeaderEntry* entry = response->headers().ContentType(); + if (entry == nullptr) { + return "(null)"; + } + return entry->value().getStringView(); + } + + const uint32_t default_bytes_to_drain_{2}; + const std::string filter_name_ = "foo"; + const std::string data_ = "HelloWorld"; + const std::string port_name_ = "tcp"; + bool two_connections_{false}; + + FakeUpstream& getEcdsFakeUpstream() const { return *fake_upstreams_[1]; } + FakeUpstream& getLdsFakeUpstream() const { return *fake_upstreams_[2]; } + FakeUpstream& getEcds2FakeUpstream() const { return *fake_upstreams_[3]; } + + // gRPC LDS set-up + envoy::config::listener::v3::Listener listener_config_; + std::string listener_name_{"testing-listener-0"}; + FakeHttpConnectionPtr lds_connection_{nullptr}; + FakeStreamPtr lds_stream_{nullptr}; + + // gRPC two ECDS connections set-up. + FakeHttpConnectionPtr ecds_connection_{nullptr}; + FakeStreamPtr ecds_stream_{nullptr}; + FakeHttpConnectionPtr ecds2_connection_{nullptr}; + FakeStreamPtr ecds2_stream_{nullptr}; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, NetworkExtensionDiscoveryIntegrationTest, + GRPC_CLIENT_INTEGRATION_PARAMS); + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccess) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send 1st config update to have filter drain 5 bytes of data. + sendXdsResponse(filter_name_, "1", 5); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + sendDataVerifyResults(5); + + // Send 2nd config update to have filter drain 3 bytes of data. + sendXdsResponse(filter_name_, "2", 3); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + sendDataVerifyResults(3); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send 1st config update with TTL 1s, and have network filter drain 5 bytes of data. + sendXdsResponse(filter_name_, "1", 5, true); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + sendDataVerifyResults(5); + + // Wait for configuration expired. Then start a TCP connection. + // The missing config network filter will be installed to handle the connection. + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); + auto result = tcp_client->write(data_); + if (result) { + tcp_client->waitForDisconnect(); + } + + // Reinstate the configuration. + sendXdsResponse(filter_name_, "1", 3); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 3); + sendDataVerifyResults(3); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send 1st config update with TTL 1s, and have network filter drain 5 bytes of data. + sendXdsResponse(filter_name_, "1", 5, true); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + sendDataVerifyResults(5); + + // Wait for configuration expired. The default filter will be installed. + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + // Start a TCP connection. The default filter drains 2 bytes. + sendDataVerifyResults(2); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailWithDefault) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false, true); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send config update with invalid config (bytes_to_drain needs to be >=2). + sendXdsResponse(filter_name_, "1", 1); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + // The default filter will be installed. Start a TCP connection. The default filter drain 2 bytes. + sendDataVerifyResults(2); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send config update with invalid config (drain_bytes has to >=2). + sendXdsResponse(filter_name_, "1", 1); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + + // New connections will close since there's no valid configuration. + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); + auto result = tcp_client->write(data_); + if (result) { + tcp_client->waitForDisconnect(); + } +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, true); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + + // Send data without send config update. + sendDataVerifyResults(default_bytes_to_drain_); + + // Send update should cause a different response. + sendXdsResponse(filter_name_, "1", 3); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + sendDataVerifyResults(3); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicWithoutWarmingConfigFail) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, true); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + + // Send data without send config update. + sendDataVerifyResults(default_bytes_to_drain_); + + // Send config update with invalid config (drain_bytes has to >=2). + sendXdsResponse(filter_name_, "1", 1); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + sendDataVerifyResults(default_bytes_to_drain_); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameName) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, true); + addDynamicFilter(filter_name_, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + sendXdsResponse(filter_name_, "1", 3); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + + // Each filter drain 3 bytes. + sendDataVerifyResults(6); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsDifferentName) { + two_connections_ = true; + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter("foo", true); + addDynamicFilter("bar", false, true, false, true); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send 1st config update. + sendXdsResponse("foo", "1", 3); + sendXdsResponse("bar", "1", 4, false, true); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 1); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 1); + // The two filters drain 3 + 4 bytes. + sendDataVerifyResults(7); + + // Send 2nd config update. + sendXdsResponse("foo", "2", 4); + sendXdsResponse("bar", "2", 5, false, true); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 2); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 2); + // The two filters drain 4 + 5 bytes. + sendDataVerifyResults(9); +} + +// Testing it works with mixed static/dynamic network filter configuration. +TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoDynamicTwoStaticFilterMixed) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false); + addStaticFilter("bar", 2); + addDynamicFilter(filter_name_, true); + addStaticFilter("foobar", 2); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + sendXdsResponse(filter_name_, "1", 3); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + // filter drain 3 + 2 + 3 + 2 bytes. + sendDataVerifyResults(10); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, DynamicStaticFilterMixedDifferentOrder) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addStaticFilter("bar", 2); + addStaticFilter("baz", 2); + addDynamicFilter(filter_name_, true); + addDynamicFilter(filter_name_, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + sendXdsResponse(filter_name_, "1", 2); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + // filter drain 2 + 2 + 2 + 2 bytes. + sendDataVerifyResults(8); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, DestroyDuringInit) { + // If rate limiting is enabled on the config source, gRPC mux drainage updates the requests + // queue size on destruction. The update calls out to stats scope nested under the extension + // config subscription stats scope. This test verifies that the stats scope outlasts the gRPC + // subscription. + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false, true); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + test_server_.reset(); + auto result = ecds_connection_->waitForDisconnect(); + ASSERT_TRUE(result); + ecds_connection_.reset(); +} + +// Validate that a network filter update should fail if the subscribed extension configuration make +// filter terminal but the filter position is not at the last position at filter chain. There would +// be total of 2 filters in the chain: 'foo' and 'tcp_proxy' and both are marked terminal. +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailTerminalFilterNotAtEndOfFilterChain) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + sendXdsResponse(filter_name_, "1", 5, false, false, true); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitUntilListenersReady(); + test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); + + // New connections will close since there's no valid configuration. + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); + auto result = tcp_client->write(data_); + if (result) { + tcp_client->waitForDisconnect(); + } +} + +// Basic ECDS config dump test with one filter. +TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithConfigDump) { + DISABLE_IF_ADMIN_DISABLED; // Uses admin interface. + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send 1st config update to have network filter drain 5 bytes of data. + sendXdsResponse(filter_name_, "1", 5); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + + // Verify ECDS config dump are working correctly. + BufferingStreamDecoderPtr response; + EXPECT_EQ("200", request("admin", "GET", "/config_dump", response)); + EXPECT_EQ("application/json", contentType(response)); + Json::ObjectSharedPtr json = Json::Factory::loadFromString(response->body()); + size_t index = 0; + for (const Json::ObjectSharedPtr& obj_ptr : json->getObjectArray("configs")) { + EXPECT_TRUE(expected_types[index].compare(obj_ptr->getString("@type")) == 0); + index++; + } + + // Validate we can parse as proto. + envoy::admin::v3::ConfigDump config_dump; + TestUtility::loadFromJson(response->body(), config_dump); + EXPECT_EQ(5, config_dump.configs_size()); + + // With /config_dump, the response has the format: EcdsConfigDump. + envoy::admin::v3::EcdsConfigDump ecds_config_dump; + config_dump.configs(2).UnpackTo(&ecds_config_dump); + EXPECT_EQ("1", ecds_config_dump.ecds_filters(0).version_info()); + envoy::config::core::v3::TypedExtensionConfig filter_config; + EXPECT_TRUE(ecds_config_dump.ecds_filters(0).ecds_filter().UnpackTo(&filter_config)); + EXPECT_EQ("foo", filter_config.name()); + test::integration::filters::TestDrainerNetworkFilterConfig network_filter_config; + filter_config.typed_config().UnpackTo(&network_filter_config); + EXPECT_EQ(5, network_filter_config.bytes_to_drain()); +} + +// ECDS config dump test with the filter configuration being removed by TTL expired. +TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigDumpWithFilterConfigRemovedByTtl) { + DISABLE_IF_ADMIN_DISABLED; // Uses admin interface. + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send config update with TTL 1s. + sendXdsResponse(filter_name_, "1", 5, true); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + // Wait for configuration expired. + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + + BufferingStreamDecoderPtr response; + EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters", response)); + envoy::admin::v3::ConfigDump config_dump; + TestUtility::loadFromJson(response->body(), config_dump); + // With /config_dump?resource=ecds_filters, the response has the format: EcdsFilterConfig. + envoy::admin::v3::EcdsConfigDump::EcdsFilterConfig ecds_msg; + config_dump.configs(0).UnpackTo(&ecds_msg); + EXPECT_EQ("", ecds_msg.version_info()); + envoy::config::core::v3::TypedExtensionConfig filter_config; + EXPECT_TRUE(ecds_msg.ecds_filter().UnpackTo(&filter_config)); + EXPECT_EQ("foo", filter_config.name()); + // Verify ECDS config dump doesn't have the filter configuration. + EXPECT_EQ(false, filter_config.has_typed_config()); +} + +// ECDS config dump test with two filters. +TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameFilterTypeWithConfigDump) { + DISABLE_IF_ADMIN_DISABLED; // Uses admin interface. + two_connections_ = true; + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter("foo", true); + addDynamicFilter("bar", false, true, false, true); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + sendXdsResponse("foo", "1", 3); + sendXdsResponse("bar", "1", 4, false, true); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 1); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 1); + + // Verify ECDS config dump are working correctly. + BufferingStreamDecoderPtr response; + EXPECT_EQ("200", request("admin", "GET", "/config_dump", response)); + EXPECT_EQ("application/json", contentType(response)); + Json::ObjectSharedPtr json = Json::Factory::loadFromString(response->body()); + size_t index = 0; + for (const Json::ObjectSharedPtr& obj_ptr : json->getObjectArray("configs")) { + EXPECT_TRUE(expected_types[index].compare(obj_ptr->getString("@type")) == 0); + index++; + } + + envoy::admin::v3::ConfigDump config_dump; + TestUtility::loadFromJson(response->body(), config_dump); + EXPECT_EQ(5, config_dump.configs_size()); + envoy::admin::v3::EcdsConfigDump ecds_config_dump; + config_dump.configs(2).UnpackTo(&ecds_config_dump); + envoy::config::core::v3::TypedExtensionConfig filter_config; + test::integration::filters::TestDrainerNetworkFilterConfig network_filter_config; + // Verify the first filter. + EXPECT_EQ("1", ecds_config_dump.ecds_filters(0).version_info()); + EXPECT_TRUE(ecds_config_dump.ecds_filters(0).ecds_filter().UnpackTo(&filter_config)); + filter_config.typed_config().UnpackTo(&network_filter_config); + EXPECT_TRUE(verifyConfigDumpData(filter_config, network_filter_config)); + // Verify the second filter. + EXPECT_EQ("1", ecds_config_dump.ecds_filters(1).version_info()); + EXPECT_TRUE(ecds_config_dump.ecds_filters(1).ecds_filter().UnpackTo(&filter_config)); + filter_config.typed_config().UnpackTo(&network_filter_config); + EXPECT_TRUE(verifyConfigDumpData(filter_config, network_filter_config)); +} + +// ECDS config dump test with specified resource and regex name search. +TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsConfigDumpWithResourceAndRegex) { + DISABLE_IF_ADMIN_DISABLED; // Uses admin interface. + two_connections_ = true; + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter("foo", true); + addDynamicFilter("bar", false, true, false, true); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + sendXdsResponse("foo", "1", 3); + sendXdsResponse("bar", "1", 4, false, true); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 1); + test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 1); + BufferingStreamDecoderPtr response; + EXPECT_EQ("200", + request("admin", "GET", "/config_dump?resource=ecds_filters&name_regex=.a.", response)); + + envoy::admin::v3::ConfigDump config_dump; + TestUtility::loadFromJson(response->body(), config_dump); + EXPECT_EQ(1, config_dump.configs_size()); + envoy::admin::v3::EcdsConfigDump::EcdsFilterConfig ecds_msg; + config_dump.configs(0).UnpackTo(&ecds_msg); + EXPECT_EQ("1", ecds_msg.version_info()); + envoy::config::core::v3::TypedExtensionConfig filter_config; + EXPECT_TRUE(ecds_msg.ecds_filter().UnpackTo(&filter_config)); + EXPECT_EQ("bar", filter_config.name()); + test::integration::filters::TestDrainerNetworkFilterConfig network_filter_config; + filter_config.typed_config().UnpackTo(&network_filter_config); + EXPECT_EQ(4, network_filter_config.bytes_to_drain()); +} + +TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyExistingConnection) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addFilterChain(); + addDynamicFilter(filter_name_, false); + initialize(); + + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + + // Send config update to have filter drain 5 bytes of data. + uint32_t bytes_to_drain = 5; + sendXdsResponse(filter_name_, "1", bytes_to_drain); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); + + // Send 2nd config update to have filter drain 3 bytes of data. + sendXdsResponse(filter_name_, "2", 3); + test_server_->waitForCounterGe( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + + ASSERT_TRUE(tcp_client->write(data_)); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + std::string received_data; + // Expect drained bytes to be 5 as the 2nd config update was performed after new connection + // establishment. + ASSERT_TRUE(fake_upstream_connection->waitForData(data_.size() - bytes_to_drain, &received_data)); + const std::string expected_data = data_.substr(bytes_to_drain); + EXPECT_EQ(expected_data, received_data); + tcp_client->close(); +} + +} // namespace +} // namespace Envoy diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index b4dbf27d69618..618ce7454eae4 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -907,9 +907,15 @@ TEST_P(ProtocolIntegrationTest, Retry) { const size_t http2_header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 24 : 27; expectUpstreamBytesSentAndReceived( +#ifdef ALIMESH + BytesCountExpectation(2686 + quic_https_extra_bytes, 635, 550 + quic_https_extra_bytes, 54), + BytesCountExpectation(2262, 548, 242, http2_header_bytes_received), + BytesCountExpectation(2204, 520, 202, 6)); +#else BytesCountExpectation(2566 + quic_https_extra_bytes, 635, 430 + quic_https_extra_bytes, 54), BytesCountExpectation(2262, 548, 196, http2_header_bytes_received), BytesCountExpectation(2204, 520, 150, 6)); +#endif } // Regression test to guarantee that buffering for retries and shadows doesn't double the body size. @@ -3717,9 +3723,15 @@ TEST_P(ProtocolIntegrationTest, HeaderOnlyBytesCountUpstream) { const size_t wire_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; expectUpstreamBytesSentAndReceived( +#ifdef ALIMESH + BytesCountExpectation(227, 38, 196, 18), + BytesCountExpectation(164, wire_bytes_received, 164, wire_bytes_received), + BytesCountExpectation(160, 5, 160, 3)); +#else BytesCountExpectation(167, 38, 136, 18), BytesCountExpectation(120, wire_bytes_received, 120, wire_bytes_received), BytesCountExpectation(116, 5, 116, 3)); +#endif } TEST_P(ProtocolIntegrationTest, HeaderOnlyBytesCountDownstream) { @@ -3729,9 +3741,15 @@ TEST_P(ProtocolIntegrationTest, HeaderOnlyBytesCountDownstream) { useAccessLog("%DOWNSTREAM_WIRE_BYTES_SENT% %DOWNSTREAM_WIRE_BYTES_RECEIVED% " "%DOWNSTREAM_HEADER_BYTES_SENT% %DOWNSTREAM_HEADER_BYTES_RECEIVED%"); testRouterRequestAndResponseWithBody(0, 0, false); +#ifdef ALIMESH + expectDownstreamBytesSentAndReceived(BytesCountExpectation(207, 51, 188, 19), + BytesCountExpectation(138, 34, 138, 34), + BytesCountExpectation(11, 10, 11, 6)); +#else expectDownstreamBytesSentAndReceived(BytesCountExpectation(124, 51, 105, 19), BytesCountExpectation(68, 34, 68, 34), BytesCountExpectation(8, 10, 8, 6)); +#endif } TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountUpstream) { @@ -3744,9 +3762,15 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountUpstream) { testRouterRequestAndResponseWithBody(100, 100, false); const size_t header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; +#ifdef ALIMESH + expectUpstreamBytesSentAndReceived(BytesCountExpectation(366, 158, 224, 27), + BytesCountExpectation(274, 122, 165, header_bytes_received), + BytesCountExpectation(263, 109, 160, 3)); +#else expectUpstreamBytesSentAndReceived(BytesCountExpectation(306, 158, 164, 27), BytesCountExpectation(229, 122, 120, header_bytes_received), BytesCountExpectation(219, 109, 116, 3)); +#endif } TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountDownstream) { @@ -3757,9 +3781,15 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountDownstream) { useAccessLog("%DOWNSTREAM_WIRE_BYTES_SENT% %DOWNSTREAM_WIRE_BYTES_RECEIVED% " "%DOWNSTREAM_HEADER_BYTES_SENT% %DOWNSTREAM_HEADER_BYTES_RECEIVED%"); testRouterRequestAndResponseWithBody(100, 100, false); +#ifdef ALIMESH + expectDownstreamBytesSentAndReceived(BytesCountExpectation(327, 190, 197, 46), + BytesCountExpectation(240, 173, 131, 34), + BytesCountExpectation(111, 113, 11, 6)); +#else expectDownstreamBytesSentAndReceived(BytesCountExpectation(244, 190, 114, 46), BytesCountExpectation(177, 173, 68, 34), BytesCountExpectation(111, 113, 8, 6)); +#endif } TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseDownstream) { @@ -3780,17 +3810,29 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseDownstream) { auto response_one = sendRequestAndWaitForResponse(default_request_headers_, request_size, default_response_headers_, response_size, 0); checkSimpleRequestSuccess(request_size, response_size, response_one.get()); +#ifdef ALIMESH + expectDownstreamBytesSentAndReceived(BytesCountExpectation(327, 190, 197, 46), + BytesCountExpectation(236, 137, 127, 34), + BytesCountExpectation(111, 137, 11, 6), 0); +#else expectDownstreamBytesSentAndReceived(BytesCountExpectation(244, 190, 114, 46), BytesCountExpectation(177, 137, 68, 34), BytesCountExpectation(111, 137, 8, 6), 0); +#endif // Reuse connection, send the second request on the connection. auto response_two = sendRequestAndWaitForResponse(default_request_headers_, request_size, default_response_headers_, response_size, 0); checkSimpleRequestSuccess(request_size, response_size, response_two.get()); +#ifdef ALIMESH + expectDownstreamBytesSentAndReceived(BytesCountExpectation(326, 190, 196, 46), + BytesCountExpectation(178, 137, 46, 27), + BytesCountExpectation(111, 137, 11, 6), 1); +#else expectDownstreamBytesSentAndReceived(BytesCountExpectation(244, 190, 114, 46), BytesCountExpectation(148, 137, 15, 27), BytesCountExpectation(111, 137, 8, 6), 1); +#endif } TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseUpstream) { @@ -3814,20 +3856,34 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseUpstream) { const size_t http2_header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; expectUpstreamBytesSentAndReceived( +#ifdef ALIMESH + BytesCountExpectation(366, 158, 224, 27), + BytesCountExpectation(273, 122, 164, http2_header_bytes_received), + BytesCountExpectation(263, 108, 160, 3), 0); +#else BytesCountExpectation(306, 158, 164, 27), BytesCountExpectation(223, 122, 120, http2_header_bytes_received), BytesCountExpectation(223, 108, 114, 3), 0); +#endif // Swap clients so the other connection is used to send the request. std::swap(codec_client_, second_client); auto response_two = sendRequestAndWaitForResponse(default_request_headers_, request_size, default_response_headers_, response_size, 0); +#ifdef ALIMESH + const size_t http2_header_bytes_sent = + (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 69 : 72; + expectUpstreamBytesSentAndReceived(BytesCountExpectation(366, 158, 224, 27), + BytesCountExpectation(181, 119, http2_header_bytes_sent, 10), + BytesCountExpectation(114, 108, 13, 3), 1); +#else const size_t http2_header_bytes_sent = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 54 : 58; expectUpstreamBytesSentAndReceived(BytesCountExpectation(306, 158, 164, 27), BytesCountExpectation(167, 119, http2_header_bytes_sent, 10), BytesCountExpectation(114, 108, 11, 3), 1); +#endif second_client->close(); } @@ -3888,9 +3944,15 @@ TEST_P(ProtocolIntegrationTest, TrailersWireBytesCountUpstream) { const size_t http2_trailer_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 49 : 52; expectUpstreamBytesSentAndReceived( +#ifdef ALIMESH + BytesCountExpectation(316, 120, 264, 67), + BytesCountExpectation(221, 81, 202, http2_trailer_bytes_received), + BytesCountExpectation(178, 33, 166, 7)); +#else BytesCountExpectation(256, 120, 204, 67), BytesCountExpectation(181, 81, 162, http2_trailer_bytes_received), BytesCountExpectation(134, 33, 122, 7)); +#endif } TEST_P(ProtocolIntegrationTest, TrailersWireBytesCountDownstream) { @@ -3904,10 +3966,15 @@ TEST_P(ProtocolIntegrationTest, TrailersWireBytesCountDownstream) { config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); testTrailers(10, 20, true, true); - +#ifdef ALIMESH + expectDownstreamBytesSentAndReceived(BytesCountExpectation(289, 140, 239, 84), + BytesCountExpectation(195, 86, 166, 67), + BytesCountExpectation(36, 26, 17, 10)); +#else expectDownstreamBytesSentAndReceived(BytesCountExpectation(206, 140, 156, 84), BytesCountExpectation(136, 86, 107, 67), BytesCountExpectation(36, 26, 14, 10)); +#endif } TEST_P(ProtocolIntegrationTest, DownstreamDisconnectBeforeRequestCompleteWireBytesCountUpstream) { @@ -3920,9 +3987,15 @@ TEST_P(ProtocolIntegrationTest, DownstreamDisconnectBeforeRequestCompleteWireByt testRouterDownstreamDisconnectBeforeRequestComplete(nullptr); +#ifdef ALIMESH + expectUpstreamBytesSentAndReceived(BytesCountExpectation(255, 0, 224, 0), + BytesCountExpectation(164, 0, 164, 0), + BytesCountExpectation(160, 0, 160, 0)); +#else expectUpstreamBytesSentAndReceived(BytesCountExpectation(195, 0, 164, 0), BytesCountExpectation(120, 0, 120, 0), BytesCountExpectation(120, 0, 120, 0)); +#endif } TEST_P(ProtocolIntegrationTest, DownstreamDisconnectBeforeRequestCompleteWireBytesCountDownstream) { @@ -3950,9 +4023,15 @@ TEST_P(ProtocolIntegrationTest, UpstreamDisconnectBeforeRequestCompleteWireBytes testRouterUpstreamDisconnectBeforeRequestComplete(); +#ifdef ALIMESH + expectUpstreamBytesSentAndReceived(BytesCountExpectation(255, 0, 224, 0), + BytesCountExpectation(164, 0, 164, 0), + BytesCountExpectation(160, 0, 160, 0)); +#else expectUpstreamBytesSentAndReceived(BytesCountExpectation(195, 0, 164, 0), BytesCountExpectation(120, 0, 120, 0), BytesCountExpectation(120, 0, 120, 0)); +#endif } TEST_P(ProtocolIntegrationTest, UpstreamDisconnectBeforeResponseCompleteWireBytesCountUpstream) { @@ -3968,9 +4047,15 @@ TEST_P(ProtocolIntegrationTest, UpstreamDisconnectBeforeResponseCompleteWireByte const size_t http2_header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; expectUpstreamBytesSentAndReceived( +#ifdef ALIMESH + BytesCountExpectation(227, 47, 196, 27), + BytesCountExpectation(163, http2_header_bytes_received, 163, http2_header_bytes_received), + BytesCountExpectation(160, 5, 160, 3)); +#else BytesCountExpectation(167, 47, 136, 27), BytesCountExpectation(120, http2_header_bytes_received, 120, http2_header_bytes_received), BytesCountExpectation(113, 5, 113, 3)); +#endif } TEST_P(DownstreamProtocolIntegrationTest, BadRequest) { diff --git a/test/integration/redirect_integration_test.cc b/test/integration/redirect_integration_test.cc index c3e7b0d3d7aae..a06122893a1a0 100644 --- a/test/integration/redirect_integration_test.cc +++ b/test/integration/redirect_integration_test.cc @@ -225,8 +225,13 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirectDownstreamBytesCount) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); +#ifdef ALIMESH + expectDownstreamBytesSentAndReceived(BytesCountExpectation(223, 63, 204, 31), + BytesCountExpectation(136, 42, 136, 42), +#else expectDownstreamBytesSentAndReceived(BytesCountExpectation(140, 63, 121, 31), BytesCountExpectation(77, 42, 77, 42), +#endif BytesCountExpectation(9, 8, 9, 6), 1); } @@ -257,12 +262,25 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirectUpstreamBytesCount) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); BytesCountExpectation http2_expected = (GetParam().http2_implementation == Http2Impl::Oghttp2) +#ifdef ALIMESH + ? BytesCountExpectation(189, 59, 189, 59) + : BytesCountExpectation(189, 64, 189, 64); +#else ? BytesCountExpectation(137, 59, 137, 59) : BytesCountExpectation(137, 64, 137, 64); +#endif + +#ifdef ALIMESH + expectUpstreamBytesSentAndReceived(BytesCountExpectation(267, 110, 236, 85), http2_expected, + BytesCountExpectation(137, 64, 137, 64), 0); + expectUpstreamBytesSentAndReceived(BytesCountExpectation(302, 38, 277, 18), + BytesCountExpectation(96, 10, 96, 10), +#else expectUpstreamBytesSentAndReceived(BytesCountExpectation(195, 110, 164, 85), http2_expected, BytesCountExpectation(137, 64, 137, 64), 0); expectUpstreamBytesSentAndReceived(BytesCountExpectation(244, 38, 219, 18), BytesCountExpectation(85, 10, 85, 10), +#endif BytesCountExpectation(85, 10, 85, 10), 1); } diff --git a/test/mocks/http/mocks.cc b/test/mocks/http/mocks.cc index 819dfd55484cb..f8a44e1a30703 100644 --- a/test/mocks/http/mocks.cc +++ b/test/mocks/http/mocks.cc @@ -73,6 +73,21 @@ template static void initializeMockStreamFilterCallbacks(T& callbacks) MockStreamDecoderFilterCallbacks::MockStreamDecoderFilterCallbacks() { initializeMockStreamFilterCallbacks(*this); ON_CALL(*this, decodingBuffer()).WillByDefault(Invoke(&buffer_, &Buffer::InstancePtr::get)); +#if defined(ALIMESH) + ON_CALL(*this, modifyDecodingBuffer(_, _)) + .WillByDefault(Invoke( + [this](std::function callback, bool backup_for_replace) -> void { + if (backup_for_replace) { + Buffer::InstancePtr tmp_data = std::make_unique(); + tmp_data->move(*buffer_.get()); + } + callback(*buffer_.get()); + })); + ON_CALL(*this, modifyDecodingBuffer(_)) + .WillByDefault(Invoke([this](std::function callback) -> void { + callback(*buffer_.get()); + })); +#endif ON_CALL(*this, addDownstreamWatermarkCallbacks(_)) .WillByDefault(Invoke([this](DownstreamWatermarkCallbacks& callbacks) -> void { diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 7a26fb2fb2ede..8305126db55cf 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -260,6 +260,10 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, MOCK_METHOD(void, setDecoderBufferLimit, (uint32_t)); MOCK_METHOD(uint32_t, decoderBufferLimit, ()); MOCK_METHOD(bool, recreateStream, (const ResponseHeaderMap* headers)); +#if defined(ALIMESH) + MOCK_METHOD(bool, recreateStream, + (const ResponseHeaderMap* headers, bool use_original_request_body)); +#endif MOCK_METHOD(void, addUpstreamSocketOptions, (const Network::Socket::OptionsSharedPtr& options)); MOCK_METHOD(Network::Socket::OptionsSharedPtr, getUpstreamSocketOptions, (), (const)); MOCK_METHOD(const Router::RouteSpecificFilterConfig*, mostSpecificPerFilterConfig, (), (const)); @@ -303,6 +307,9 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, MOCK_METHOD(RequestTrailerMap&, addDecodedTrailers, ()); MOCK_METHOD(MetadataMapVector&, addDecodedMetadata, ()); MOCK_METHOD(const Buffer::Instance*, decodingBuffer, ()); +#if defined(ALIMESH) + MOCK_METHOD(void, modifyDecodingBuffer, (std::function, bool)); +#endif MOCK_METHOD(void, modifyDecodingBuffer, (std::function)); MOCK_METHOD(void, encode1xxHeaders_, (HeaderMap & headers)); MOCK_METHOD(void, encodeHeaders_, (ResponseHeaderMap & headers, bool end_stream)); @@ -671,6 +678,9 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(ServerHeaderValidatorPtr, makeHeaderValidator, (Protocol protocol)); MOCK_METHOD(bool, appendXForwardedPort, (), (const)); MOCK_METHOD(bool, addProxyProtocolConnectionState, (), (const)); +#if defined(ALIMESH) + MOCK_METHOD(std::chrono::seconds, keepaliveHeaderTimeout, (), (const)); +#endif std::unique_ptr internal_address_config_ = std::make_unique(); @@ -933,7 +943,40 @@ MATCHER_P(HeaderMapEqualWithMaxSize, rhs, "") { } MATCHER_P(HeaderMapEqualRef, rhs, "") { - const bool equal = (arg == *rhs); +#if defined(ALIMESH) + bool equal = true; + + auto getHeaderItems = [](const Envoy::Http::HeaderMap& header, + std::vector>& dst) { + auto f = [&dst](const Envoy::Http::HeaderEntry& header) -> Envoy::Http::HeaderMap::Iterate { + dst.push_back(std::make_pair(header.key().getStringView(), header.value().getStringView())); + return Envoy::Http::HeaderMap::Iterate::Continue; + }; + + header.iterate(f); + }; + + std::vector> arg_header, rhs_header; + + getHeaderItems(arg, arg_header); + getHeaderItems((*rhs), rhs_header); + + auto i = arg_header.begin(); + auto j = rhs_header.begin(); + + for (; i != arg_header.end(); ++i, ++j) { + + if (i->first == "req-start-time") { + continue; + } + if (i->first != j->first || i->second != j->second) { + equal = false; + break; + } + } +#else + const bool equal = (arg == *rhs) +#endif if (!equal) { *result_listener << "\n" << TestUtility::addLeftAndRightPadding("header map:") << "\n" diff --git a/test/mocks/redis/BUILD b/test/mocks/redis/BUILD index 6ff8990aae059..95bfd2b45a375 100644 --- a/test/mocks/redis/BUILD +++ b/test/mocks/redis/BUILD @@ -1,8 +1,30 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_cc_mock", "envoy_package", ) licenses(["notice"]) # Apache 2 envoy_package() + +envoy_cc_mock( + name = "redis_mocks", + srcs = ["mocks.cc"], + hdrs = ["mocks.h"], + external_deps = [ + "abseil_strings", + ], + deps = [ + "//envoy/access_log:access_log_interface", + "//envoy/buffer:buffer_interface", + "//envoy/event:dispatcher_interface", + "//envoy/redis:async_client_interface", + "//envoy/http:filter_interface", + "//source/common/http:conn_manager_config_interface", + "//source/common/http:filter_manager_lib", + "//source/common/http:header_map_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/upstream:host_mocks", + ], +) diff --git a/test/mocks/redis/mocks.cc b/test/mocks/redis/mocks.cc new file mode 100644 index 0000000000000..0c2c2e0f5b128 --- /dev/null +++ b/test/mocks/redis/mocks.cc @@ -0,0 +1,32 @@ +#include "mocks.h" + +#include "envoy/buffer/buffer.h" +#include "envoy/common/optref.h" +#include "envoy/event/dispatcher.h" +#include "envoy/http/header_map.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Redis { + +MockRedisAsyncClient::MockRedisAsyncClient() { + ON_CALL(*this, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); +} +MockRedisAsyncClient::~MockRedisAsyncClient() = default; + +MockRedisPoolRequest::MockRedisPoolRequest(MockRedisAsyncClient* client, std::string&& request) + : client_(client), request_(request) {} +MockRedisPoolRequest::~MockRedisPoolRequest() = default; + +MockRedisAsyncClientCallbacks::MockRedisAsyncClientCallbacks() = default; +MockRedisAsyncClientCallbacks::~MockRedisAsyncClientCallbacks() = default; + +} // namespace Redis +} // namespace Envoy diff --git a/test/mocks/redis/mocks.h b/test/mocks/redis/mocks.h new file mode 100644 index 0000000000000..b57c2f858c536 --- /dev/null +++ b/test/mocks/redis/mocks.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "envoy/access_log/access_log.h" +#include "envoy/redis/async_client.h" +#include "envoy/http/filter.h" +#include "envoy/matcher/matcher.h" + +#include "source/common/http/utility.h" + +#include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/host.h" +#include "test/test_common/printers.h" + +#include "absl/strings/ascii.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "gmock/gmock.h" + +using testing::Return; + +namespace Envoy { +namespace Redis { + +class MockRedisAsyncClient : public Redis::AsyncClient { +public: + MockRedisAsyncClient(); + ~MockRedisAsyncClient() override; + + Redis::PoolRequest* send(std::string&& query, Callbacks& callbacks) override { + return send_(query, callbacks); + } + + MOCK_METHOD(void, initialize, (Redis::AsyncClientConfig config), (override)); + + MOCK_METHOD(Redis::PoolRequest*, send_, (std::string & query, Callbacks& callbacks)); + + MOCK_METHOD(Event::Dispatcher&, dispatcher, (), (override)); + + NiceMock dispatcher_; +}; + +class MockRedisPoolRequest : public Redis::PoolRequest { +public: + MockRedisPoolRequest(MockRedisAsyncClient* client, std::string&& request); + ~MockRedisPoolRequest() override; + + MOCK_METHOD(void, cancel, ()); + + MockRedisAsyncClient* client_; + std::string request_; +}; + +class MockRedisAsyncClientCallbacks : public Redis::AsyncClient::Callbacks { +public: + MockRedisAsyncClientCallbacks(); + ~MockRedisAsyncClientCallbacks() override; + + // Redis::AsyncClient::Callbacks + void onSuccess(std::string_view query, std::string&& response) override { + onSuccess_(query, response); + } + MOCK_METHOD(void, onFailure, (std::string_view query), (override)); + + MOCK_METHOD(void, onSuccess_, (std::string_view query, std::string& response)); +}; + +} // namespace Redis +} // namespace Envoy diff --git a/test/mocks/router/mocks.cc b/test/mocks/router/mocks.cc index caeed352f87f9..9f725882b5923 100644 --- a/test/mocks/router/mocks.cc +++ b/test/mocks/router/mocks.cc @@ -28,6 +28,12 @@ MockInternalRedirectPolicy::MockInternalRedirectPolicy() { ON_CALL(*this, enabled()).WillByDefault(Return(false)); } +#if defined(ALIMESH) +MockInternalActiveRedirectPolicy::MockInternalActiveRedirectPolicy() { + ON_CALL(*this, enabled()).WillByDefault(Return(false)); +} +#endif + MockRetryState::MockRetryState() = default; void MockRetryState::expectHeadersRetry() { @@ -114,6 +120,10 @@ MockRouteEntry::MockRouteEntry() { ON_CALL(*this, connectConfig()).WillByDefault(Invoke([this]() { return connect_config_.has_value() ? makeOptRef(connect_config_.value()) : absl::nullopt; })); +#if defined(ALIMESH) + ON_CALL(*this, internalActiveRedirectPolicy()) + .WillByDefault(ReturnRef(internal_active_redirect_policy_)); +#endif ON_CALL(*this, earlyDataPolicy()).WillByDefault(ReturnRef(early_data_policy_)); path_matcher_ = std::make_shared>(); ON_CALL(*this, pathMatcher()).WillByDefault(ReturnRef(path_matcher_)); @@ -163,6 +173,9 @@ MockRouteConfigProviderManager::~MockRouteConfigProviderManager() = default; MockScopedConfig::MockScopedConfig() { ON_CALL(*this, getRouteConfig(_)).WillByDefault(Return(route_config_)); +#if defined(ALIMESH) + ON_CALL(*this, getRouteConfig(_, _, _)).WillByDefault(Return(route_config_)); +#endif } MockScopedConfig::~MockScopedConfig() = default; diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index c2769096ae100..6670883159010 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -162,6 +162,22 @@ class MockInternalRedirectPolicy : public InternalRedirectPolicy { MOCK_METHOD(bool, isCrossSchemeRedirectAllowed, (), (const)); }; +#if defined(ALIMESH) +class MockInternalActiveRedirectPolicy : public InternalActiveRedirectPolicy { +public: + MockInternalActiveRedirectPolicy(); + MOCK_METHOD(bool, enabled, (), (const)); + MOCK_METHOD(bool, shouldRedirectForResponseCode, (const Http::Code& response_code), (const)); + MOCK_METHOD(std::vector, predicates, (), (const)); + MOCK_METHOD(uint32_t, maxInternalRedirects, (), (const)); + MOCK_METHOD(bool, isCrossSchemeRedirectAllowed, (), (const)); + MOCK_METHOD(void, evaluateHeaders, (Http::HeaderMap&, const StreamInfo::StreamInfo*), (const)); + MOCK_METHOD(std::string, redirectUrl, (absl::optional), (const)); + MOCK_METHOD(bool, forcedUseOriginalHost, (), (const)); + MOCK_METHOD(bool, forcedAddHeaderBeforeRouteMatcher, (), (const)); +}; +#endif + class MockInternalRedirectPredicate : public InternalRedirectPredicate { public: MOCK_METHOD(bool, acceptTargetRoute, (StreamInfo::FilterState&, absl::string_view, bool, bool)); @@ -440,6 +456,10 @@ class MockRouteEntry : public RouteEntry { MOCK_METHOD(const EarlyDataPolicy&, earlyDataPolicy, (), (const)); MOCK_METHOD(const RouteStatsContextOptRef, routeStatsContext, (), (const)); +#if defined(ALIMESH) + MOCK_METHOD(const InternalActiveRedirectPolicy&, internalActiveRedirectPolicy, (), (const)); +#endif + std::string cluster_name_{"fake_cluster"}; std::string route_name_{"fake_route_name"}; std::multimap opaque_config_; @@ -459,6 +479,10 @@ class MockRouteEntry : public RouteEntry { testing::NiceMock path_match_criterion_; UpgradeMap upgrade_map_; absl::optional connect_config_; + +#if defined(ALIMESH) + testing::NiceMock internal_active_redirect_policy_; +#endif testing::NiceMock early_data_policy_; }; @@ -591,6 +615,12 @@ class MockScopedConfig : public ScopedConfig { MOCK_METHOD(ConfigConstSharedPtr, getRouteConfig, (const ScopeKeyPtr& scope_key), (const)); +#if defined(ALIMESH) + MOCK_METHOD(ConfigConstSharedPtr, getRouteConfig, + (const ScopeKeyBuilder*, const Http::HeaderMap&, const StreamInfo::StreamInfo*), + (const)); +#endif + std::shared_ptr route_config_{new NiceMock()}; }; @@ -614,7 +644,15 @@ class MockScopeKeyBuilder : public ScopeKeyBuilder { MockScopeKeyBuilder(); ~MockScopeKeyBuilder() override; +#if defined(ALIMESH) + MOCK_METHOD(ScopeKeyPtr, computeScopeKey, + (const Http::HeaderMap&, const StreamInfo::StreamInfo*, + std::function& recompute), + (const)); + MOCK_METHOD(ScopeKeyPtr, computeScopeKey, (const Http::HeaderMap&), (const)); +#else MOCK_METHOD(ScopeKeyPtr, computeScopeKey, (const Http::HeaderMap&), (const)); +#endif }; class MockGenericConnPool : public GenericConnPool { diff --git a/test/mocks/server/factory_context.cc b/test/mocks/server/factory_context.cc index 47712f6f92aa8..a9fa3f0a8c203 100644 --- a/test/mocks/server/factory_context.cc +++ b/test/mocks/server/factory_context.cc @@ -47,7 +47,7 @@ MockFactoryContext::MockFactoryContext() MockFactoryContext::~MockFactoryContext() = default; -MockUpstreamHttpFactoryContext::MockUpstreamHttpFactoryContext() { +MockUpstreamFactoryContext::MockUpstreamFactoryContext() { ON_CALL(*this, getServerFactoryContext()).WillByDefault(ReturnRef(server_factory_context_)); ON_CALL(*this, initManager()).WillByDefault(ReturnRef(init_manager_)); ON_CALL(*this, scope()).WillByDefault(ReturnRef(scope_)); diff --git a/test/mocks/server/factory_context.h b/test/mocks/server/factory_context.h index e1327228ebeb5..756160cb1c0c3 100644 --- a/test/mocks/server/factory_context.h +++ b/test/mocks/server/factory_context.h @@ -84,9 +84,9 @@ class MockFactoryContext : public virtual ListenerFactoryContext { testing::NiceMock api_; }; -class MockUpstreamHttpFactoryContext : public UpstreamHttpFactoryContext { +class MockUpstreamFactoryContext : public UpstreamFactoryContext { public: - MockUpstreamHttpFactoryContext(); + MockUpstreamFactoryContext(); MOCK_METHOD(ServerFactoryContext&, getServerFactoryContext, (), (const)); MOCK_METHOD(Init::Manager&, initManager, ()); diff --git a/test/mocks/server/server_factory_context.cc b/test/mocks/server/server_factory_context.cc index 0acd1b671908a..d316900d1e0df 100644 --- a/test/mocks/server/server_factory_context.cc +++ b/test/mocks/server/server_factory_context.cc @@ -38,6 +38,10 @@ MockServerFactoryContext::~MockServerFactoryContext() = default; MockStatsConfig::MockStatsConfig() = default; MockStatsConfig::~MockStatsConfig() = default; +StatelessMockServerFactoryContext::StatelessMockServerFactoryContext() + : filter_config_provider_manager_( + std::make_shared()) {} + } // namespace Configuration } // namespace Server } // namespace Envoy diff --git a/test/mocks/server/server_factory_context.h b/test/mocks/server/server_factory_context.h index d7bd11f4719f1..7e22a91146597 100644 --- a/test/mocks/server/server_factory_context.h +++ b/test/mocks/server/server_factory_context.h @@ -79,6 +79,10 @@ class MockServerFactoryContext : public virtual ServerFactoryContext { MOCK_METHOD(ServerLifecycleNotifier&, lifecycleNotifier, ()); MOCK_METHOD(StatsConfig&, statsConfig, (), ()); MOCK_METHOD(AccessLog::AccessLogManager&, accessLogManager, (), ()); + Configuration::DownstreamHTTPFilterConfigProviderManagerSharedPtr + downstreamHttpFilterConfigProviderManager() override { + return filter_config_provider_manager_; + } testing::NiceMock cluster_manager_; testing::NiceMock dispatcher_; @@ -101,13 +105,15 @@ class MockServerFactoryContext : public virtual ServerFactoryContext { Router::ContextImpl router_context_; envoy::config::bootstrap::v3::Bootstrap bootstrap_; testing::NiceMock options_; + Configuration::DownstreamHTTPFilterConfigProviderManagerSharedPtr filter_config_provider_manager_{ + std::make_shared()}; }; // Stateless mock ServerFactoryContext for cases where it needs to be used concurrently in different // threads. Global state in the MockServerFactoryContext causes thread safety issues in this case. class StatelessMockServerFactoryContext : public virtual ServerFactoryContext { public: - StatelessMockServerFactoryContext() = default; + StatelessMockServerFactoryContext(); ~StatelessMockServerFactoryContext() override = default; MOCK_METHOD(Upstream::ClusterManager&, clusterManager, ()); @@ -134,6 +140,11 @@ class StatelessMockServerFactoryContext : public virtual ServerFactoryContext { MOCK_METHOD(ServerLifecycleNotifier&, lifecycleNotifier, ()); MOCK_METHOD(StatsConfig&, statsConfig, (), ()); MOCK_METHOD(AccessLog::AccessLogManager&, accessLogManager, (), ()); + Configuration::DownstreamHTTPFilterConfigProviderManagerSharedPtr + downstreamHttpFilterConfigProviderManager() override { + return filter_config_provider_manager_; + } + Configuration::DownstreamHTTPFilterConfigProviderManagerSharedPtr filter_config_provider_manager_; }; } // namespace Configuration diff --git a/test/mocks/stream_info/mocks.h b/test/mocks/stream_info/mocks.h index 469169332a5b9..4b43c667376f2 100644 --- a/test/mocks/stream_info/mocks.h +++ b/test/mocks/stream_info/mocks.h @@ -142,6 +142,11 @@ class MockStreamInfo : public StreamInfo { MOCK_METHOD(bool, isShadow, (), (const, override)); MOCK_METHOD(void, setDownstreamTransportFailureReason, (absl::string_view failure_reason)); MOCK_METHOD(absl::string_view, downstreamTransportFailureReason, (), (const)); +#ifdef ALIMESH + MOCK_METHOD(void, setCustomSpanTag, (absl::string_view, absl::string_view)); + MOCK_METHOD((const absl::flat_hash_map&), getCustomSpanTagMap, (), (const)); +#endif + Envoy::Event::SimulatedTimeSystem ts_; SystemTime start_time_; MonotonicTime start_time_monotonic_; diff --git a/test/mocks/upstream/BUILD b/test/mocks/upstream/BUILD index 52e6e6571caca..97b9706f0d342 100644 --- a/test/mocks/upstream/BUILD +++ b/test/mocks/upstream/BUILD @@ -233,6 +233,9 @@ envoy_cc_mock( "//test/mocks/upstream:cluster_priority_set_mocks", "//test/mocks/upstream:load_balancer_mocks", ], + alimesh_deps = [ + "//test/mocks/redis:redis_mocks", + ], ) envoy_cc_mock( diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index 8ebfc4f9610db..76817f1ec3699 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -35,6 +35,10 @@ class MockDetectorHostMonitor : public DetectorHostMonitor { MOCK_METHOD(double, successRate, (DetectorHostMonitor::SuccessRateMonitorType type), (const)); MOCK_METHOD(void, successRate, (DetectorHostMonitor::SuccessRateMonitorType type, double new_success_rate)); + +#if defined(ALIMESH) + MOCK_METHOD(void, forceEjectHost, ()); +#endif }; class MockEventLogger : public EventLogger { diff --git a/test/mocks/upstream/thread_local_cluster.cc b/test/mocks/upstream/thread_local_cluster.cc index 5842f04d8807a..40e3a60c0c36f 100644 --- a/test/mocks/upstream/thread_local_cluster.cc +++ b/test/mocks/upstream/thread_local_cluster.cc @@ -18,6 +18,9 @@ MockThreadLocalCluster::MockThreadLocalCluster() { ON_CALL(*this, tcpConnPool(_, _)) .WillByDefault(Return(Upstream::TcpPoolData([]() {}, &tcp_conn_pool_))); ON_CALL(*this, httpAsyncClient()).WillByDefault(ReturnRef(async_client_)); +#if defined(ALIMESH) + ON_CALL(*this, redisAsyncClient()).WillByDefault(ReturnRef(redis_async_client_)); +#endif } MockThreadLocalCluster::~MockThreadLocalCluster() = default; diff --git a/test/mocks/upstream/thread_local_cluster.h b/test/mocks/upstream/thread_local_cluster.h index bd445fe75da76..d70b125e99ff9 100644 --- a/test/mocks/upstream/thread_local_cluster.h +++ b/test/mocks/upstream/thread_local_cluster.h @@ -3,6 +3,7 @@ #include "envoy/upstream/thread_local_cluster.h" #include "test/mocks/http/conn_pool.h" +#include "test/mocks/redis/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/tcp/mocks.h" @@ -37,6 +38,9 @@ class MockThreadLocalCluster : public ThreadLocalCluster { (ResourcePriority priority, LoadBalancerContext* context)); MOCK_METHOD(MockHost::MockCreateConnectionData, tcpConn_, (LoadBalancerContext * context)); MOCK_METHOD(Http::AsyncClient&, httpAsyncClient, ()); +#if defined(ALIMESH) + MOCK_METHOD(Redis::AsyncClient&, redisAsyncClient, ()); +#endif MOCK_METHOD(Tcp::AsyncTcpClientPtr, tcpAsyncClient, (LoadBalancerContext * context, Tcp::AsyncTcpClientOptionsConstSharedPtr options)); @@ -44,6 +48,9 @@ class MockThreadLocalCluster : public ThreadLocalCluster { NiceMock lb_; NiceMock conn_pool_; NiceMock async_client_; +#if defined(ALIMESH) + NiceMock redis_async_client_; +#endif NiceMock tcp_conn_pool_; }; diff --git a/test/server/BUILD b/test/server/BUILD index 62060da63fa5b..e6aa1131c0a91 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -9,7 +9,7 @@ load( "envoy_select_admin_functionality", "envoy_select_hot_restart", ) -load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//bazel:repositories.bzl", "DARWIN_SKIP_TARGETS", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 @@ -272,6 +272,7 @@ envoy_cc_fuzz_test( ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), + "//bazel:darwin": envoy_all_extensions(DARWIN_SKIP_TARGETS), "//bazel:gcc_build": [], "//conditions:default": envoy_all_extensions(), }), diff --git a/test/server/admin/BUILD b/test/server/admin/BUILD index 950a66bc4c3f0..ff703d8272f86 100644 --- a/test/server/admin/BUILD +++ b/test/server/admin/BUILD @@ -220,6 +220,7 @@ envoy_cc_test( deps = [ ":admin_instance_lib", "//test/integration/filters:test_listener_filter_lib", + "//test/integration/filters:test_network_filter_lib", ], ) diff --git a/test/server/admin/admin_test.cc b/test/server/admin/admin_test.cc index 106f7656e8ef5..6d791c34c06cc 100644 --- a/test/server/admin/admin_test.cc +++ b/test/server/admin/admin_test.cc @@ -130,6 +130,7 @@ TEST_P(AdminInstanceTest, Help) { Http::TestResponseHeaderMapImpl header_map; Buffer::OwnedImpl response; EXPECT_EQ(Http::Code::OK, getCallback("/help", header_map, response)); +#if defined(ALIMESH) const std::string expected = R"EOF(admin commands are: /: Admin home page /certs: print certs on machine @@ -145,6 +146,7 @@ TEST_P(AdminInstanceTest, Help) { /drain_listeners (POST): drain listeners graceful: When draining listeners, enter a graceful drain period prior to closing listeners. This behaviour and duration is configurable via server options or CLI inboundonly: Drains all inbound listeners. traffic_direction field in envoy_v3_api_msg_config.listener.v3.Listener is used to determine whether a listener is inbound or outbound. + /endpoints: print endpoints info related the service /healthcheck/fail (POST): cause the server to fail health checks /healthcheck/ok (POST): cause the server to pass health checks /heap_dump: dump current Envoy heap (if supported) @@ -182,6 +184,60 @@ TEST_P(AdminInstanceTest, Help) { /stats/recentlookups/disable (POST): disable recording of reset stat-name lookup names /stats/recentlookups/enable (POST): enable recording of reset stat-name lookup names )EOF"; +#else + const std::string expected = R"EOF(admin commands are: + /: Admin home page + /certs: print certs on machine + /clusters: upstream cluster status + /config_dump: dump current Envoy configs (experimental) + resource: The resource to dump + mask: The mask to apply. When both resource and mask are specified, the mask is applied to every element in the desired repeated field so that only a subset of fields are returned. The mask is parsed as a ProtobufWkt::FieldMask + name_regex: Dump only the currently loaded configurations whose names match the specified regex. Can be used with both resource and mask query parameters. + include_eds: Dump currently loaded configuration including EDS. See the response definition for more information + /contention: dump current Envoy mutex contention stats (if enabled) + /cpuprofiler (POST): enable/disable the CPU profiler + enable: enables the CPU profiler; One of (y, n) + /drain_listeners (POST): drain listeners + graceful: When draining listeners, enter a graceful drain period prior to closing listeners. This behaviour and duration is configurable via server options or CLI + inboundonly: Drains all inbound listeners. traffic_direction field in envoy_v3_api_msg_config.listener.v3.Listener is used to determine whether a listener is inbound or outbound. + /healthcheck/fail (POST): cause the server to fail health checks + /healthcheck/ok (POST): cause the server to pass health checks + /heap_dump: dump current Envoy heap (if supported) + /heapprofiler (POST): enable/disable the heap profiler + enable: enable/disable the heap profiler; One of (y, n) + /help: print out list of admin commands + /hot_restart_version: print the hot restart compatibility version + /init_dump: dump current Envoy init manager information (experimental) + mask: The desired component to dump unready targets. The mask is parsed as a ProtobufWkt::FieldMask. For example, get the unready targets of all listeners with /init_dump?mask=listener` + /listeners: print listener info + format: File format to use; One of (text, json) + /logging (POST): query/change logging levels + paths: Change multiple logging levels by setting to :,:. + level: desired logging level; One of (, trace, debug, info, warning, error, critical, off) + /memory: print current allocation/heap usage + /quitquitquit (POST): exit the server + /ready: print server state, return 200 if LIVE, otherwise return 503 + /reopen_logs (POST): reopen access logs + /reset_counters (POST): reset all counters to zero + /runtime: print runtime values + /runtime_modify (POST): Adds or modifies runtime values as passed in query parameters. To delete a previously added key, use an empty string as the value. Note that deletion only applies to overrides added via this endpoint; values loaded from disk can be modified via override but not deleted. E.g. ?key1=value1&key2=value2... + /server_info: print server version/status information + /stats: print server stats + usedonly: Only include stats that have been written by system since restart + filter: Regular expression (Google re2) for filtering stats + format: Format to use; One of (html, active-html, text, json) + type: Stat types to include.; One of (All, Counters, Histograms, Gauges, TextReadouts) + histogram_buckets: Histogram bucket display mode; One of (cumulative, disjoint, detailed, none) + /stats/prometheus: print server stats in prometheus format + usedonly: Only include stats that have been written by system since restart + text_readouts: Render text_readouts as new gaugues with value 0 (increases Prometheus data size) + filter: Regular expression (Google re2) for filtering stats + /stats/recentlookups: Show recent stat-name lookups + /stats/recentlookups/clear (POST): clear list of stat-name lookups and counter + /stats/recentlookups/disable (POST): disable recording of reset stat-name lookup names + /stats/recentlookups/enable (POST): enable recording of reset stat-name lookup names +)EOF"; +#endif EXPECT_EQ(expected, response.toString()); } diff --git a/test/server/admin/config_dump_handler_test.cc b/test/server/admin/config_dump_handler_test.cc index bc3c1b0ed5203..962039e3c8e9a 100644 --- a/test/server/admin/config_dump_handler_test.cc +++ b/test/server/admin/config_dump_handler_test.cc @@ -1,4 +1,5 @@ #include "test/integration/filters/test_listener_filter.pb.h" +#include "test/integration/filters/test_network_filter.pb.h" #include "test/server/admin/admin_instance.h" using testing::HasSubstr; @@ -793,16 +794,27 @@ TEST_P(AdminInstanceTest, FieldMasksWorkWhenFetchingAllResources) { ProtobufTypes::MessagePtr testDumpEcdsConfig(const Matchers::StringMatcher&) { auto msg = std::make_unique(); - auto* ecds = msg->mutable_ecds_filters()->Add(); - ecds->set_version_info("1"); - ecds->mutable_last_updated()->set_seconds(5); - envoy::config::core::v3::TypedExtensionConfig filter_config; - filter_config.set_name("foo"); + auto* ecds_listener = msg->mutable_ecds_filters()->Add(); + ecds_listener->set_version_info("1"); + ecds_listener->mutable_last_updated()->set_seconds(5); + envoy::config::core::v3::TypedExtensionConfig listener_filter_config; + listener_filter_config.set_name("foo"); auto listener_config = test::integration::filters::TestTcpListenerFilterConfig(); listener_config.set_drain_bytes(5); - filter_config.mutable_typed_config()->PackFrom(listener_config); - ecds->mutable_ecds_filter()->PackFrom(filter_config); + listener_filter_config.mutable_typed_config()->PackFrom(listener_config); + ecds_listener->mutable_ecds_filter()->PackFrom(listener_filter_config); + + auto* ecds_network = msg->mutable_ecds_filters()->Add(); + ecds_network->set_version_info("1"); + ecds_network->mutable_last_updated()->set_seconds(5); + envoy::config::core::v3::TypedExtensionConfig network_filter_config; + network_filter_config.set_name("bar"); + auto network_config = test::integration::filters::TestDrainerNetworkFilterConfig(); + network_config.set_bytes_to_drain(5); + network_filter_config.mutable_typed_config()->PackFrom(network_config); + ecds_network->mutable_ecds_filter()->PackFrom(network_filter_config); + return msg; } @@ -824,6 +836,19 @@ TEST_P(AdminInstanceTest, ConfigDumpEcds) { } }, "last_updated": "1970-01-01T00:00:05Z" + }, + { + "@type": "type.googleapis.com/envoy.admin.v3.EcdsConfigDump.EcdsFilterConfig", + "version_info": "1", + "ecds_filter": { + "@type": "type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig", + "name": "bar", + "typed_config": { + "@type": "type.googleapis.com/test.integration.filters.TestDrainerNetworkFilterConfig", + "bytes_to_drain": 5 + } + }, + "last_updated": "1970-01-01T00:00:05Z" } ] } @@ -847,6 +872,14 @@ TEST_P(AdminInstanceTest, ConfigDumpEcdsByResourceAndMask) { "@type": "type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig", "name": "foo" } + }, + { + "@type": "type.googleapis.com/envoy.admin.v3.EcdsConfigDump.EcdsFilterConfig", + "version_info": "1", + "ecds_filter": { + "@type": "type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig", + "name": "bar" + } } ] } diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 940c8ac0a34d9..89a86c5a73929 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -1,5 +1,5 @@ load("//bazel:envoy_build_system.bzl", "envoy_cc_fuzz_test", "envoy_cc_test", "envoy_cc_test_library", "envoy_package", "envoy_proto_library") -load("//bazel:repositories.bzl", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") +load("//bazel:repositories.bzl", "DARWIN_SKIP_TARGETS", "PPC_SKIP_TARGETS", "WINDOWS_SKIP_TARGETS") load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") licenses(["notice"]) # Apache 2 @@ -99,6 +99,7 @@ envoy_cc_fuzz_test( ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), + "//bazel:darwin": envoy_all_extensions(DARWIN_SKIP_TARGETS), "//bazel:gcc_build": [], "//conditions:default": envoy_all_extensions(), }), diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index 5937455017027..1d2f134194845 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -53,6 +53,11 @@ bool TestUtility::headerMapEqualIgnoreOrder(const Http::HeaderMap& lhs, absl::flat_hash_set rhs_keys; lhs.iterate([&lhs_keys](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { const std::string key{header.key().getStringView()}; +#if defined(ALIMESH) + if (key == Http::CustomHeaders::get().AliExtendedValues.TriStartTime.get()) { + return Http::HeaderMap::Iterate::Continue; + } +#endif lhs_keys.insert(key); return Http::HeaderMap::Iterate::Continue; }); diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 5e3394bf5559c..05565c350218d 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -147,6 +147,18 @@ template class WasmHttpFilterTestBase : public W context_->setEncoderFilterCallbacks(encoder_callbacks_); } +#if defined(ALIMESH) + template void doRecover() { + std::shared_ptr new_handle; + if (WasmTestBase::plugin_handle_->doRecover(new_handle)) { + WasmTestBase::plugin_handle_ = std::static_pointer_cast(new_handle); + WasmTestBase::wasm_ = WasmTestBase::plugin_handle_->wasmHandle(); + WasmTestBase::wasm_->wasm()->lifecycleStats().recover_total_.inc(); + setupFilterBase(); + } + } +#endif + std::unique_ptr context_; NiceMock decoder_callbacks_; NiceMock encoder_callbacks_; diff --git a/tools/project/BUILD b/tools/project/BUILD new file mode 100644 index 0000000000000..c072f656e28dc --- /dev/null +++ b/tools/project/BUILD @@ -0,0 +1,47 @@ +load("//bazel:envoy_build_system.bzl", "envoy_package") +load("@envoy_repo//:path.bzl", "PATH") +load("//tools/base:envoy_python.bzl", "envoy_entry_point") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_entry_point( + name = "release", + args = [ + "release", + PATH, + ], + pkg = "envoy.base.utils", + script = "envoy.project", +) + +envoy_entry_point( + name = "dev", + args = [ + "dev", + PATH, + ], + pkg = "envoy.base.utils", + script = "envoy.project", +) + +envoy_entry_point( + name = "sync", + args = [ + "sync", + PATH, + ], + pkg = "envoy.base.utils", + script = "envoy.project", +) + +envoy_entry_point( + name = "publish", + args = [ + "publish", + PATH, + ], + pkg = "envoy.base.utils", + script = "envoy.project", +) diff --git a/tools/proto_format/format_api.py b/tools/proto_format/format_api.py index 34f38588804cc..eec43435ff88b 100644 --- a/tools/proto_format/format_api.py +++ b/tools/proto_format/format_api.py @@ -38,6 +38,11 @@ 'envoy.extensions.filters.network.kafka_broker.v3', 'envoy.extensions.filters.network.mysql_proxy.v3', 'envoy.extensions.filters.network.rocketmq_proxy.v3', + + # Ingress gateway. + 'envoy.extensions.filters.http.http_dubbo_transcoder.v3', + 'envoy.extensions.custom_cluster_plugins.cluster_fallback.v3', + 'envoy.extensions.upstreams.http.dubbo_tcp.v3', ] BUILD_FILE_TEMPLATE = string.Template( diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 83eb11a753d44..2c117b95be65a 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1444,6 +1444,9 @@ crlf ep suri transid +WAF +TRI +tmd routable vhosts infos From 7d44bc8044058627a9738a9217993b9db017d41d Mon Sep 17 00:00:00 2001 From: zty98751 Date: Thu, 1 Aug 2024 15:21:14 +0800 Subject: [PATCH 221/274] rename alimesh to higress --- bazel/BUILD | 8 ++-- bazel/envoy_binary.bzl | 4 +- bazel/envoy_internal.bzl | 6 +-- bazel/envoy_library.bzl | 6 +-- bazel/envoy_test.bzl | 16 +++---- contrib/contrib_build_config.bzl | 7 +-- envoy/http/filter.h | 6 +-- envoy/http/header_map.h | 2 +- envoy/router/BUILD | 2 +- envoy/router/router.h | 4 +- envoy/router/scopes.h | 4 +- envoy/stream_info/stream_info.h | 2 +- envoy/upstream/BUILD | 4 +- envoy/upstream/outlier_detection.h | 2 +- envoy/upstream/thread_local_cluster.h | 2 +- source/common/http/async_client_impl.cc | 2 +- source/common/http/async_client_impl.h | 4 +- source/common/http/conn_manager_config.h | 2 +- source/common/http/conn_manager_impl.cc | 12 ++--- source/common/http/conn_manager_impl.h | 2 +- source/common/http/conn_manager_utility.cc | 2 +- source/common/http/filter_manager.cc | 6 +-- source/common/http/filter_manager.h | 14 +++--- source/common/http/headers.h | 2 +- source/common/protobuf/utility.cc | 2 +- source/common/protobuf/utility.h | 2 +- source/common/router/BUILD | 6 +-- source/common/router/config_impl.cc | 16 +++---- source/common/router/config_impl.h | 20 ++++---- source/common/router/delegating_route_impl.h | 2 +- source/common/router/router.cc | 18 +++---- source/common/router/router.h | 6 +-- source/common/router/scoped_config_impl.cc | 8 ++-- source/common/router/scoped_config_impl.h | 14 +++--- source/common/router/upstream_request.cc | 2 +- source/common/secret/secret_manager_impl.h | 2 +- source/common/stream_info/stream_info_impl.h | 4 +- source/common/tracing/http_tracer_impl.cc | 2 +- source/common/tracing/tracer_impl.cc | 2 +- source/common/upstream/BUILD | 2 +- .../common/upstream/cluster_manager_impl.cc | 4 +- source/common/upstream/cluster_manager_impl.h | 4 +- .../common/upstream/outlier_detection_impl.cc | 2 +- .../common/upstream/outlier_detection_impl.h | 2 +- source/common/upstream/upstream_impl.h | 2 +- source/exe/BUILD | 2 +- source/extensions/common/wasm/context.cc | 48 +++++++++---------- source/extensions/common/wasm/context.h | 16 +++---- .../extensions/common/wasm/stats_handler.cc | 4 +- source/extensions/common/wasm/stats_handler.h | 12 ++--- source/extensions/common/wasm/wasm.cc | 10 ++-- source/extensions/common/wasm/wasm.h | 6 +-- source/extensions/common/wasm/wasm_vm.cc | 2 +- .../filters/common/ext_authz/ext_authz.h | 2 +- .../common/ext_authz/ext_authz_http_impl.cc | 4 +- .../common/ext_authz/ext_authz_http_impl.h | 2 +- .../filters/http/custom_response/config.cc | 2 +- .../filters/http/custom_response/config.h | 2 +- .../custom_response/custom_response_filter.cc | 10 ++-- .../custom_response/custom_response_filter.h | 2 +- .../filters/http/custom_response/policy.h | 4 +- .../http/on_demand/on_demand_update.cc | 4 +- .../filters/http/wasm/wasm_filter.h | 2 +- .../filters/network/common/redis/BUILD | 2 +- .../filters/network/common/redis/client.h | 4 +- .../filters/network/common/redis/codec.h | 6 +-- .../network/common/redis/codec_impl.cc | 4 +- .../filters/network/common/redis/codec_impl.h | 6 +-- .../filters/network/common/redis/utility.cc | 2 +- .../filters/network/common/redis/utility.h | 2 +- .../network/http_connection_manager/config.cc | 2 +- .../network/http_connection_manager/config.h | 6 +-- .../filters/network/wasm/wasm_filter.h | 2 +- .../tcp/health_checker_impl.cc | 2 +- .../redirect_policy/redirect_policy.cc | 14 +++--- .../redirect_policy/redirect_policy.h | 2 +- .../extensions/tracers/skywalking/tracer.cc | 6 +-- .../transport_sockets/tls/context_impl.cc | 2 +- source/server/admin/admin.h | 4 +- .../formatter/substitution_formatter_test.cc | 2 +- .../http/conn_manager_impl_fuzz_test.cc | 4 +- test/common/http/conn_manager_impl_test.cc | 2 +- test/common/http/conn_manager_impl_test_2.cc | 8 ++-- .../common/http/conn_manager_impl_test_base.h | 4 +- test/common/protobuf/utility_test.cc | 2 +- test/common/router/BUILD | 4 +- test/common/router/config_impl_test.cc | 14 +++--- test/common/router/router_2_test.cc | 14 +++--- test/common/router/router_test.cc | 4 +- test/common/router/router_test_base.cc | 2 +- test/common/router/router_test_base.h | 4 +- .../common/router/router_upstream_log_test.cc | 2 +- test/common/router/scoped_config_impl_test.cc | 6 +-- test/common/router/scoped_rds_test.cc | 8 ++-- test/config_test/config_test.cc | 2 +- test/extensions/bootstrap/wasm/wasm_test.cc | 2 +- test/extensions/common/wasm/context_test.cc | 2 +- test/extensions/common/wasm/wasm_test.cc | 12 ++--- .../ext_authz/ext_authz_http_impl_test.cc | 2 +- .../http/cors/cors_filter_integration_test.cc | 2 +- .../custom_response_filter_test.cc | 8 ++-- .../extensions/filters/http/ext_proc/utils.cc | 2 +- .../http/on_demand/on_demand_filter_test.cc | 6 +-- .../http/tap/tap_filter_integration_test.cc | 4 +- .../filters/http/wasm/wasm_filter_test.cc | 6 +-- .../filters/network/common/redis/BUILD | 4 +- .../network/common/redis/client_impl_test.cc | 2 +- .../network/common/redis/codec_impl_test.cc | 2 +- .../filters/network/common/redis/mocks.cc | 4 +- .../filters/network/common/redis/mocks.h | 4 +- .../filters/network/wasm/config_test.cc | 4 +- .../dns_resolver/cares/dns_impl_test.cc | 2 +- .../tracers/skywalking/tracer_test.cc | 2 +- .../tls/context_impl_test.cc | 2 +- test/integration/header_integration_test.cc | 2 +- .../http2_flood_integration_test.cc | 2 +- test/integration/http_integration.cc | 4 +- test/integration/protocol_integration_test.cc | 28 +++++------ test/integration/redirect_integration_test.cc | 6 +-- test/mocks/http/mocks.cc | 2 +- test/mocks/http/mocks.h | 8 ++-- test/mocks/router/mocks.cc | 6 +-- test/mocks/router/mocks.h | 10 ++-- test/mocks/stream_info/mocks.h | 2 +- test/mocks/upstream/BUILD | 2 +- test/mocks/upstream/host.h | 2 +- test/mocks/upstream/thread_local_cluster.cc | 2 +- test/mocks/upstream/thread_local_cluster.h | 4 +- test/server/admin/admin_test.cc | 2 +- test/test_common/utility.cc | 2 +- test/test_common/wasm_base.h | 2 +- 131 files changed, 339 insertions(+), 344 deletions(-) diff --git a/bazel/BUILD b/bazel/BUILD index 1f4853b1b0277..e0a820aee8f91 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -673,11 +673,11 @@ config_setting( define_values = {"FUZZING_ENGINE": "oss-fuzz"}, ) -# By default we enable AliMesh build. If want to build community -# version then build Envoy with flag of '--define alimesh=false'. +# By default we enable Higress build. If want to build community +# version then build Envoy with flag of '--define higress=false'. config_setting( - name = "alimesh", - values = {"define": "alimesh=false"}, + name = "higress", + values = {"define": "higress=false"}, ) alias( diff --git a/bazel/envoy_binary.bzl b/bazel/envoy_binary.bzl index b95d36e924981..86aea5c49bb0f 100644 --- a/bazel/envoy_binary.bzl +++ b/bazel/envoy_binary.bzl @@ -9,7 +9,7 @@ load( "envoy_select_exported_symbols", "envoy_stdlib_deps", "tcmalloc_external_dep", - "envoy_select_alimesh", + "envoy_select_higress", ) # Envoy C++ binary targets should be specified with this function. @@ -87,7 +87,7 @@ def _envoy_linkopts(): "@envoy//bazel:boringssl_fips": [], "@envoy//bazel:windows_x86_64": [], "//conditions:default": ["-pie"], - }) + envoy_select_exported_symbols(["-Wl,-E"]) + envoy_select_alimesh(["-lcrypt"]) + }) + envoy_select_exported_symbols(["-Wl,-E"]) + envoy_select_higress(["-lcrypt"]) def _envoy_stamped_deps(): return select({ diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index a4608d6a7d601..62b89d2f0daab 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -126,7 +126,7 @@ def envoy_copts(repository, test = False): _envoy_select_perf_annotation(["-DENVOY_PERF_ANNOTATION"]) + \ _envoy_select_perfetto(["-DENVOY_PERFETTO"]) + \ envoy_select_google_grpc(["-DENVOY_GOOGLE_GRPC"], repository) + \ - envoy_select_alimesh(["-DALIMESH"]) + \ + envoy_select_higress(["-DHIGRESS"]) + \ envoy_select_signal_trace(["-DENVOY_HANDLE_SIGNALS"], repository) + \ _envoy_select_path_normalization_by_default(["-DENVOY_NORMALIZE_PATH_BY_DEFAULT"], repository) @@ -193,9 +193,9 @@ def _envoy_select_perf_annotation(xs): "//conditions:default": [], }) -def envoy_select_alimesh(xs): +def envoy_select_higress(xs): return select({ - "@envoy//bazel:alimesh": [], + "@envoy//bazel:higress": [], "//conditions:default": xs, }) diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index 828e70b15c518..e12841eb222b5 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -103,7 +103,7 @@ def envoy_cc_library( tags = [], deps = [], strip_include_prefix = None, - alimesh_deps = [], + higress_deps = [], include_prefix = None, textual_hdrs = None, alwayslink = None, @@ -113,8 +113,8 @@ def envoy_cc_library( deps += tcmalloc_external_deps(repository) deps = deps + select({ - "@envoy//bazel:alimesh": [], - "//conditions:default": alimesh_deps, + "@envoy//bazel:higress": [], + "//conditions:default": higress_deps, }) # If alwayslink is not specified, allow turning it off via --define=library_autolink=disabled diff --git a/bazel/envoy_test.bzl b/bazel/envoy_test.bzl index 8bc7bc4327f8e..9e13c3fdad299 100644 --- a/bazel/envoy_test.bzl +++ b/bazel/envoy_test.bzl @@ -16,7 +16,7 @@ load( "envoy_select_force_libcpp", "envoy_stdlib_deps", "tcmalloc_external_dep", - "envoy_select_alimesh", + "envoy_select_higress", ) # Envoy C++ related test infrastructure (that want gtest, gmock, but may be @@ -73,7 +73,7 @@ def _envoy_test_linkopts(): # TODO(mattklein123): It's not great that we universally link against the following libs. # In particular, -latomic and -lrt are not needed on all platforms. Make this more granular. "//conditions:default": ["-pthread", "-lrt", "-ldl"], - }) + envoy_select_force_libcpp([], ["-lstdc++fs", "-latomic"]) + envoy_select_alimesh(["-lcrypt"]) + envoy_dbg_linkopts() + envoy_select_exported_symbols(["-Wl,-E"]) + }) + envoy_select_force_libcpp([], ["-lstdc++fs", "-latomic"]) + envoy_select_higress(["-lcrypt"]) + envoy_dbg_linkopts() + envoy_select_exported_symbols(["-Wl,-E"]) # Envoy C++ fuzz test targets. These are not included in coverage runs. def envoy_cc_fuzz_test( @@ -152,7 +152,7 @@ def envoy_cc_test( repository = "", external_deps = [], deps = [], - alimesh_deps = [], + higress_deps = [], tags = [], args = [], copts = [], @@ -167,8 +167,8 @@ def envoy_cc_test( coverage_tags = tags + ([] if coverage else ["nocoverage"]) deps = deps + select({ - "@envoy//bazel:alimesh": [], - "//conditions:default": alimesh_deps, + "@envoy//bazel:higress": [], + "//conditions:default": higress_deps, }) native.cc_test( @@ -205,7 +205,7 @@ def envoy_cc_test_library( data = [], external_deps = [], deps = [], - alimesh_deps = [], + higress_deps = [], repository = "", tags = [], include_prefix = None, @@ -214,8 +214,8 @@ def envoy_cc_test_library( **kargs): deps = deps + select({ - "@envoy//bazel:alimesh": [], - "//conditions:default": alimesh_deps, + "@envoy//bazel:higress": [], + "//conditions:default": higress_deps, }) disable_pch = kargs.pop("disable_pch", True) diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index adc5998da57a2..4667b31e3b165 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -43,11 +43,6 @@ CONTRIB_EXTENSIONS = { "envoy.tls.key_providers.cryptomb": "//contrib/cryptomb/private_key_providers/source:config", "envoy.tls.key_providers.qat": "//contrib/qat/private_key_providers/source:config", - # - # Tracers - # - - "envoy.tracers.eagleeye": "//contrib/eagleeye/tracers/source:config", # # Custom cluster plugins @@ -71,7 +66,7 @@ CONTRIB_EXTENSIONS = { # Connection Balance extensions # - # "envoy.network.connection_balance.dlb": "//contrib/network/connection_balance/dlb/source:connection_balancer", + "envoy.network.connection_balance.dlb": "//contrib/network/connection_balance/dlb/source:connection_balancer", # # Regex engines diff --git a/envoy/http/filter.h b/envoy/http/filter.h index 39ef8d12182d5..124176cdd47ba 100644 --- a/envoy/http/filter.h +++ b/envoy/http/filter.h @@ -494,7 +494,7 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, * Allows modifying the decoding buffer. May only be called before any data has been continued * past the calling filter. */ -#if defined(ALIMESH) +#if defined(HIGRESS) virtual void modifyDecodingBuffer(std::function callback, bool /* backup_for_replace */) { return modifyDecodingBuffer(callback); @@ -729,7 +729,7 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, * @param original_response_headers Headers used for logging in the access logs and for charging * stats. Ignored if null. */ -#if defined(ALIMESH) +#if defined(HIGRESS) virtual bool recreateStream(const ResponseHeaderMap* original_response_headers, bool /* use_original_request_body */) { return recreateStream(original_response_headers); @@ -764,7 +764,7 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, */ virtual absl::optional upstreamOverrideHost() const PURE; -#if defined(ALIMESH) +#if defined(HIGRESS) virtual bool needBuffering() const { return false; } virtual void setNeedBuffering(bool) {} #endif diff --git a/envoy/http/header_map.h b/envoy/http/header_map.h index 1d45282f9c4ba..bc8f8c512a5ef 100644 --- a/envoy/http/header_map.h +++ b/envoy/http/header_map.h @@ -84,7 +84,7 @@ class LowerCaseString { // Implicit conversion to absl::string_view. operator absl::string_view() const { return string_; } -#if defined(ALIMESH) +#if defined(HIGRESS) virtual ~LowerCaseString() = default; protected: diff --git a/envoy/router/BUILD b/envoy/router/BUILD index 4f90118315209..881c582b2a02a 100644 --- a/envoy/router/BUILD +++ b/envoy/router/BUILD @@ -61,7 +61,7 @@ envoy_cc_library( envoy_cc_library( name = "router_interface", hdrs = ["router.h"], - alimesh_deps = [ + higress_deps = [ "//contrib/envoy/http:active_redirect_policy_interface", ], external_deps = ["abseil_optional"], diff --git a/envoy/router/router.h b/envoy/router/router.h index db7b71832f8c1..f7a2c1ea80c6c 100644 --- a/envoy/router/router.h +++ b/envoy/router/router.h @@ -33,7 +33,7 @@ #include "absl/types/optional.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "contrib/envoy/http/active_redirect_policy.h" #endif @@ -1102,7 +1102,7 @@ class RouteEntry : public ResponseEntry { */ virtual const std::string& routeName() const PURE; -#if defined(ALIMESH) +#if defined(HIGRESS) virtual const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const PURE; #endif /** diff --git a/envoy/router/scopes.h b/envoy/router/scopes.h index 5dbe09e25d256..8e4dbb7a646b5 100644 --- a/envoy/router/scopes.h +++ b/envoy/router/scopes.h @@ -92,7 +92,7 @@ class ScopeKeyBuilder { public: virtual ~ScopeKeyBuilder() = default; -#if defined(ALIMESH) +#if defined(HIGRESS) virtual ScopeKeyPtr computeScopeKey(const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info, std::function& recompute) const PURE; @@ -122,7 +122,7 @@ class ScopedConfig : public Envoy::Config::ConfigProvider::Config { */ virtual ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr& scope_key) const PURE; -#if defined(ALIMESH) +#if defined(HIGRESS) virtual ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder* builder, const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info = nullptr) const PURE; diff --git a/envoy/stream_info/stream_info.h b/envoy/stream_info/stream_info.h index d2f0bae9cfb7c..0e274326546dc 100644 --- a/envoy/stream_info/stream_info.h +++ b/envoy/stream_info/stream_info.h @@ -911,7 +911,7 @@ class StreamInfo { */ virtual void setDownstreamTransportFailureReason(absl::string_view failure_reason) PURE; -#ifdef ALIMESH +#ifdef HIGRESS /** * @param key the filter state key set by wasm filter. * @param value the filter state value set by wasm filter. diff --git a/envoy/upstream/BUILD b/envoy/upstream/BUILD index 9b43ecc3bc2eb..a4a31a39ec829 100644 --- a/envoy/upstream/BUILD +++ b/envoy/upstream/BUILD @@ -39,7 +39,7 @@ envoy_cc_library( "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], - alimesh_deps = [ + higress_deps = [ "//envoy/redis:async_client_interface", ], ) @@ -145,7 +145,7 @@ envoy_cc_library( "//envoy/http:async_client_interface", "//envoy/tcp:async_tcp_client_interface", ], - alimesh_deps = [ + higress_deps = [ "//envoy/redis:async_client_interface", ], ) diff --git a/envoy/upstream/outlier_detection.h b/envoy/upstream/outlier_detection.h index aefd450cac1da..cf09deef85754 100644 --- a/envoy/upstream/outlier_detection.h +++ b/envoy/upstream/outlier_detection.h @@ -111,7 +111,7 @@ class DetectorHostMonitor { */ virtual double successRate(SuccessRateMonitorType type) const PURE; -#if defined(ALIMESH) +#if defined(HIGRESS) virtual void forceEjectHost() PURE; #endif }; diff --git a/envoy/upstream/thread_local_cluster.h b/envoy/upstream/thread_local_cluster.h index 0dd4162950c52..7787acd751a69 100644 --- a/envoy/upstream/thread_local_cluster.h +++ b/envoy/upstream/thread_local_cluster.h @@ -143,7 +143,7 @@ class ThreadLocalCluster { * owns the client. */ virtual Http::AsyncClient& httpAsyncClient() PURE; -#if defined(ALIMESH) +#if defined(HIGRESS) virtual Redis::AsyncClient& redisAsyncClient() PURE; #endif diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 977f897f7f695..a35509dcc6267 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -33,7 +33,7 @@ const AsyncStreamImpl::RouteEntryImpl::ConnectConfigOptRef AsyncStreamImpl::RouteEntryImpl::connect_config_nullopt_; const std::list AsyncStreamImpl::NullCommonConfig::internal_only_headers_; -#if defined(ALIMESH) +#if defined(HIGRESS) const Router::InternalActiveRedirectPoliciesImpl AsyncStreamImpl::RouteEntryImpl::internal_active_redirect_policy_; #endif diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 68f7f33014e55..65f78f3882463 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -324,7 +324,7 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, const ConnectConfigOptRef connectConfig() const override { return connect_config_nullopt_; } -#if defined(ALIMESH) +#if defined(HIGRESS) const Router::InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { return internal_active_redirect_policy_; } @@ -349,7 +349,7 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, static const std::multimap opaque_config_; static const NullPathMatchCriterion path_match_criterion_; -#if defined(ALIMESH) +#if defined(HIGRESS) static const Router::InternalActiveRedirectPoliciesImpl internal_active_redirect_policy_; #endif diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 7d4ec929aecfe..ce2910e4182b7 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -539,7 +539,7 @@ class ConnectionManagerConfig { */ virtual bool addProxyProtocolConnectionState() const PURE; -#if defined(ALIMESH) +#if defined(HIGRESS) /** * @return the timeout seconds will be set in the "Keep-Alive" response header. * Zero indicates this behavior is disabled. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 97bdcc5372725..8b23cf77edc8c 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -750,7 +750,7 @@ void ConnectionManagerImpl::RdsRouteConfigUpdateRequester::requestRouteConfigUpd const auto& host_header = absl::AsciiStrToLower(parent_.request_headers_->getHostValue()); requestVhdsUpdate(host_header, thread_local_dispatcher, std::move(route_config_updated_cb)); return; -#if defined(ALIMESH) +#if defined(HIGRESS) Router::ScopeKeyPtr scope_key = parent_.snapped_scoped_routes_config_->computeScopeKey( scope_key_builder_.ptr(), *parent_.request_headers_, &parent_.connection()->streamInfo()); #else @@ -1003,7 +1003,7 @@ void ConnectionManagerImpl::ActiveStream::onStreamMaxDurationReached() { void ConnectionManagerImpl::ActiveStream::chargeStats(const ResponseHeaderMap& headers) { uint64_t response_code = Utility::getResponseStatus(headers); -#if defined(ALIMESH) +#if defined(HIGRESS) if (Grpc::Common::hasGrpcContentType(headers)) { absl::optional grpc_status = Grpc::Common::getGrpcStatus(headers); if (grpc_status.has_value()) { @@ -1516,7 +1516,7 @@ void ConnectionManagerImpl::startDrainSequence() { } void ConnectionManagerImpl::ActiveStream::snapScopedRouteConfig() { -#if defined(ALIMESH) +#if defined(HIGRESS) snapped_route_config_ = snapped_scoped_routes_config_->getRouteConfig( connection_manager_.config_.scopeKeyBuilder().ptr(), *request_headers_, &connection()->streamInfo()); @@ -1796,7 +1796,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ResponseHeaderMap& heade blockRouteCache(); } -#if defined(ALIMESH) +#if defined(HIGRESS) if (!state_.is_tunneling_ && connection_manager_.codec_->protocol() < Protocol::Http2) { if (connection_manager_.drain_state_ != DrainState::NotDraining) { // If the connection manager is draining send "Connection: Close" on HTTP/1.1 connections. @@ -2153,7 +2153,7 @@ void ConnectionManagerImpl::ActiveStream::onRequestDataTooLarge() { connection_manager_.stats_.named_.downstream_rq_too_large_.inc(); } -#if defined(ALIMESH) +#if defined(HIGRESS) void ConnectionManagerImpl::ActiveStream::recreateStream( StreamInfo::FilterStateSharedPtr filter_state) { return recreateStream(filter_state, false); @@ -2168,7 +2168,7 @@ void ConnectionManagerImpl::ActiveStream::recreateStream( response_encoder_ = nullptr; Buffer::InstancePtr request_data = std::make_unique(); -#if defined(ALIMESH) +#if defined(HIGRESS) bool proxy_body = false; const auto& original_buffered_request_data = filter_manager_.originalBufferedRequestData(); if (use_original_request_body && original_buffered_request_data != nullptr && diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 9cd02c97fbe35..859fd18a546f2 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -285,7 +285,7 @@ class ConnectionManagerImpl : Logger::Loggable, } void disarmRequestTimeout() override; void resetIdleTimer() override; -#if defined(ALIMESH) +#if defined(HIGRESS) void recreateStream(StreamInfo::FilterStateSharedPtr filter_state, bool backup_for_replace) override; #endif diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 810e41081f214..984b33ba96686 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -242,7 +242,7 @@ ConnectionManagerUtility::MutateRequestHeadersResult ConnectionManagerUtility::m cleanInternalHeaders(request_headers, edge_request, route_config.internalOnlyHeaders()); } -#if defined(ALIMESH) +#if defined(HIGRESS) request_headers.setReferenceKey(Http::CustomHeaders::get().AliExtendedValues.XEnvoyOriginalHost, request_headers.getHostValue()); #endif diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 051c2b2258dfb..24bb8fa02a848 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -421,7 +421,7 @@ const Buffer::Instance* ActiveStreamDecoderFilter::decodingBuffer() { return parent_.buffered_request_data_.get(); } -#if defined(ALIMESH) +#if defined(HIGRESS) void ActiveStreamDecoderFilter::modifyDecodingBuffer( std::function callback) { modifyDecodingBuffer(callback, false); @@ -1549,7 +1549,7 @@ void ActiveStreamDecoderFilter::setDecoderBufferLimit(uint32_t limit) { uint32_t ActiveStreamDecoderFilter::decoderBufferLimit() { return parent_.buffer_limit_; } -#if defined(ALIMESH) +#if defined(HIGRESS) bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) { return recreateStream(headers, false); } @@ -1580,7 +1580,7 @@ bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) parent_.filter_manager_callbacks_.chargeStats(*headers); } -#if defined(ALIMESH) +#if defined(HIGRESS) parent_.filter_manager_callbacks_.recreateStream(parent_.streamInfo().filterState(), use_original_request_body); #else diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 83f4e885cb14c..5f3a4d99526db 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -210,7 +210,7 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, void continueDecoding() override; const Buffer::Instance* decodingBuffer() override; -#if defined(ALIMESH) +#if defined(HIGRESS) void modifyDecodingBuffer(std::function callback, bool backup_for_replace) override; #endif @@ -236,7 +236,7 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, removeDownstreamWatermarkCallbacks(DownstreamWatermarkCallbacks& watermark_callbacks) override; void setDecoderBufferLimit(uint32_t limit) override; uint32_t decoderBufferLimit() override; -#if defined(ALIMESH) +#if defined(HIGRESS) bool recreateStream(const Http::ResponseHeaderMap* original_response_headers, bool use_original_request_body) override; #endif @@ -248,7 +248,7 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, Buffer::BufferMemoryAccountSharedPtr account() const override; void setUpstreamOverrideHost(absl::string_view host) override; absl::optional upstreamOverrideHost() const override; -#if defined(ALIMESH) +#if defined(HIGRESS) bool needBuffering() const override { return need_buffering_; } void setNeedBuffering(bool need) override { need_buffering_ = need; } #endif @@ -266,7 +266,7 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, StreamDecoderFilterSharedPtr handle_; bool is_grpc_request_{}; -#if defined(ALIMESH) +#if defined(HIGRESS) bool need_buffering_{}; #endif }; @@ -466,7 +466,7 @@ class FilterManagerCallbacks { /** * Called when the stream should be re-created, e.g. for an internal redirect. */ -#if defined(ALIMESH) +#if defined(HIGRESS) virtual void recreateStream(StreamInfo::FilterStateSharedPtr filter_state, bool /* use_original_request_body */) { recreateStream(filter_state); @@ -837,7 +837,7 @@ class FilterManager : public ScopeTrackedObject, Buffer::InstancePtr& bufferedRequestData() { return buffered_request_data_; } -#if defined(ALIMESH) +#if defined(HIGRESS) Buffer::InstancePtr& originalBufferedRequestData() { return original_buffered_request_data_; } #endif @@ -1023,7 +1023,7 @@ class FilterManager : public ScopeTrackedObject, std::unique_ptr request_metadata_map_vector_; Buffer::InstancePtr buffered_response_data_; Buffer::InstancePtr buffered_request_data_; -#if defined(ALIMESH) +#if defined(HIGRESS) Buffer::InstancePtr original_buffered_request_data_; #endif uint32_t buffer_limit_{0}; diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 37fdfea890ae4..ec9fa4c8ea197 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -129,7 +129,7 @@ class CustomHeaderValues { const std::string Wildcard{"*"}; } VaryValues; -#if defined(ALIMESH) +#if defined(HIGRESS) struct { const LowerCaseString TriArriveTime{"req-arrive-time"}; const LowerCaseString TriCostTime{"req-cost-time"}; diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index 1f9df858683d0..0ceb95edb52ea 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -508,7 +508,7 @@ void redact(Protobuf::Message* message, bool ancestor_is_sensitive) { redact(reflection->MutableRepeatedMessage(message, field_descriptor, i), sensitive); } } else if (reflection->HasField(*message, field_descriptor)) { -#if defined(ALIMESH) +#if defined(HIGRESS) // The content of the poll_delay field cannot be displayed because the typed_config field of // PrivateKeyProvider is set to "udpa.annotations.sensitive". However, the content of the // poll_delay field is not sensitive data. To facilitate debugging, we support outputting diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index eb15f9a3d0f2e..8012a84f5fc80 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -39,7 +39,7 @@ ((message).has_##field_name() ? DurationUtil::durationToMilliseconds((message).field_name()) \ : (default_value)) -#if defined(ALIMESH) +#if defined(HIGRESS) // Obtain the seconds value of a google.protobuf.Duration field if set. Otherwise, return the // default value. #define PROTOBUF_GET_SECONDS_OR_DEFAULT(message, field_name, default_value) \ diff --git a/source/common/router/BUILD b/source/common/router/BUILD index 44c48aca4971a..3e060d824cf1b 100644 --- a/source/common/router/BUILD +++ b/source/common/router/BUILD @@ -35,7 +35,7 @@ envoy_cc_library( name = "config_lib", srcs = ["config_impl.cc"], hdrs = ["config_impl.h"], - alimesh_deps = [ + higress_deps = [ "//contrib/common/active_redirect/source:active_redirect_policy_lib", ], external_deps = ["abseil_optional"], @@ -217,7 +217,7 @@ envoy_cc_library( "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], - alimesh_deps = [ + higress_deps = [ "//source/common/http:header_utility_lib", ], ) @@ -287,7 +287,7 @@ envoy_cc_library( "router.h", "upstream_request.h", ], - alimesh_deps = [ + higress_deps = [ "//envoy/stats:timespan_interface", ], deps = [ diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 2934520bd5977..d2bead7d62198 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -525,7 +525,7 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, vhost_->globalRouteConfig().maxDirectResponseBodySizeBytes())), per_filter_configs_(route.typed_per_filter_config(), optional_http_filters, factory_context, validator), -#if !defined(ALIMESH) +#if !defined(HIGRESS) route_name_(route.name()), time_source_(factory_context.mainThreadDispatcher().timeSource()), #else route_name_(route.name()), time_source_(factory_context.mainThreadDispatcher().timeSource()), @@ -606,7 +606,7 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, weighted_clusters_config_ = std::make_unique( weighted_clusters, total_weight, route.route().weighted_clusters().header_name()); -#if defined(ALIMESH) +#if defined(HIGRESS) if (route.route().weighted_clusters().has_inline_cluster_specifier_plugin()) { cluster_specifier_plugin_ = getClusterSpecifierPluginByTheProto( route.route().weighted_clusters().inline_cluster_specifier_plugin(), validator, @@ -880,7 +880,7 @@ void RouteEntryImplBase::finalizeRequestHeaders(Http::RequestHeaderMap& headers, absl::optional container; if (!getPathRewrite(headers, container).empty() || regex_rewrite_ != nullptr || path_rewriter_ != nullptr) { -#if defined(ALIMESH) +#if defined(HIGRESS) // We need to store the original path of access log when user enable the suppress_envoy_headers // option. if (!insert_envoy_original_path) { @@ -1146,7 +1146,7 @@ std::unique_ptr RouteEntryImplBase::buildInternalRed return std::make_unique(policy_config, validator, current_route_name); } -#if defined(ALIMESH) +#if defined(HIGRESS) std::unique_ptr RouteEntryImplBase::buildActiveInternalRedirectPolicy( const envoy::config::route::v3::RouteAction& route_config, @@ -1381,7 +1381,7 @@ RouteConstSharedPtr RouteEntryImplBase::pickWeightedCluster(const Http::HeaderMa } if (selected_value >= begin && selected_value < end) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (cluster_specifier_plugin_ != nullptr) { auto request_header = dynamic_cast(&headers); if (!cluster->clusterHeaderName().get().empty() && @@ -1883,7 +1883,7 @@ VirtualHostImpl::VirtualHostImpl( } } -#if defined(ALIMESH) +#if defined(HIGRESS) for (const auto& server_name : virtual_host.allow_server_names()) { auto isWildcardServerName = absl::StartsWith(server_name, "*."); if (absl::StrContains(server_name, '*') && !isWildcardServerName) { @@ -1903,7 +1903,7 @@ VirtualHostImpl::VirtualHostImpl( const std::shared_ptr VirtualHostImpl::SSL_REDIRECT_ROUTE{ new SslRedirectRoute()}; -#if defined(ALIMESH) +#if defined(HIGRESS) const SslPermanentRedirector SslPermanentRedirectRoute::SSL_PERMANENT_REDIRECTOR; const std::shared_ptr VirtualHostImpl::SSL_PERMANENT_REDIRECT_ROUTE{new SslPermanentRedirectRoute}; @@ -1969,7 +1969,7 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb return nullptr; } -#if defined(ALIMESH) +#if defined(HIGRESS) // First check for sni redirect. if (allow_server_names_.empty()) { goto SNI_CHECK_PASS; diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 53a7519dfe009..fd012818f04b4 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -39,7 +39,7 @@ #include "absl/container/node_hash_map.h" #include "absl/types/optional.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "contrib/common/active_redirect/source/active_redirect_policy_impl.h" #endif @@ -162,7 +162,7 @@ class SslRedirectRoute : public Route { typed_metadata_; }; -#if defined(ALIMESH) +#if defined(HIGRESS) class SslPermanentRedirector : public SslRedirector { public: Http::Code responseCode() const override { return Http::Code::PermanentRedirect; } @@ -446,7 +446,7 @@ class VirtualHostImpl : Logger::Loggable { enum class SslRequirements : uint8_t { None, ExternalOnly, All }; static const std::shared_ptr SSL_REDIRECT_ROUTE; -#if defined(ALIMESH) +#if defined(HIGRESS) static const std::shared_ptr SSL_PERMANENT_REDIRECT_ROUTE; static const std::shared_ptr SNI_REDIRECT_ROUTE; #endif @@ -457,7 +457,7 @@ class VirtualHostImpl : Logger::Loggable { std::vector routes_; Matcher::MatchTreeSharedPtr matcher_; -#if defined(ALIMESH) +#if defined(HIGRESS) std::vector allow_server_names_; #endif }; @@ -770,7 +770,7 @@ class RouteEntryImplBase : public RouteEntryAndRoute, } return DefaultInternalRedirectPolicy::get(); } -#if defined(ALIMESH) +#if defined(HIGRESS) const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { if (internal_active_redirect_policy_ != nullptr) { return *internal_active_redirect_policy_; @@ -891,7 +891,7 @@ class RouteEntryImplBase : public RouteEntryAndRoute, // path matching to ignore the path-parameters. absl::string_view sanitizePathBeforePathMatching(const absl::string_view path) const; -#if defined(ALIMESH) +#if defined(HIGRESS) class DynamicRouteEntry : public RouteEntryAndRoute, public std::enable_shared_from_this { #else @@ -1028,7 +1028,7 @@ class RouteEntryImplBase : public RouteEntryAndRoute, parent_->traversePerFilterConfig(filter_name, cb); }; -#if defined(ALIMESH) +#if defined(HIGRESS) const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { return parent_->internalActiveRedirectPolicy(); } @@ -1135,7 +1135,7 @@ class RouteEntryImplBase : public RouteEntryAndRoute, const Http::LowerCaseString& clusterHeaderName() const { return cluster_header_name_; } -#if defined(ALIMESH) +#if defined(HIGRESS) RouteConstSharedPtr getRouteConstSharedPtr() const override { return shared_from_this(); } #endif @@ -1268,7 +1268,7 @@ class RouteEntryImplBase : public RouteEntryAndRoute, PathRewriterSharedPtr buildPathRewriter(envoy::config::route::v3::Route route, ProtobufMessage::ValidationVisitor& validator) const; -#if defined(ALIMESH) +#if defined(HIGRESS) std::unique_ptr buildActiveInternalRedirectPolicy(const envoy::config::route::v3::RouteAction& route_config, ProtobufMessage::ValidationVisitor& validator, @@ -1329,7 +1329,7 @@ class RouteEntryImplBase : public RouteEntryAndRoute, PerFilterConfigs per_filter_configs_; const std::string route_name_; TimeSource& time_source_; -#if defined(ALIMESH) +#if defined(HIGRESS) std::unique_ptr internal_active_redirect_policy_; #endif EarlyDataPolicyPtr early_data_policy_; diff --git a/source/common/router/delegating_route_impl.h b/source/common/router/delegating_route_impl.h index 0e77d79b4f489..d05fbdd823d3b 100644 --- a/source/common/router/delegating_route_impl.h +++ b/source/common/router/delegating_route_impl.h @@ -117,7 +117,7 @@ class DelegatingRouteEntry : public Router::RouteEntry { const EarlyDataPolicy& earlyDataPolicy() const override; const RouteStatsContextOptRef routeStatsContext() const override; -#if defined(ALIMESH) +#if defined(HIGRESS) const InternalActiveRedirectPolicy& internalActiveRedirectPolicy() const override { return base_route_->routeEntry()->internalActiveRedirectPolicy(); } diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 72727c6512c1f..b92f6a1655a0a 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -42,7 +42,7 @@ #include "source/common/stream_info/uint32_accessor_impl.h" #include "source/common/tracing/http_tracer_impl.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "source/common/http/path_utility.h" #endif @@ -319,7 +319,7 @@ Stats::StatName Filter::upstreamZone(Upstream::HostDescriptionConstSharedPtr ups return upstream_host ? upstream_host->localityZoneStatName() : config_.empty_stat_name_; } -#if defined(ALIMESH) +#if defined(HIGRESS) void Filter::chargeUpstreamGrpcCode(uint64_t http_status_code, uint64_t grpc_response_code, const Http::ResponseHeaderMap& response_headers, Upstream::HostDescriptionConstSharedPtr upstream_host, @@ -720,7 +720,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, } callbacks_->streamInfo().setAttemptCount(attempt_count_); -#if defined(ALIMESH) +#if defined(HIGRESS) Http::HeaderString start_time; start_time.setInteger(std::chrono::duration_cast( callbacks_->streamInfo().startTime().time_since_epoch()) @@ -871,7 +871,7 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea // a backoff timer. ASSERT(upstream_requests_.size() <= 1); -#if defined(ALIMESH) +#if defined(HIGRESS) bool buffering = (retry_state_ && retry_state_->enabled()) || callbacks_->needBuffering() || (!active_shadow_policies_.empty() && !streaming_shadows_) || (route_entry_ && route_entry_->internalRedirectPolicy().enabled()); @@ -1529,7 +1529,7 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt upstream_request.upstreamHost()->outlierDetector().putHttpResponseCode(response_code); } -#if defined(ALIMESH) +#if defined(HIGRESS) static Envoy::Http::LowerCaseString shutdown_key("micro.service.shutdown.endpoint"); if (!headers->get(shutdown_key).empty()) { upstream_request.upstreamHost()->outlierDetector().forceEjectHost(); @@ -1599,7 +1599,7 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt // next downstream. } -#if defined(ALIMESH) +#if defined(HIGRESS) if (route_entry_->internalActiveRedirectPolicy().enabled() && route_entry_->internalActiveRedirectPolicy().shouldRedirectForResponseCode( static_cast(response_code)) && @@ -1638,7 +1638,7 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt MonotonicTime response_received_time = dispatcher.timeSource().monotonicTime(); std::chrono::milliseconds ms = std::chrono::duration_cast( response_received_time - downstream_request_complete_time_); -#if defined(ALIMESH) +#if defined(HIGRESS) std::chrono::milliseconds duration_ms = std::chrono::duration_cast( response_received_time - callbacks_->streamInfo().startTimeMonotonic()); Http::HeaderString cost_time; @@ -1674,7 +1674,7 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt upstream_request.upstreamCanary( (headers->EnvoyUpstreamCanary() && headers->EnvoyUpstreamCanary()->value() == "true") || upstream_request.upstreamHost()->canary()); -#if defined(ALIMESH) +#if defined(HIGRESS) if (grpc_status.has_value()) { chargeUpstreamGrpcCode(response_code, grpc_to_http_status, *headers, upstream_request.upstreamHost(), false); @@ -1974,7 +1974,7 @@ bool Filter::convertRequestHeadersForInternalRedirect(Http::RequestHeaderMap& do return true; } -#if defined(ALIMESH) +#if defined(HIGRESS) bool Filter::setupActiveRedirect(const Http::ResponseHeaderMap&, UpstreamRequest&) { ENVOY_STREAM_LOG(debug, "attempting internal active redirect", *callbacks_); diff --git a/source/common/router/router.h b/source/common/router/router.h index f83f561d3ad77..f79b1d5b70f5a 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -44,7 +44,7 @@ #include "source/common/upstream/load_balancer_impl.h" #include "source/common/upstream/upstream_factory_context_impl.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "envoy/stats/timespan.h" #endif @@ -582,7 +582,7 @@ class Filter : Logger::Loggable, void onPerTryTimeoutCommon(UpstreamRequest& upstream_request, Stats::Counter& error_counter, const std::string& response_code_details); Stats::StatName upstreamZone(Upstream::HostDescriptionConstSharedPtr upstream_host); -#if defined(ALIMESH) +#if defined(HIGRESS) void chargeUpstreamGrpcCode(uint64_t http_status_code, uint64_t grpc_response_code, const Http::ResponseHeaderMap& response_headers, Upstream::HostDescriptionConstSharedPtr upstream_host, bool dropped); @@ -646,7 +646,7 @@ class Filter : Logger::Loggable, uint64_t grpc_to_http_status); Http::Context& httpContext() { return config_.http_context_; } -#if defined(ALIMESH) +#if defined(HIGRESS) bool setupActiveRedirect(const Http::ResponseHeaderMap& headers, UpstreamRequest& upstream_request); bool convertRequestHeadersForInternalActiveRedirect(Http::RequestHeaderMap& downstream_headers); diff --git a/source/common/router/scoped_config_impl.cc b/source/common/router/scoped_config_impl.cc index 20aed94c319a0..bd4a6412a3183 100644 --- a/source/common/router/scoped_config_impl.cc +++ b/source/common/router/scoped_config_impl.cc @@ -5,14 +5,14 @@ #include "source/common/protobuf/utility.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "source/common/http/header_utility.h" #endif namespace Envoy { namespace Router { -#if defined(ALIMESH) +#if defined(HIGRESS) namespace { std::string maskFirstDNSLabel(absl::string_view host) { @@ -281,7 +281,7 @@ ScopeKeyBuilderImpl::ScopeKeyBuilderImpl(ScopedRoutes::ScopeKeyBuilder&& config) : ScopeKeyBuilderBase(std::move(config)) { for (const auto& fragment_builder : config_.fragments()) { switch (fragment_builder.type_case()) { -#if defined(ALIMESH) +#if defined(HIGRESS) case ScopedRoutes::ScopeKeyBuilder::FragmentBuilder::kHostValueExtractor: fragment_builders_.emplace_back(std::make_unique( ScopedRoutes::ScopeKeyBuilder::FragmentBuilder(fragment_builder))); @@ -305,7 +305,7 @@ ScopeKeyPtr ScopeKeyBuilderImpl::computeScopeKey(const Http::HeaderMap& headers) ScopeKey key; for (const auto& builder : fragment_builders_) { // returns nullopt if a null fragment is found. -#if defined(ALIMESH) +#if defined(HIGRESS) ReComputeCbPtr recompute_fragment_cb = std::make_shared(); std::unique_ptr fragment = builder->computeFragment(headers, nullptr, recompute_fragment_cb); diff --git a/source/common/router/scoped_config_impl.h b/source/common/router/scoped_config_impl.h index c8250517c7929..96425a4c74a2d 100644 --- a/source/common/router/scoped_config_impl.h +++ b/source/common/router/scoped_config_impl.h @@ -22,7 +22,7 @@ namespace Router { using envoy::extensions::filters::network::http_connection_manager::v3::ScopedRoutes; -#if defined(ALIMESH) +#if defined(HIGRESS) using ReComputeCb = std::function()>; using ReComputeCbPtr = std::shared_ptr; using ReComputeCbWeakPtr = std::weak_ptr; @@ -37,7 +37,7 @@ class FragmentBuilderBase { : config_(std::move(config)) {} virtual ~FragmentBuilderBase() = default; -#if defined(ALIMESH) +#if defined(HIGRESS) virtual std::unique_ptr computeFragment(const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info, ReComputeCbPtr& recompute) const PURE; @@ -56,7 +56,7 @@ class HeaderValueExtractorImpl : public FragmentBuilderBase { public: explicit HeaderValueExtractorImpl(ScopedRoutes::ScopeKeyBuilder::FragmentBuilder&& config); -#if defined(ALIMESH) +#if defined(HIGRESS) std::unique_ptr computeFragment(const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info, ReComputeCbPtr& recompute) const override; @@ -72,7 +72,7 @@ class HeaderValueExtractorImpl : public FragmentBuilderBase { header_value_extractor_config_; }; -#if defined(ALIMESH) +#if defined(HIGRESS) class HostValueExtractorImpl : public FragmentBuilderBase { public: explicit HostValueExtractorImpl(ScopedRoutes::ScopeKeyBuilder::FragmentBuilder&& config); @@ -118,7 +118,7 @@ class ScopeKeyBuilderImpl : public ScopeKeyBuilderBase { public: explicit ScopeKeyBuilderImpl(ScopedRoutes::ScopeKeyBuilder&& config); -#if defined(ALIMESH) +#if defined(HIGRESS) ScopeKeyPtr computeScopeKey(const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info, std::function& recompute) const override; // only for test @@ -177,7 +177,7 @@ class ScopedConfigImpl : public ScopedConfig { Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr& scope_key) const override; -#if defined(ALIMESH) +#if defined(HIGRESS) Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder* scope_key_builder, const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info) const override; @@ -201,7 +201,7 @@ class NullScopedConfigImpl : public ScopedConfig { Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr&) const override { return std::make_shared(); } -#if defined(ALIMESH) +#if defined(HIGRESS) Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder*, const Http::HeaderMap&, const StreamInfo::StreamInfo*) const override { return std::make_shared(); diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 4cb4e98a25672..2b70090128f7f 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -111,7 +111,7 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, auto upstream_host = conn_pool_->host(); if (span_ != nullptr) { span_->injectContext(*parent_.downstreamHeaders(), upstream_host); -#if defined(ALIMESH) +#if defined(HIGRESS) if (upstream_host != nullptr && upstream_host->address() != nullptr && upstream_host->address()->ip() != nullptr) { const std::string& address = upstream_host->address()->ip()->addressAsString(); diff --git a/source/common/secret/secret_manager_impl.h b/source/common/secret/secret_manager_impl.h index 28d14eca970d0..90246d92ad334 100644 --- a/source/common/secret/secret_manager_impl.h +++ b/source/common/secret/secret_manager_impl.h @@ -16,7 +16,7 @@ namespace Envoy { namespace Secret { -#if defined(ALIMESH) +#if defined(HIGRESS) class SecretManagerImpl : public SecretManager, public Logger::Loggable { #else class SecretManagerImpl : public SecretManager { diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h index 61e702667c393..21a7a098dc1df 100644 --- a/source/common/stream_info/stream_info_impl.h +++ b/source/common/stream_info/stream_info_impl.h @@ -404,7 +404,7 @@ struct StreamInfoImpl : public StreamInfo { return downstream_transport_failure_reason_; } -#ifdef ALIMESH +#ifdef HIGRESS void setCustomSpanTag(std::string_view key, std::string_view value) override { auto it = custom_span_tags_.find(key); if (it != custom_span_tags_.end()) { @@ -475,7 +475,7 @@ struct StreamInfoImpl : public StreamInfo { BytesMeterSharedPtr downstream_bytes_meter_; bool is_shadow_{false}; std::string downstream_transport_failure_reason_; -#ifdef ALIMESH +#ifdef HIGRESS absl::flat_hash_map custom_span_tags_; #endif }; diff --git a/source/common/tracing/http_tracer_impl.cc b/source/common/tracing/http_tracer_impl.cc index b99baed213183..39aedb0885e8e 100644 --- a/source/common/tracing/http_tracer_impl.cc +++ b/source/common/tracing/http_tracer_impl.cc @@ -218,7 +218,7 @@ void HttpTracerUtility::setCommonTags(Span& span, const StreamInfo::StreamInfo& span.setTag(Tracing::Tags::get().Component, Tracing::Tags::get().Proxy); -#ifdef ALIMESH +#ifdef HIGRESS // Wasm filter state const auto& custom_span_tags = stream_info.getCustomSpanTagMap(); for (const auto& it: custom_span_tags) { diff --git a/source/common/tracing/tracer_impl.cc b/source/common/tracing/tracer_impl.cc index 35112b3a05f74..cf4df7a17fc2d 100644 --- a/source/common/tracing/tracer_impl.cc +++ b/source/common/tracing/tracer_impl.cc @@ -156,7 +156,7 @@ SpanPtr TracerImpl::startSpan(const Config& config, TraceContext& trace_context, if (active_span) { active_span->setTag(Tracing::Tags::get().NodeId, local_info_.nodeName()); active_span->setTag(Tracing::Tags::get().Zone, local_info_.zoneName()); -#if defined(ALIMESH) +#if defined(HIGRESS) const std::string& remote_address = stream_info.downstreamAddressProvider().localAddress()->ip()->addressAsString(); if (stream_info.downstreamAddressProvider().localAddress()->ip()->version() == diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 23b0e2e088aaa..e3e63553be383 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -131,7 +131,7 @@ envoy_cc_library( "//source/common/http/http3:conn_pool_lib", "//source/common/http:conn_pool_grid", ]), - alimesh_deps = [ + higress_deps = [ "//source/common/redis:async_client_lib", ] ) diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 45e0f68c2ec26..6fd11863118e8 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -51,7 +51,7 @@ #include "source/common/quic/client_connection_factory_impl.h" #endif -#if defined(ALIMESH) +#if defined(HIGRESS) #include "source/common/redis/async_client_impl.h" #endif @@ -1207,7 +1207,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::httpAsyncClient return *lazy_http_async_client_; } -#if defined(ALIMESH) +#if defined(HIGRESS) Redis::AsyncClient& ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::redisAsyncClient() { using Extensions::NetworkFilters::Common::Redis::RedisCommandStats; diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index f01783ce7aa10..247d981379edb 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -525,7 +525,7 @@ class ClusterManagerImpl : public ClusterManager, LoadBalancerContext* context) override; Host::CreateConnectionData tcpConn(LoadBalancerContext* context) override; Http::AsyncClient& httpAsyncClient() override; -#if defined(ALIMESH) +#if defined(HIGRESS) Redis::AsyncClient& redisAsyncClient() override; #endif Tcp::AsyncTcpClientPtr @@ -576,7 +576,7 @@ class ClusterManagerImpl : public ClusterManager, // Current active LB. LoadBalancerPtr lb_; Http::AsyncClientPtr lazy_http_async_client_; -#if defined(ALIMESH) +#if defined(HIGRESS) Redis::AsyncClientPtr lazy_redis_async_client_; #endif // Stores QUICHE specific objects which live through out the life time of the cluster and can diff --git a/source/common/upstream/outlier_detection_impl.cc b/source/common/upstream/outlier_detection_impl.cc index 8eedf688b8c98..c4e1a2c300d2d 100644 --- a/source/common/upstream/outlier_detection_impl.cc +++ b/source/common/upstream/outlier_detection_impl.cc @@ -65,7 +65,7 @@ void DetectorHostMonitorImpl::updateCurrentSuccessRateBucket() { local_origin_sr_monitor_.updateCurrentSuccessRateBucket(); } -#if defined(ALIMESH) +#if defined(HIGRESS) void DetectorHostMonitorImpl::forceEjectHost() { std::shared_ptr detector = detector_.lock(); if (!detector) { diff --git a/source/common/upstream/outlier_detection_impl.h b/source/common/upstream/outlier_detection_impl.h index 16da9e886a496..e8caf5dd37336 100644 --- a/source/common/upstream/outlier_detection_impl.h +++ b/source/common/upstream/outlier_detection_impl.h @@ -166,7 +166,7 @@ class DetectorHostMonitorImpl : public DetectorHostMonitor { void localOriginFailure(); void localOriginNoFailure(); -#if defined(ALIMESH) +#if defined(HIGRESS) void forceEjectHost() override; #endif // handlers for setting and getting jitter, used to add a random value diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index bcbebb6356f49..8cbf785d5599c 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -190,7 +190,7 @@ class DetectorHostMonitorNullImpl : public Outlier::DetectorHostMonitor { const absl::optional& lastUnejectionTime() override { return time_; } double successRate(SuccessRateMonitorType) const override { return -1; } -#if defined(ALIMESH) +#if defined(HIGRESS) void forceEjectHost() override {} #endif diff --git a/source/exe/BUILD b/source/exe/BUILD index d56d7a99c8394..dfcb358f032a7 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -49,7 +49,7 @@ envoy_cc_library( "//bazel:darwin": envoy_all_extensions(DARWIN_SKIP_TARGETS), "//conditions:default": envoy_all_extensions(), }), - alimesh_deps = [ + higress_deps = [ # "//external:basic_auth_lib", ], ) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 91348a69eebbe..25f45866d3ae8 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -59,7 +59,7 @@ namespace { // FilterState prefix for CelState values. constexpr absl::string_view CelStateKeyPrefix = "wasm."; -#if defined(ALIMESH) +#if defined(HIGRESS) constexpr absl::string_view CustomeTraceSpanTagPrefix = "trace_span_tag."; constexpr std::string_view ClearRouteCacheKey = "clear_route_cache"; constexpr std::string_view DisableClearRouteCache = "off"; @@ -473,7 +473,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co using google::api::expr::runtime::CelProtoWrapper; using google::api::expr::runtime::CelValue; -#if defined(ALIMESH) +#if defined(HIGRESS) Envoy::Http::StreamFilterCallbacks* filter_callbacks = decoder_callbacks_; if (filter_callbacks == nullptr) { filter_callbacks = encoder_callbacks_; @@ -566,7 +566,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co } break; case PropertyToken::ROUTE_NAME: -#if defined(ALIMESH) +#if defined(HIGRESS) if (info && !info->getRouteName().empty()) { return CelValue::CreateString(&info->getRouteName()); } @@ -762,7 +762,7 @@ WasmResult Context::addHeaderMapValue(WasmHeaderMapType type, std::string_view k } const Http::LowerCaseString lower_key{std::string(key)}; map->addCopy(lower_key, std::string(value)); -#if defined(ALIMESH) +#if defined(HIGRESS) if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && !disable_clear_route_cache_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); @@ -844,7 +844,7 @@ WasmResult Context::setHeaderMapPairs(WasmHeaderMapType type, const Pairs& pairs const Http::LowerCaseString lower_key{std::string(p.first)}; map->addCopy(lower_key, std::string(p.second)); } -#if defined(ALIMESH) +#if defined(HIGRESS) if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && !disable_clear_route_cache_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); @@ -864,7 +864,7 @@ WasmResult Context::removeHeaderMapValue(WasmHeaderMapType type, std::string_vie } const Http::LowerCaseString lower_key{std::string(key)}; map->remove(lower_key); -#if defined(ALIMESH) +#if defined(HIGRESS) if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && !disable_clear_route_cache_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); @@ -885,7 +885,7 @@ WasmResult Context::replaceHeaderMapValue(WasmHeaderMapType type, std::string_vi } const Http::LowerCaseString lower_key{std::string(key)}; map->setCopy(lower_key, toAbslStringView(value)); -#if defined(ALIMESH) +#if defined(HIGRESS) if (type == WasmHeaderMapType::RequestHeaders && decoder_callbacks_ && !disable_clear_route_cache_) { decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); @@ -953,7 +953,7 @@ BufferInterface* Context::getBuffer(WasmBufferType type) { std::string_view(static_cast(body.linearize(body.length())), body.length())); } return nullptr; -#if defined(ALIMESH) +#if defined(HIGRESS) case WasmBufferType::RedisCallResponse: return buffer_.set(rootContext()->redis_call_response_); #endif @@ -964,7 +964,7 @@ BufferInterface* Context::getBuffer(WasmBufferType type) { } } -#if defined(ALIMESH) +#if defined(HIGRESS) /** * The goal here is to have the wasm filter cache the original body when replacing the entire body * using the backup_for_replace mechanism of modifyDecodingBuffer. A special case to consider here @@ -1080,7 +1080,7 @@ WasmResult Context::httpCall(std::string_view cluster, const Pairs& request_head return WasmResult::Ok; } -#if defined(ALIMESH) +#if defined(HIGRESS) WasmResult Context::redisInit(std::string_view cluster, std::string_view username, std::string_view password, int timeout_milliseconds) { auto cluster_string = std::string(cluster.substr(0, cluster.find('?'))); @@ -1306,7 +1306,7 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { if (!stream_info) { return WasmResult::NotFound; } -#ifdef ALIMESH +#ifdef HIGRESS if (absl::StartsWith(path, CustomeTraceSpanTagPrefix)) { stream_info->setCustomSpanTag(path.substr(CustomeTraceSpanTagPrefix.size()), value); return WasmResult::Ok; @@ -1327,7 +1327,7 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { StreamInfo::FilterState::StateType::Mutable, prototype.life_span_); } -#if defined(ALIMESH) +#if defined(HIGRESS) if (path == ClearRouteCacheKey) { disable_clear_route_cache_ = value == DisableClearRouteCache; } else if (path == SetDecoderBufferLimit && decoder_callbacks_) { @@ -1557,7 +1557,7 @@ Context::~Context() { for (auto& p : grpc_stream_) { p.second.stream_->resetStream(); } -#if defined(ALIMESH) +#if defined(HIGRESS) for (auto& p : redis_request_) { p.second.request_->cancel(); } @@ -1626,7 +1626,7 @@ Network::FilterStatus Context::onNewConnection() { }; Network::FilterStatus Context::onData(::Envoy::Buffer::Instance& data, bool end_stream) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1643,7 +1643,7 @@ Network::FilterStatus Context::onData(::Envoy::Buffer::Instance& data, bool end_ } Network::FilterStatus Context::onWrite(::Envoy::Buffer::Instance& data, bool end_stream) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1665,7 +1665,7 @@ Network::FilterStatus Context::onWrite(::Envoy::Buffer::Instance& data, bool end } void Context::onEvent(Network::ConnectionEvent event) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1702,7 +1702,7 @@ void Context::log(const Http::RequestHeaderMap* request_headers, if (!stream_info.requestComplete().has_value()) { return; } -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1911,7 +1911,7 @@ Http::FilterHeadersStatus Context::decodeHeaders(Http::RequestHeaderMap& headers } Http::FilterDataStatus Context::decodeData(::Envoy::Buffer::Instance& data, bool end_stream) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1939,7 +1939,7 @@ Http::FilterDataStatus Context::decodeData(::Envoy::Buffer::Instance& data, bool } Http::FilterTrailersStatus Context::decodeTrailers(Http::RequestTrailerMap& trailers) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1955,7 +1955,7 @@ Http::FilterTrailersStatus Context::decodeTrailers(Http::RequestTrailerMap& trai } Http::FilterMetadataStatus Context::decodeMetadata(Http::MetadataMap& request_metadata) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1980,7 +1980,7 @@ Http::Filter1xxHeadersStatus Context::encode1xxHeaders(Http::ResponseHeaderMap&) Http::FilterHeadersStatus Context::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -1997,7 +1997,7 @@ Http::FilterHeadersStatus Context::encodeHeaders(Http::ResponseHeaderMap& header } Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool end_stream) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -2025,7 +2025,7 @@ Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool } Http::FilterTrailersStatus Context::encodeTrailers(Http::ResponseTrailerMap& trailers) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { @@ -2041,7 +2041,7 @@ Http::FilterTrailersStatus Context::encodeTrailers(Http::ResponseTrailerMap& tra } Http::FilterMetadataStatus Context::encodeMetadata(Http::MetadataMap& response_metadata) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (destroyed_ || !in_vm_context_created_) { #else if (!in_vm_context_created_) { diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 960adb0a11f68..6006e7291f360 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -218,7 +218,7 @@ class Context : public proxy_wasm::ContextBase, Pairs additional_headers, uint32_t grpc_status, std::string_view details) override; void clearRouteCache() override { -#if defined(ALIMESH) +#if defined(HIGRESS) if (decoder_callbacks_ && !disable_clear_route_cache_) { #else if (decoder_callbacks_) { @@ -244,7 +244,7 @@ class Context : public proxy_wasm::ContextBase, // Buffer BufferInterface* getBuffer(WasmBufferType type) override; -#if defined(ALIMESH) +#if defined(HIGRESS) WasmResult setBuffer(WasmBufferType type, size_t start, size_t length, std::string_view data) override; #endif @@ -256,7 +256,7 @@ class Context : public proxy_wasm::ContextBase, std::string_view request_body, const Pairs& request_trailers, int timeout_millisconds, uint32_t* token_ptr) override; -#if defined(ALIMESH) +#if defined(HIGRESS) // Redis WasmResult redisInit(std::string_view cluster, std::string_view username, std::string_view password, int timeout_milliseconds) override; @@ -332,7 +332,7 @@ class Context : public proxy_wasm::ContextBase, Http::AsyncClient::Request* request_; }; -#if defined(ALIMESH) +#if defined(HIGRESS) struct RedisAsyncClientHandler : public Redis::AsyncClient::Callbacks { // Redis::AsyncClient::Callbacks void onSuccess(std::string_view, std::string&& response) override { @@ -397,7 +397,7 @@ class Context : public proxy_wasm::ContextBase, void onHttpCallSuccess(uint32_t token, Envoy::Http::ResponseMessagePtr&& response); void onHttpCallFailure(uint32_t token, Http::AsyncClient::FailureReason reason); -#if defined(ALIMESH) +#if defined(HIGRESS) void onRedisCallSuccess(uint32_t token, std::string&& response); void onRedisCallFailure(uint32_t token); #endif @@ -447,7 +447,7 @@ class Context : public proxy_wasm::ContextBase, // Only available during onHttpCallResponse. Envoy::Http::ResponseMessagePtr* http_call_response_{}; -#if defined(ALIMESH) +#if defined(HIGRESS) // Only available during onRedisCallResponse. std::string redis_call_response_{}; #endif @@ -479,7 +479,7 @@ class Context : public proxy_wasm::ContextBase, // MB: must be a node-type map as we take persistent references to the entries. std::map http_request_; -#if defined(ALIMESH) +#if defined(HIGRESS) std::map redis_request_; #endif std::map grpc_call_request_; @@ -496,7 +496,7 @@ class Context : public proxy_wasm::ContextBase, // Filter state prototype declaration. absl::flat_hash_map state_prototypes_; -#if defined(ALIMESH) +#if defined(HIGRESS) bool disable_clear_route_cache_ = false; #endif }; diff --git a/source/extensions/common/wasm/stats_handler.cc b/source/extensions/common/wasm/stats_handler.cc index 78678710c7cbd..20f849497cf5a 100644 --- a/source/extensions/common/wasm/stats_handler.cc +++ b/source/extensions/common/wasm/stats_handler.cc @@ -70,7 +70,7 @@ void LifecycleStatsHandler::onEvent(WasmEvent event) { switch (event) { case WasmEvent::VmShutDown: lifecycle_stats_.active_.set(--active_wasms); -#ifdef ALIMESH +#ifdef HIGRESS if (is_crashed_) { is_crashed_ = false; if (lifecycle_stats_.crash_.value() > 0) { @@ -83,7 +83,7 @@ void LifecycleStatsHandler::onEvent(WasmEvent event) { lifecycle_stats_.active_.set(++active_wasms); lifecycle_stats_.created_.inc(); break; -#ifdef ALIMESH +#ifdef HIGRESS case WasmEvent::RuntimeError: if (!is_crashed_) { is_crashed_ = true; diff --git a/source/extensions/common/wasm/stats_handler.h b/source/extensions/common/wasm/stats_handler.h index 5c600ffd40779..2f492cc9c77e6 100644 --- a/source/extensions/common/wasm/stats_handler.h +++ b/source/extensions/common/wasm/stats_handler.h @@ -31,7 +31,7 @@ struct CreateWasmStats { CREATE_WASM_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) }; -#ifdef ALIMESH +#ifdef HIGRESS #define LIFECYCLE_STATS(COUNTER, GAUGE, PLUGIN_COUNTER, PLUGIN_GAUGE) \ COUNTER(created) \ GAUGE(active, NeverImport) \ @@ -46,7 +46,7 @@ struct CreateWasmStats { #endif struct LifecycleStats { -#ifdef ALIMESH +#ifdef HIGRESS LIFECYCLE_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) #else @@ -72,7 +72,7 @@ enum class WasmEvent : int { RuntimeError, VmCreated, VmShutDown, -#ifdef ALIMESH +#ifdef HIGRESS RecoverError, #endif }; @@ -107,7 +107,7 @@ CreateStatsHandler& getCreateStatsHandler(); class LifecycleStatsHandler { public: -#ifdef ALIMESH +#ifdef HIGRESS LifecycleStatsHandler(const Stats::ScopeSharedPtr& scope, std::string runtime, std::string plugin_name) : lifecycle_stats_(LifecycleStats{LIFECYCLE_STATS( @@ -128,13 +128,13 @@ class LifecycleStatsHandler { void onEvent(WasmEvent event); static int64_t getActiveVmCount(); -#ifdef ALIMESH +#ifdef HIGRESS LifecycleStats& stats() { return lifecycle_stats_; } #endif protected: LifecycleStats lifecycle_stats_; -#ifdef ALIMESH +#ifdef HIGRESS bool is_crashed_ = false; #endif }; diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index bded3d747898a..ab96bed40594a 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -60,7 +60,7 @@ inline Wasm* getWasm(WasmHandleSharedPtr& base_wasm_handle) { return static_cast(base_wasm_handle->wasm().get()); } -#ifdef ALIMESH +#ifdef HIGRESS WasmEvent failStateToWasmEvent(FailState state) { switch (state) { case FailState::Ok: @@ -111,7 +111,7 @@ Wasm::Wasm(WasmConfig& config, absl::string_view vm_key, const Stats::ScopeShare custom_stat_namespace_(stat_name_pool_.add(CustomStatNamespace)), cluster_manager_(cluster_manager), dispatcher_(dispatcher), time_source_(dispatcher.timeSource()), -#ifdef ALIMESH +#ifdef HIGRESS lifecycle_stats_handler_(LifecycleStatsHandler(scope, config.config().vm_config().runtime(), config.config().name())) { #else @@ -136,7 +136,7 @@ Wasm::Wasm(WasmHandleSharedPtr base_wasm_handle, Event::Dispatcher& dispatcher) time_source_(dispatcher.timeSource()), lifecycle_stats_handler_(getWasm(base_wasm_handle)->lifecycle_stats_handler_) { lifecycle_stats_handler_.onEvent(WasmEvent::VmCreated); -#ifdef ALIMESH +#ifdef HIGRESS auto* vm = wasm_vm(); if (vm) { vm->addFailCallback([this](FailState fail_state) { @@ -194,7 +194,7 @@ Wasm::~Wasm() { } } -#if defined(ALIMESH) +#if defined(HIGRESS) bool PluginHandleSharedPtrThreadLocal::recover() { if (handle_ == nullptr || handle_->wasmHandle() == nullptr || handle_->wasmHandle()->wasm() == nullptr) { @@ -376,7 +376,7 @@ WasmEvent toWasmEvent(const std::shared_ptr& wasm) { return WasmEvent::ConfigureFailed; case FailState::RuntimeError: return WasmEvent::RuntimeError; -#if defined(ALIMESH) +#if defined(HIGRESS) case FailState::RecoverError: return WasmEvent::RecoverError; #endif diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index aa5069e86c98a..e6974a97c4d4f 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -90,7 +90,7 @@ class Wasm : public WasmBase, Logger::Loggable { } void setFailStateForTesting(proxy_wasm::FailState fail_state) { failed_ = fail_state; } -#if defined(ALIMESH) +#if defined(HIGRESS) LifecycleStats& lifecycleStats() { return lifecycle_stats_handler_.stats(); } #endif @@ -157,7 +157,7 @@ class PluginHandle : public PluginHandleBase { using PluginHandleSharedPtr = std::shared_ptr; -#if defined(ALIMESH) +#if defined(HIGRESS) class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject, public Logger::Loggable { public: @@ -172,7 +172,7 @@ class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject { private: PluginHandleSharedPtr handle_; -#if defined(ALIMESH) +#if defined(HIGRESS) MonotonicTime last_recover_time_; #endif }; diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index e2c1a93aa4f63..ef73a3132f046 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -37,7 +37,7 @@ proxy_wasm::LogLevel EnvoyWasmVmIntegration::getLogLevel() { } void EnvoyWasmVmIntegration::error(std::string_view message) { -#ifdef ALIMESH +#ifdef HIGRESS ENVOY_LOG(error, absl::StrReplaceAll(message, {{"\n", "\\n"}})); #else ENVOY_LOG(error, message); diff --git a/source/extensions/filters/common/ext_authz/ext_authz.h b/source/extensions/filters/common/ext_authz/ext_authz.h index b17e53372600d..88cea0589a365 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz.h +++ b/source/extensions/filters/common/ext_authz/ext_authz.h @@ -56,7 +56,7 @@ class HeaderValues { const Http::LowerCaseString EnvoyAuthHeadersToRemove{ absl::StrCat(prefix(), "-auth-headers-to-remove")}; -#if defined(ALIMESH) +#if defined(HIGRESS) const Http::LowerCaseString XMseExternalAuthzCheckResult{"x-mse-external-authz-check-result"}; #endif const Http::LowerCaseString EnvoyAuthFailureModeAllowed{ diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index 3427188295b8e..855a0f36d94f2 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -276,7 +276,7 @@ void RawHttpClientImpl::onBeforeFinalizeUpstreamSpan( } } -#if defined(ALIMESH) +#if defined(HIGRESS) bool isAuthorizationPass(const Http::ResponseHeaderMap& headers) { const uint64_t status_code = Http::Utility::getResponseStatus(headers); @@ -332,7 +332,7 @@ ResponsePtr RawHttpClientImpl::toResponse(Http::ResponseMessagePtr message) { message->headers().remove(storage_header_name); // Create an Ok authorization response. -#if !defined(ALIMESH) +#if !defined(HIGRESS) if (status_code == enumToInt(Http::Code::OK)) { #else if (isAuthorizationPass(message->headers())) { diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h index b6ea8f809e39c..1e196f9d441aa 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h @@ -110,7 +110,7 @@ class ClientConfig { using ClientConfigSharedPtr = std::shared_ptr; -#if defined(ALIMESH) +#if defined(HIGRESS) bool isAuthorizationPass(const Http::ResponseHeaderMap& headers); #endif diff --git a/source/extensions/filters/http/custom_response/config.cc b/source/extensions/filters/http/custom_response/config.cc index 21fd0aaba84e1..f1ace55337c58 100644 --- a/source/extensions/filters/http/custom_response/config.cc +++ b/source/extensions/filters/http/custom_response/config.cc @@ -47,7 +47,7 @@ createMatcher(const envoy::extensions::filters::http::custom_response::v3::Custo FilterConfig::FilterConfig( const envoy::extensions::filters::http::custom_response::v3::CustomResponse& config, Server::Configuration::ServerFactoryContext& context, Stats::StatName stats_prefix) -#if defined(ALIMESH) +#if defined(HIGRESS) : stats_prefix_(stats_prefix), matcher_{createMatcher(config, context, stats_prefix)}, max_request_bytes_(config.with_request_body().max_request_bytes()) { } diff --git a/source/extensions/filters/http/custom_response/config.h b/source/extensions/filters/http/custom_response/config.h index 05de03bee3b77..0bee4b9c551a6 100644 --- a/source/extensions/filters/http/custom_response/config.h +++ b/source/extensions/filters/http/custom_response/config.h @@ -36,7 +36,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { PolicySharedPtr getPolicy(const ::Envoy::Http::ResponseHeaderMap& headers, const StreamInfo::StreamInfo& stream_info) const; -#if defined(ALIMESH) +#if defined(HIGRESS) bool withRequestBody() const { return max_request_bytes_ > 0; } uint32_t maxRequestBytes() const { return max_request_bytes_; } #endif diff --git a/source/extensions/filters/http/custom_response/custom_response_filter.cc b/source/extensions/filters/http/custom_response/custom_response_filter.cc index ad3066b401544..caf9513c51109 100644 --- a/source/extensions/filters/http/custom_response/custom_response_filter.cc +++ b/source/extensions/filters/http/custom_response/custom_response_filter.cc @@ -16,7 +16,7 @@ namespace CustomResponse { Http::FilterHeadersStatus CustomResponseFilter::decodeHeaders(Http::RequestHeaderMap& header_map, bool) { -#if defined(ALIMESH) +#if defined(HIGRESS) downstream_headers_ = &header_map; const FilterConfig* config = nullptr; if (decoder_callbacks_ && decoder_callbacks_->route()) { @@ -58,7 +58,7 @@ Http::FilterHeadersStatus CustomResponseFilter::encodeHeaders(Http::ResponseHead // If filter state for custom response exists, it means this response is a // custom response. Apply the custom response mutations to the response from // the remote source and return. -#if defined(ALIMESH) +#if defined(HIGRESS) auto filter_state = encoder_callbacks_->streamInfo().filterState()->getDataMutable( CustomResponseFilterState::kFilterStateName); @@ -77,7 +77,7 @@ Http::FilterHeadersStatus CustomResponseFilter::encodeHeaders(Http::ResponseHead // policy. Note that since the traversal is least to most specific, we can't // return early when a match is found. PolicySharedPtr policy; -#if defined(ALIMESH) +#if defined(HIGRESS) if (has_rules_) { #endif decoder_callbacks_->traversePerFilterConfig( @@ -92,7 +92,7 @@ Http::FilterHeadersStatus CustomResponseFilter::encodeHeaders(Http::ResponseHead } } }); -#if defined(ALIMESH) +#if defined(HIGRESS) } #endif if (!policy) { @@ -101,7 +101,7 @@ Http::FilterHeadersStatus CustomResponseFilter::encodeHeaders(Http::ResponseHead // A valid custom response was not found. We should just pass through. if (!policy) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (filter_state) { // Trigger policy process the response filter_state->remain_redirect_times = 0; diff --git a/source/extensions/filters/http/custom_response/custom_response_filter.h b/source/extensions/filters/http/custom_response/custom_response_filter.h index ce164f6bb4d65..e9f8183e6480a 100644 --- a/source/extensions/filters/http/custom_response/custom_response_filter.h +++ b/source/extensions/filters/http/custom_response/custom_response_filter.h @@ -50,7 +50,7 @@ class CustomResponseFilter : public ::Envoy::Http::PassThroughFilter, const std::shared_ptr config_; ::Envoy::Http::RequestHeaderMap* downstream_headers_ = nullptr; bool on_local_reply_called_ = false; -#if defined(ALIMESH) +#if defined(HIGRESS) bool has_rules_ = false; #endif }; diff --git a/source/extensions/filters/http/custom_response/policy.h b/source/extensions/filters/http/custom_response/policy.h index f5e0929751bf8..e539e6f940f73 100644 --- a/source/extensions/filters/http/custom_response/policy.h +++ b/source/extensions/filters/http/custom_response/policy.h @@ -34,7 +34,7 @@ using PolicySharedPtr = std::shared_ptr; struct CustomResponseFilterState : public std::enable_shared_from_this, public StreamInfo::FilterState::Object { -#if defined(ALIMESH) +#if defined(HIGRESS) CustomResponseFilterState(PolicySharedPtr a_policy, absl::optional<::Envoy::Http::Code> code, int32_t max_redirect_times) : policy(a_policy), original_response_code(code), remain_redirect_times(max_redirect_times) {} @@ -45,7 +45,7 @@ struct CustomResponseFilterState : public std::enable_shared_from_this original_response_code; -#if defined(ALIMESH) +#if defined(HIGRESS) int32_t remain_redirect_times; #endif static constexpr absl::string_view kFilterStateName = "envoy.filters.http.custom_response"; diff --git a/source/extensions/filters/http/on_demand/on_demand_update.cc b/source/extensions/filters/http/on_demand/on_demand_update.cc index 4d6b348de5350..ca23555019fb9 100644 --- a/source/extensions/filters/http/on_demand/on_demand_update.cc +++ b/source/extensions/filters/http/on_demand/on_demand_update.cc @@ -193,7 +193,7 @@ void OnDemandRouteUpdate::onDestroy() { // This is the callback which is called when an update requested in requestRouteConfigUpdate() // has been propagated to workers, at which point the request processing is restarted from the // beginning. -#if defined(ALIMESH) +#if defined(HIGRESS) void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool) { #else void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool route_exists) { @@ -205,7 +205,7 @@ void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool route_exists) { return; } -#if !defined(ALIMESH) +#if !defined(HIGRESS) if (route_exists && // route can be resolved after an on-demand // VHDS update !callbacks_->decodingBuffer() && // Redirects with body not yet supported. diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index 4e97a0a96946e..b7b73f2d35d61 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -42,7 +42,7 @@ class FilterConfig : Logger::Loggable { if (handle->wasmHandle()) { wasm = handle->wasmHandle()->wasm().get(); } -#if defined(ALIMESH) +#if defined(HIGRESS) auto failed = false; if (!wasm) { failed = true; diff --git a/source/extensions/filters/network/common/redis/BUILD b/source/extensions/filters/network/common/redis/BUILD index 4ddece1186fca..f030f66a31e92 100644 --- a/source/extensions/filters/network/common/redis/BUILD +++ b/source/extensions/filters/network/common/redis/BUILD @@ -51,7 +51,7 @@ envoy_cc_library( ":redis_command_stats_lib", "//envoy/upstream:cluster_manager_interface", ], - alimesh_deps = [ + higress_deps = [ "//envoy/upstream:upstream_interface", ], ) diff --git a/source/extensions/filters/network/common/redis/client.h b/source/extensions/filters/network/common/redis/client.h index bb1ceb7e0db6a..a156aa2d95623 100644 --- a/source/extensions/filters/network/common/redis/client.h +++ b/source/extensions/filters/network/common/redis/client.h @@ -7,7 +7,7 @@ #include "source/extensions/filters/network/common/redis/codec_impl.h" #include "source/extensions/filters/network/common/redis/redis_command_stats.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "envoy/redis/async_client.h" #endif @@ -18,7 +18,7 @@ namespace Common { namespace Redis { namespace Client { -#if defined(ALIMESH) +#if defined(HIGRESS) using PoolRequest = Envoy::Redis::PoolRequest; #else /** diff --git a/source/extensions/filters/network/common/redis/codec.h b/source/extensions/filters/network/common/redis/codec.h index 1d767baf6f10a..46366c930e742 100644 --- a/source/extensions/filters/network/common/redis/codec.h +++ b/source/extensions/filters/network/common/redis/codec.h @@ -165,7 +165,7 @@ class DecoderCallbacks { virtual void onRespValue(RespValuePtr&& value) PURE; }; -#if defined(ALIMESH) +#if defined(HIGRESS) class RawDecoderCallbacks { public: virtual ~RawDecoderCallbacks() = default; @@ -204,7 +204,7 @@ class DecoderFactory { virtual DecoderPtr create(DecoderCallbacks& callbacks) PURE; }; -#if defined(ALIMESH) +#if defined(HIGRESS) class RawDecoderFactory { public: virtual ~RawDecoderFactory() = default; @@ -230,7 +230,7 @@ class Encoder { using EncoderPtr = std::unique_ptr; -#if defined(ALIMESH) +#if defined(HIGRESS) class RawEncoder { public: virtual ~RawEncoder() = default; diff --git a/source/extensions/filters/network/common/redis/codec_impl.cc b/source/extensions/filters/network/common/redis/codec_impl.cc index 4b411cc74864e..6b447eb39a8db 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.cc +++ b/source/extensions/filters/network/common/redis/codec_impl.cc @@ -549,7 +549,7 @@ void DecoderImpl::parseSlice(const Buffer::RawSlice& slice) { } } -#if defined(ALIMESH) +#if defined(HIGRESS) void RawDecoderImpl::decode(Buffer::Instance& data) { for (const Buffer::RawSlice& slice : data.getRawSlices()) { parseSlice(slice); @@ -883,7 +883,7 @@ void EncoderImpl::encodeSimpleString(const std::string& string, Buffer::Instance out.add(string); out.add("\r\n", 2); } -#if defined(ALIMESH) +#if defined(HIGRESS) void RawEncoderImpl::encode(std::string_view value, Buffer::Instance& out) { out.add(value); } #endif diff --git a/source/extensions/filters/network/common/redis/codec_impl.h b/source/extensions/filters/network/common/redis/codec_impl.h index ffaa9cb179bde..73178b8dd0203 100644 --- a/source/extensions/filters/network/common/redis/codec_impl.h +++ b/source/extensions/filters/network/common/redis/codec_impl.h @@ -64,7 +64,7 @@ class DecoderImpl : public Decoder, Logger::Loggable { std::forward_list pending_value_stack_; }; -#if defined(ALIMESH) +#if defined(HIGRESS) class RawDecoderImpl : public Decoder, Logger::Loggable { public: RawDecoderImpl(RawDecoderCallbacks& callbacks) : callbacks_(callbacks) {} @@ -122,7 +122,7 @@ class DecoderFactoryImpl : public DecoderFactory { return DecoderPtr{new DecoderImpl(callbacks)}; } }; -#if defined(ALIMESH) +#if defined(HIGRESS) class RawDecoderFactoryImpl : public RawDecoderFactory { public: // RedisProxy::RawDecoderFactory @@ -147,7 +147,7 @@ class EncoderImpl : public Encoder { void encodeInteger(int64_t integer, Buffer::Instance& out); void encodeSimpleString(const std::string& string, Buffer::Instance& out); }; -#if defined(ALIMESH) +#if defined(HIGRESS) class RawEncoderImpl : public RawEncoder { public: // RedisProxy::RawEncoder diff --git a/source/extensions/filters/network/common/redis/utility.cc b/source/extensions/filters/network/common/redis/utility.cc index 263bfc50db306..63bd8c0a34a21 100644 --- a/source/extensions/filters/network/common/redis/utility.cc +++ b/source/extensions/filters/network/common/redis/utility.cc @@ -37,7 +37,7 @@ RespValuePtr makeError(const std::string& error) { response->asString() = error; return response; } -#if defined(ALIMESH) +#if defined(HIGRESS) std::string makeRawError(const std::string& error) { std::string result; result.append(fmt::format("-{}\r\n", error)); diff --git a/source/extensions/filters/network/common/redis/utility.h b/source/extensions/filters/network/common/redis/utility.h index 139b7e832cc88..2c7259ecac17e 100644 --- a/source/extensions/filters/network/common/redis/utility.h +++ b/source/extensions/filters/network/common/redis/utility.h @@ -18,7 +18,7 @@ class AuthRequest : public Redis::RespValue { }; RespValuePtr makeError(const std::string& error); -#if defined(ALIMESH) +#if defined(HIGRESS) std::string makeRawError(const std::string& error); std::string makeRawAuthRequest(const std::string& password); std::string makeRawAuthRequest(const std::string& username, const std::string& password); diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 4ccd5a6beb343..1d6ce67d75c68 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -385,7 +385,7 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( createHeaderValidatorFactory(config, context.getServerFactoryContext())), append_x_forwarded_port_(config.append_x_forwarded_port()), add_proxy_protocol_connection_state_( -#if defined(ALIMESH) +#if defined(HIGRESS) PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, add_proxy_protocol_connection_state, true)), keepalive_header_timeout_(PROTOBUF_GET_SECONDS_OR_DEFAULT(config, keepalive_header_timeout, KeepaliveHeaderTimeoutSeconds)) { diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 8915e085efbb0..39c77861d6ddf 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -267,7 +267,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, bool addProxyProtocolConnectionState() const override { return add_proxy_protocol_connection_state_; } -#if defined(ALIMESH) +#if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } #endif @@ -356,7 +356,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, static const uint64_t RequestTimeoutMs = 0; // request header timeout is disabled by default static const uint64_t RequestHeaderTimeoutMs = 0; -#if defined(ALIMESH) +#if defined(HIGRESS) // keep-alive response header is disabled by default static const uint64_t KeepaliveHeaderTimeoutSeconds = 0; #endif @@ -368,7 +368,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, const Http::HeaderValidatorFactoryPtr header_validator_factory_; const bool append_x_forwarded_port_; const bool add_proxy_protocol_connection_state_; -#if defined(ALIMESH) +#if defined(HIGRESS) const std::chrono::seconds keepalive_header_timeout_; #endif }; diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index 1df4c08cad873..9a6e21eb81268 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -42,7 +42,7 @@ class FilterConfig : Logger::Loggable { if (handle->wasmHandle()) { wasm = handle->wasmHandle()->wasm().get(); } -#if defined(ALIMESH) +#if defined(HIGRESS) auto failed = false; if (!wasm) { failed = true; diff --git a/source/extensions/health_checkers/tcp/health_checker_impl.cc b/source/extensions/health_checkers/tcp/health_checker_impl.cc index 59e71186a0d56..43ff922f678a9 100644 --- a/source/extensions/health_checkers/tcp/health_checker_impl.cc +++ b/source/extensions/health_checkers/tcp/health_checker_impl.cc @@ -137,7 +137,7 @@ void TcpHealthCheckerImpl::TcpActiveHealthCheckSession::onInterval() { client_->addReadFilter(session_callbacks_); expect_close_ = false; -#if defined(ALIMESH) +#if defined(HIGRESS) try { client_->connect(); } catch (const EnvoyException& ex) { diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc index 1b2dcbc35558c..39066fc58d1cd 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc @@ -63,7 +63,7 @@ RedirectPolicy::RedirectPolicy( ? std::make_unique<::Envoy::Http::Utility::RedirectConfig>( createRedirectConfig(config.redirect_action())) : nullptr}, -#if defined(ALIMESH) +#if defined(HIGRESS) use_original_request_uri_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_request_uri, false)), keep_original_response_code_( @@ -83,7 +83,7 @@ RedirectPolicy::RedirectPolicy( request_header_parser_( Envoy::Router::HeaderParser::configure(config.request_headers_to_add())), modify_request_headers_action_(createModifyRequestHeadersAction(config, context)) { -#if defined(ALIMESH) +#if defined(HIGRESS) // Ensure that exactly one of uri_ or redirect_action_ or use_original_request_uri_ is specified. ASSERT(int(uri_ != nullptr) + int(redirect_action_ != nullptr) + int(use_original_request_uri_) == 1); @@ -129,7 +129,7 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( // the remote source and return. auto encoder_callbacks = custom_response_filter.encoderCallbacks(); auto decoder_callbacks = custom_response_filter.decoderCallbacks(); -#if defined(ALIMESH) +#if defined(HIGRESS) auto* filter_state = encoder_callbacks->streamInfo() .filterState() @@ -220,7 +220,7 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( }); ::Envoy::Http::Utility::Url absolute_url; -#if defined(ALIMESH) +#if defined(HIGRESS) if (use_original_request_uri_) { std::string real_original_host; const auto x_envoy_original_host = downstream_headers->getByKey( @@ -258,7 +258,7 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( path_and_query = path_and_query.substr(0, fragment_pos); downstream_headers->setPath(path_and_query); -#if defined(ALIMESH) +#if defined(HIGRESS) } #endif if (decoder_callbacks->downstreamCallbacks()) { @@ -280,13 +280,13 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( // redirect will take place. return ::Envoy::Http::FilterHeadersStatus::Continue; } -#if !defined(ALIMESH) +#if !defined(HIGRESS) downstream_headers->setMethod(::Envoy::Http::Headers::get().MethodValues.Get); #endif downstream_headers->remove(::Envoy::Http::Headers::get().ContentLength); // Cache the original response code. absl::optional<::Envoy::Http::Code> original_response_code; -#if defined(ALIMESH) +#if defined(HIGRESS) if (keep_original_response_code_) { absl::optional current_code = ::Envoy::Http::Utility::getResponseStatusOrNullopt(headers); diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.h b/source/extensions/http/custom_response/redirect_policy/redirect_policy.h index 5180fa34bf23d..da399a98f6b1e 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.h +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.h @@ -70,7 +70,7 @@ class RedirectPolicy : public Extensions::HttpFilters::CustomResponse::Policy, // Remote source the request should be redirected to. const std::unique_ptr uri_; const std::unique_ptr redirect_action_; -#if defined(ALIMESH) +#if defined(HIGRESS) bool use_original_request_uri_; bool keep_original_response_code_; uint32_t max_internal_redirects_; diff --git a/source/extensions/tracers/skywalking/tracer.cc b/source/extensions/tracers/skywalking/tracer.cc index 7c89535364019..a66e24513ec9d 100644 --- a/source/extensions/tracers/skywalking/tracer.cc +++ b/source/extensions/tracers/skywalking/tracer.cc @@ -2,7 +2,7 @@ #include -#if defined(ALIMESH) +#if defined(HIGRESS) #include "source/common/common/base64.h" #endif @@ -20,7 +20,7 @@ const Http::LowerCaseString& skywalkingPropagationHeaderKey() { CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, "sw8"); } -#if defined(ALIMESH) +#if defined(HIGRESS) const Http::LowerCaseString& skywalkingPropagationHeaderKeyTraceId() { CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, "sw8-traceid"); } @@ -65,7 +65,7 @@ void Span::injectContext(Tracing::TraceContext& trace_context, tracing_context_->createSW8HeaderValue({remote_address.data(), remote_address.size()}); if (sw8_header.has_value()) { trace_context.setByReferenceKey(skywalkingPropagationHeaderKey(), sw8_header.value()); -#if defined(ALIMESH) +#if defined(HIGRESS) std::vector result = absl::StrSplit(sw8_header.value(), '-'); std::string sw8_trace_id = ""; if (result.size() > 1) { diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index c39d840f51c18..271fb97f9cebb 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -235,7 +235,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ctx.is_ecdsa_ = true; } break; case EVP_PKEY_RSA: { -#if !defined(ALIMESH) +#if !defined(HIGRESS) // We require RSA certificates with 2048-bit or larger keys. const RSA* rsa_public_key = EVP_PKEY_get0_RSA(public_key.get()); // Since we checked the key type above, this should be valid. diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index b3e0faac120a5..6f4233d575bc5 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -227,7 +227,7 @@ class AdminImpl : public Admin, } bool appendXForwardedPort() const override { return false; } bool addProxyProtocolConnectionState() const override { return true; } -#if defined(ALIMESH) +#if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return {}; } #endif @@ -317,7 +317,7 @@ class AdminImpl : public Admin, NullScopeKeyBuilder() = default; ~NullScopeKeyBuilder() override = default; -#if defined(ALIMESH) +#if defined(HIGRESS) Router::ScopeKeyPtr computeScopeKey(const Http::HeaderMap&, const StreamInfo::StreamInfo*, std::function&) const override { return nullptr; diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 1576d826c5150..f51ad583bd86b 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -913,7 +913,7 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { } // The test environment does not support IPV6 -#if !defined(ALIMESH) +#if !defined(HIGRESS) // Validate for IPv6 address address = Network::Address::InstanceConstSharedPtr{new Network::Address::Ipv6Instance("::1", 9443)}; diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 6aa90f7442fa2..0ec5bf4855998 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -241,7 +241,7 @@ class FuzzConfig : public ConnectionManagerConfig { } bool appendXForwardedPort() const override { return false; } bool addProxyProtocolConnectionState() const override { return true; } -#if defined(ALIMESH) +#if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } #endif @@ -297,7 +297,7 @@ class FuzzConfig : public ConnectionManagerConfig { std::vector ip_detection_extensions_{}; std::vector early_header_mutations_; std::unique_ptr proxy_status_config_; -#if defined(ALIMESH) +#if defined(HIGRESS) std::chrono::seconds keepalive_header_timeout_{}; #endif }; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 2d0ee4a824083..bdb2658180d8b 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -1384,7 +1384,7 @@ TEST_F(HttpConnectionManagerImplTest, DateHeaderPresent) { doRemoteClose(); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(HttpConnectionManagerImplTest, KeepaliveHeaderNotAppend) { setup(false, ""); setUpEncoderAndDecoder(false, false); diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index e955fb9a117c5..93c71e44bb2d7 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2750,7 +2750,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSessionTrace) { TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteNotFound) { setup(false, "", true, true); setupFilterChain(1, 0); // Recreate the chain for second stream. -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*static_cast( scopedRouteConfigProvider()->config().get()), getRouteConfig(_, _, _)) @@ -2793,7 +2793,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteNotFound) { TEST_F(HttpConnectionManagerImplTest, TestSrdsUpdate) { setup(false, "", true, true); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*static_cast( scopedRouteConfigProvider()->config().get()), getRouteConfig(_, _, _)) @@ -2867,7 +2867,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsCrossScopeReroute) { std::shared_ptr route2 = std::make_shared>(); EXPECT_CALL(*route_config1, route(_, _, _, _)).WillRepeatedly(Return(route1)); EXPECT_CALL(*route_config2, route(_, _, _, _)).WillRepeatedly(Return(route2)); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*static_cast( scopedRouteConfigProvider()->config().get()), getRouteConfig(_, _, _)) @@ -2957,7 +2957,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteFound) { std::shared_ptr fake_cluster1 = std::make_shared>(); EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(fake_cluster1.get())); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*scopedRouteConfigProvider()->config(), getRouteConfig(_, _, _)) // 1. decodeHeaders() snapping route config. diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index fa0e210196aa2..5472fb7a81394 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -176,7 +176,7 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { return add_proxy_protocol_connection_state_; } -#if defined(ALIMESH) +#if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } #endif // Simple helper to wrapper filter to the factory function. @@ -280,7 +280,7 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { std::vector ip_detection_extensions_{}; std::vector early_header_mutations_{}; bool add_proxy_protocol_connection_state_ = true; -#if defined(ALIMESH) +#if defined(HIGRESS) std::chrono::seconds keepalive_header_timeout_{}; #endif diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 485386381600b..5d89275dc343d 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -1751,7 +1751,7 @@ TEST(DurationUtilTest, NoThrow) { } } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST(DurationUtilTest, ConvertDurationToJsonString) { { ProtobufWkt::Duration duration; diff --git a/test/common/router/BUILD b/test/common/router/BUILD index 61060a0b7ce38..30d644a028287 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -145,7 +145,7 @@ envoy_cc_test( "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], - alimesh_deps = [ + higress_deps = [ "//test/mocks/stream_info:stream_info_mocks", ], ) @@ -181,7 +181,7 @@ envoy_cc_test( "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], - alimesh_deps = [ + higress_deps = [ "//test/mocks/stream_info:stream_info_mocks", ], ) diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index d3db1acdaba55..02a3a86a54191 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -54,7 +54,7 @@ using ::testing::NiceMock; using ::testing::Pair; using ::testing::Return; using ::testing::ReturnRef; -#if defined(ALIMESH) +#if defined(HIGRESS) using ::testing::ReturnPointee; #endif @@ -3169,7 +3169,7 @@ TEST_F(RouterMatcherHashPolicyTest, HashIpv4DifferentAddresses) { } } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(RouterMatcherHashPolicyTest, DISABLED_HashIpv6DifferentAddresses) { #else TEST_F(RouterMatcherHashPolicyTest, HashIpv6DifferentAddresses) { @@ -3702,7 +3702,7 @@ TEST_F(RouteMatcherTest, ClusterSpecifierPlugin) { EXPECT_EQ(mock_route.get(), config.route(genHeaders("some_cluster", "/bar", "GET"), 0).get()); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(RouteMatcherTest, WeightedClusterSpecifierPlugin) { const std::string yaml = R"EOF( cluster_specifier_plugins: @@ -7397,7 +7397,7 @@ TEST_F(CustomRequestHeadersTest, AddNewHeader) { EXPECT_EQ("127.0.0.1", headers.get_("x-client-ip")); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(CustomRequestHeadersTest, AddMseOriginalPathHeader) { const std::string yaml = R"EOF( virtual_hosts: @@ -10614,7 +10614,7 @@ TEST_F(RouteConfigurationV2, InternalRedirectPolicyDropsInvalidRedirectCodeCause internal_redirect_policy.shouldRedirectForResponseCode(static_cast(200))); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(RouteConfigurationV2, InternalActiveRedirectIsDisabledWhenNotSpecifiedInRouteAction) { const std::string yaml = R"EOF( virtual_hosts: @@ -11940,7 +11940,7 @@ TEST_F(RouteMatchOverrideTest, NullRouteOnRequireTlsAll) { }, genHeaders("bat.com", "/", "GET")); EXPECT_NE(nullptr, dynamic_cast(accepted_route.get())); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_EQ(Http::Code::MovedPermanently, dynamic_cast(accepted_route.get()) ->directResponseEntry() @@ -12027,7 +12027,7 @@ TEST_F(CommonConfigImplTest, TestCommonConfig) { shared_config.ignorePathParametersInPathMatching()); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(RouteMatchOverrideTest, NullRouteOnExactAllowServerNames) { const std::string yaml = R"EOF( virtual_hosts: diff --git a/test/common/router/router_2_test.cc b/test/common/router/router_2_test.cc index 53870c1571a9a..1ecafa562d38a 100644 --- a/test/common/router/router_2_test.cc +++ b/test/common/router/router_2_test.cc @@ -57,9 +57,9 @@ TEST_F(RouterTestSuppressEnvoyHeaders, MaintenanceMode) { router_->decodeHeaders(headers, true); } -// if ALIMESH defined, x-envoy-upstream-service-time will be added anyway. +// if HIGRESS defined, x-envoy-upstream-service-time will be added anyway. // see https://code.alibaba-inc.com/Ingress/envoy/codereview/13276137 -#ifndef ALIMESH +#ifndef HIGRESS // Validate that x-envoy-upstream-service-time is not added when Envoy header // suppression is enabled. // TODO(htuch): Probably should be TEST_P with @@ -435,7 +435,7 @@ TEST_F(RouterTestChildSpan, BasicFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); #endif router_->decodeHeaders(headers, true); @@ -489,7 +489,7 @@ TEST_F(RouterTestChildSpan, ResetFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); #endif router_->decodeHeaders(headers, true); @@ -546,7 +546,7 @@ TEST_F(RouterTestChildSpan, CancelFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); #endif router_->decodeHeaders(headers, true); @@ -600,7 +600,7 @@ TEST_F(RouterTestChildSpan, ResetRetryFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span_1)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*child_span_1, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); #endif router_->decodeHeaders(headers, true); @@ -645,7 +645,7 @@ TEST_F(RouterTestChildSpan, ResetRetryFlow) { EXPECT_CALL(callbacks_.active_span_, spawnChild_(_, "router observability_name egress", _)) .WillOnce(Return(child_span_2)); EXPECT_CALL(callbacks_, tracingConfig()).Times(2); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(*child_span_2, setTag(Eq(Tracing::Tags::get().PeerIpv4), Eq("10.0.0.5"))); #endif EXPECT_CALL(*child_span_2, setTag(Eq(Tracing::Tags::get().RetryCount), Eq("1"))); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 7b7aee44a9122..48b5d7eb21109 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -4364,7 +4364,7 @@ TEST_F(RouterTest, CrossSchemeRedirectAllowedByPolicy) { router_->onDestroy(); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(RouterTest, InternalActiveRedirectRejectedWhenReachingMaxInternalRedirect) { enableActiveRedirects("http://www.foo.com", 3); setNumPreviousRedirect(3); @@ -6005,7 +6005,7 @@ TEST_F(RouterTest, CanaryStatusFalse) { .value()); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(RouterTest, DISABLED_AutoHostRewriteEnabled) { #else TEST_F(RouterTest, AutoHostRewriteEnabled) { diff --git a/test/common/router/router_test_base.cc b/test/common/router/router_test_base.cc index fa0ff45ab2afd..df2d029f15771 100644 --- a/test/common/router/router_test_base.cc +++ b/test/common/router/router_test_base.cc @@ -243,7 +243,7 @@ void RouterTestBase::setNumPreviousRedirect(uint32_t num_previous_redirects) { StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); } -#if defined(ALIMESH) +#if defined(HIGRESS) void RouterTestBase::enableActiveRedirects(std::string redirect_url, uint32_t max_internal_redirects, bool forced_use_original_host, diff --git a/test/common/router/router_test_base.h b/test/common/router/router_test_base.h index ffd3d2616aaa5..67c9bd1ca4a58 100644 --- a/test/common/router/router_test_base.h +++ b/test/common/router/router_test_base.h @@ -51,7 +51,7 @@ class RouterTestFilter : public Filter { return &downstream_connection_; } -#if defined(ALIMESH) +#if defined(HIGRESS) Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) override { auto status = Filter::decodeHeaders(headers, end_stream); @@ -99,7 +99,7 @@ class RouterTestBase : public testing::Test { void expectNewStreamWithImmediateEncoder(Http::RequestEncoder& encoder, Http::ResponseDecoder** decoder, Http::Protocol protocol); -#if defined(ALIMESH) +#if defined(HIGRESS) void enableActiveRedirects(std::string redirect_url, uint32_t max_internal_redirects = 1, bool forced_use_original_host = false, bool forced_add_header_before_route_matcher = false); diff --git a/test/common/router/router_upstream_log_test.cc b/test/common/router/router_upstream_log_test.cc index a6ed19e56bb94..3413dbe32089c 100644 --- a/test/common/router/router_upstream_log_test.cc +++ b/test/common/router/router_upstream_log_test.cc @@ -77,7 +77,7 @@ class TestFilter : public Filter { return &downstream_connection_; } -#if defined(ALIMESH) +#if defined(HIGRESS) Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) override { auto status = Filter::decodeHeaders(headers, end_stream); diff --git a/test/common/router/scoped_config_impl_test.cc b/test/common/router/scoped_config_impl_test.cc index 3f6a245ab77ff..518091bd1ead6 100644 --- a/test/common/router/scoped_config_impl_test.cc +++ b/test/common/router/scoped_config_impl_test.cc @@ -6,7 +6,7 @@ #include "source/common/router/scoped_config_impl.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "source/common/network/address_impl.h" #include "test/mocks/stream_info/mocks.h" #endif @@ -22,7 +22,7 @@ namespace { using ::Envoy::Http::TestRequestHeaderMapImpl; using ::testing::NiceMock; -#if defined(ALIMESH) +#if defined(HIGRESS) using ::testing::ReturnPointee; #endif @@ -358,7 +358,7 @@ TEST(ScopeKeyBuilderImplTest, Parse) { EXPECT_EQ(key, nullptr); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST(ScopeKeyBuilderImplTest, ParseHostAndPort) { std::string yaml_plain = R"EOF( fragments: diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index e1021771b2c4c..510f59965d17a 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -18,7 +18,7 @@ #include "source/common/protobuf/message_validator_impl.h" #include "source/common/router/scoped_rds.h" -#if defined(ALIMESH) +#if defined(HIGRESS) #include "source/common/network/address_impl.h" #include "test/mocks/stream_info/mocks.h" #endif @@ -46,7 +46,7 @@ using testing::IsNull; using testing::NiceMock; using testing::Return; using testing::ReturnRef; -#if defined(ALIMESH) +#if defined(HIGRESS) using testing::ReturnPointee; #endif @@ -339,7 +339,7 @@ TEST_F(InlineScopedRoutesTest, ConfigLoadAndDump) { class ScopedRdsTest : public ScopedRoutesTestBase { protected: -#if defined(ALIMESH) +#if defined(HIGRESS) void setupHostScope(const OptionalHttpFilters optional_http_filters = OptionalHttpFilters()) { ON_CALL(server_factory_context_.cluster_manager_, adsMux()) .WillByDefault(Return(std::make_shared<::Envoy::Config::NullGrpcMuxImpl>())); @@ -1916,7 +1916,7 @@ route_configuration_name: foo_routes EXPECT_EQ(config->name(), "foo_routes"); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(ScopedRdsTest, HostScopeMultipleResourcesSotw) { setupHostScope(); diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index 2cd465c5c804f..3a20103ed4678 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -49,7 +49,7 @@ static std::vector unsuported_win32_configs = { #if defined(WIN32) && !defined(SO_ORIGINAL_DST) "configs_original-dst-cluster_proxy_config.yaml", #endif -#if defined(ALIMESH) +#if defined(HIGRESS) // The test platform does not support udp Gro feature. "udp_envoy.yaml" #endif diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 6e036339c0245..f7f5ec784f998 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -144,7 +144,7 @@ TEST_P(WasmTestMatrix, LoggingWithEnvVars) { setWasmCode("logging"); auto wasm_weak = std::weak_ptr(wasm_); -#ifdef ALIMESH +#ifdef HIGRESS // auto wasm_handler = // std::make_unique(std::move(wasm_), *dispatcher_); auto wasm_handler = std::make_unique(std::move(wasm_)); diff --git a/test/extensions/common/wasm/context_test.cc b/test/extensions/common/wasm/context_test.cc index cdb362c3f1a88..6dac22888db2b 100644 --- a/test/extensions/common/wasm/context_test.cc +++ b/test/extensions/common/wasm/context_test.cc @@ -243,7 +243,7 @@ TEST_F(ContextTest, FindValueTest) { EXPECT_FALSE(ctx_.FindValue("plugin_name", &arena).has_value()); } -#ifdef ALIMESH +#ifdef HIGRESS TEST_F(ContextTest, SetCustomSpanTagTest) { Http::MockStreamDecoderFilterCallbacks decoder_callbacks; Envoy::StreamInfo::MockStreamInfo decoder_si; diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 282f4f2b92d0f..8e8b7fed1101e 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -103,7 +103,7 @@ TEST_P(WasmCommonTest, WasmFailState) { envoy::extensions::wasm::v3::PluginConfig plugin_config; auto plugin = std::make_shared( plugin_config, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); -#ifdef ALIMESH +#ifdef HIGRESS // auto wasm = std::make_shared( // std::make_unique(plugin->wasmConfig(), "", scope, *api, cluster_manager, *dispatcher), // *dispatcher); @@ -203,7 +203,7 @@ TEST_P(WasmCommonTest, Logging) { [](Wasm*, const std::shared_ptr&) -> ContextBase* { return nullptr; }); EXPECT_EQ(std::unique_ptr(wasm->createContext(plugin)), nullptr); auto wasm_weak = std::weak_ptr(wasm); -#ifdef ALIMESH +#ifdef HIGRESS // auto wasm_handle = // std::make_shared(std::move(wasm), *dispatcher); auto wasm_handle = std::make_shared(std::move(wasm)); @@ -717,7 +717,7 @@ TEST_P(WasmCommonTest, VmCache) { EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); -#ifdef ALIMESH +#ifdef HIGRESS // return std::make_shared(wasm, *dispatcher); return std::make_shared(wasm); #else @@ -839,7 +839,7 @@ TEST_P(WasmCommonTest, RemoteCode) { EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); -#ifdef ALIMESH +#ifdef HIGRESS return std::make_shared(wasm); // return std::make_shared(wasm, *dispatcher); #else @@ -965,7 +965,7 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); -#ifdef ALIMESH +#ifdef HIGRESS return std::make_shared(wasm); // return std::make_shared(wasm, *dispatcher); #else @@ -1301,7 +1301,7 @@ TEST_P(WasmCommonTest, ThreadLocalCopyRetainsEnforcement) { EXPECT_TRUE(wasm->load(code, false)); EXPECT_TRUE(wasm->initialize()); -#ifdef ALIMESH +#ifdef HIGRESS // auto wasm_handle = // std::make_shared(std::move(wasm), *dispatcher); auto wasm_handle = std::make_shared(std::move(wasm)); diff --git a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc index b4389de8ef7e6..94a464a62df92 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc @@ -298,7 +298,7 @@ TEST_F(ExtAuthzHttpClientTest, ContentLengthEqualZeroWithAllowedHeaders) { EXPECT_EQ(message_ptr->headers().getMethodValue(), "POST"); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(ExtAuthzHttpClientTest, IsAuthorizationPass) { { // 200 code without x-mse-external-authz-check-result diff --git a/test/extensions/filters/http/cors/cors_filter_integration_test.cc b/test/extensions/filters/http/cors/cors_filter_integration_test.cc index a6374046bda32..ee58f92e19e7b 100644 --- a/test/extensions/filters/http/cors/cors_filter_integration_test.cc +++ b/test/extensions/filters/http/cors/cors_filter_integration_test.cc @@ -230,7 +230,7 @@ class CorsFilterIntegrationTest : public testing::TestWithParam, Http::TestResponseHeaderMapImpl& expected_response_headers) { response_headers.remove(Envoy::Http::LowerCaseString{"date"}); response_headers.remove(Envoy::Http::LowerCaseString{"x-envoy-upstream-service-time"}); -#if defined(ALIMESH) +#if defined(HIGRESS) response_headers.remove(Envoy::Http::LowerCaseString{"req-cost-time"}); response_headers.remove(Envoy::Http::LowerCaseString{"req-start-time"}); response_headers.remove(Envoy::Http::LowerCaseString{"req-arrive-time"}); diff --git a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc index c952c5c55ea77..2260345bf99a8 100644 --- a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc +++ b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc @@ -39,7 +39,7 @@ class CustomResponseFilterTest : public testing::Test { filter_ = std::make_unique(config_); filter_->setEncoderFilterCallbacks(encoder_callbacks_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); -#if defined(ALIMESH) +#if defined(HIGRESS) ON_CALL(decoder_callbacks_, recreateStream(_)).WillByDefault(Return(true)); ON_CALL(decoder_callbacks_, recreateStream(_, _)).WillByDefault(Return(true)); #endif @@ -95,7 +95,7 @@ TEST_F(CustomResponseFilterTest, RemoteData) { ::Envoy::Http::TestRequestHeaderMapImpl request_headers{}; EXPECT_EQ(filter_->decodeHeaders(request_headers, false), ::Envoy::Http::FilterHeadersStatus::Continue); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)); #else EXPECT_CALL(decoder_callbacks_, recreateStream(_)); @@ -214,7 +214,7 @@ TEST_F(CustomResponseFilterTest, InvalidSchemeRedirect) { stats_store_.findCounterByString("stats.custom_response_invalid_uri").value().get().value()); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_F(CustomResponseFilterTest, SingleRedirectCustomStatus) { // Create config with invalid scheme field. createConfig(R"EOF( @@ -250,7 +250,7 @@ TEST_F(CustomResponseFilterTest, SingleRedirectCustomStatus) { ::Envoy::Http::TestRequestHeaderMapImpl request_headers{{"Host", "example.foo"}}; EXPECT_EQ(filter_->decodeHeaders(request_headers, false), ::Envoy::Http::FilterHeadersStatus::Continue); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_CALL(decoder_callbacks_, recreateStream(_, false)); #else EXPECT_CALL(decoder_callbacks_, recreateStream(_)); diff --git a/test/extensions/filters/http/ext_proc/utils.cc b/test/extensions/filters/http/ext_proc/utils.cc index dda06aef0f4bc..4711af69dd396 100644 --- a/test/extensions/filters/http/ext_proc/utils.cc +++ b/test/extensions/filters/http/ext_proc/utils.cc @@ -9,7 +9,7 @@ namespace ExternalProcessing { const absl::flat_hash_set ExtProcTestUtility::ignoredHeaders() { CONSTRUCT_ON_FIRST_USE(absl::flat_hash_set, "x-request-id", -#ifdef ALIMESH +#ifdef HIGRESS "x-envoy-upstream-service-time", "req-cost-time", "req-arrive-time", "resp-start-time"); #else diff --git a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc index 4ab8b2dcfdebb..d20d02ab520b3 100644 --- a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc +++ b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc @@ -134,7 +134,7 @@ TEST_F(OnDemandFilterTest, TEST_F(OnDemandFilterTest, TestOnRouteConfigUpdateCompletionContinuesDecodingWithRedirectWithBody) { Buffer::OwnedImpl buffer; EXPECT_CALL(decoder_callbacks_, continueDecoding()); -#ifndef ALIMESH +#ifndef HIGRESS EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(&buffer)); #endif filter_->onRouteConfigUpdateCompletion(true); @@ -143,7 +143,7 @@ TEST_F(OnDemandFilterTest, TestOnRouteConfigUpdateCompletionContinuesDecodingWit // tests onRouteConfigUpdateCompletion() when ActiveStream recreation fails TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionContinuesDecodingIfRedirectFails) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); -#ifndef ALIMESH +#ifndef HIGRESS EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(false)); #endif @@ -152,7 +152,7 @@ TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionContinuesDecodingIfRedir // tests onRouteConfigUpdateCompletion() when route was resolved TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionRestartsActiveStream) { -#ifndef ALIMESH +#ifndef HIGRESS EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(true)); #endif diff --git a/test/extensions/filters/http/tap/tap_filter_integration_test.cc b/test/extensions/filters/http/tap/tap_filter_integration_test.cc index db206deb8c7f8..b49f259d98f2c 100644 --- a/test/extensions/filters/http/tap/tap_filter_integration_test.cc +++ b/test/extensions/filters/http/tap/tap_filter_integration_test.cc @@ -333,7 +333,7 @@ config_id: test_config_id admin_response_->waitForBodyData(1); envoy::data::tap::v3::TraceWrapper trace; TestUtility::loadFromYaml(admin_response_->body(), trace); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_EQ(trace.http_buffered_trace().request().headers().size(), 10); EXPECT_EQ(trace.http_buffered_trace().response().headers().size(), 7); #else @@ -351,7 +351,7 @@ config_id: test_config_id // Wait for the tap message. admin_response_->waitForBodyData(1); TestUtility::loadFromYaml(admin_response_->body(), trace); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_EQ(trace.http_buffered_trace().request().headers().size(), 9); EXPECT_EQ(trace.http_buffered_trace().response().headers().size(), 8); #else diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 256280763966f..f05b1716a8801 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -528,7 +528,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { filter().onDestroy(); } -#if defined(ALIMESH) +#if defined(HIGRESS) // Script that replaces the batched buffered body. TEST_P(WasmHttpFilterTest, BodyRequestReplaceBatchedBufferedBody) { setupTest("body"); @@ -755,7 +755,7 @@ TEST_P(WasmHttpFilterTest, AccessLogCreate) { AccessLog::AccessLogType::NotSet); filter().onDestroy(); } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_P(WasmHttpFilterTest, RedisCall) { if (std::get<1>(GetParam()) == "rust") { // This feature is not supported in rust @@ -1832,7 +1832,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { } } -#if defined(ALIMESH) +#if defined(HIGRESS) TEST_P(WasmHttpFilterTest, GetRouteName) { if (std::get<1>(GetParam()) == "rust") { return; diff --git a/test/extensions/filters/network/common/redis/BUILD b/test/extensions/filters/network/common/redis/BUILD index 7a066e8495fee..4e7fa6c859820 100644 --- a/test/extensions/filters/network/common/redis/BUILD +++ b/test/extensions/filters/network/common/redis/BUILD @@ -20,7 +20,7 @@ envoy_cc_mock( "//source/extensions/filters/network/common/redis:codec_lib", "//test/test_common:printers_lib", ], - alimesh_deps = [ + higress_deps = [ "//source/extensions/filters/network/common/redis:raw_client_lib", ], ) @@ -63,7 +63,7 @@ envoy_cc_test( "//test/test_common:simulated_time_system_lib", "@envoy_api//envoy/extensions/filters/network/redis_proxy/v3:pkg_cc_proto", ], - alimesh_deps = [ + higress_deps = [ "//source/extensions/filters/network/common/redis:raw_client_lib", ], ) diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index 41a9fb419ff70..3fe9f05962777 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -1224,7 +1224,7 @@ TEST(RedisClientFactoryImplTest, Basic) { *stats_.rootScope(), auth_username, auth_password, false); client->close(); } -#if defined(ALIMESH) +#if defined(HIGRESS) class RedisRawClientDefaultConfig : public Config { std::chrono::milliseconds opTimeout() const override { return std::chrono::milliseconds(20); } // Cluster is not supported diff --git a/test/extensions/filters/network/common/redis/codec_impl_test.cc b/test/extensions/filters/network/common/redis/codec_impl_test.cc index 26af8f0254be7..aaaf6698f5151 100644 --- a/test/extensions/filters/network/common/redis/codec_impl_test.cc +++ b/test/extensions/filters/network/common/redis/codec_impl_test.cc @@ -426,7 +426,7 @@ TEST_F(RedisEncoderDecoderImplTest, InvalidBulkStringExpectLF) { buffer_.add("$1\r\na\ra"); EXPECT_THROW(decoder_.decode(buffer_), ProtocolError); } -#if defined(ALIMESH) +#if defined(HIGRESS) class RedisRawEncoderDecoderImplTest : public testing::Test, RawDecoderCallbacks { public: RedisRawEncoderDecoderImplTest() : decoder_(*this) {} diff --git a/test/extensions/filters/network/common/redis/mocks.cc b/test/extensions/filters/network/common/redis/mocks.cc index 6107d8ae55687..56cf240dff71f 100644 --- a/test/extensions/filters/network/common/redis/mocks.cc +++ b/test/extensions/filters/network/common/redis/mocks.cc @@ -29,7 +29,7 @@ MockEncoder::MockEncoder() { } MockEncoder::~MockEncoder() = default; -#if defined(ALIMESH) +#if defined(HIGRESS) MockRawEncoder::MockRawEncoder() { ON_CALL(*this, encode(_, _)) .WillByDefault(Invoke([this](std::string_view value, Buffer::Instance& out) -> void { @@ -62,7 +62,7 @@ MockPoolRequest::~MockPoolRequest() = default; MockClientCallbacks::MockClientCallbacks() = default; MockClientCallbacks::~MockClientCallbacks() = default; -#if defined(ALIMESH) +#if defined(HIGRESS) MockRawClientCallbacks::MockRawClientCallbacks() = default; MockRawClientCallbacks::~MockRawClientCallbacks() = default; #endif diff --git a/test/extensions/filters/network/common/redis/mocks.h b/test/extensions/filters/network/common/redis/mocks.h index a9be66ad1c53c..5f6bc3f57e294 100644 --- a/test/extensions/filters/network/common/redis/mocks.h +++ b/test/extensions/filters/network/common/redis/mocks.h @@ -35,7 +35,7 @@ class MockEncoder : public Common::Redis::Encoder { private: Common::Redis::EncoderImpl real_encoder_; }; -#if defined(ALIMESH) +#if defined(HIGRESS) class MockRawEncoder : public Common::Redis::RawEncoder { public: MockRawEncoder(); @@ -122,7 +122,7 @@ class MockClientCallbacks : public ClientCallbacks { (Common::Redis::RespValuePtr & value, const std::string& host_address, bool ask_redirection)); }; -#if defined(ALIMESH) +#if defined(HIGRESS) class MockRawClientCallbacks : public RawClientCallbacks { public: MockRawClientCallbacks(); diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index cb7cac3886fdd..4a07e66324f90 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -190,7 +190,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailClosed) { NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); filter_config.wasmForTest()->fail(proxy_wasm::FailState::RuntimeError, ""); auto context = filter_config.createFilter(); -#ifdef ALIMESH +#ifdef HIGRESS EXPECT_NE(context->wasm(), nullptr); #else EXPECT_EQ(context->wasm(), nullptr); @@ -217,7 +217,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailOpen) { TestUtility::loadFromYaml(yaml, proto_config); NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); filter_config.wasmForTest()->fail(proxy_wasm::FailState::RuntimeError, ""); -#ifdef ALIMESH +#ifdef HIGRESS EXPECT_NE(filter_config.createFilter(), nullptr); #else EXPECT_EQ(filter_config.createFilter(), nullptr); 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 a756af894221e..313304b34d1f9 100644 --- a/test/extensions/network/dns_resolver/cares/dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/cares/dns_impl_test.cc @@ -1121,7 +1121,7 @@ TEST_P(DnsImplTest, DestroyChannelOnResetNetworking) { // This test will failed beacuse of c-ares libray, we can fix it in the next version merge, see // https://github.com/envoyproxy/envoy/pull/33711 -#ifndef ALIMESH +#ifndef HIGRESS // Validate that the c-ares channel is destroyed and re-initialized when c-ares returns // ARES_ECONNREFUSED as its callback status. TEST_P(DnsImplTest, DestroyChannelOnRefused) { diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc index 2b96ebca67edc..e5d0fc1b25484 100644 --- a/test/extensions/tracers/skywalking/tracer_test.cc +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -218,7 +218,7 @@ TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { EXPECT_NE(0, third_child_span->spanEntity()->endTime()); } -#if defined(ALIMESH) +#if defined(HIGRESS) { Envoy::Tracing::SpanPtr org_child_span_with_traceid_header = org_span->spawnChild(mock_tracing_config_, "TestChild", mock_time_source_.systemTime()); diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 87245aac80794..3b6f702c9712f 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -1223,7 +1223,7 @@ TEST_F(ClientContextConfigImplTest, RSA2048Cert) { auto cleanup = cleanUpHelper(context); } -#if !defined(ALIMESH) +#if !defined(HIGRESS) // Validate that 1024-bit RSA certificates are rejected. TEST_F(ClientContextConfigImplTest, RSA1024Cert) { envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index 0e8b4edcfeef1..3c9d2bbfb1e7d 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -443,7 +443,7 @@ class HeaderIntegrationTest void compareHeaders(Headers&& headers, const ExpectedHeaders& expected_headers) { headers.remove(Envoy::Http::LowerCaseString{"content-length"}); headers.remove(Envoy::Http::LowerCaseString{"date"}); -#if defined(ALIMESH) +#if defined(HIGRESS) headers.remove(Envoy::Http::LowerCaseString{"req-start-time"}); headers.remove(Envoy::Http::LowerCaseString{"req-cost-time"}); headers.remove(Envoy::Http::LowerCaseString{"x-envoy-original-host"}); diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index 8914eb02f44bb..35bc505870bfc 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -369,7 +369,7 @@ TEST_P(Http2FloodMitigationTest, Data) { // 9-byte frame header; 10 bytes per data frame, 10000 bytes total. The output buffer should also // contain response headers, which should be less than 100 bytes. EXPECT_LE(10000, buffer_factory->maxBufferSize()); -#if defined(ALIMESH) +#if defined(HIGRESS) EXPECT_GE(20000, buffer_factory->maxBufferSize()); #else EXPECT_GE(10100, buffer_factory->maxBufferSize()); diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index a189e1d272963..fe2640ae1b0bd 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -674,7 +674,7 @@ void HttpIntegrationTest::testRouterUpstreamProtocolError(const std::string& exp FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); std::string data; -#if defined(ALIMESH) +#if defined(HIGRESS) // We added some custom TRI headers, so the request data changed. ASSERT_TRUE(fake_upstream_connection->waitForData(247, &data)); #else @@ -1419,7 +1419,7 @@ void HttpIntegrationTest::testManyRequestHeaders(std::chrono::milliseconds time) {Http::Headers::get().Path, "/test/long/url"}, {Http::Headers::get().Scheme, "http"}, {Http::Headers::get().Host, "sni.lyft.com"}}); -#if defined(ALIMESH) +#if defined(HIGRESS) for (int i = 0; i < 9000; i++) { big_headers->addCopy(Http::LowerCaseString(std::to_string(i)), std::string(0, 'a')); } diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 618ce7454eae4..5e2a5fb43899a 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -907,7 +907,7 @@ TEST_P(ProtocolIntegrationTest, Retry) { const size_t http2_header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 24 : 27; expectUpstreamBytesSentAndReceived( -#ifdef ALIMESH +#ifdef HIGRESS BytesCountExpectation(2686 + quic_https_extra_bytes, 635, 550 + quic_https_extra_bytes, 54), BytesCountExpectation(2262, 548, 242, http2_header_bytes_received), BytesCountExpectation(2204, 520, 202, 6)); @@ -3723,7 +3723,7 @@ TEST_P(ProtocolIntegrationTest, HeaderOnlyBytesCountUpstream) { const size_t wire_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; expectUpstreamBytesSentAndReceived( -#ifdef ALIMESH +#ifdef HIGRESS BytesCountExpectation(227, 38, 196, 18), BytesCountExpectation(164, wire_bytes_received, 164, wire_bytes_received), BytesCountExpectation(160, 5, 160, 3)); @@ -3741,7 +3741,7 @@ TEST_P(ProtocolIntegrationTest, HeaderOnlyBytesCountDownstream) { useAccessLog("%DOWNSTREAM_WIRE_BYTES_SENT% %DOWNSTREAM_WIRE_BYTES_RECEIVED% " "%DOWNSTREAM_HEADER_BYTES_SENT% %DOWNSTREAM_HEADER_BYTES_RECEIVED%"); testRouterRequestAndResponseWithBody(0, 0, false); -#ifdef ALIMESH +#ifdef HIGRESS expectDownstreamBytesSentAndReceived(BytesCountExpectation(207, 51, 188, 19), BytesCountExpectation(138, 34, 138, 34), BytesCountExpectation(11, 10, 11, 6)); @@ -3762,7 +3762,7 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountUpstream) { testRouterRequestAndResponseWithBody(100, 100, false); const size_t header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; -#ifdef ALIMESH +#ifdef HIGRESS expectUpstreamBytesSentAndReceived(BytesCountExpectation(366, 158, 224, 27), BytesCountExpectation(274, 122, 165, header_bytes_received), BytesCountExpectation(263, 109, 160, 3)); @@ -3781,7 +3781,7 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountDownstream) { useAccessLog("%DOWNSTREAM_WIRE_BYTES_SENT% %DOWNSTREAM_WIRE_BYTES_RECEIVED% " "%DOWNSTREAM_HEADER_BYTES_SENT% %DOWNSTREAM_HEADER_BYTES_RECEIVED%"); testRouterRequestAndResponseWithBody(100, 100, false); -#ifdef ALIMESH +#ifdef HIGRESS expectDownstreamBytesSentAndReceived(BytesCountExpectation(327, 190, 197, 46), BytesCountExpectation(240, 173, 131, 34), BytesCountExpectation(111, 113, 11, 6)); @@ -3810,7 +3810,7 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseDownstream) { auto response_one = sendRequestAndWaitForResponse(default_request_headers_, request_size, default_response_headers_, response_size, 0); checkSimpleRequestSuccess(request_size, response_size, response_one.get()); -#ifdef ALIMESH +#ifdef HIGRESS expectDownstreamBytesSentAndReceived(BytesCountExpectation(327, 190, 197, 46), BytesCountExpectation(236, 137, 127, 34), BytesCountExpectation(111, 137, 11, 6), 0); @@ -3824,7 +3824,7 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseDownstream) { auto response_two = sendRequestAndWaitForResponse(default_request_headers_, request_size, default_response_headers_, response_size, 0); checkSimpleRequestSuccess(request_size, response_size, response_two.get()); -#ifdef ALIMESH +#ifdef HIGRESS expectDownstreamBytesSentAndReceived(BytesCountExpectation(326, 190, 196, 46), BytesCountExpectation(178, 137, 46, 27), BytesCountExpectation(111, 137, 11, 6), 1); @@ -3856,7 +3856,7 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseUpstream) { const size_t http2_header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; expectUpstreamBytesSentAndReceived( -#ifdef ALIMESH +#ifdef HIGRESS BytesCountExpectation(366, 158, 224, 27), BytesCountExpectation(273, 122, 164, http2_header_bytes_received), BytesCountExpectation(263, 108, 160, 3), 0); @@ -3871,7 +3871,7 @@ TEST_P(ProtocolIntegrationTest, HeaderAndBodyWireBytesCountReuseUpstream) { auto response_two = sendRequestAndWaitForResponse(default_request_headers_, request_size, default_response_headers_, response_size, 0); -#ifdef ALIMESH +#ifdef HIGRESS const size_t http2_header_bytes_sent = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 69 : 72; expectUpstreamBytesSentAndReceived(BytesCountExpectation(366, 158, 224, 27), @@ -3944,7 +3944,7 @@ TEST_P(ProtocolIntegrationTest, TrailersWireBytesCountUpstream) { const size_t http2_trailer_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 49 : 52; expectUpstreamBytesSentAndReceived( -#ifdef ALIMESH +#ifdef HIGRESS BytesCountExpectation(316, 120, 264, 67), BytesCountExpectation(221, 81, 202, http2_trailer_bytes_received), BytesCountExpectation(178, 33, 166, 7)); @@ -3966,7 +3966,7 @@ TEST_P(ProtocolIntegrationTest, TrailersWireBytesCountDownstream) { config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); testTrailers(10, 20, true, true); -#ifdef ALIMESH +#ifdef HIGRESS expectDownstreamBytesSentAndReceived(BytesCountExpectation(289, 140, 239, 84), BytesCountExpectation(195, 86, 166, 67), BytesCountExpectation(36, 26, 17, 10)); @@ -3987,7 +3987,7 @@ TEST_P(ProtocolIntegrationTest, DownstreamDisconnectBeforeRequestCompleteWireByt testRouterDownstreamDisconnectBeforeRequestComplete(nullptr); -#ifdef ALIMESH +#ifdef HIGRESS expectUpstreamBytesSentAndReceived(BytesCountExpectation(255, 0, 224, 0), BytesCountExpectation(164, 0, 164, 0), BytesCountExpectation(160, 0, 160, 0)); @@ -4023,7 +4023,7 @@ TEST_P(ProtocolIntegrationTest, UpstreamDisconnectBeforeRequestCompleteWireBytes testRouterUpstreamDisconnectBeforeRequestComplete(); -#ifdef ALIMESH +#ifdef HIGRESS expectUpstreamBytesSentAndReceived(BytesCountExpectation(255, 0, 224, 0), BytesCountExpectation(164, 0, 164, 0), BytesCountExpectation(160, 0, 160, 0)); @@ -4047,7 +4047,7 @@ TEST_P(ProtocolIntegrationTest, UpstreamDisconnectBeforeResponseCompleteWireByte const size_t http2_header_bytes_received = (GetParam().http2_implementation == Http2Impl::Oghttp2) ? 10 : 13; expectUpstreamBytesSentAndReceived( -#ifdef ALIMESH +#ifdef HIGRESS BytesCountExpectation(227, 47, 196, 27), BytesCountExpectation(163, http2_header_bytes_received, 163, http2_header_bytes_received), BytesCountExpectation(160, 5, 160, 3)); diff --git a/test/integration/redirect_integration_test.cc b/test/integration/redirect_integration_test.cc index a06122893a1a0..81ac19c5788c9 100644 --- a/test/integration/redirect_integration_test.cc +++ b/test/integration/redirect_integration_test.cc @@ -225,7 +225,7 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirectDownstreamBytesCount) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); -#ifdef ALIMESH +#ifdef HIGRESS expectDownstreamBytesSentAndReceived(BytesCountExpectation(223, 63, 204, 31), BytesCountExpectation(136, 42, 136, 42), #else @@ -262,7 +262,7 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirectUpstreamBytesCount) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); BytesCountExpectation http2_expected = (GetParam().http2_implementation == Http2Impl::Oghttp2) -#ifdef ALIMESH +#ifdef HIGRESS ? BytesCountExpectation(189, 59, 189, 59) : BytesCountExpectation(189, 64, 189, 64); #else @@ -270,7 +270,7 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirectUpstreamBytesCount) { : BytesCountExpectation(137, 64, 137, 64); #endif -#ifdef ALIMESH +#ifdef HIGRESS expectUpstreamBytesSentAndReceived(BytesCountExpectation(267, 110, 236, 85), http2_expected, BytesCountExpectation(137, 64, 137, 64), 0); expectUpstreamBytesSentAndReceived(BytesCountExpectation(302, 38, 277, 18), diff --git a/test/mocks/http/mocks.cc b/test/mocks/http/mocks.cc index f8a44e1a30703..3c4337d590375 100644 --- a/test/mocks/http/mocks.cc +++ b/test/mocks/http/mocks.cc @@ -73,7 +73,7 @@ template static void initializeMockStreamFilterCallbacks(T& callbacks) MockStreamDecoderFilterCallbacks::MockStreamDecoderFilterCallbacks() { initializeMockStreamFilterCallbacks(*this); ON_CALL(*this, decodingBuffer()).WillByDefault(Invoke(&buffer_, &Buffer::InstancePtr::get)); -#if defined(ALIMESH) +#if defined(HIGRESS) ON_CALL(*this, modifyDecodingBuffer(_, _)) .WillByDefault(Invoke( [this](std::function callback, bool backup_for_replace) -> void { diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 8305126db55cf..b52be457f564a 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -260,7 +260,7 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, MOCK_METHOD(void, setDecoderBufferLimit, (uint32_t)); MOCK_METHOD(uint32_t, decoderBufferLimit, ()); MOCK_METHOD(bool, recreateStream, (const ResponseHeaderMap* headers)); -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(bool, recreateStream, (const ResponseHeaderMap* headers, bool use_original_request_body)); #endif @@ -307,7 +307,7 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, MOCK_METHOD(RequestTrailerMap&, addDecodedTrailers, ()); MOCK_METHOD(MetadataMapVector&, addDecodedMetadata, ()); MOCK_METHOD(const Buffer::Instance*, decodingBuffer, ()); -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(void, modifyDecodingBuffer, (std::function, bool)); #endif MOCK_METHOD(void, modifyDecodingBuffer, (std::function)); @@ -678,7 +678,7 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(ServerHeaderValidatorPtr, makeHeaderValidator, (Protocol protocol)); MOCK_METHOD(bool, appendXForwardedPort, (), (const)); MOCK_METHOD(bool, addProxyProtocolConnectionState, (), (const)); -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(std::chrono::seconds, keepaliveHeaderTimeout, (), (const)); #endif @@ -943,7 +943,7 @@ MATCHER_P(HeaderMapEqualWithMaxSize, rhs, "") { } MATCHER_P(HeaderMapEqualRef, rhs, "") { -#if defined(ALIMESH) +#if defined(HIGRESS) bool equal = true; auto getHeaderItems = [](const Envoy::Http::HeaderMap& header, diff --git a/test/mocks/router/mocks.cc b/test/mocks/router/mocks.cc index 9f725882b5923..72a6f6cb13814 100644 --- a/test/mocks/router/mocks.cc +++ b/test/mocks/router/mocks.cc @@ -28,7 +28,7 @@ MockInternalRedirectPolicy::MockInternalRedirectPolicy() { ON_CALL(*this, enabled()).WillByDefault(Return(false)); } -#if defined(ALIMESH) +#if defined(HIGRESS) MockInternalActiveRedirectPolicy::MockInternalActiveRedirectPolicy() { ON_CALL(*this, enabled()).WillByDefault(Return(false)); } @@ -120,7 +120,7 @@ MockRouteEntry::MockRouteEntry() { ON_CALL(*this, connectConfig()).WillByDefault(Invoke([this]() { return connect_config_.has_value() ? makeOptRef(connect_config_.value()) : absl::nullopt; })); -#if defined(ALIMESH) +#if defined(HIGRESS) ON_CALL(*this, internalActiveRedirectPolicy()) .WillByDefault(ReturnRef(internal_active_redirect_policy_)); #endif @@ -173,7 +173,7 @@ MockRouteConfigProviderManager::~MockRouteConfigProviderManager() = default; MockScopedConfig::MockScopedConfig() { ON_CALL(*this, getRouteConfig(_)).WillByDefault(Return(route_config_)); -#if defined(ALIMESH) +#if defined(HIGRESS) ON_CALL(*this, getRouteConfig(_, _, _)).WillByDefault(Return(route_config_)); #endif } diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index 6670883159010..84807f146e45e 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -162,7 +162,7 @@ class MockInternalRedirectPolicy : public InternalRedirectPolicy { MOCK_METHOD(bool, isCrossSchemeRedirectAllowed, (), (const)); }; -#if defined(ALIMESH) +#if defined(HIGRESS) class MockInternalActiveRedirectPolicy : public InternalActiveRedirectPolicy { public: MockInternalActiveRedirectPolicy(); @@ -456,7 +456,7 @@ class MockRouteEntry : public RouteEntry { MOCK_METHOD(const EarlyDataPolicy&, earlyDataPolicy, (), (const)); MOCK_METHOD(const RouteStatsContextOptRef, routeStatsContext, (), (const)); -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(const InternalActiveRedirectPolicy&, internalActiveRedirectPolicy, (), (const)); #endif @@ -480,7 +480,7 @@ class MockRouteEntry : public RouteEntry { UpgradeMap upgrade_map_; absl::optional connect_config_; -#if defined(ALIMESH) +#if defined(HIGRESS) testing::NiceMock internal_active_redirect_policy_; #endif testing::NiceMock early_data_policy_; @@ -615,7 +615,7 @@ class MockScopedConfig : public ScopedConfig { MOCK_METHOD(ConfigConstSharedPtr, getRouteConfig, (const ScopeKeyPtr& scope_key), (const)); -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(ConfigConstSharedPtr, getRouteConfig, (const ScopeKeyBuilder*, const Http::HeaderMap&, const StreamInfo::StreamInfo*), (const)); @@ -644,7 +644,7 @@ class MockScopeKeyBuilder : public ScopeKeyBuilder { MockScopeKeyBuilder(); ~MockScopeKeyBuilder() override; -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(ScopeKeyPtr, computeScopeKey, (const Http::HeaderMap&, const StreamInfo::StreamInfo*, std::function& recompute), diff --git a/test/mocks/stream_info/mocks.h b/test/mocks/stream_info/mocks.h index 4b43c667376f2..7332afa7b7287 100644 --- a/test/mocks/stream_info/mocks.h +++ b/test/mocks/stream_info/mocks.h @@ -142,7 +142,7 @@ class MockStreamInfo : public StreamInfo { MOCK_METHOD(bool, isShadow, (), (const, override)); MOCK_METHOD(void, setDownstreamTransportFailureReason, (absl::string_view failure_reason)); MOCK_METHOD(absl::string_view, downstreamTransportFailureReason, (), (const)); -#ifdef ALIMESH +#ifdef HIGRESS MOCK_METHOD(void, setCustomSpanTag, (absl::string_view, absl::string_view)); MOCK_METHOD((const absl::flat_hash_map&), getCustomSpanTagMap, (), (const)); #endif diff --git a/test/mocks/upstream/BUILD b/test/mocks/upstream/BUILD index 97b9706f0d342..e7fd96cd44441 100644 --- a/test/mocks/upstream/BUILD +++ b/test/mocks/upstream/BUILD @@ -233,7 +233,7 @@ envoy_cc_mock( "//test/mocks/upstream:cluster_priority_set_mocks", "//test/mocks/upstream:load_balancer_mocks", ], - alimesh_deps = [ + higress_deps = [ "//test/mocks/redis:redis_mocks", ], ) diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index 76817f1ec3699..25989561f6e9b 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -36,7 +36,7 @@ class MockDetectorHostMonitor : public DetectorHostMonitor { MOCK_METHOD(void, successRate, (DetectorHostMonitor::SuccessRateMonitorType type, double new_success_rate)); -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(void, forceEjectHost, ()); #endif }; diff --git a/test/mocks/upstream/thread_local_cluster.cc b/test/mocks/upstream/thread_local_cluster.cc index 40e3a60c0c36f..1ac8c6695195b 100644 --- a/test/mocks/upstream/thread_local_cluster.cc +++ b/test/mocks/upstream/thread_local_cluster.cc @@ -18,7 +18,7 @@ MockThreadLocalCluster::MockThreadLocalCluster() { ON_CALL(*this, tcpConnPool(_, _)) .WillByDefault(Return(Upstream::TcpPoolData([]() {}, &tcp_conn_pool_))); ON_CALL(*this, httpAsyncClient()).WillByDefault(ReturnRef(async_client_)); -#if defined(ALIMESH) +#if defined(HIGRESS) ON_CALL(*this, redisAsyncClient()).WillByDefault(ReturnRef(redis_async_client_)); #endif } diff --git a/test/mocks/upstream/thread_local_cluster.h b/test/mocks/upstream/thread_local_cluster.h index d70b125e99ff9..2622f77274468 100644 --- a/test/mocks/upstream/thread_local_cluster.h +++ b/test/mocks/upstream/thread_local_cluster.h @@ -38,7 +38,7 @@ class MockThreadLocalCluster : public ThreadLocalCluster { (ResourcePriority priority, LoadBalancerContext* context)); MOCK_METHOD(MockHost::MockCreateConnectionData, tcpConn_, (LoadBalancerContext * context)); MOCK_METHOD(Http::AsyncClient&, httpAsyncClient, ()); -#if defined(ALIMESH) +#if defined(HIGRESS) MOCK_METHOD(Redis::AsyncClient&, redisAsyncClient, ()); #endif MOCK_METHOD(Tcp::AsyncTcpClientPtr, tcpAsyncClient, @@ -48,7 +48,7 @@ class MockThreadLocalCluster : public ThreadLocalCluster { NiceMock lb_; NiceMock conn_pool_; NiceMock async_client_; -#if defined(ALIMESH) +#if defined(HIGRESS) NiceMock redis_async_client_; #endif NiceMock tcp_conn_pool_; diff --git a/test/server/admin/admin_test.cc b/test/server/admin/admin_test.cc index 6d791c34c06cc..81e6315f709e5 100644 --- a/test/server/admin/admin_test.cc +++ b/test/server/admin/admin_test.cc @@ -130,7 +130,7 @@ TEST_P(AdminInstanceTest, Help) { Http::TestResponseHeaderMapImpl header_map; Buffer::OwnedImpl response; EXPECT_EQ(Http::Code::OK, getCallback("/help", header_map, response)); -#if defined(ALIMESH) +#if defined(HIGRESS) const std::string expected = R"EOF(admin commands are: /: Admin home page /certs: print certs on machine diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index 1d2f134194845..fc825fa455c01 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -53,7 +53,7 @@ bool TestUtility::headerMapEqualIgnoreOrder(const Http::HeaderMap& lhs, absl::flat_hash_set rhs_keys; lhs.iterate([&lhs_keys](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { const std::string key{header.key().getStringView()}; -#if defined(ALIMESH) +#if defined(HIGRESS) if (key == Http::CustomHeaders::get().AliExtendedValues.TriStartTime.get()) { return Http::HeaderMap::Iterate::Continue; } diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 05565c350218d..1cf6d2d1b9482 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -147,7 +147,7 @@ template class WasmHttpFilterTestBase : public W context_->setEncoderFilterCallbacks(encoder_callbacks_); } -#if defined(ALIMESH) +#if defined(HIGRESS) template void doRecover() { std::shared_ptr new_handle; if (WasmTestBase::plugin_handle_->doRecover(new_handle)) { From 0e879db6ea4ece750b3e7394bcf6f4a630e63401 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 5 Aug 2024 15:04:37 +0800 Subject: [PATCH 222/274] add missing BUILD file --- .../filters/http/http_dubbo_transcoder/v3/BUILD | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/BUILD diff --git a/api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/BUILD b/api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) From 60ea17eb376a3c9fd71af9257ea17bd1ee4202a1 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Mon, 26 Aug 2024 07:50:24 +0000 Subject: [PATCH 223/274] feature: llm_inference_filter --- api/BUILD | 1 + .../filters/http/llm_inference/v3/BUILD | 7 + .../http/llm_inference/v3/llm_inference.proto | 21 + contrib/contrib_build_config.bzl | 1 + contrib/extensions_metadata.yaml | 5 + .../llm_inference/filters/http/source/BUILD | 37 + .../filters/http/source/config.cc | 82 + .../filters/http/source/config.h | 39 + .../filters/http/source/inference/BUILD | 27 + .../source/inference/inference_context.cc | 1495 +++++++++++++++++ .../http/source/inference/inference_context.h | 85 + .../http/source/inference/inference_task.cc | 15 + .../http/source/inference/inference_task.h | 52 + .../http/source/inference/inference_thread.cc | 79 + .../http/source/inference/inference_thread.h | 79 + .../filters/http/source/inference/utils.hpp | 516 ++++++ .../http/source/llm_inference_filter.cc | 160 ++ .../http/source/llm_inference_filter.h | 86 + 18 files changed, 2787 insertions(+) create mode 100644 api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD create mode 100644 api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto create mode 100644 contrib/llm_inference/filters/http/source/BUILD create mode 100644 contrib/llm_inference/filters/http/source/config.cc create mode 100644 contrib/llm_inference/filters/http/source/config.h create mode 100644 contrib/llm_inference/filters/http/source/inference/BUILD create mode 100644 contrib/llm_inference/filters/http/source/inference/inference_context.cc create mode 100644 contrib/llm_inference/filters/http/source/inference/inference_context.h create mode 100644 contrib/llm_inference/filters/http/source/inference/inference_task.cc create mode 100644 contrib/llm_inference/filters/http/source/inference/inference_task.h create mode 100644 contrib/llm_inference/filters/http/source/inference/inference_thread.cc create mode 100644 contrib/llm_inference/filters/http/source/inference/inference_thread.h create mode 100644 contrib/llm_inference/filters/http/source/inference/utils.hpp create mode 100644 contrib/llm_inference/filters/http/source/llm_inference_filter.cc create mode 100644 contrib/llm_inference/filters/http/source/llm_inference_filter.h diff --git a/api/BUILD b/api/BUILD index 72caa644e1896..16b2487284916 100644 --- a/api/BUILD +++ b/api/BUILD @@ -78,6 +78,7 @@ proto_library( "//contrib/envoy/extensions/filters/http/language/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/squash/v3:pkg", "//contrib/envoy/extensions/filters/http/sxg/v3alpha:pkg", + "//contrib/envoy/extensions/filters/http/llm_inference/v3:pkg", "//contrib/envoy/extensions/filters/network/client_ssl_auth/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/action/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/codecs/dubbo/v3:pkg", diff --git a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD new file mode 100644 index 0000000000000..e8aa6b3a4bfd5 --- /dev/null +++ b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD @@ -0,0 +1,7 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto new file mode 100644 index 0000000000000..c4d3f85d7b534 --- /dev/null +++ b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.llm_inference.v3; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +message modelParameter { + int32 n_threads = 1; + int32 n_parallel = 2; + map modelpath = 3; +} + +message modelChosen { + string usemodel = 1; + int32 first_byte_timeout = 2; + int32 inference_timeout = 3; + bool embedding = 4; +} diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 4667b31e3b165..77219192e66c1 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -10,6 +10,7 @@ CONTRIB_EXTENSIONS = { "envoy.filters.http.language": "//contrib/language/filters/http/source:config_lib", "envoy.filters.http.squash": "//contrib/squash/filters/http/source:config", "envoy.filters.http.sxg": "//contrib/sxg/filters/http/source:config", + "envoy.filters.http.llm_inference": "//contrib/llm_inference/filters/http/source:config", # # Upstreams diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index 2314202b50f37..2628a9d8931d5 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -28,6 +28,11 @@ envoy.filters.http.sxg: - envoy.filters.http security_posture: robust_to_untrusted_downstream status: alpha +envoy.filters.http.llm_inference: + categories: + - envoy.filters.http + security_posture: requires_trusted_downstream_and_upstream + status: wip envoy.filters.network.client_ssl_auth: categories: - envoy.filters.network diff --git a/contrib/llm_inference/filters/http/source/BUILD b/contrib/llm_inference/filters/http/source/BUILD new file mode 100644 index 0000000000000..2e372b0eb9e5a --- /dev/null +++ b/contrib/llm_inference/filters/http/source/BUILD @@ -0,0 +1,37 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "llm_inference_filter_lib", + srcs = ["llm_inference_filter.cc"], + hdrs = ["llm_inference_filter.h"], + deps = [ + "@envoy_api//contrib/envoy/extensions/filters/http/llm_inference/v3:pkg_cc_proto", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "//contrib/llm_inference/filters/http/source/inference:inference", + "//source/common/http:header_map_lib", + "//source/common/http:header_utility_lib", + "//source/common/http:headers_lib", + "//source/common/protobuf:utility_lib", + ], +) + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":llm_inference_filter_lib", + "//envoy/registry", + "//source/extensions/filters/http/common:factory_base_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/llm_inference/v3:pkg_cc_proto", + ], +) diff --git a/contrib/llm_inference/filters/http/source/config.cc b/contrib/llm_inference/filters/http/source/config.cc new file mode 100644 index 0000000000000..d67c94a298331 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/config.cc @@ -0,0 +1,82 @@ +#include "contrib/llm_inference/filters/http/source/config.h" + +#include "contrib/llm_inference/filters/http/source/llm_inference_filter.h" +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +class InferenceSingleton : public Envoy::Singleton::Instance { +public: + InferenceSingleton(Thread::ThreadFactory& thread_factory) + : inference_thread_(thread_factory) {} + + std::shared_ptr load(std::shared_ptr singleton, const ModelParameter& model_parameter, + const ModelChosen& model_chosen, const std::string& model_path) { + std::shared_ptr ctx; + absl::MutexLock lock(&mu_); + auto it = ctx_.find(model_chosen.model_name); + if (it != ctx_.end()) { + ctx = it->second.lock(); + } + if (!ctx) { + ctx = std::make_shared(singleton, inference_thread_, model_parameter, model_path, model_chosen); + ctx_[model_chosen.model_name] = ctx; + } + return ctx; + } + +private: + InferenceThread inference_thread_; + absl::Mutex mu_; + absl::flat_hash_map> ctx_ ABSL_GUARDED_BY(mu_); +}; + +SINGLETON_MANAGER_REGISTRATION(http_inference_singleton); + +Http::FilterFactoryCb LLMInferenceFilterConfigFactory::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::llm_inference::v3::modelParameter& proto_config, + const std::string&, Server::Configuration::FactoryContext& context) { + + LLMInferenceFilterConfigSharedPtr config = + std::make_shared(LLMInferenceFilterConfig(proto_config)); + + std::shared_ptr inference = + context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(http_inference_singleton), [&context] { + return std::make_shared(context.api().threadFactory()); + }); + + InferenceContextSharedPtr ctx; + auto modelpath = config->modelPath(); + if (modelpath.contains(model_Chosen_.model_name)) { + ctx = inference->load(inference, config->modelParameter(), model_Chosen_, modelpath[model_Chosen_.model_name]); + } + + return [config, ctx](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(std::make_shared(config, ctx)); + }; +} + + +Router::RouteSpecificFilterConfigConstSharedPtr LLMInferenceFilterConfigFactory::createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::llm_inference::v3::modelChosen& proto_config, + Server::Configuration::ServerFactoryContext&, ProtobufMessage::ValidationVisitor&) { + LLMInferenceFilterConfigPerRouteSharedPtr config = + std::make_shared(LLMInferenceFilterConfigPerRoute(proto_config)); + + model_Chosen_ = config->modelChosen(); + return config; +} + +/** + * Static registration for this llm inference filter. @see RegisterFactory. + */ +REGISTER_FACTORY(LLMInferenceFilterConfigFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/contrib/llm_inference/filters/http/source/config.h b/contrib/llm_inference/filters/http/source/config.h new file mode 100644 index 0000000000000..7a07f506f2db3 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/config.h @@ -0,0 +1,39 @@ +#pragma once + +#include "contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.pb.h" +#include "contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.pb.validate.h" +#include "contrib/llm_inference/filters/http/source/inference/inference_task.h" + +#include "source/extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +/** + * Config registration for the inference filter. @see NamedHttpFilterConfigFactory. + */ +class LLMInferenceFilterConfigFactory + : public Common::FactoryBase { +public: + LLMInferenceFilterConfigFactory() : FactoryBase("envoy.filters.http.llm_inference") {} + +private: + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::llm_inference::v3::modelParameter& proto_config, + const std::string&, + Server::Configuration::FactoryContext&) override; + + Router::RouteSpecificFilterConfigConstSharedPtr createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::llm_inference::v3::modelChosen& proto_config, + Server::Configuration::ServerFactoryContext&, ProtobufMessage::ValidationVisitor&) override; + + ModelChosen model_Chosen_; +}; + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/llm_inference/filters/http/source/inference/BUILD b/contrib/llm_inference/filters/http/source/inference/BUILD new file mode 100644 index 0000000000000..3e9d793da4afe --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/BUILD @@ -0,0 +1,27 @@ +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", +) + +licenses(["notice"]) # Apache 2 + +envoy_cc_library( + name = "inference", + srcs = [ + "inference_context.cc", + "inference_task.cc", + "inference_thread.cc", + ], + hdrs = [ + "inference_context.h", + "inference_task.h", + "inference_thread.h", + "utils.hpp", + ], + deps = [ + "//source/extensions/filters/http/common:factory_base_lib", + "@com_google_absl//absl/base", + ], + visibility = ["//visibility:public"], + external_deps = ["llama"], +) \ No newline at end of file diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.cc b/contrib/llm_inference/filters/http/source/inference/inference_context.cc new file mode 100644 index 0000000000000..e1e9e78dc16c0 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.cc @@ -0,0 +1,1495 @@ +#include "common/sampling.cpp" +#include "common/common.cpp" +#include "common/json-schema-to-grammar.cpp" +#include "common/grammar-parser.cpp" +#include "utils.hpp" +#include "contrib/llm_inference/filters/http/source/inference/inference_context.h" +#include +#include +#include + +char const *LLAMA_COMMIT = ""; +char const *LLAMA_COMPILER = ""; +char const *LLAMA_BUILD_TARGET = ""; +int LLAMA_BUILD_NUMBER = 1; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +gpt_params params; + +struct server_slot { + int id; + int id_task = -1; + + struct slot_params params; + + slot_state state = SLOT_STATE_IDLE; + slot_command command = SLOT_COMMAND_NONE; + + // used to determine the slot that has been used the longest + int64_t t_last_used = -1; + + // generation props + int32_t n_ctx = 0; // context size per slot + int32_t n_past = 0; + int32_t n_decoded = 0; + int32_t n_remaining = -1; + int32_t i_batch = -1; + int32_t n_predict = -1; // TODO: disambiguate from params.n_predict + + int32_t n_prompt_tokens = 0; + int32_t n_prompt_tokens_processed = 0; + + json prompt; + + // when a task is submitted, we first tokenize the prompt and store it here + std::vector prompt_tokens; + + std::string generated_text; + std::vector cache_tokens; + std::vector generated_token_probs; + + bool infill = false; + bool embedding = false; + bool has_next_token = true; + bool truncated = false; + bool stopped_eos = false; + bool stopped_word = false; + bool stopped_limit = false; + + bool oaicompat = false; + + std::string oaicompat_model; + std::string stopping_word; + + // sampling + llama_token sampled; + struct llama_sampling_params sparams; + llama_sampling_context * ctx_sampling = nullptr; + json json_schema; + + int32_t ga_i = 0; // group-attention state + int32_t ga_n = 1; // group-attention factor + int32_t ga_w = 512; // group-attention width + + int32_t n_past_se = 0; // self-extend + + // stats + size_t n_sent_text = 0; // number of sent text character + size_t n_sent_token_probs = 0; + + int64_t t_start_process_prompt; + int64_t t_start_generation; + + double t_prompt_processing; // ms + double t_token_generation; // ms + + void reset() { + n_prompt_tokens = 0; + generated_text = ""; + truncated = false; + stopped_eos = false; + stopped_word = false; + stopped_limit = false; + stopping_word = ""; + n_past = 0; + n_sent_text = 0; + n_sent_token_probs = 0; + infill = false; + ga_i = 0; + n_past_se = 0; + + generated_token_probs.clear(); + } + + bool has_budget(gpt_params &global_params) { + if (params.n_predict == -1 && global_params.n_predict == -1) { + return true; // limitless + } + + n_remaining = -1; + + if (params.n_predict != -1) { + n_remaining = params.n_predict - n_decoded; + } else if (global_params.n_predict != -1) { + n_remaining = global_params.n_predict - n_decoded; + } + + return n_remaining > 0; // no budget + } + + bool available() const { + return state == SLOT_STATE_IDLE && command == SLOT_COMMAND_NONE; + } + + bool is_processing() const { + return (state == SLOT_STATE_IDLE && command == SLOT_COMMAND_LOAD_PROMPT) || state == SLOT_STATE_PROCESSING; + } + + void add_token_string(const completion_token_output & token) { + if (command == SLOT_COMMAND_RELEASE) { + return; + } + generated_token_probs.push_back(token); + } + + void release() { + if (state == SLOT_STATE_PROCESSING) { + t_token_generation = (ggml_time_us() - t_start_generation) / 1e3; + command = SLOT_COMMAND_RELEASE; + } + } + + json get_formated_timings() const { + return json { + {"prompt_n", n_prompt_tokens_processed}, + {"prompt_ms", t_prompt_processing}, + {"prompt_per_token_ms", t_prompt_processing / n_prompt_tokens_processed}, + {"prompt_per_second", 1e3 / t_prompt_processing * n_prompt_tokens_processed}, + + {"predicted_n", n_decoded}, + {"predicted_ms", t_token_generation}, + {"predicted_per_token_ms", t_token_generation / n_decoded}, + {"predicted_per_second", 1e3 / t_token_generation * n_decoded}, + }; + } + + size_t find_stopping_strings(const std::string & text, const size_t last_token_size, const stop_type type) { + size_t stop_pos = std::string::npos; + + for (const std::string & word : params.antiprompt) { + size_t pos; + + if (type == STOP_TYPE_FULL) { + const size_t tmp = word.size() + last_token_size; + const size_t from_pos = text.size() > tmp ? text.size() - tmp : 0; + + pos = text.find(word, from_pos); + } else { + pos = find_partial_stop_string(word, text); + } + + if (pos != std::string::npos && (stop_pos == std::string::npos || pos < stop_pos)) { + if (type == STOP_TYPE_FULL) { + stopped_word = true; + stopping_word = word; + has_next_token = false; + } + stop_pos = pos; + } + } + + return stop_pos; + } + + void print_timings() const { + char buffer[512]; + + double t_token = t_prompt_processing / n_prompt_tokens_processed; + double n_tokens_second = 1e3 / t_prompt_processing * n_prompt_tokens_processed; + + snprintf(buffer, 512, "prompt eval time = %10.2f ms / %5d tokens (%8.2f ms per token, %8.2f tokens per second)", + t_prompt_processing, n_prompt_tokens_processed, + t_token, n_tokens_second); + + LOG_INFO(buffer, { + {"id_slot", id}, + {"id_task", id_task}, + {"t_prompt_processing", t_prompt_processing}, + {"n_prompt_tokens_processed", n_prompt_tokens_processed}, + {"t_token", t_token}, + {"n_tokens_second", n_tokens_second}, + }); + + t_token = t_token_generation / n_decoded; + n_tokens_second = 1e3 / t_token_generation * n_decoded; + + snprintf(buffer, 512, "generation eval time = %10.2f ms / %5d runs (%8.2f ms per token, %8.2f tokens per second)", + t_token_generation, n_decoded, + t_token, n_tokens_second); + + LOG_INFO(buffer, { + {"id_slot", id}, + {"id_task", id_task}, + {"t_token_generation", t_token_generation}, + {"n_decoded", n_decoded}, + {"t_token", t_token}, + {"n_tokens_second", n_tokens_second}, + }); + + snprintf(buffer, 512, " total time = %10.2f ms", t_prompt_processing + t_token_generation); + + LOG_INFO(buffer, { + {"id_slot", id}, + {"id_task", id_task}, + {"t_prompt_processing", t_prompt_processing}, + {"t_token_generation", t_token_generation}, + {"t_total", t_prompt_processing + t_token_generation}, + }); + } +}; + +struct server_task { + int id = -1; + int id_target = -1; + + server_task_type type; + json data; + + bool infill = false; + bool embedding = false; +}; + +/* ================================================================= */ +/* Constructors */ +/* ================================================================= */ + +InferenceContext::InferenceContext(Singleton::InstanceSharedPtr owner, InferenceThread& inference_thread, + const ModelParameter& model_parameter, const std::string& model_path, const ModelChosen& model_chosen):owner_(owner), + inference_thread_(inference_thread), model_name_(model_chosen.model_name) { + loadModel(model_parameter, model_path, model_chosen); +} + +/* ================================================================= */ +/* Destructors */ +/* ================================================================= */ + +InferenceContext::~InferenceContext() { + llama_kv_cache_clear(ctx); + if (ctx) { + llama_free(ctx); + ctx = nullptr; + } + + if (model) { + llama_free_model(model); + model = nullptr; + } + + // Clear any sampling context + for (server_slot & slot : slots) { + if (slot.ctx_sampling != nullptr) { + llama_sampling_free(slot.ctx_sampling); + } + } + llama_batch_free(batch); + llama_backend_free(); +} + +/* ================================================================= */ +/* get task id */ +/* ================================================================= */ + +int InferenceContext::getId() { + return inference_thread_.getId(); +} + +/* ================================================================= */ +/* load model */ +/* ================================================================= */ + +bool InferenceContext::loadModel(const ModelParameter& model_parameter, const std::string& model_path, const ModelChosen& model_chosen) { + params.n_threads = model_parameter.n_threads; + params.n_parallel = model_parameter.n_parallel; + params.embedding = model_chosen.embedding; + + params.model = model_path; + + gpt_params_handle_model_default(params); + + if (params.model_alias == "unknown") { + params.model_alias = params.model; + } + llama_backend_init(); + llama_numa_init(params.numa); + + // load the model + { + // dedicate one sequence to the system prompt + params.n_parallel += 1; + llama_init_result llama_init = llama_init_from_gpt_params(params); + model = llama_init.model; + ctx = llama_init.context; + params.n_parallel -= 1; // but be sneaky about it + if (model == nullptr) { + return false; + } + n_ctx = llama_n_ctx(ctx); + + add_bos_token = llama_add_bos_token(model); + has_eos_token = !llama_add_eos_token(model); + } + // init slot + { + const int32_t n_ctx_slot = n_ctx / params.n_parallel; + + LOG_INFO("initializing slots", {{"n_slots", params.n_parallel}}); + + for (int i = 0; i < params.n_parallel; i++) { + server_slot slot; + + slot.id = i; + slot.n_ctx = n_ctx_slot; + slot.n_predict = params.n_predict; + + LOG_INFO("new slot", { + {"id_slot", slot.id}, + {"n_ctx_slot", slot.n_ctx} + }); + + const int ga_n = params.grp_attn_n; + const int ga_w = params.grp_attn_w; + if (ga_n != 1) { + GGML_ASSERT(ga_n > 0 && "ga_n must be positive"); // NOLINT + GGML_ASSERT(ga_w % ga_n == 0 && "ga_w must be a multiple of ga_n"); // NOLINT + LOG_INFO("slot self-extend", { + {"id_slot", slot.id}, + {"ga_n", ga_n}, + {"ga_w", ga_w} + }); + } + slot.ga_i = 0; + slot.ga_n = ga_n; + slot.ga_w = ga_w; + + slot.reset(); + + slots.push_back(slot); + + // the update_slots() logic will always submit a maximum of n_batch tokens + // note that n_batch can be > n_ctx (e.g. for non-causal attention models such as BERT where the KV cache is not used) + { + const int32_t n_batch = llama_n_batch(ctx); + + // only a single seq_id per token is needed + batch = llama_batch_init(std::max(n_batch, params.n_parallel), 0, 1); + } + } + } + + LOG_INFO("model loaded", {}); + + // if a custom chat template is not supplied, we will use the one that comes with the model (if any) + { + llama_chat_message chat[] = {{"user", "test"}}; + + if (!(llama_chat_apply_template(model, nullptr, chat, 1, true, nullptr, 0) > 0)) { + chat_template_ = "chatml"; + } + } + return true; +} + +/* ================================================================= */ +/* Preparation for model inference, + After preparation, asynchronous thread will be called to handle inference tasks */ +/* ================================================================= */ + +void InferenceContext::modelInference(LookupBodyCallback&& cb, std::shared_ptr&& task_meta_data, int& inference_timeout) { + callback_body_[task_meta_data->id] = std::move(cb); + completion_id_ = gen_chatcmplid(); + inference_timeout_ = inference_timeout * 1e6; + server_task task; + task.id = task_meta_data->id; + task.id_target = task_meta_data->id_target; + task.infill = task_meta_data->infill; + try { + task.data = json::parse(task_meta_data->data); + } catch (const std::exception &) { + sendError(task.id, "request data is wrong", ERROR_TYPE_INVALID_REQUEST); + return; + } + + switch (task_meta_data->type) { + case InferencetasktypeTypeCompletion: + { + is_openai_ = true; + task.data = oaicompat_completion_params_parse(model, task.data, chat_template_); + task.type = SERVER_TASK_TYPE_COMPLETION; + task.embedding = false; + } break; + case InferencetasktypeTypeEmbeedings: + { + task.embedding = true; + is_openai_ = false; + // an input prompt can be a string or a list of tokens (integer) + json prompt; + if (task.data.count("input") != 0) { + is_openai_ = true; + prompt = task.data.at("input"); + } else if (task.data.count("content") != 0) { + // with "content", we only support single prompt + prompt = std::vector{task.data.at("content")}; + } else { + sendError(task.id, "input or content must be provided", ERROR_TYPE_INVALID_REQUEST); + return; + } + task.data = json{{"prompt", prompt}}; + task.type = SERVER_TASK_TYPE_COMPLETION; + } break; + case InferencetasktypeTypeCancel: + { + task.type = SERVER_TASK_TYPE_CANCEL; + break; + } + } + + inference_thread_.addTask([this, task](){ + this->processSingleTask(task); + }); +} + +/* ================================================================= */ +/* handle inference tasks, + and assign slot to the task */ +/* ================================================================= */ + +void InferenceContext::processSingleTask(const server_task & task) { + switch (task.type) { + case SERVER_TASK_TYPE_COMPLETION: + { + server_slot * slot = nullptr; + std::string prompt; + if (task.data.contains("prompt") && task.data.at("prompt").is_string()) { + prompt = json_value(task.data, "prompt", std::string()); + } + + slot = getAvailableSlot(prompt); + + if (slot == nullptr) { + // if no slot is available, we defer this task for processing later + inference_thread_.addTask([this, task](){ + this->processSingleTask(task); + }); + return; + } + + if (!slot->available()) { + // if this slot isn't available, we defer this task for processing later + inference_thread_.addTask([this, task](){ + this->processSingleTask(task); + }); + return; + } + + slot->reset(); + + slot->id_task = task.id; + slot->infill = task.infill; + slot->embedding = task.embedding; + if (!launchSlotWithTask(*slot, task)) { + return; + } + } break; + case SERVER_TASK_TYPE_CANCEL: + { + // release slot linked with the task id + for (auto & use_slot : slots) { + if (use_slot.id_task == task.id_target) { + use_slot.release(); + break; + } + } + } break; + case SERVER_TASK_TYPE_NEXT_RESPONSE: + { + // do nothing + } break; + } + updateSlots(); +} + +server_slot * InferenceContext::getAvailableSlot(const std::string & prompt) { + server_slot * ret = nullptr; + // find the slot that has at least n% prompt similarity + if (ret == nullptr && slot_prompt_similarity != 0.0f && !prompt.empty()) { + int max_lcp_len = 0; + float similarity = 0; + + for (server_slot & slot : slots) { + // skip the slot if it is not available + if (!slot.available()) { + continue; + } + + // skip the slot if it does not contains prompt + if (!slot.prompt.is_string()) { + continue; + } + + // current slot's prompt + std::string slot_prompt = slot.prompt.get(); + + // length of the current slot's prompt + int slot_prompt_len = slot_prompt.size(); + + // length of the Longest Common Prefix between the current slot's prompt and the input prompt + int lcp_len = common_part(slot_prompt, prompt); + + // fraction of the common substring length compared to the current slot's prompt length + similarity = static_cast(lcp_len) / slot_prompt_len; + + // select the current slot if the criteria match + if (lcp_len > max_lcp_len && similarity > slot_prompt_similarity) { + max_lcp_len = lcp_len; + ret = &slot; + } + } + } + + // find the slot that has been least recently used + if (ret == nullptr) { + int64_t t_last = ggml_time_us(); + for (server_slot & slot : slots) { + // skip the slot if it is not available + if (!slot.available()) { + continue; + } + + // select the current slot if the criteria match + if (slot.t_last_used < t_last) { + t_last = slot.t_last_used; + ret = &slot; + } + } + } + return ret; +} + +bool InferenceContext::launchSlotWithTask(server_slot & slot, const server_task & task) { + slot_params default_params; + llama_sampling_params default_sparams; + auto & data = task.data; + + if (data.count("__oaicompat") != 0) { + slot.oaicompat = true; + slot.oaicompat_model = json_value(data, "model", std::string(DEFAULT_OAICOMPAT_MODEL)); + } else { + slot.oaicompat = false; + slot.oaicompat_model = ""; + } + slot.params.stream = json_value(data, "stream", false); + slot.params.cache_prompt = json_value(data, "cache_prompt", false); + slot.params.n_predict = json_value(data, "n_predict", default_params.n_predict); + slot.sparams.top_k = json_value(data, "top_k", default_sparams.top_k); + slot.sparams.top_p = json_value(data, "top_p", default_sparams.top_p); + slot.sparams.min_p = json_value(data, "min_p", default_sparams.min_p); + slot.sparams.tfs_z = json_value(data, "tfs_z", default_sparams.tfs_z); + slot.sparams.typical_p = json_value(data, "typical_p", default_sparams.typical_p); + slot.sparams.temp = json_value(data, "temperature", default_sparams.temp); + slot.sparams.dynatemp_range = json_value(data, "dynatemp_range", default_sparams.dynatemp_range); + slot.sparams.dynatemp_exponent = json_value(data, "dynatemp_exponent", default_sparams.dynatemp_exponent); + slot.sparams.penalty_last_n = json_value(data, "repeat_last_n", default_sparams.penalty_last_n); + slot.sparams.penalty_repeat = json_value(data, "repeat_penalty", default_sparams.penalty_repeat); + slot.sparams.penalty_freq = json_value(data, "frequency_penalty", default_sparams.penalty_freq); + slot.sparams.penalty_present = json_value(data, "presence_penalty", default_sparams.penalty_present); + slot.sparams.mirostat = json_value(data, "mirostat", default_sparams.mirostat); + slot.sparams.mirostat_tau = json_value(data, "mirostat_tau", default_sparams.mirostat_tau); + slot.sparams.mirostat_eta = json_value(data, "mirostat_eta", default_sparams.mirostat_eta); + slot.sparams.penalize_nl = json_value(data, "penalize_nl", default_sparams.penalize_nl); + slot.params.n_keep = json_value(data, "n_keep", slot.params.n_keep); + slot.params.n_discard = json_value(data, "n_discard", default_params.n_discard); + slot.sparams.seed = json_value(data, "seed", default_sparams.seed); + slot.sparams.n_probs = json_value(data, "n_probs", default_sparams.n_probs); + slot.sparams.min_keep = json_value(data, "min_keep", default_sparams.min_keep); + + // process "json_schema" and "grammar" + if (data.contains("json_schema") && !data.at("json_schema").is_null() && data.contains("grammar") && !data.at("grammar").is_null()) { + sendError(task.id, "Either \"json_schema\" or \"grammar\" can be specified, but not both", ERROR_TYPE_INVALID_REQUEST); + return false; + } else if (data.contains("json_schema") && !data.contains("grammar")) { + try { + auto schema = json_value(data, "json_schema", json::object()); + slot.sparams.grammar = json_schema_to_grammar(schema); + } catch (const std::exception & e) { + sendError(task.id, std::string("\"json_schema\": ") + e.what(), ERROR_TYPE_INVALID_REQUEST); + return false; + } + } else { + slot.sparams.grammar = json_value(data, "grammar", default_sparams.grammar); + } + + if (slot.params.cache_prompt && slot.ga_n != 1) { + slot.params.cache_prompt = false; + } + + if (slot.n_predict > 0 && slot.params.n_predict > slot.n_predict) { + slot.params.n_predict = slot.n_predict; + } + + // infill + slot.params.input_prefix = json_value(data, "input_prefix", default_params.input_prefix); + slot.params.input_suffix = json_value(data, "input_suffix", default_params.input_suffix); + + // get prompt + { + const auto & prompt = data.find("prompt"); + if (prompt == data.end()) { + sendError(task.id, "Either \"prompt\" or \"messages\" must be provided", ERROR_TYPE_INVALID_REQUEST); + return false; + } else { + slot.prompt = *prompt; + } + if (slot.prompt.is_array() && slot.prompt.empty()) { + sendError(task.id, "\"prompt\" cannot be an empty array", ERROR_TYPE_INVALID_REQUEST); + return false; + } + } + + // penalize user-provided tokens + { + slot.sparams.penalty_prompt_tokens.clear(); + slot.sparams.use_penalty_prompt_tokens = false; + + const auto & penalty_prompt = data.find("penalty_prompt"); + + if (penalty_prompt != data.end()) { + if (penalty_prompt->is_string()) { + const auto penalty_prompt_string = penalty_prompt->get(); + slot.sparams.penalty_prompt_tokens = llama_tokenize(model, penalty_prompt_string, false); + + if (slot.params.n_predict > 0) { + slot.sparams.penalty_prompt_tokens.reserve(slot.sparams.penalty_prompt_tokens.size() + slot.params.n_predict); + } + slot.sparams.use_penalty_prompt_tokens = true; + } + else if (penalty_prompt->is_array()) { + const auto n_tokens = penalty_prompt->size(); + slot.sparams.penalty_prompt_tokens.reserve(n_tokens + std::max(0, slot.params.n_predict)); + + const int n_vocab = llama_n_vocab(model); + for (const auto & penalty_token : *penalty_prompt) { + if (penalty_token.is_number_integer()) { + const auto tok = penalty_token.get(); + if (tok >= 0 && tok < n_vocab) { + slot.sparams.penalty_prompt_tokens.push_back(tok); + } + } + } + slot.sparams.use_penalty_prompt_tokens = true; + } + } + } + + { + slot.sparams.logit_bias.clear(); + + if (json_value(data, "ignore_eos", false)) { + slot.sparams.logit_bias[llama_token_eos(model)] = -INFINITY; + } + + const auto & logit_bias = data.find("logit_bias"); + if (logit_bias != data.end() && logit_bias->is_array()) { + const int n_vocab = llama_n_vocab(model); + for (const auto & el : *logit_bias) { + // TODO: we may want to throw errors here, in case "el" is incorrect + if (el.is_array() && el.size() == 2) { + float bias; + if (el[1].is_number()) { + bias = el[1].get(); + } else if (el[1].is_boolean() && !el[1].get()) { + bias = -INFINITY; + } else { + continue; + } + + if (el[0].is_number_integer()) { + llama_token tok = el[0].get(); + if (tok >= 0 && tok < n_vocab) { + slot.sparams.logit_bias[tok] = bias; + } + } else if (el[0].is_string()) { + auto toks = llama_tokenize(model, el[0].get(), false); + for (auto tok : toks) { + slot.sparams.logit_bias[tok] = bias; + } + } + } + } + } + } + + { + slot.params.antiprompt.clear(); + + const auto & stop = data.find("stop"); + if (stop != data.end() && stop->is_array()) { + for (const auto & word : *stop) { + if (!word.empty()) { + slot.params.antiprompt.push_back(word); + } + } + } + } + + { + const auto & samplers_sequence = data.find("samplers"); + if (samplers_sequence != data.end() && samplers_sequence->is_array()) { + std::vector sampler_names; + for (const auto & sampler_name : *samplers_sequence) { + if (sampler_name.is_string()) { + sampler_names.emplace_back(sampler_name); + } + } + slot.sparams.samplers_sequence = llama_sampling_types_from_names(sampler_names, false); + } else { + slot.sparams.samplers_sequence = default_sparams.samplers_sequence; + } + } + + { + if (slot.ctx_sampling != nullptr) { + llama_sampling_free(slot.ctx_sampling); + } + slot.ctx_sampling = llama_sampling_init(slot.sparams); + if (slot.ctx_sampling == nullptr) { + // for now, the only error that may happen here is invalid grammar + sendError(task.id, "Failed to parse grammar", ERROR_TYPE_INVALID_REQUEST); + return false; + } + } + + slot.command = SLOT_COMMAND_LOAD_PROMPT; + slot.prompt_tokens.clear(); + + return true; +} + +/* ================================================================= */ +/* do the hard job, use llama.cpp api to inference */ +/* ================================================================= */ + +std::vector tokenize(llama_context *ctx, const json & json_prompt, bool add_special) { + // TODO: currently, we tokenize using special tokens by default + // this is not always correct (see https://github.com/ggerganov/llama.cpp/pull/4160#issuecomment-1824826216) + // but it's better compared to completely ignoring ChatML and other chat templates + const bool TMP_FORCE_SPECIAL = true; + + // If `add_bos` is true, we only add BOS, when json_prompt is a string, + // or the first element of the json_prompt array is a string. + std::vector prompt_tokens; + + if (json_prompt.is_array()) { + bool first = true; + for (const auto & p : json_prompt) { + if (p.is_string()) { + auto s = p.template get(); + + std::vector p; + if (first) { + p = ::llama_tokenize(ctx, s, add_special, TMP_FORCE_SPECIAL); + first = false; + } else { + p = ::llama_tokenize(ctx, s, false, TMP_FORCE_SPECIAL); + } + + prompt_tokens.insert(prompt_tokens.end(), p.begin(), p.end()); + } else { + if (first) { + first = false; + } + + prompt_tokens.push_back(p.template get()); + } + } + } else { + auto s = json_prompt.template get(); + prompt_tokens = ::llama_tokenize(ctx, s, add_special, TMP_FORCE_SPECIAL); + } + + return prompt_tokens; +} + +bool InferenceContext::processToken(completion_token_output & result, server_slot & slot) { + // remember which tokens were sampled - used for repetition penalties during sampling + const std::string token_str = llama_token_to_piece(ctx, result.tok, false); + slot.sampled = result.tok; + + // search stop word and delete it + slot.generated_text += token_str; + slot.has_next_token = true; + + if (slot.ctx_sampling->params.use_penalty_prompt_tokens && result.tok != -1) { + // we can change penalty_prompt_tokens because it is always created from scratch each request + slot.ctx_sampling->params.penalty_prompt_tokens.push_back(result.tok); + } + + // check if there is incomplete UTF-8 character at the end + bool incomplete = false; + for (unsigned i = 1; i < 5 && i <= slot.generated_text.size(); ++i) { + unsigned char c = slot.generated_text[slot.generated_text.size() - i]; + if ((c & 0xC0) == 0x80) { + // continuation byte: 10xxxxxx + continue; + } + if ((c & 0xE0) == 0xC0) { + // 2-byte character: 110xxxxx ... + incomplete = i < 2; + } else if ((c & 0xF0) == 0xE0) { + // 3-byte character: 1110xxxx ... + incomplete = i < 3; + } else if ((c & 0xF8) == 0xF0) { + // 4-byte character: 11110xxx ... + incomplete = i < 4; + } + // else 1-byte character or invalid byte + break; + } + + if (!incomplete) { + size_t pos = std::min(slot.n_sent_text, slot.generated_text.size()); + + const std::string str_test = slot.generated_text.substr(pos); + bool is_stop_full = false; + + size_t stop_pos = slot.find_stopping_strings(str_test, token_str.size(), STOP_TYPE_FULL); + if (stop_pos != std::string::npos) { + is_stop_full = true; + slot.generated_text.erase( + slot.generated_text.begin() + pos + stop_pos, + slot.generated_text.end()); + pos = std::min(slot.n_sent_text, slot.generated_text.size()); + } else { + is_stop_full = false; + stop_pos = slot.find_stopping_strings(str_test, token_str.size(), STOP_TYPE_PARTIAL); + } + + // check if there is any token to predict + if (stop_pos == std::string::npos || (!slot.has_next_token && !is_stop_full && stop_pos > 0)) { + // no send the stop word in the response + result.text_to_send = slot.generated_text.substr(pos, std::string::npos); + slot.n_sent_text += result.text_to_send.size(); + // add the token to slot queue and cache + } + + slot.add_token_string(result); + if (slot.params.stream) { + sendPartialResponse(result, slot); + } + } + + if (incomplete) { + slot.has_next_token = true; + } + + // check the limits + if (slot.n_decoded > 0 && slot.has_next_token && !slot.has_budget(params)) { + slot.stopped_limit = true; + slot.has_next_token = false; + } + + if (ggml_time_us() - slot.t_start_generation > inference_timeout_) { + slot.stopped_limit = true; + slot.has_next_token = false; + } + + if (llama_token_is_eog(model, result.tok)) { + slot.stopped_eos = true; + slot.has_next_token = false; + } + + auto n_ctx_train = llama_n_ctx_train(model); + if (slot.params.n_predict < 1 && slot.n_predict < 1 && slot.ga_n == 1 + && slot.n_prompt_tokens + slot.n_decoded >= n_ctx_train) { + slot.truncated = true; + slot.stopped_limit = true; + slot.has_next_token = false; // stop prediction + } + + return slot.has_next_token; // continue +} + +void InferenceContext::updateSlots() { + // release slots + for (auto & slot : slots) { + if (slot.command == SLOT_COMMAND_RELEASE) { + slot.state = SLOT_STATE_IDLE; + slot.command = SLOT_COMMAND_NONE; + slot.t_last_used = ggml_time_us(); + } + } + + // check if all slots are idle + { + bool all_idle = true; + + for (auto & slot : slots) { + if (slot.state != SLOT_STATE_IDLE || slot.command != SLOT_COMMAND_NONE) { + all_idle = false; + break; + } + } + + if (all_idle) { + LOG_INFO("all slots are idle", {}); + if (system_prompt.empty() && clean_kv_cache) { + // clear the entire KV cache + llama_kv_cache_clear(ctx); + } + return; + } + } + { + server_task task; + task.type = SERVER_TASK_TYPE_NEXT_RESPONSE; + task.id_target = -1; + task.id = getId(); + inference_thread_.addTask([this, task](){ + this->processSingleTask(task); + }); + } + + // start populating the batch for this iteration + llama_batch_clear(batch); + + // frist, add sampled tokens from any ongoing sequences + for (auto & slot : slots) { + if (slot.state == SLOT_STATE_IDLE) { + continue; + } + slot.i_batch = batch.n_tokens; + + const int32_t slot_npast = slot.n_past_se > 0 ? slot.n_past_se : slot.n_past; + + // TODO: we always have to take into account the "system_tokens" + // this is not great and needs to be improved somehow + llama_batch_add(batch, slot.sampled, system_tokens.size() + slot_npast, { slot.id + 1 }, true); + + slot.n_past += 1; + + if (slot.params.cache_prompt) { + slot.cache_tokens.push_back(slot.sampled); + } + } + + // process in chunks of params.n_batch + + int32_t n_batch = llama_n_batch(ctx); + int32_t n_ubatch = llama_n_ubatch(ctx); + + // next, batch any pending prompts without exceeding n_batch + for (auto & slot : slots) { + // this slot still has a prompt to be processed + if (slot.state == SLOT_STATE_IDLE && slot.command == SLOT_COMMAND_LOAD_PROMPT) { + auto & prompt_tokens = slot.prompt_tokens; + + // we haven't tokenized the prompt yet - do it now: + if (prompt_tokens.empty()) { + + slot.t_start_process_prompt = ggml_time_us(); + slot.t_start_generation = 0; + + if (slot.infill) { + bool suff_rm_leading_spc = true; + // if (params.input_suffix.find_first_of(' ') == 0 && params.input_suffix.size() > 1) { + // params.input_suffix.erase(0, 1); + // suff_rm_leading_spc = false; + // } + + auto prefix_tokens = tokenize(ctx, slot.params.input_prefix, false); + auto suffix_tokens = tokenize(ctx, slot.params.input_suffix, false); + + const int space_token = 29871; // TODO: this should not be hardcoded + if (suff_rm_leading_spc && !suffix_tokens.empty() && suffix_tokens[0] == space_token) { + suffix_tokens.erase(suffix_tokens.begin()); + } + + prefix_tokens.insert(prefix_tokens.begin(), llama_token_prefix(model)); + prefix_tokens.insert(prefix_tokens.begin(), llama_token_bos(model)); // always add BOS + prefix_tokens.insert(prefix_tokens.end(), llama_token_suffix(model)); + prefix_tokens.insert(prefix_tokens.end(), suffix_tokens.begin(), suffix_tokens.end()); + prefix_tokens.push_back(llama_token_middle(model)); + prompt_tokens = prefix_tokens; + } else { + prompt_tokens = tokenize(ctx, slot.prompt, system_prompt.empty()); // add BOS if there isn't system prompt + } + + slot.n_past = 0; + slot.n_prompt_tokens = prompt_tokens.size(); + + // empty prompt passed -> release the slot and send empty response + if (prompt_tokens.empty()) { + LOG_INFO("empty prompt - releasing slot", { + {"id_slot", slot.id}, + {"id_task", slot.id_task} + }); + slot.state = SLOT_STATE_PROCESSING; + slot.command = SLOT_COMMAND_NONE; + slot.release(); + slot.print_timings(); + sendFinalResponse(slot); + continue; + } + + if (slot.embedding) { + // this prompt is too large to process - discard it + if (slot.n_prompt_tokens > n_ubatch) { + slot.state = SLOT_STATE_PROCESSING; + slot.command = SLOT_COMMAND_NONE; + slot.release(); + sendError(slot.id_task, "input is too large to process. increase the physical batch size", ERROR_TYPE_SERVER); + continue; + } + } else { + if (slot.params.n_keep < 0) { + slot.params.n_keep = slot.n_prompt_tokens; + } + slot.params.n_keep = std::min(slot.n_ctx - 4, slot.params.n_keep); + + // if input prompt is too big, truncate it (if group attention self-extend is disabled) + if (slot.ga_n == 1 && slot.n_prompt_tokens >= slot.n_ctx) { + const int n_left = slot.n_ctx - slot.params.n_keep; + + const int n_block_size = n_left / 2; + const int erased_blocks = (slot.n_prompt_tokens - slot.params.n_keep - n_block_size) / n_block_size; + + std::vector new_tokens( + prompt_tokens.begin(), + prompt_tokens.begin() + slot.params.n_keep); + + new_tokens.insert( + new_tokens.end(), + prompt_tokens.begin() + slot.params.n_keep + erased_blocks * n_block_size, + prompt_tokens.end()); + + prompt_tokens = std::move(new_tokens); + + slot.truncated = true; + slot.n_prompt_tokens = prompt_tokens.size(); + + GGML_ASSERT(slot.n_prompt_tokens < slot.n_ctx); + } + + llama_sampling_reset(slot.ctx_sampling); + + if (!slot.params.cache_prompt) { + slot.n_past_se = 0; + slot.ga_i = 0; + } else { + GGML_ASSERT(slot.ga_n == 1); + + // reuse any previously computed tokens that are common with the new prompt + slot.n_past = common_part(slot.cache_tokens, prompt_tokens); + + // push the prompt into the sampling context (do not apply grammar) + for (int i = 0; i < slot.n_past; ++i) { + llama_sampling_accept(slot.ctx_sampling, ctx, slot.cache_tokens[i], false); + } + } + } + + if (slot.n_past == slot.n_prompt_tokens && slot.n_past > 0) { + // we have to evaluate at least 1 token to generate logits. + LOG_INFO("we have to evaluate at least 1 token to generate logits", { + { "id_slot", slot.id }, + { "id_task", slot.id_task } + }); + + slot.n_past--; + if (slot.ga_i > 0) { + slot.n_past_se--; + } + } + + slot.n_prompt_tokens_processed = 0; + } + + if (slot.embedding) { + // cannot fit the prompt in the current batch - will try next iter + if (batch.n_tokens + slot.n_prompt_tokens > n_batch) { + continue; + } + } + + // keep only the common part + int p0 = static_cast(system_tokens.size()) + slot.n_past; + if (!llama_kv_cache_seq_rm(ctx, slot.id + 1, p0, -1)) { + // could not partially delete (likely using a non-Transformer model) + llama_kv_cache_seq_rm(ctx, slot.id + 1, -1, -1); + + p0 = static_cast(system_tokens.size()); + if (p0 != 0) { + // copy over the system prompt when there is one + llama_kv_cache_seq_cp(ctx, 0, slot.id + 1, -1, -1); + } + + // there is no common part left (except for the system prompt) + slot.n_past = 0; + slot.n_past_se = 0; + slot.ga_i = 0; + // TODO: is the system prompt ever in the sampling context? + llama_sampling_reset(slot.ctx_sampling); + } + + // remove the non-common part from the cache + slot.cache_tokens.resize(slot.n_past); + LOG_INFO("kv cache rm [p0, end)", { + { "id_slot", slot.id }, + { "id_task", slot.id_task }, + { "p0", p0 } + }); + int32_t slot_npast = slot.n_past_se > 0 ? slot.n_past_se : slot.n_past; + + int32_t ga_i = slot.ga_i; + int32_t ga_n = slot.ga_n; + int32_t ga_w = slot.ga_w; + + // add prompt tokens for processing in the current batch + // TODO: the self-extend stuff here is a mess - simplify and/or abstract it somehow + for (; slot.n_past < slot.n_prompt_tokens && batch.n_tokens < n_batch; ++slot.n_past) { + if (slot.ga_n != 1) { + while (slot_npast >= ga_i + ga_w) { + const int bd = (ga_w/ga_n)*(ga_n - 1); + slot_npast -= bd; + ga_i += ga_w/ga_n; + } + } + + llama_batch_add(batch, prompt_tokens[slot.n_past], system_tokens.size() + slot_npast, { slot.id + 1 }, false); + + if (slot.params.cache_prompt) { + slot.cache_tokens.push_back(prompt_tokens[slot.n_past]); + } + + slot.n_prompt_tokens_processed++; + slot_npast++; + } + + // entire prompt has been processed - start decoding new tokens + if (slot.n_past == slot.n_prompt_tokens) { + slot.state = SLOT_STATE_PROCESSING; + slot.command = SLOT_COMMAND_NONE; + + GGML_ASSERT(batch.n_tokens > 0); + + // extract the logits only for the last token + batch.logits[batch.n_tokens - 1] = true; + + slot.n_decoded = 0; + slot.i_batch = batch.n_tokens - 1; + } + } + + if (batch.n_tokens >= n_batch) { + break; + } + } + + if (batch.n_tokens == 0) { + return; + } + // process the created batch of tokens + + for (int32_t i = 0; i < batch.n_tokens; i += n_batch) { + const int32_t n_tokens = std::min(n_batch, batch.n_tokens - i); + for (auto & slot : slots) { + if (slot.ga_n != 1) { + // context extension via Self-Extend + // TODO: simplify and/or abstract this + while (slot.n_past_se >= slot.ga_i + slot.ga_w) { + const int ib = (slot.ga_n * slot.ga_i) / slot.ga_w; + const int bd = (slot.ga_w / slot.ga_n) * (slot.ga_n - 1); + const int dd = (slot.ga_w / slot.ga_n) - ib * bd - slot.ga_w; + + LOG_TEE("\n"); + LOG_TEE("shift: [%6d, %6d] + %6d -> [%6d, %6d]\n", slot.ga_i, slot.n_past_se, ib * bd, slot.ga_i + ib * bd, slot.n_past_se + ib * bd); + LOG_TEE("div: [%6d, %6d] / %6d -> [%6d, %6d]\n", slot.ga_i + ib * bd, slot.ga_i + ib * bd + slot.ga_w, slot.ga_n, (slot.ga_i + ib * bd) / slot.ga_n, (slot.ga_i + ib * bd + slot.ga_w) / slot.ga_n); + LOG_TEE("shift: [%6d, %6d] + %6d -> [%6d, %6d]\n", slot.ga_i + ib * bd + slot.ga_w, slot.n_past_se + ib * bd, dd, slot.ga_i + ib * bd + slot.ga_w + dd, slot.n_past_se + ib * bd + dd); + + llama_kv_cache_seq_add(ctx, slot.id + 1, slot.ga_i, slot.n_past_se, ib * bd); + llama_kv_cache_seq_div(ctx, slot.id + 1, slot.ga_i + ib * bd, slot.ga_i + ib * bd + slot.ga_w, slot.ga_n); + llama_kv_cache_seq_add(ctx, slot.id + 1, slot.ga_i + ib * bd + slot.ga_w, slot.n_past_se + ib * bd, dd); + + slot.n_past_se -= bd; + + slot.ga_i += slot.ga_w / slot.ga_n; + + LOG_TEE("\nn_past_old = %d, n_past = %d, ga_i = %d\n\n", slot.n_past_se + bd, slot.n_past_se, slot.ga_i); + } + + slot.n_past_se += n_tokens; + } + } + + llama_batch batch_view = + { + n_tokens, + batch.token + i, + nullptr, + batch.pos + i, + batch.n_seq_id + i, + batch.seq_id + i, + batch.logits + i, + 0, 0, 0, // unused + }; + + const int ret = llama_decode(ctx, batch_view); + + if (ret != 0) { + if (n_batch == 1 || ret < 0) { + + for (auto & slot : slots) { + slot.state = SLOT_STATE_PROCESSING; + slot.command = SLOT_COMMAND_NONE; + slot.release(); + sendError(slot.id_task, "Input prompt is too big compared to KV size. Please try increasing KV size.", ERROR_TYPE_SERVER); + } + break; // break loop of n_batch + } + + // retry with half the batch size to try to find a free slot in the KV cache + n_batch /= 2; + i -= n_batch; + + continue; // continue loop of n_batch + } + + for (auto & slot : slots) { + if (slot.state != SLOT_STATE_PROCESSING || slot.i_batch < static_cast(i) || slot.i_batch >= static_cast(i + n_tokens)) { + continue; // continue loop of slots + } + + // prompt evaluated for embedding + if (slot.embedding) { + sendEmbedding(slot, batch_view); + slot.release(); + slot.i_batch = -1; + continue; // continue loop of slots + } + + completion_token_output result; + + const llama_token id = llama_sampling_sample(slot.ctx_sampling, ctx, nullptr, slot.i_batch - i); + + llama_sampling_accept(slot.ctx_sampling, ctx, id, true); + + slot.n_decoded += 1; + if (slot.n_decoded == 1) { + slot.t_start_generation = ggml_time_us(); + slot.t_prompt_processing = (slot.t_start_generation - slot.t_start_process_prompt) / 1e3; + } + + llama_token_data_array cur_p = { slot.ctx_sampling->cur.data(), slot.ctx_sampling->cur.size(), false }; + result.tok = id; + + const size_t n_probs = std::min(cur_p.size, static_cast(slot.sparams.n_probs)); + if (n_probs > 0) { + const size_t n_valid = slot.ctx_sampling->n_valid; + + // Make sure at least n_probs top tokens are at the front of the vector: + if (slot.sparams.temp == 0.0f && n_probs > n_valid) { + llama_sample_top_k(ctx, &cur_p, n_probs, 0); + } + + if (slot.sparams.temp == 0.0f) { + // With greedy sampling the probabilities have possibly not been calculated. + for (size_t i = 0; i < n_probs; ++i) { + result.probs.push_back({ + cur_p.data[i].id, + i == 0 ? 1.0f : 0.0f + }); + } + } else { + for (size_t i = 0; i < n_probs; ++i) { + result.probs.push_back({ + cur_p.data[i].id, + i >= n_valid ? 0.0f : cur_p.data[i].p // Tokens filtered out due to e.g. top_k have 0 probability. + }); + } + } + } + + if (!processToken(result, slot)) { + slot.release(); + slot.print_timings(); + sendFinalResponse(slot); + } + + slot.i_batch = -1; + } + } + +} + +/* ================================================================= */ +/* +The top part mainly does the work of loading models and doing model inference, +and the bottom part mainly does the work of sending generated tokens. +*/ +/* ================================================================= */ + +void InferenceContext::sendPartialResponse(completion_token_output& tkn, server_slot& slot) { + json res; + res = json { + {"content", tkn.text_to_send}, + {"stop", false}, + {"id_slot", slot.id}, + {"multimodal", false} + }; + + if (slot.sparams.n_probs > 0) { + const std::vector to_send_toks = llama_tokenize(ctx, tkn.text_to_send, false); + const size_t probs_pos = std::min(slot.n_sent_token_probs, slot.generated_token_probs.size()); + const size_t probs_stop_pos = std::min(slot.n_sent_token_probs + to_send_toks.size(), slot.generated_token_probs.size()); + + std::vector probs_output; + if (probs_pos < probs_stop_pos) { + probs_output = std::vector( + slot.generated_token_probs.begin() + probs_pos, + slot.generated_token_probs.begin() + probs_stop_pos); + } + slot.n_sent_token_probs = probs_stop_pos; + + res["completion_probabilities"] = probs_vector_to_json(ctx, probs_output); + } + + if (slot.oaicompat) { + res["oaicompat_token_ctr"] = slot.n_decoded; + res["model"] = slot.oaicompat_model; + } + + if (is_openai_) { + std::vector result_array = format_partial_response_oaicompat(res, completion_id_); + for (auto it = result_array.begin(); it != result_array.end(); ++it) { + if (!it->empty()) { + const std::string str = + "data: " + + it->dump(-1, ' ', false, json::error_handler_t::replace) + + "\n\n"; + if (callback_body_.find(slot.id_task) != callback_body_.end()) { + LookupBodyCallback& cb = callback_body_[slot.id_task]; + cb(ModelInferenceResult{true, false, str, NO_ERROR}); + } + } + } + } else { + const std::string str = + "data: " + + res.dump(-1, ' ', false, json::error_handler_t::replace) + + "\n\n"; + if (callback_body_.find(slot.id_task) != callback_body_.end()) { + LookupBodyCallback& cb = callback_body_[slot.id_task]; + cb(ModelInferenceResult{true, false, str,NO_ERROR}); + } + } +} + +void InferenceContext::sendFinalResponse(server_slot & slot) { + json res; + res = json { + {"content", !slot.params.stream ? slot.generated_text : ""}, + {"id_slot", slot.id}, + {"stop", true}, + {"model", model_name_}, + {"tokens_predicted", slot.n_decoded}, + {"tokens_evaluated", slot.n_prompt_tokens}, + // {"generation_settings", get_formated_generation(slot)}, + {"prompt", slot.prompt}, + {"truncated", slot.truncated}, + {"stopped_eos", slot.stopped_eos}, + {"stopped_word", slot.stopped_word}, + {"stopped_limit", slot.stopped_limit}, + {"stopping_word", slot.stopping_word}, + {"tokens_cached", slot.n_past}, + {"timings", slot.get_formated_timings()} + }; + + + if (slot.sparams.n_probs > 0) { + std::vector probs; + if (!slot.params.stream && slot.stopped_word) { + const std::vector stop_word_toks = llama_tokenize(ctx, slot.stopping_word, false); + + size_t safe_offset = std::min(slot.generated_token_probs.size(), stop_word_toks.size()); + probs = std::vector( + slot.generated_token_probs.begin(), + slot.generated_token_probs.end() - safe_offset); + } else { + probs = std::vector( + slot.generated_token_probs.begin(), + slot.generated_token_probs.end()); + } + + res["completion_probabilities"] = probs_vector_to_json(ctx, probs); + } + + if (slot.oaicompat) { + res["oaicompat_token_ctr"] = slot.n_decoded; + res["model"] = slot.oaicompat_model; + } + + + if (is_openai_) { + res = format_final_response_oaicompat(model_name_, res, completion_id_); + } + if (callback_body_.find(slot.id_task) != callback_body_.end()) { + LookupBodyCallback& cb = callback_body_[slot.id_task]; + cb(ModelInferenceResult{true, true, res.dump(-1, ' ', false, json::error_handler_t::replace), NO_ERROR}); + } +} + +void InferenceContext::sendEmbedding(server_slot & slot, const llama_batch & batch) { + json res; + const int n_embd = llama_n_embd(model); + + std::vector embd_res(n_embd, 0.0f); + + for (int i = 0; i < batch.n_tokens; ++i) { + if (!batch.logits[i] || batch.seq_id[i][0] != slot.id + 1) { + continue; + } + + const float * embd = llama_get_embeddings_seq(ctx, batch.seq_id[i][0]); + if (embd == nullptr) { + embd = llama_get_embeddings_ith(ctx, i); + } + + if (embd == nullptr) { + res = json { + {"embedding", std::vector(n_embd, 0.0f)}, + }; + + continue; + } + + llama_embd_normalize(embd, embd_res.data(), n_embd); + + res = json { + {"embedding", embd_res}, + }; + } + + json responses; + if (res.count("results")) { + // result for multi-task + responses = res.at("results"); + } else { + // result for single task + responses = std::vector{res}; + } + + // write JSON response + json root = is_openai_ + ? format_embeddings_response_oaicompat(model_name_, responses) + : responses[0]; + + if (callback_body_.find(slot.id_task) != callback_body_.end()) { + LookupBodyCallback& cb = callback_body_[slot.id_task]; + cb(ModelInferenceResult{true, true, root.dump(-1, ' ', false, json::error_handler_t::replace), NO_ERROR}); + } +} + +void InferenceContext::sendError(const int& id_task, const std::string & error, const enum error_type type = ERROR_TYPE_SERVER) { + if (callback_body_.find(id_task) != callback_body_.end()) { + LookupBodyCallback& cb = callback_body_[id_task]; + cb(ModelInferenceResult{true, false, error,type}); + } +} + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.h b/contrib/llm_inference/filters/http/source/inference/inference_context.h new file mode 100644 index 0000000000000..7191b709be625 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.h @@ -0,0 +1,85 @@ +#pragma once + +#include "contrib/llm_inference/filters/http/source/inference/inference_thread.h" +#include "contrib/llm_inference/filters/http/source/inference/inference_task.h" +#include "source/extensions/filters/http/common/factory_base.h" + +#include +#include +#include +#include "llama.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +struct server_task; +struct server_slot; +struct completion_token_output; + +struct ModelInferenceResult { + bool inference_successed = false; + bool stopped = false; + std::string ss; + error_type type; +}; + +using LookupBodyCallback = std::function; + +class InferenceContext { +public: + + InferenceContext(Envoy::Singleton::InstanceSharedPtr, InferenceThread&, const ModelParameter&, const std::string&, const ModelChosen&); + ~InferenceContext(); + bool loadModel(const ModelParameter&, const std::string&, const ModelChosen&); + void modelInference(LookupBodyCallback&& cb, std::shared_ptr&&, int&); + int getId(); + +private: + + server_slot * getAvailableSlot(const std::string &); + bool launchSlotWithTask(server_slot &, const server_task &); + void updateSlots(); + void processSingleTask(const server_task &); + bool processToken(completion_token_output &, server_slot &); + void sendPartialResponse(completion_token_output&, server_slot &); + void sendFinalResponse(server_slot &); + void sendEmbedding(server_slot &, const llama_batch &); + void sendError(const int &, const std::string &, const enum error_type); + + const Envoy::Singleton::InstanceSharedPtr owner_; + InferenceThread& inference_thread_; + absl::flat_hash_map callback_body_; + std::string model_name_; + + llama_model * model = nullptr; + llama_context * ctx = nullptr; + llama_batch batch; + bool clean_kv_cache = true; + bool add_bos_token = true; + bool has_eos_token = true; + int32_t n_ctx; // total context for all clients / slots + + // system prompt + std::string system_prompt; + std::vector system_tokens; + + // slots / clients + std::vector slots; + + // Necessary similarity of prompt for slot selection + float slot_prompt_similarity = 0.0f; + + std::string chat_template_ = ""; + std::string completion_id_; + bool is_openai_; + int64_t inference_timeout_; +}; + +using InferenceContextSharedPtr = std::shared_ptr; + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/contrib/llm_inference/filters/http/source/inference/inference_task.cc b/contrib/llm_inference/filters/http/source/inference/inference_task.cc new file mode 100644 index 0000000000000..0b97ba1806e3b --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/inference_task.cc @@ -0,0 +1,15 @@ +#include "contrib/llm_inference/filters/http/source/inference/inference_task.h" +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +InferenceTaskMetaData::InferenceTaskMetaData(const std::string& data,bool infill, int id, InferenceTaskType type, int id_target): + data(data), type(type),infill(infill),id(id), id_target(id_target) {} + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/contrib/llm_inference/filters/http/source/inference/inference_task.h b/contrib/llm_inference/filters/http/source/inference/inference_task.h new file mode 100644 index 0000000000000..f41c7c6dcda13 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/inference_task.h @@ -0,0 +1,52 @@ +#pragma once + +#include +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +struct ModelParameter { + int n_threads = 32; + int n_parallel = 1; +}; + +struct ModelChosen { + std::string model_name; + int first_byte_timeout = 10; + int inference_timeout = 90; + bool embedding = false; +}; + +// https://community.openai.com/t/openai-chat-list-of-error-codes-and-types/357791/11 +enum error_type { + ERROR_TYPE_INVALID_REQUEST, + ERROR_TYPE_AUTHENTICATION, + ERROR_TYPE_SERVER, + ERROR_TYPE_NOT_FOUND, + ERROR_TYPE_PERMISSION, + ERROR_TYPE_UNAVAILABLE, // custom error + ERROR_TYPE_NOT_SUPPORTED, // custom error + NO_ERROR, +}; + +enum InferenceTaskType { + InferencetasktypeTypeCompletion, + InferencetasktypeTypeEmbeedings, + InferencetasktypeTypeCancel, +}; + +struct InferenceTaskMetaData { + InferenceTaskMetaData(const std::string&,bool,int, InferenceTaskType,int); + std::string data; + InferenceTaskType type; + bool infill = false; + bool embedding = false; + int id = -1; + int id_target = -1; +}; + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/contrib/llm_inference/filters/http/source/inference/inference_thread.cc b/contrib/llm_inference/filters/http/source/inference/inference_thread.cc new file mode 100644 index 0000000000000..0e84c34584471 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/inference_thread.cc @@ -0,0 +1,79 @@ +#include "contrib/llm_inference/filters/http/source/inference/inference_thread.h" + +#include "envoy/thread/thread.h" +#include "inference_context.h" +#include +#include +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { +InferenceThread::InferenceThread(Thread::ThreadFactory& thread_factory) + : thread_(thread_factory.createThread([this]() { work(); })) {} + + +InferenceThread::~InferenceThread() { + terminate(); + thread_->join(); +} + +void InferenceThread::addTask(std::function callback) { + { + absl::MutexLock lock(&tasks_mu_); + tasks_.push_back(std::move(callback)); + } + // Signal to unblock InferenceThread + signal(); +} + +int InferenceThread::getId() { + { + absl::MutexLock lock(&id_mu_); + id_++; + return id_; + } +} + +void InferenceThread::signal() { + absl::MutexLock lock(&mu_); + signalled_ = true; +} + +void InferenceThread::terminate() { + absl::MutexLock lock(&mu_); + terminating_ = true; + signalled_ = true; +} + +bool InferenceThread::waitForSignal() { + absl::MutexLock lock(&mu_); + // Worth noting here that if `signalled_` is already true, the lock is not released + // until idle_ is false again, so waitForIdle will not return until `signalled_` + // stays false for the duration of an eviction cycle. + mu_.Await(absl::Condition(&signalled_)); + signalled_ = false; + return !terminating_; +} + +void InferenceThread::work() { + while (waitForSignal()) { + std::vector> tasks; + { + // Take a local copy of the set of tasks, so we don't hold the lock while + // work is being performed. + absl::MutexLock lock(&tasks_mu_); + tasks = std::move(tasks_); + } + + for (const std::function& callback_context_function: tasks) { + callback_context_function(); + } + } +} + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/llm_inference/filters/http/source/inference/inference_thread.h b/contrib/llm_inference/filters/http/source/inference/inference_thread.h new file mode 100644 index 0000000000000..b0d172df0893c --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/inference_thread.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include + +#include "envoy/thread/thread.h" + +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" +#include "source/extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +struct server_task; + +class InferenceThread{ +public: + InferenceThread(Thread::ThreadFactory& thread_factory); + ~InferenceThread(); + + /** + * Adds the given inference task. + */ + void addTask(std::function); + + /** + * get the inference task id. + */ + int getId(); + + /** + * Signals the inference thread that it's time to check the current task + * and perform if necessary. + */ + void signal(); + +private: + /** + * The function that runs on the thread. + */ + void work(); + + /** + * @return false if terminating, true if `signalled_` is true or the run-again period + * has passed. + */ + bool waitForSignal(); + + /** + * Notifies the thread to terminate. + */ + void terminate(); + + absl::Mutex mu_ ABSL_ACQUIRED_BEFORE(tasks_mu_); + bool signalled_ ABSL_GUARDED_BY(mu_) = false; + bool terminating_ ABSL_GUARDED_BY(mu_) = false; + + absl::Mutex tasks_mu_ ABSL_ACQUIRED_BEFORE(mu_); + std::vector> tasks_ ABSL_GUARDED_BY(tasks_mu_); + + Thread::ThreadPtr thread_; + + std::function callback_context_function_; + + int id_ ABSL_GUARDED_BY(id_mu_) = false; + absl::Mutex id_mu_; + +}; + + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/llm_inference/filters/http/source/inference/utils.hpp b/contrib/llm_inference/filters/http/source/inference/utils.hpp new file mode 100644 index 0000000000000..84b681817a84c --- /dev/null +++ b/contrib/llm_inference/filters/http/source/inference/utils.hpp @@ -0,0 +1,516 @@ +#pragma once + +#include "common/json.hpp" +#include "llama.h" +#include +#include +#include +#include +#include + +#define DEFAULT_OAICOMPAT_MODEL "gpt-3.5-turbo-0613" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +using json = nlohmann::ordered_json; + +enum server_task_type { + SERVER_TASK_TYPE_COMPLETION, + SERVER_TASK_TYPE_CANCEL, + SERVER_TASK_TYPE_NEXT_RESPONSE, +}; + +enum stop_type { + STOP_TYPE_FULL, + STOP_TYPE_PARTIAL, +}; + +struct slot_params { + bool stream = true; + bool cache_prompt = false; // remember the prompt to avoid reprocessing all prompt + + int32_t n_keep = 0; // number of tokens to keep from initial prompt + int32_t n_discard = 0; // number of tokens after n_keep that may be discarded when shifting context, 0 defaults to half + int32_t n_predict = -1; // new tokens to predict + + std::vector antiprompt; + + json input_prefix; + json input_suffix; +}; + +enum slot_state { + SLOT_STATE_IDLE, + SLOT_STATE_PROCESSING, +}; + +enum slot_command { + SLOT_COMMAND_NONE, + SLOT_COMMAND_LOAD_PROMPT, + SLOT_COMMAND_RELEASE, +}; + +struct completion_token_output { + llama_token tok; + std::string text_to_send; + + struct token_prob { + llama_token tok; + float prob; + }; + + std::vector probs; +}; + +#define LOG_INFO( MSG, ...) server_log("INFO", __func__, __LINE__, MSG, __VA_ARGS__) + +static inline void server_log(const char * level, const char * function, int line, const char * message, const json & extra) { + std::stringstream ss_tid; + ss_tid << std::this_thread::get_id(); + json log = json{ + {"tid", ss_tid.str()}, + {"timestamp", time(nullptr)}, + }; + + if (1) { + log.merge_patch({ + {"level", level}, + {"function", function}, + {"line", line}, + {"msg", message}, + }); + + if (!extra.empty()) { + log.merge_patch(extra); + } + + printf("%s\n", log.dump(-1, ' ', false, json::error_handler_t::replace).c_str()); + } else { + char buf[1024]; + snprintf(buf, 1024, "%4s [%24s] %s", level, function, message); + + if (!extra.empty()) { + log.merge_patch(extra); + } + std::stringstream ss; + ss << buf << " |"; + for (const auto & el : log.items()) + { + const std::string value = el.value().dump(-1, ' ', false, json::error_handler_t::replace); + ss << " " << el.key() << "=" << value; + } + + const std::string str = ss.str(); + printf("%.*s\n", static_cast(str.size()), str.data()); + } + fflush(stdout); +} + +template +static T json_value(const json & body, const std::string & key, const T & default_value) { + // Fallback null to default value + if (body.contains(key) && !body.at(key).is_null()) { + try { + return body.at(key); + } catch (NLOHMANN_JSON_NAMESPACE::detail::type_error const &) { + std::stringstream ss; + ss << "Wrong type supplied for parameter '" << key << "'. Expected '" << json(default_value).type_name() << "', using default value."; + return default_value; + } + } else { + return default_value; + } +} + +// Format given chat. If tmpl is empty, we take the template from model metadata +inline std::string format_chat(const struct llama_model * model, const std::string & tmpl, const std::vector & messages) { + size_t alloc_size = 0; + + // vector holding all allocated string to be passed to llama_chat_apply_template + + std::vector str(messages.size() * 2); + std::vector chat(messages.size()); + for (size_t i = 0; i < messages.size(); ++i) { + const auto & curr_msg = messages[i]; + str[i*2 + 0] = json_value(curr_msg, "role", std::string("")); + str[i*2 + 1] = json_value(curr_msg, "content", std::string("")); + alloc_size += str[i*2 + 1].length(); + chat[i].role = str[i*2 + 0].c_str(); + chat[i].content = str[i*2 + 1].c_str(); + } + + const char * ptr_tmpl = tmpl.empty() ? nullptr : tmpl.c_str(); + std::vector buf(alloc_size * 2); + + // run the first time to get the total output length + int32_t res = llama_chat_apply_template(model, ptr_tmpl, chat.data(), chat.size(), true, buf.data(), buf.size()); + + // if it turns out that our buffer is too small, we resize it + if (static_cast(res) > buf.size()) { + buf.resize(res); + res = llama_chat_apply_template(model, ptr_tmpl, chat.data(), chat.size(), true, buf.data(), buf.size()); + } + + const std::string formatted_chat(buf.data(), res); + + // LOG_VERBOSE("formatted_chat", {{"text", formatted_chat.c_str()}}); + + return formatted_chat; +} + +static std::string random_string() { + static const std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + + std::random_device rd; + std::mt19937 generator(rd()); + + std::string result(32, ' '); + + for (int i = 0; i < 32; ++i) { + result[i] = str[generator() % str.size()]; + } + + return result; +} + +static std::string gen_chatcmplid() { + std::stringstream chatcmplid; + chatcmplid << "chatcmpl-" << random_string(); + + return chatcmplid.str(); +} + +static size_t common_part(const std::vector & a, const std::vector & b) { + size_t i; + for (i = 0; i < a.size() && i < b.size() && a[i] == b[i]; i++) {} + + return i; +} + +static size_t common_part(const std::string & a, const std::string & b) { + size_t i; + for (i = 0; i < a.size() && i < b.size() && a[i] == b[i]; i++) {} + + return i; +} + +// format incomplete utf-8 multibyte character for output +static std::string tokens_to_output_formatted_string(const llama_context * ctx, const llama_token token) { + std::string out = token == -1 ? "" : llama_token_to_piece(ctx, token); + + // if the size is 1 and first bit is 1, meaning it's a partial character + // (size > 1 meaning it's already a known token) + if (out.size() == 1 && (out[0] & 0x80) == 0x80) { + std::stringstream ss; + ss << std::hex << (out[0] & 0xff); + std::string res(ss.str()); + out = "byte: \\x" + res; + } + + return out; +} + +// convert a vector of completion_token_output to json +static json probs_vector_to_json(const llama_context * ctx, const std::vector & probs) { + json out = json::array(); + + for (const auto & prob : probs) { + json probs_for_token = json::array(); + + for (const auto & p : prob.probs) { + const std::string tok_str = tokens_to_output_formatted_string(ctx, p.tok); + probs_for_token.push_back(json { + {"tok_str", tok_str}, + {"prob", p.prob}, + }); + } + + const std::string tok_str = tokens_to_output_formatted_string(ctx, prob.tok); + out.push_back(json { + {"content", tok_str}, + {"probs", probs_for_token}, + }); + } + + return out; +} + +// +// OAI utils +// +static json oaicompat_completion_params_parse( + const struct llama_model * model, + const json & body, /* openai api json semantics */ + const std::string & chat_template) { + json llama_params; + + llama_params["__oaicompat"] = true; + + // Map OpenAI parameters to llama.cpp parameters + // + // For parameters that are defined by the OpenAI documentation (e.g. + // temperature), we explicitly specify OpenAI's intended default; we + // need to do that because sometimes OpenAI disagrees with llama.cpp + // + // https://platform.openai.com/docs/api-reference/chat/create + // llama_sampling_params default_sparams; + llama_params["model"] = json_value(body, "model", std::string("unknown")); + llama_params["frequency_penalty"] = json_value(body, "frequency_penalty", 0.0); + llama_params["logit_bias"] = json_value(body, "logit_bias", json::object()); + llama_params["n_predict"] = json_value(body, "max_tokens", -1); + llama_params["presence_penalty"] = json_value(body, "presence_penalty", 0.0); + llama_params["seed"] = json_value(body, "seed", LLAMA_DEFAULT_SEED); + llama_params["stream"] = json_value(body, "stream", false); + llama_params["temperature"] = json_value(body, "temperature", 1.0); + llama_params["top_p"] = json_value(body, "top_p", 1.0); + + // very dangeuros! + // Apply chat template to the list of messages + llama_params["prompt"] = format_chat(model, chat_template, body.at("messages")); + + // Handle "stop" field + if (body.contains("stop") && body.at("stop").is_string()) { + llama_params["stop"] = json::array({body.at("stop").get()}); + } else { + llama_params["stop"] = json_value(body, "stop", json::array()); + } + + // Handle "response_format" field + if (body.contains("response_format")) { + json response_format = json_value(body, "response_format", json::object()); + std::string response_type = json_value(response_format, "type", std::string()); + if (response_type == "json_object") { + llama_params["json_schema"] = json_value(response_format, "schema", json::object()); + } else if (!response_type.empty() && response_type != "text") { + throw std::runtime_error("response_format type must be one of \"text\" or \"json_object\", but got: " + response_type); + } + } + + // Handle "n" field + int n_choices = json_value(body, "n", 1); + if (n_choices != 1) { + throw std::runtime_error("Only one completion choice is allowed"); + } + + // Handle "logprobs" field + // TODO: The response format of this option is not yet OAI-compatible, but seems like no one really using it; We may need to fix it in the future + if (body.contains("logprobs")) { + llama_params["n_probs"] = json_value(body, "top_logprobs", 20); + } else if (body.contains("top_logprobs")) { + throw std::runtime_error("top_logprobs requires logprobs to be set to true"); + } + + // Params supported by OAI but unsupported by llama.cpp + static const std::vector unsupported_params { "tools", "tool_choice" }; + for (auto & param : unsupported_params) { + if (body.contains(param)) { + throw std::runtime_error("Unsupported param: " + param); + } + } + + // Copy remaining properties to llama_params + // This allows user to use llama.cpp-specific params like "mirostat", "tfs_z",... via OAI endpoint. + // See "launch_slot_with_task()" for a complete list of params supported by llama.cpp + for (const auto & item : body.items()) { + // Exception: if "n_predict" is present, we overwrite the value specified earlier by "max_tokens" + if (!llama_params.contains(item.key()) || item.key() == "n_predict") { + llama_params[item.key()] = item.value(); + } + } + + return llama_params; +} + +static bool ends_with(const std::string & str, const std::string & suffix) { + return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); +} + +static size_t find_partial_stop_string(const std::string &stop, const std::string &text) { + if (!text.empty() && !stop.empty()) { + const char text_last_char = text.back(); + for (int64_t char_index = stop.size() - 1; char_index >= 0; char_index--) { + if (stop[char_index] == text_last_char) { + const std::string current_partial = stop.substr(0, char_index + 1); + if (ends_with(text, current_partial)) { + return text.size() - char_index - 1; + } + } + } + } + + return std::string::npos; +} + +static json format_final_response_oaicompat(const std::string& model_name, json result, const std::string & completion_id, bool streaming = false) { + bool stopped_word = result.count("stopped_word") != 0; + bool stopped_eos = json_value(result, "stopped_eos", false); + int num_tokens_predicted = json_value(result, "tokens_predicted", 0); + int num_prompt_tokens = json_value(result, "tokens_evaluated", 0); + std::string content = json_value(result, "content", std::string("")); + + std::string finish_reason = "length"; + if (stopped_word || stopped_eos) { + finish_reason = "stop"; + } + + json choices = + streaming ? json::array({json{{"finish_reason", finish_reason}, + {"index", 0}, + {"delta", json::object()}}}) + : json::array({json{{"finish_reason", finish_reason}, + {"index", 0}, + {"message", json{{"content", content}, + {"role", "assistant"}}}}}); + + std::time_t t = std::time(0); + + json res = json { + {"choices", choices}, + {"created", t}, + {"model", model_name}, + {"object", streaming ? "chat.completion.chunk" : "chat.completion"}, + {"usage", json { + {"completion_tokens", num_tokens_predicted}, + {"prompt_tokens", num_prompt_tokens}, + {"total_tokens", num_tokens_predicted + num_prompt_tokens} + }}, + {"id", completion_id} + }; + + if (result.contains("completion_probabilities")) { + res["completion_probabilities"] = json_value(result, "completion_probabilities", json::array()); + } + + return res; +} + +static std::vector format_partial_response_oaicompat(json result, const std::string & completion_id) { + if (!result.contains("model") || !result.contains("oaicompat_token_ctr")) { + return std::vector({result}); + } + + bool first = json_value(result, "oaicompat_token_ctr", 0) == 0; + std::string modelname = json_value(result, "model", std::string(DEFAULT_OAICOMPAT_MODEL)); + + bool stopped_word = json_value(result, "stopped_word", false); + bool stopped_eos = json_value(result, "stopped_eos", false); + bool stopped_limit = json_value(result, "stopped_limit", false); + std::string content = json_value(result, "content", std::string("")); + + std::string finish_reason; + if (stopped_word || stopped_eos) { + finish_reason = "stop"; + } + if (stopped_limit) { + finish_reason = "length"; + } + + std::time_t t = std::time(0); + + json choices; + + if (!finish_reason.empty()) { + choices = json::array({json{{"finish_reason", finish_reason}, + {"index", 0}, + {"delta", json::object()}}}); + } else { + if (first) { + if (content.empty()) { + choices = json::array({json{{"finish_reason", nullptr}, + {"index", 0}, + {"delta", json{{"role", "assistant"}}}}}); + } else { + // We have to send this as two updates to conform to openai behavior + json initial_ret = json{{"choices", json::array({json{ + {"finish_reason", nullptr}, + {"index", 0}, + {"delta", json{ + {"role", "assistant"} + }}}})}, + {"created", t}, + {"id", completion_id}, + {"model", modelname}, + {"object", "chat.completion.chunk"}}; + + json second_ret = json{ + {"choices", json::array({json{{"finish_reason", nullptr}, + {"index", 0}, + {"delta", json{ + {"content", content}}} + }})}, + {"created", t}, + {"id", completion_id}, + {"model", modelname}, + {"object", "chat.completion.chunk"}}; + + return std::vector({initial_ret, second_ret}); + } + } else { + // Some idiosyncrasy in task processing logic makes several trailing calls + // with empty content, we ignore these at the calee site. + if (content.empty()) { + return std::vector({json::object()}); + } + + choices = json::array({json{ + {"finish_reason", nullptr}, + {"index", 0}, + {"delta", + json{ + {"content", content}, + }}, + }}); + } + } + + json ret = json { + {"choices", choices}, + {"created", t}, + {"id", completion_id}, + {"model", modelname}, + {"object", "chat.completion.chunk"} + }; + if (!finish_reason.empty()) { + int num_tokens_predicted = json_value(result, "tokens_predicted", 0); + int num_prompt_tokens = json_value(result, "tokens_evaluated", 0); + ret.push_back({"usage", json { + {"completion_tokens", num_tokens_predicted}, + {"prompt_tokens", num_prompt_tokens}, + {"total_tokens", num_tokens_predicted + num_prompt_tokens} + }}); + } + + return std::vector({ret}); +} + +static json format_embeddings_response_oaicompat(const std::string& model_name, const json & embeddings) { + json data = json::array(); + int i = 0; + for (auto & elem : embeddings) { + data.push_back(json{ + {"embedding", json_value(elem, "embedding", json::array())}, + {"index", i++}, + {"object", "embedding"} + }); + } + + json res = json { + {"model", model_name}, + {"object", "list"}, + {"usage", json { + {"prompt_tokens", 0}, + {"total_tokens", 0} + }}, + {"data", data} + }; + + return res; +} + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc new file mode 100644 index 0000000000000..d039a6f64dd75 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc @@ -0,0 +1,160 @@ +#include "contrib/llm_inference/filters/http/source/llm_inference_filter.h" + +#include "inference/inference_context.h" +#include "source/common/buffer/buffer_impl.h" + +#include "envoy/server/filter_config.h" + +#include "source/common/http/utility.h" +#include "source/common/protobuf/utility.h" +#include "source/common/http/headers.h" +#include "source/common/http/header_map_impl.h" +#include +#include +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +LLMInferenceFilterConfig::LLMInferenceFilterConfig( + const envoy::extensions::filters::http::llm_inference::v3::modelParameter& proto_config) + : modelParameter_{proto_config.n_threads(), proto_config.n_parallel()}, + modelPath_(proto_config.modelpath()) {} + +LLMInferenceFilterConfigPerRoute::LLMInferenceFilterConfigPerRoute( + const envoy::extensions::filters::http::llm_inference::v3::modelChosen& proto_config) + : modelChosen_{proto_config.usemodel() ,proto_config.first_byte_timeout(), proto_config.inference_timeout(), proto_config.embedding()} {} + +LLMInferenceFilter::LLMInferenceFilter(LLMInferenceFilterConfigSharedPtr config, InferenceContextSharedPtr ctx) + : config_(config), ctx_(ctx) {} + +LLMInferenceFilter::~LLMInferenceFilter() {} + +void LLMInferenceFilter::onDestroy() { + if (id_task_ != -1) { + ctx_->modelInference([](ModelInferenceResult&&) { + }, std::make_shared("{}", false, ctx_->getId(), InferencetasktypeTypeCancel, id_task_), inference_timeout_); + } +} + +const ModelParameter LLMInferenceFilter::modelParameter() const { + return config_->modelParameter(); +} + +const ModelPath LLMInferenceFilter::modelPath() const { + return config_->modelPath(); +} + +Http::FilterHeadersStatus LLMInferenceFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { + if (end_stream) { + // If this is a header-only request, we don't need to do any inference. + return Http::FilterHeadersStatus::Continue; + } + + // Route-level configuration. + const auto* per_route_inference_settings = + Http::Utility::resolveMostSpecificPerFilterConfig( + "envoy.filters.http.llm_inference", decoder_callbacks_->route()); + if (!per_route_inference_settings) { + return Http::FilterHeadersStatus::Continue; + } else { + auto per_route_config = per_route_inference_settings->modelChosen(); + first_byte_timeout_ = per_route_config.first_byte_timeout; + inference_timeout_ = per_route_config.inference_timeout; + } + + // check header + const absl::string_view headersPath = headers.getPathValue(); + if (headersPath == "/v1/chat/completions") { + task_type_ = InferencetasktypeTypeCompletion; + } else if (headersPath == "/v1/embeddings") { + task_type_ = InferencetasktypeTypeEmbeedings; + } else { + return Http::FilterHeadersStatus::Continue; + } + + return Http::FilterHeadersStatus::StopIteration; +} + +Http::FilterDataStatus LLMInferenceFilter::decodeData(Buffer::Instance& data, bool end_stream) { + if (!end_stream) { + id_task_ = ctx_->getId(); + getHeaders(std::make_shared(data.toString(), false, id_task_, task_type_, -1)); + } + return Http::FilterDataStatus::StopIterationNoBuffer; +} + +void LLMInferenceFilter::getHeaders(std::shared_ptr&& task_meta_data) { + // set first byte timeout + timer_ = decoder_callbacks_->dispatcher().createTimer([this]() -> void { + decoder_callbacks_->continueDecoding(); + }); + timer_->enableTimer(std::chrono::seconds(first_byte_timeout_)); + + LLMInferenceFilterWeakPtr self = weak_from_this(); + // The dispatcher needs to be captured because there's no guarantee that + // decoder_callbacks_->dispatcher() is thread-safe. + ctx_->modelInference([self, &dispatcher = decoder_callbacks_->dispatcher()](ModelInferenceResult&& body) { + // The callback is posted to the dispatcher to make sure it is called on the worker thread. + dispatcher.post( + [self, body = std::move(body)]() mutable { + if (LLMInferenceFilterSharedPtr llm_inference_filter = self.lock()) { + llm_inference_filter->onBody(std::move(body)); + } + } + ); + }, std::move(task_meta_data), inference_timeout_); +} + +void LLMInferenceFilter::onBody(ModelInferenceResult&& body) { + timer_->disableTimer(); + if (!body.inference_successed) { + switch (body.type) { + case ERROR_TYPE_INVALID_REQUEST: + decoder_callbacks_->sendLocalReply(Http::Code::BadRequest, body.ss, nullptr, absl::nullopt, ""); + break; + case ERROR_TYPE_AUTHENTICATION: + decoder_callbacks_->sendLocalReply(Http::Code::Unauthorized, body.ss, nullptr, absl::nullopt, ""); + break; + case ERROR_TYPE_SERVER: + decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError, body.ss, nullptr, absl::nullopt, ""); + break; + case ERROR_TYPE_NOT_FOUND: + decoder_callbacks_->sendLocalReply(Http::Code::NotFound, body.ss, nullptr, absl::nullopt, ""); + break; + case ERROR_TYPE_PERMISSION: + decoder_callbacks_->sendLocalReply(Http::Code::Forbidden, body.ss, nullptr, absl::nullopt, ""); + break; + case ERROR_TYPE_UNAVAILABLE: + decoder_callbacks_->sendLocalReply(Http::Code::ServiceUnavailable, body.ss, nullptr, absl::nullopt, ""); + break; + case ERROR_TYPE_NOT_SUPPORTED: + decoder_callbacks_->sendLocalReply(Http::Code::NotImplemented, body.ss, nullptr, absl::nullopt, ""); + break; + case NO_ERROR: + break; + } + } else { + if (!header_) { + Http::ResponseHeaderMapPtr headers{Http::createHeaderMap({{Http::Headers::get().Status, "200"}})}; + decoder_callbacks_->encodeHeaders(std::move(headers), false, "good"); + header_ = true; + } + + request_data_ = std::make_unique(body.ss); + + if (body.stopped) { + decoder_callbacks_->encodeData(*request_data_, true); + } else { + decoder_callbacks_->encodeData(*request_data_, false); + } + } +} +// //测多次,查看内存情况 + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/llm_inference/filters/http/source/llm_inference_filter.h b/contrib/llm_inference/filters/http/source/llm_inference_filter.h new file mode 100644 index 0000000000000..bf9982df41571 --- /dev/null +++ b/contrib/llm_inference/filters/http/source/llm_inference_filter.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include "source/extensions/filters/http/common/pass_through_filter.h" +#include "contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.pb.h" +#include "contrib/llm_inference/filters/http/source/inference/inference_context.h" +#include "contrib/llm_inference/filters/http/source/inference/inference_task.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace LLMInference { + +using ModelPath = Protobuf::Map; + +class LLMInferenceFilterConfig : public Router::RouteSpecificFilterConfig { +public: + LLMInferenceFilterConfig(const envoy::extensions::filters::http::llm_inference::v3::modelParameter& proto_config); + + const ModelParameter& modelParameter() const {return modelParameter_;} + const ModelPath& modelPath() const {return modelPath_; } + +private: + const ModelParameter modelParameter_; + const ModelPath modelPath_; +}; + +using LLMInferenceFilterConfigSharedPtr = std::shared_ptr; + +class LLMInferenceFilterConfigPerRoute : public Router::RouteSpecificFilterConfig { +public: + LLMInferenceFilterConfigPerRoute(const envoy::extensions::filters::http::llm_inference::v3::modelChosen& proto_config); + + const ModelChosen& modelChosen() const {return modelChosen_;}; + +private: + const ModelChosen modelChosen_; +}; + +using LLMInferenceFilterConfigPerRouteSharedPtr = std::shared_ptr; + +class LLMInferenceFilter : public Http::PassThroughDecoderFilter, + public std::enable_shared_from_this { +public: + LLMInferenceFilter(LLMInferenceFilterConfigSharedPtr, InferenceContextSharedPtr); + ~LLMInferenceFilter(); + + // Http::StreamFilterBase + void onDestroy() override; + + // Http::StreamDecoderFilter + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override; + + Http::FilterDataStatus decodeData(Buffer::Instance&, bool) override; + + void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override { + decoder_callbacks_ = &callbacks; + } + + void getHeaders(std::shared_ptr&&); + void onBody(ModelInferenceResult&&); + +private: + const LLMInferenceFilterConfigSharedPtr config_; + const InferenceContextSharedPtr ctx_; + + Http::StreamDecoderFilterCallbacks* decoder_callbacks_; + Event::TimerPtr timer_; + InferenceTaskType task_type_; + Buffer::InstancePtr request_data_; + int first_byte_timeout_ = 10; + int inference_timeout_ = 90; + int id_task_ = -1; + bool header_ = false; + const ModelParameter modelParameter() const; + const ModelPath modelPath() const; +}; + +using LLMInferenceFilterSharedPtr = std::shared_ptr; +using LLMInferenceFilterWeakPtr = std::weak_ptr; + +} // namespace LLMInference +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file From d261d60b30f79078dc4c24c0c8795664864b3b92 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Mon, 26 Aug 2024 08:50:00 +0000 Subject: [PATCH 224/274] add llama.cpp module --- .../filters/http/llm_inference/v3/BUILD | 2 + .../http/llm_inference/v3/llm_inference.proto | 9 +++ api/versioning/BUILD | 3 +- bazel/foreign_cc/BUILD | 20 +++++++ bazel/repositories.bzl | 12 ++++ bazel/repository_locations.bzl | 12 ++++ .../http/source/llm_inference_filter.cc | 3 +- envoy.yaml | 6 ++ lds.yaml | 59 +++++++++++++++++++ tools/proto_format/format_api.py | 1 + 10 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 envoy.yaml create mode 100644 lds.yaml diff --git a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD index e8aa6b3a4bfd5..ee92fb652582e 100644 --- a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD +++ b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/BUILD @@ -1,3 +1,5 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 diff --git a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto index c4d3f85d7b534..b3e6865129207 100644 --- a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto +++ b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto @@ -5,17 +5,26 @@ package envoy.extensions.filters.http.llm_inference.v3; import "udpa/annotations/status.proto"; import "validate/validate.proto"; +option java_package = "io.envoyproxy.envoy.extensions.filters.http.llm_inference.v3"; +option java_outer_classname = "LlmInferenceProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/llm_inference/v3;llm_inferencev3"; option (udpa.annotations.file_status).package_version_status = ACTIVE; message modelParameter { int32 n_threads = 1; + int32 n_parallel = 2; + map modelpath = 3; } message modelChosen { string usemodel = 1; + int32 first_byte_timeout = 2; + int32 inference_timeout = 3; + bool embedding = 4; } diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 885d9d7db6176..002b89383d313 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -15,6 +15,7 @@ proto_library( "//contrib/envoy/extensions/config/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/language/v3alpha:pkg", + "//contrib/envoy/extensions/filters/http/llm_inference/v3:pkg", "//contrib/envoy/extensions/filters/http/squash/v3:pkg", "//contrib/envoy/extensions/filters/http/sxg/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/client_ssl_auth/v3:pkg", @@ -36,9 +37,9 @@ proto_library( "//contrib/envoy/extensions/network/connection_balance/dlb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/qat/v3alpha:pkg", - "//contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3:pkg", "//contrib/envoy/extensions/regex_engines/hyperscan/v3alpha:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", + "//contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3:pkg", "//contrib/envoy/extensions/vcl/v3alpha:pkg", "//envoy/admin/v3:pkg", "//envoy/config/accesslog/v3:pkg", diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index c413c04189eb9..edaf86d960dbb 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -570,3 +570,23 @@ envoy_cmake( }), working_directory = "build/cmake", ) + +envoy_cmake( + name = "llama", + cache_entries = { + "CMAKE_INSTALL_LIBDIR": "lib", + "BUILD_SHARED_LIBS": "off", + "GGML_OPENMP": "off", + }, + lib_source = "@com_github_ggerganov_llama//:all", + out_static_libs = select({ + "//conditions:default": [ + "libllama.a", + "libggml.a", + ], + }), + tags = ["skip_on_windows"], + postfix_script = select({ + "//conditions:default": "rm -rf $INSTALLDIR/include/common && mkdir $INSTALLDIR/include/common && cp -rL $EXT_BUILD_ROOT/external/com_github_ggerganov_llama/common/* $INSTALLDIR/include/common", + }), +) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 1ff622513f7df..a37fd64f6554e 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -278,6 +278,7 @@ def envoy_dependencies(skip_targets = []): _com_github_google_libprotobuf_mutator() _com_github_google_libsxg() _com_github_google_tcmalloc() + _com_github_ggerganov_llama() _com_github_gperftools_gperftools() _com_github_grpc_grpc() _com_github_unicode_org_icu() @@ -1238,6 +1239,17 @@ def _com_github_google_tcmalloc(): actual = "@com_github_google_tcmalloc//tcmalloc:malloc_extension", ) +def _com_github_ggerganov_llama(): + external_http_archive( + name = "com_github_ggerganov_llama", + build_file_content = BUILD_ALL_CONTENT, + ) + + native.bind( + name = "llama", + actual = "@envoy//bazel/foreign_cc:llama", + ) + def _com_github_gperftools_gperftools(): external_http_archive( name = "com_github_gperftools_gperftools", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 634aca51bfec6..e374478f0cd88 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -358,6 +358,18 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "Apache-2.0", license_url = "https://github.com/google/tcmalloc/blob/{version}/LICENSE", ), + com_github_ggerganov_llama = dict( + project_name = "llama.cpp", + project_desc = "LLM inference in C/C++", + project_url = "https://github.com/ggerganov/llama.cpp", + version = "a07c32ea54850c989f0ef6989da5b955b77b7172", + sha256 = "4a5aaa9f4329dc5364ff6e4eea9ee977adce24051f5a6ba099faaaaa57a47149", + strip_prefix = "llama.cpp-{version}", + urls = ["https://github.com/ggerganov/llama.cpp/archive/{version}.zip"], + use_category = ["dataplane_core"], + release_date = "2024-08-23", + cpe = "N/A", + ), com_github_gperftools_gperftools = dict( project_name = "gperftools", project_desc = "tcmalloc and profiling libraries", diff --git a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc index d039a6f64dd75..65cdd31d28839 100644 --- a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc +++ b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc @@ -55,8 +55,7 @@ Http::FilterHeadersStatus LLMInferenceFilter::decodeHeaders(Http::RequestHeaderM // Route-level configuration. const auto* per_route_inference_settings = - Http::Utility::resolveMostSpecificPerFilterConfig( - "envoy.filters.http.llm_inference", decoder_callbacks_->route()); + Http::Utility::resolveMostSpecificPerFilterConfig(decoder_callbacks_); if (!per_route_inference_settings) { return Http::FilterHeadersStatus::Continue; } else { diff --git a/envoy.yaml b/envoy.yaml new file mode 100644 index 0000000000000..30ec05728416e --- /dev/null +++ b/envoy.yaml @@ -0,0 +1,6 @@ +node: + cluster: cluster-1 + id: envoy-instance-1 +dynamic_resources: + lds_config: + path: ./lds.yaml \ No newline at end of file diff --git a/lds.yaml b/lds.yaml new file mode 100644 index 0000000000000..f3bccd63e1e41 --- /dev/null +++ b/lds.yaml @@ -0,0 +1,59 @@ +version_info: "0" +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: listener_http + http_filters: + - name: envoy.filters.http.llm_inference + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelParameter + n_threads : 64 + n_parallel : 2 + modelpath: { + "qwen2": "/home/yuanjq/model/qwen2-7b-instruct-q5_k_m.gguf", + "llama3": "/home/yuanjq/model/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf", + "bge": "/home/yuanjq/model/bge-small-zh-v1.5-f32.gguf" + } + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: route + virtual_hosts: + - name: llm_inference_service + domains: ["api.openai.com"] + routes: + - match: + # prefix: "/v1/embeddings" + # typed_per_filter_config: + # envoy.filters.http.llm_inference: + # "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelChosen + # usemodel: "qwen2" + # first_byte_timeout : 2 + # inference_timeout : 90 + # embedding : true + # direct_response: + # status: 504 + # body: + # inline_string: "inference timeout" + prefix: "/v1/chat/completions" + typed_per_filter_config: + envoy.filters.http.llm_inference: + "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelChosen + usemodel: "qwen2" + first_byte_timeout : 4 + inference_timeout : 90 + embedding : false + direct_response: + status: 504 + body: + inline_string: "inference timeout" \ No newline at end of file diff --git a/tools/proto_format/format_api.py b/tools/proto_format/format_api.py index eec43435ff88b..6304598c111a7 100644 --- a/tools/proto_format/format_api.py +++ b/tools/proto_format/format_api.py @@ -29,6 +29,7 @@ # Extensions moved from core to contrib. 'envoy.extensions.filters.http.dynamo.v3', 'envoy.extensions.filters.http.squash.v3', + 'envoy.extensions.filters.http.llm_inference.v3', 'envoy.extensions.filters.network.client_ssl_auth.v3', 'envoy.extensions.filters.network.generic_proxy.action.v3', 'envoy.extensions.filters.network.generic_proxy.codecs.dubbo.v3', From a65e32b300ef12e2727cb3c9097490092993f235 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Mon, 26 Aug 2024 08:56:51 +0000 Subject: [PATCH 225/274] //contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3:pkg in place --- api/versioning/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 002b89383d313..32939e2249848 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -37,9 +37,9 @@ proto_library( "//contrib/envoy/extensions/network/connection_balance/dlb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/qat/v3alpha:pkg", + "//contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3:pkg", "//contrib/envoy/extensions/regex_engines/hyperscan/v3alpha:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", - "//contrib/envoy/extensions/upstreams/http/dubbo_tcp/v3:pkg", "//contrib/envoy/extensions/vcl/v3alpha:pkg", "//envoy/admin/v3:pkg", "//envoy/config/accesslog/v3:pkg", From 0d5f3bba373f809da00dd8e68992ac7bd01d8a21 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Mon, 26 Aug 2024 21:40:27 +0800 Subject: [PATCH 226/274] clear yaml file and add test file --- .../http/source/llm_inference_filter.cc | 1 - .../llm_inference/filters/http/test/test.sh | 26 ++++++++ envoy.yaml | 6 -- lds.yaml | 59 ------------------- 4 files changed, 26 insertions(+), 66 deletions(-) create mode 100755 contrib/llm_inference/filters/http/test/test.sh delete mode 100644 envoy.yaml delete mode 100644 lds.yaml diff --git a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc index 65cdd31d28839..50135e2dd68af 100644 --- a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc +++ b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc @@ -151,7 +151,6 @@ void LLMInferenceFilter::onBody(ModelInferenceResult&& body) { } } } -// //测多次,查看内存情况 } // namespace LLMInference } // namespace HttpFilters diff --git a/contrib/llm_inference/filters/http/test/test.sh b/contrib/llm_inference/filters/http/test/test.sh new file mode 100755 index 0000000000000..3843a67559565 --- /dev/null +++ b/contrib/llm_inference/filters/http/test/test.sh @@ -0,0 +1,26 @@ +free -m | awk '{print " " ,$0}' | grep -i total > ./mem_result.txt +while true +do + curl -s http://localhost:10000/v1/chat/completions \ + -H "host:api.openai.com" \ + -d '{ + "model": "qwen2", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Can you introduce USA?" + } + ], + "stream": true + }' > /dev/null & + # 获取当前日期和时间 + current_date=$(date '+%Y-%m-%d %H:%M:%S') + + # 使用free命令获取内存信息,并将日期和时间附加到每行 + free -m | awk -v date="$current_date" 'NR>1{print date, $0}' | grep -i mem >> ./mem_result.txt + sleep 30 +done \ No newline at end of file diff --git a/envoy.yaml b/envoy.yaml deleted file mode 100644 index 30ec05728416e..0000000000000 --- a/envoy.yaml +++ /dev/null @@ -1,6 +0,0 @@ -node: - cluster: cluster-1 - id: envoy-instance-1 -dynamic_resources: - lds_config: - path: ./lds.yaml \ No newline at end of file diff --git a/lds.yaml b/lds.yaml deleted file mode 100644 index f3bccd63e1e41..0000000000000 --- a/lds.yaml +++ /dev/null @@ -1,59 +0,0 @@ -version_info: "0" -resources: -- "@type": type.googleapis.com/envoy.config.listener.v3.Listener - name: listener_0 - address: - socket_address: - address: 0.0.0.0 - port_value: 10000 - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: listener_http - http_filters: - - name: envoy.filters.http.llm_inference - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelParameter - n_threads : 64 - n_parallel : 2 - modelpath: { - "qwen2": "/home/yuanjq/model/qwen2-7b-instruct-q5_k_m.gguf", - "llama3": "/home/yuanjq/model/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf", - "bge": "/home/yuanjq/model/bge-small-zh-v1.5-f32.gguf" - } - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - route_config: - name: route - virtual_hosts: - - name: llm_inference_service - domains: ["api.openai.com"] - routes: - - match: - # prefix: "/v1/embeddings" - # typed_per_filter_config: - # envoy.filters.http.llm_inference: - # "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelChosen - # usemodel: "qwen2" - # first_byte_timeout : 2 - # inference_timeout : 90 - # embedding : true - # direct_response: - # status: 504 - # body: - # inline_string: "inference timeout" - prefix: "/v1/chat/completions" - typed_per_filter_config: - envoy.filters.http.llm_inference: - "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelChosen - usemodel: "qwen2" - first_byte_timeout : 4 - inference_timeout : 90 - embedding : false - direct_response: - status: 504 - body: - inline_string: "inference timeout" \ No newline at end of file From 77ae6bedd687faaa8bf9b8916e2018625335132c Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Wed, 28 Aug 2024 14:38:33 +0800 Subject: [PATCH 227/274] apply context-shift if needed --- .../source/inference/inference_context.cc | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.cc b/contrib/llm_inference/filters/http/source/inference/inference_context.cc index e1e9e78dc16c0..349904c569a5b 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.cc +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.cc @@ -942,6 +942,45 @@ void InferenceContext::updateSlots() { }); } + // apply context-shift if needed + // TODO: simplify and improve + for (server_slot & slot : slots) { + if (slot.ga_n == 1) { + if (slot.is_processing() && static_cast(system_tokens.size()) + slot.n_past >= slot.n_ctx - 1) { + // Shift context + const int n_keep = slot.params.n_keep + add_bos_token; + const int n_left = static_cast(system_tokens.size()) + slot.n_past - n_keep; + const int n_discard = slot.params.n_discard ? slot.params.n_discard : (n_left / 2); + + LOG_INFO("slot context shift", { + {"id_slot", slot.id}, + {"id_task", slot.id_task}, + {"n_keep", n_keep}, + {"n_left", n_left}, + {"n_discard", n_discard}, + {"n_ctx", n_ctx}, + {"n_past", slot.n_past}, + {"n_system_tokens", system_tokens.size()}, + {"n_cache_tokens", slot.cache_tokens.size()} + }); + + llama_kv_cache_seq_rm (ctx, slot.id + 1, n_keep , n_keep + n_discard); + llama_kv_cache_seq_add(ctx, slot.id + 1, n_keep + n_discard, system_tokens.size() + slot.n_past, -n_discard); + + if (slot.params.cache_prompt) { + for (size_t i = n_keep + n_discard; i < slot.cache_tokens.size(); i++) { + slot.cache_tokens[i - n_discard] = slot.cache_tokens[i]; + } + + slot.cache_tokens.resize(slot.cache_tokens.size() - n_discard); + } + + slot.n_past -= n_discard; + + slot.truncated = true; + } + } + } // start populating the batch for this iteration llama_batch_clear(batch); From a4eace85c7eb1a9833effbe9013649be3e34066b Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Wed, 28 Aug 2024 20:20:43 +0800 Subject: [PATCH 228/274] Modify path matching to suffix matching and fix sendError bug --- .../filters/http/source/inference/inference_context.cc | 2 +- .../llm_inference/filters/http/source/llm_inference_filter.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.cc b/contrib/llm_inference/filters/http/source/inference/inference_context.cc index 349904c569a5b..dd3c47688ec92 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.cc +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.cc @@ -1524,7 +1524,7 @@ void InferenceContext::sendEmbedding(server_slot & slot, const llama_batch & bat void InferenceContext::sendError(const int& id_task, const std::string & error, const enum error_type type = ERROR_TYPE_SERVER) { if (callback_body_.find(id_task) != callback_body_.end()) { LookupBodyCallback& cb = callback_body_[id_task]; - cb(ModelInferenceResult{true, false, error,type}); + cb(ModelInferenceResult{false, false, error,type}); } } diff --git a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc index 50135e2dd68af..54feee5d2db39 100644 --- a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc +++ b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc @@ -66,9 +66,9 @@ Http::FilterHeadersStatus LLMInferenceFilter::decodeHeaders(Http::RequestHeaderM // check header const absl::string_view headersPath = headers.getPathValue(); - if (headersPath == "/v1/chat/completions") { + if (absl::EndsWith(headersPath, "/v1/chat/completions")) { task_type_ = InferencetasktypeTypeCompletion; - } else if (headersPath == "/v1/embeddings") { + } else if (absl::EndsWith(headersPath, "/v1/embeddings")) { task_type_ = InferencetasktypeTypeEmbeedings; } else { return Http::FilterHeadersStatus::Continue; From 1fd373ae529a0e1bbbdd39b882e02e1ea787f0e1 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Fri, 30 Aug 2024 16:25:20 +0800 Subject: [PATCH 229/274] append original response code detail when internal redirect --- source/common/http/filter_manager.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 24bb8fa02a848..3ff524e04b060 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -1565,8 +1565,17 @@ bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) return false; } +#if defined(HIGRESS) + const auto& original_details = parent_.streamInfo().responseCodeDetails(); + parent_.streamInfo().setResponseCodeDetails( + original_details ? absl::StrCat(StreamInfo::ResponseCodeDetails::get().InternalRedirect, ":", + original_details.value()) + : StreamInfo::ResponseCodeDetails::get().InternalRedirect); + +#else parent_.streamInfo().setResponseCodeDetails( StreamInfo::ResponseCodeDetails::get().InternalRedirect); +#endif if (headers != nullptr) { // The call to setResponseHeaders is needed to ensure that the headers are properly logged in From a20514683055b4823686b8028c0bb6ffb4ee291e Mon Sep 17 00:00:00 2001 From: zty98751 Date: Fri, 30 Aug 2024 16:39:54 +0800 Subject: [PATCH 230/274] Fix the bug where the handler was not released when wasm failed (a follow-up PR will be submitted to the upstream) --- source/extensions/common/wasm/context.cc | 48 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 057b27427eb6b..616119cf7b85e 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1120,6 +1120,10 @@ WasmResult Context::redisCall(std::string_view cluster, std::string_view query, } void Context::onRedisCallSuccess(uint32_t token, std::string&& response) { + if (wasm()->isFailed()) { + redis_request_.erase(token); + return; + } if (proxy_wasm::current_context_ != nullptr) { // We are in a reentrant call, so defer. wasm()->addAfterVmCallAction([this, token, response = std::move(response)]() mutable { @@ -1135,13 +1139,21 @@ void Context::onRedisCallSuccess(uint32_t token, std::string&& response) { uint32_t body_size = response.size(); redis_call_response_ = std::move(response); + // Deferred "after VM call" actions are going to be executed upon returning from + // ContextBase::*, which might include deleting Context object via proxy_done(). + wasm()->addAfterVmCallAction([this, handler] { + redis_call_response_.clear(); + redis_request_.erase(handler); + }); proxy_wasm::ContextBase::onRedisCallResponse( token, static_cast(proxy_wasm::RedisStatus::Ok), body_size); - redis_call_response_.clear(); - redis_request_.erase(handler); } void Context::onRedisCallFailure(uint32_t token) { + if (wasm()->isFailed()) { + redis_request_.erase(token); + return; + } if (proxy_wasm::current_context_ != nullptr) { // We are in a reentrant call, so defer. wasm()->addAfterVmCallAction([this, token] { onRedisCallFailure(token); }); @@ -1154,10 +1166,14 @@ void Context::onRedisCallFailure(uint32_t token) { } status_code_ = static_cast(WasmResult::BrokenConnection); status_message_ = "reset"; + // Deferred "after VM call" actions are going to be executed upon returning from + // ContextBase::*, which might include deleting Context object via proxy_done(). + wasm()->addAfterVmCallAction([this, handler] { + status_message_ = ""; + redis_request_.erase(handler); + }); proxy_wasm::ContextBase::onRedisCallResponse( token, static_cast(proxy_wasm::RedisStatus::NetworkError), 0); - status_message_ = ""; - redis_request_.erase(handler); } #endif @@ -2064,6 +2080,12 @@ void Context::setEncoderFilterCallbacks(Envoy::Http::StreamEncoderFilterCallback void Context::onHttpCallSuccess(uint32_t token, Envoy::Http::ResponseMessagePtr&& response) { // TODO: convert this into a function in proxy-wasm-cpp-host and use here. +#if defined(HIGRESS) + if (wasm()->isFailed()) { + http_request_.erase(token); + return; + } +#endif if (proxy_wasm::current_context_ != nullptr) { // We are in a reentrant call, so defer. wasm()->addAfterVmCallAction([this, token, response = response.release()] { @@ -2088,6 +2110,12 @@ void Context::onHttpCallSuccess(uint32_t token, Envoy::Http::ResponseMessagePtr& } void Context::onHttpCallFailure(uint32_t token, Http::AsyncClient::FailureReason reason) { +#if defined(HIGRESS) + if (wasm()->isFailed()) { + http_request_.erase(token); + return; + } +#endif if (proxy_wasm::current_context_ != nullptr) { // We are in a reentrant call, so defer. wasm()->addAfterVmCallAction([this, token, reason] { onHttpCallFailure(token, reason); }); @@ -2118,6 +2146,12 @@ void Context::onGrpcReceiveWrapper(uint32_t token, ::Envoy::Buffer::InstancePtr grpc_call_request_.erase(token); } }; +#if defined(HIGRESS) + if (wasm()->isFailed()) { + cleanup(); + return; + } +#endif if (wasm()->on_grpc_receive_) { grpc_receive_buffer_ = std::move(response); uint32_t response_size = grpc_receive_buffer_->length(); @@ -2154,6 +2188,12 @@ void Context::onGrpcCloseWrapper(uint32_t token, const Grpc::Status::GrpcStatus& } } }; +#if defined(HIGRESS) + if (wasm()->isFailed()) { + cleanup(); + return; + } +#endif if (wasm()->on_grpc_close_) { status_code_ = static_cast(status); status_message_ = toAbslStringView(message); From 318c7b5c77dfbacdddedce6f3d79ce7a80f07dea Mon Sep 17 00:00:00 2001 From: zty98751 Date: Fri, 30 Aug 2024 16:42:40 +0800 Subject: [PATCH 231/274] forbid to acccess to admin interfaces via routing --- source/common/http/headers.h | 1 + source/common/router/config_impl.cc | 9 +++++++++ source/server/admin/admin.cc | 10 ++++++++++ 3 files changed, 20 insertions(+) diff --git a/source/common/http/headers.h b/source/common/http/headers.h index ec9fa4c8ea197..0ecf0c1fb2951 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -137,6 +137,7 @@ class CustomHeaderValues { const LowerCaseString TriRespStartTime{"resp-start-time"}; const LowerCaseString EnvoyOriginalHost{"original-host"}; const LowerCaseString XEnvoyOriginalHost{"x-envoy-original-host"}; + const LowerCaseString XEnvoyRouteIdentifier{"x-envoy-route-identifier"}; } AliExtendedValues; #endif }; diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index d2bead7d62198..773d7370467ba 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -59,6 +59,10 @@ namespace { constexpr uint32_t DEFAULT_MAX_DIRECT_RESPONSE_BODY_SIZE_BYTES = 4096; +#if defined(ALIMESH) +constexpr absl::string_view EnvoyRouteIdentifierValue = "true"; +#endif + void mergeTransforms(Http::HeaderTransforms& dest, const Http::HeaderTransforms& src) { dest.headers_to_append_or_add.insert(dest.headers_to_append_or_add.end(), src.headers_to_append_or_add.begin(), @@ -844,6 +848,11 @@ void RouteEntryImplBase::finalizeRequestHeaders(Http::RequestHeaderMap& headers, header_parser->evaluateHeaders(headers, stream_info); } +#if defined(HIGRESS) + headers.setReferenceKey(Http::CustomHeaders::get().AliExtendedValues.XEnvoyRouteIdentifier, + EnvoyRouteIdentifierValue); +#endif + // Restore the port if this was a CONNECT request. // Note this will restore the port for HTTP/2 CONNECT-upgrades as well as as HTTP/1.1 style // CONNECT requests. diff --git a/source/server/admin/admin.cc b/source/server/admin/admin.cc index 4df13d2de9712..9908361cc7742 100644 --- a/source/server/admin/admin.cc +++ b/source/server/admin/admin.cc @@ -353,6 +353,16 @@ Admin::RequestPtr AdminImpl::makeRequest(AdminStream& admin_stream) const { for (const UrlHandler& handler : handlers_) { if (path_and_query.compare(0, query_index, handler.prefix_) == 0) { +#if defined(ALIMESH) + if (handler.prefix_ != "/stats/prometheus") { + auto route_identifier = admin_stream.getRequestHeaders().getByKey( + Http::CustomHeaders::get().AliExtendedValues.XEnvoyRouteIdentifier); + if (route_identifier) { + return Admin::makeStaticTextRequest( + "Access to admin interfaces via routing is forbidden.", Http::Code::Forbidden); + } + } +#endif if (handler.mutates_server_state_) { const absl::string_view method = admin_stream.getRequestHeaders().getMethodValue(); if (method != Http::Headers::get().MethodValues.Post) { From 8c3cc1ac252c8c6d0441d6a9c0b7371610ba32bb Mon Sep 17 00:00:00 2001 From: zty98751 Date: Fri, 30 Aug 2024 16:55:00 +0800 Subject: [PATCH 232/274] fix macro typo --- source/common/router/config_impl.cc | 2 +- source/server/admin/admin.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 773d7370467ba..08b3442bbb631 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -59,7 +59,7 @@ namespace { constexpr uint32_t DEFAULT_MAX_DIRECT_RESPONSE_BODY_SIZE_BYTES = 4096; -#if defined(ALIMESH) +#if defined(HIGRESS) constexpr absl::string_view EnvoyRouteIdentifierValue = "true"; #endif diff --git a/source/server/admin/admin.cc b/source/server/admin/admin.cc index 9908361cc7742..de40405a1dee3 100644 --- a/source/server/admin/admin.cc +++ b/source/server/admin/admin.cc @@ -353,7 +353,7 @@ Admin::RequestPtr AdminImpl::makeRequest(AdminStream& admin_stream) const { for (const UrlHandler& handler : handlers_) { if (path_and_query.compare(0, query_index, handler.prefix_) == 0) { -#if defined(ALIMESH) +#if defined(HIGRESS) if (handler.prefix_ != "/stats/prometheus") { auto route_identifier = admin_stream.getRequestHeaders().getByKey( Http::CustomHeaders::get().AliExtendedValues.XEnvoyRouteIdentifier); From 43ffe2684ab9431b61e2287825f8dfaa120a54be Mon Sep 17 00:00:00 2001 From: zty98751 Date: Thu, 5 Sep 2024 20:48:10 +0800 Subject: [PATCH 233/274] fix fallback cluster bug --- source/common/router/config_impl.cc | 4 +++- source/common/router/config_impl.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 08b3442bbb631..9029da2d66434 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -1399,7 +1399,9 @@ RouteConstSharedPtr RouteEntryImplBase::pickWeightedCluster(const Http::HeaderMa static_cast(cluster.get())); return cluster_specifier_plugin_->route(route, *request_header); } - return cluster_specifier_plugin_->route(cluster, *request_header); + auto route = std::make_shared(cluster.get(), shared_from_this(), + cluster->clusterName()); + return cluster_specifier_plugin_->route(route, *request_header); } #endif if (!cluster->clusterHeaderName().get().empty() && diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index fd012818f04b4..9534ecae2ff9d 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -1034,8 +1034,8 @@ class RouteEntryImplBase : public RouteEntryAndRoute, } RouteConstSharedPtr clone(const std::string& name) const { - return std::make_shared( - parent_, shared_from_this(), name); + return std::make_shared(parent_, owner_, + name); } virtual RouteConstSharedPtr getRouteConstSharedPtr() const { return shared_from_this(); } From 47886388b6dd8be247300c178d5a003213c53e36 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Thu, 5 Sep 2024 22:34:46 +0800 Subject: [PATCH 234/274] update proxy wasm cpp host --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 634aca51bfec6..38177af574758 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1334,8 +1334,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "db24a6a09455429ef0f9ebd974f9219eb4a8a6c2", - sha256 = "20cd31530c1e5c7aaad8d85597e1d0f7792d76d41c8de31c9125dbdb07d481dc", + version = "08aef9c153fa21ac7ea2e1874823ce95d98520e9", + sha256 = "1965d9090d662dbe1215d6ec09bebe415af943c5df3f37dcba7b067926398ac7", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From c56c089a77b7df0f4d863f1e06684fdbb8ec2c9b Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 9 Sep 2024 20:54:24 +0800 Subject: [PATCH 235/274] Let the filter config hold a shared pointer to the base handler, ensuring the base handler is destructed in the main thread. --- bazel/repository_locations.bzl | 4 ++-- source/extensions/filters/http/wasm/wasm_filter.cc | 1 + source/extensions/filters/http/wasm/wasm_filter.h | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 38177af574758..4e64320b5dcc5 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1334,8 +1334,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "08aef9c153fa21ac7ea2e1874823ce95d98520e9", - sha256 = "1965d9090d662dbe1215d6ec09bebe415af943c5df3f37dcba7b067926398ac7", + version = "ef59c0433755e8702b5537a092f3733d8189286f", + sha256 = "8d0b36873bff970449dafd84f0c122858fb415ba5dc60c82ec45e6fddb46dc58", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], diff --git a/source/extensions/filters/http/wasm/wasm_filter.cc b/source/extensions/filters/http/wasm/wasm_filter.cc index 75e06e69b735a..235b36dcdb70a 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.cc +++ b/source/extensions/filters/http/wasm/wasm_filter.cc @@ -13,6 +13,7 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::wasm::v3::Was config.config(), context.direction(), context.localInfo(), &context.listenerMetadata()); auto callback = [plugin, this](const Common::Wasm::WasmHandleSharedPtr& base_wasm) { + base_wasm_handle_ = base_wasm; // NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call. tls_slot_->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { return std::make_shared( diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index b7b73f2d35d61..49ae1f1a84ae9 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -81,6 +81,7 @@ class FilterConfig : Logger::Loggable { private: ThreadLocal::TypedSlotPtr tls_slot_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + Envoy::Extensions::Common::Wasm::WasmHandleSharedPtr base_wasm_handle_; }; using FilterConfigSharedPtr = std::shared_ptr; From d053c4e914aeefd4ebb3199a6335970a2a9555fd Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 10 Sep 2024 15:46:46 +0800 Subject: [PATCH 236/274] upgrade wamr to 2.0.0 --- bazel/envoy_select.bzl | 3 +- bazel/foreign_cc/BUILD | 150 +++++++++++++++++++++++++++++++-- bazel/repositories.bzl | 11 +++ bazel/repository_locations.bzl | 24 +++++- 4 files changed, 177 insertions(+), 11 deletions(-) diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index 7cd774bd460e4..cfea2b6fda6d4 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -173,7 +173,8 @@ def envoy_select_wasm_v8_bool(): def envoy_select_wasm_wamr(xs): return select({ "@envoy//bazel:wasm_wamr": xs, - "//conditions:default": [], + "@envoy//bazel:higress": [], + "//conditions:default": xs, }) # Selects the given values depending on the Wasm runtimes enabled in the current build. diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index c413c04189eb9..f73fe6f05a8c4 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -465,24 +465,162 @@ envoy_cmake( }), ) +envoy_cmake( + name = "llvm_15_0_7", + cache_entries = { + # Disable both: BUILD and INCLUDE, since some of the INCLUDE + # targets build code instead of only generating build files. + "LLVM_BUILD_BENCHMARKS": "off", + "LLVM_BUILD_DOCS": "off", + "LLVM_BUILD_EXAMPLES": "off", + "LLVM_BUILD_TESTS": "off", + "LLVM_BUILD_TOOLS": "off", + "LLVM_ENABLE_IDE": "off", + "LLVM_ENABLE_LIBEDIT": "off", + "LLVM_ENABLE_LIBXML2": "off", + "LLVM_ENABLE_TERMINFO": "off", + "LLVM_ENABLE_ZLIB": "off", + "LLVM_ENABLE_ZSTD": "off", + "LLVM_INCLUDE_BENCHMARKS": "off", + "LLVM_INCLUDE_DOCS": "off", + "LLVM_INCLUDE_EXAMPLES": "off", + "LLVM_INCLUDE_TESTS": "off", + "LLVM_INCLUDE_TOOLS": "off", + "LLVM_TARGETS_TO_BUILD": "X86", + "LLVM_USE_PERF": "on", + "CMAKE_CXX_FLAGS": "-Wno-unused-command-line-argument", + }, + generate_args = ["-GNinja"] + select({ + # `lld` doesn't work on MacOS + "@platforms//os:linux": ["-DLLVM_USE_LINKER=lld"], + "//conditions:default": [], + }) + select({ + "//bazel:dbg_build": ["-DCMAKE_BUILD_TYPE=Debug"], + "//conditions:default": ["-DCMAKE_BUILD_TYPE=MinSizeRel"], + }), + lib_source = "@org_llvm_llvm_15_0_7//:all", + out_data_dirs = [ + "bin", + "include", + "lib", + "libexec", + "share", + ], + out_static_libs = [ + # How to get the library list: + # build LLVM with "-DLLVM_INCLUDE_TOOLS=ON" + # cd bin and run "./llvm-config --libnames" + "libLLVMWindowsManifest.a", + "libLLVMXRay.a", + "libLLVMLibDriver.a", + "libLLVMDlltoolDriver.a", + "libLLVMCoverage.a", + "libLLVMLineEditor.a", + "libLLVMX86Disassembler.a", + "libLLVMX86AsmParser.a", + "libLLVMX86CodeGen.a", + "libLLVMX86Desc.a", + "libLLVMX86Info.a", + "libLLVMOrcJIT.a", + "libLLVMMCJIT.a", + "libLLVMJITLink.a", + "libLLVMInterpreter.a", + "libLLVMExecutionEngine.a", + "libLLVMRuntimeDyld.a", + "libLLVMOrcTargetProcess.a", + "libLLVMOrcShared.a", + "libLLVMDWP.a", + "libLLVMSymbolize.a", + "libLLVMDebugInfoPDB.a", + "libLLVMDebugInfoGSYM.a", + "libLLVMOption.a", + "libLLVMObjectYAML.a", + "libLLVMMCA.a", + "libLLVMMCDisassembler.a", + "libLLVMLTO.a", + "libLLVMPasses.a", + "libLLVMCFGuard.a", + "libLLVMCoroutines.a", + "libLLVMObjCARCOpts.a", + "libLLVMipo.a", + "libLLVMVectorize.a", + "libLLVMLinker.a", + "libLLVMInstrumentation.a", + "libLLVMFrontendOpenMP.a", + "libLLVMFrontendOpenACC.a", + "libLLVMExtensions.a", + "libLLVMDWARFLinker.a", + "libLLVMGlobalISel.a", + "libLLVMMIRParser.a", + "libLLVMAsmPrinter.a", + "libLLVMDebugInfoMSF.a", + "libLLVMDebugInfoDWARF.a", + "libLLVMSelectionDAG.a", + "libLLVMCodeGen.a", + "libLLVMIRReader.a", + "libLLVMAsmParser.a", + "libLLVMInterfaceStub.a", + "libLLVMFileCheck.a", + "libLLVMFuzzMutate.a", + "libLLVMTarget.a", + "libLLVMScalarOpts.a", + "libLLVMInstCombine.a", + "libLLVMAggressiveInstCombine.a", + "libLLVMTransformUtils.a", + "libLLVMBitWriter.a", + "libLLVMAnalysis.a", + "libLLVMProfileData.a", + "libLLVMObject.a", + "libLLVMTextAPI.a", + "libLLVMMCParser.a", + "libLLVMMC.a", + "libLLVMDebugInfoCodeView.a", + "libLLVMBitReader.a", + "libLLVMCore.a", + "libLLVMRemarks.a", + "libLLVMBitstreamReader.a", + "libLLVMBinaryFormat.a", + "libLLVMTableGen.a", + "libLLVMSupport.a", + "libLLVMDemangle.a", + "libLLVMPerfJITEvents.a", + ], + working_directory = "llvm", +) + envoy_cmake( name = "wamr", cache_entries = { - "WAMR_BUILD_AOT": "0", - "WAMR_BUILD_FAST_INTERP": "1", + # aot/jit by default + "LLVM_DIR": "$EXT_BUILD_DEPS/copy_llvm_15_0_7/llvm/lib/cmake/llvm", + "WAMR_BUILD_AOT": "1", + "WAMR_BUILD_FAST_INTERP": "0", "WAMR_BUILD_INTERP": "1", - "WAMR_BUILD_JIT": "0", + "WAMR_BUILD_JIT": "1", + # disable WASI "WAMR_BUILD_LIBC_WASI": "0", - "WAMR_BUILD_MULTI_MODULE": "0", + "WAMR_BUILD_LIBC_BUILTIN": "0", + # MVP + "WAMR_BUILD_BULK_MEMORY": "1", + "WAMR_BUILD_REF_TYPES": "1", + # only for jit and aot "WAMR_BUILD_SIMD": "0", "WAMR_BUILD_TAIL_CALL": "1", "WAMR_BUILD_WASM_CACHE": "0", - "WAMR_DISABLE_HW_BOUND_CHECK": "0", - "WAMR_DISABLE_STACK_HW_BOUND_CHECK": "1", + "WAMR_BUILD_MULTI_MODULE": "0", + # enable below to enhance development experience + # name section + "WAMR_BUILD_CUSTOM_NAME_SECTION": "1", + "WAMR_BUILD_LOAD_CUSTOM_SECTION": "1", + # output call stack if meet a trap + "WAMR_BUILD_DUMP_CALL_STACK": "1", + # linux perf. only for jit and aot + "WAMR_BUILD_LINUX_PERF": "1", }, lib_source = "@com_github_wamr//:all", out_static_libs = ["libvmlib.a"], tags = ["skip_on_windows"], + deps = [":llvm_15_0_7"], ) envoy_cmake( diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 1ff622513f7df..3802813240891 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -347,6 +347,7 @@ def envoy_dependencies(skip_targets = []): _kafka_deps() _org_llvm_llvm() + _org_llvm_llvm_15_0_7() _com_github_wamr() _com_github_wavm_wavm() _com_github_wasmtime() @@ -1260,6 +1261,16 @@ def _org_llvm_llvm(): actual = "@envoy//bazel/foreign_cc:llvm", ) +def _org_llvm_llvm_15_0_7(): + external_http_archive( + name = "org_llvm_llvm_15_0_7", + build_file_content = BUILD_ALL_CONTENT, + ) + native.bind( + name = "llvm-15_0_7", + actual = "@envoy//bazel/foreign_cc:llvm_15_0_7", + ) + def _com_github_wamr(): external_http_archive( name = "com_github_wamr", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 4e64320b5dcc5..6440639ccf770 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -984,22 +984,38 @@ REPOSITORY_LOCATIONS_SPEC = dict( release_date = "2021-07-09", use_category = ["dataplane_ext"], extensions = [ - "envoy.wasm.runtime.wamr", "envoy.wasm.runtime.wavm", ], cpe = "cpe:2.3:a:llvm:*:*", license = "Apache-2.0", license_url = "https://github.com/llvm/llvm-project/blob/llvmorg-{version}/llvm/LICENSE.TXT", ), + org_llvm_llvm_15_0_7 = dict( + project_name = "LLVM_15_0_7", + project_desc = "LLVM Compiler Infrastructure", + project_url = "https://llvm.org", + version = "15.0.7", + sha256 = "8b5fcb24b4128cf04df1b0b9410ce8b1a729cb3c544e6da885d234280dedeac6", + strip_prefix = "llvm-project-{version}.src", + urls = ["https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/llvm-project-{version}.src.tar.xz"], + release_date = "2023-01-12", + use_category = ["dataplane_ext"], + extensions = [ + "envoy.wasm.runtime.wamr", + ], + cpe = "cpe:2.3:a:llvm:*:*", + license = "Apache-2.0", + license_url = "https://github.com/llvm/llvm-project/blob/llvmorg-{version}/llvm/LICENSE.TXT", + ), com_github_wamr = dict( project_name = "Webassembly Micro Runtime", project_desc = "A standalone runtime with a small footprint for WebAssembly", project_url = "https://github.com/bytecodealliance/wasm-micro-runtime", - version = "WAMR-1.2.2", - sha256 = "d328fc1e19c54cfdb4248b861de54b62977b9b85c0a40eaaeb9cd9b628c0c788", + version = "WAMR-2.0.0", + sha256 = "7663a34b61d6d0ff90778d9be37efde92e2f28ec9baad89f7b18555f0db435ab", strip_prefix = "wasm-micro-runtime-{version}", urls = ["https://github.com/bytecodealliance/wasm-micro-runtime/archive/{version}.tar.gz"], - release_date = "2023-05-16", + release_date = "2024-04-23", use_category = ["dataplane_ext"], extensions = ["envoy.wasm.runtime.wamr"], cpe = "N/A", From 4741b1f5f1158376eba9dbb1e5419bd4c9905fd8 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 10 Sep 2024 16:03:42 +0800 Subject: [PATCH 237/274] update proxy wasm cpp host --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6440639ccf770..3aa865acb4f03 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1350,8 +1350,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "ef59c0433755e8702b5537a092f3733d8189286f", - sha256 = "8d0b36873bff970449dafd84f0c122858fb415ba5dc60c82ec45e6fddb46dc58", + version = "7288fa8ae91933ab5e9cc79e5ef08d711dd466e7", + sha256 = "e1c4215e612648969d13e9416bdd78feb67caa89f83725c4605057ecc263335e", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From 9c9c3b717c9a3dd8cb8772ef5de86938aa1c93a8 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 10 Sep 2024 18:00:53 +0800 Subject: [PATCH 238/274] fix wamr --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 3aa865acb4f03..a48c1de7e12a0 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1350,8 +1350,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "7288fa8ae91933ab5e9cc79e5ef08d711dd466e7", - sha256 = "e1c4215e612648969d13e9416bdd78feb67caa89f83725c4605057ecc263335e", + version = "7850d1721fe3dd2ccfb86a06116f76c23b1f1bf8", + sha256 = "740690fc1d749849f6e24b5bc48a07dabc0565a7d03b6cd13425dba693956c57", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From 9acc597a3de972f27a97913b1e15e3a011af68e3 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Fri, 13 Sep 2024 16:50:56 +0800 Subject: [PATCH 239/274] Load the model based on filter configuration instead of router configuration --- .../http/llm_inference/v3/llm_inference.proto | 6 +-- .../filters/http/source/config.cc | 37 +++++++++++-------- .../filters/http/source/config.h | 2 - .../source/inference/inference_context.cc | 10 ++--- .../http/source/inference/inference_context.h | 5 ++- .../http/source/inference/inference_task.h | 1 - .../http/source/llm_inference_filter.cc | 35 +++++++----------- .../http/source/llm_inference_filter.h | 21 ++++++----- 8 files changed, 58 insertions(+), 59 deletions(-) diff --git a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto index b3e6865129207..d83e78862095a 100644 --- a/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto +++ b/api/contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.proto @@ -16,7 +16,9 @@ message modelParameter { int32 n_parallel = 2; - map modelpath = 3; + map chat_modelpath = 3; + + map embedding_modelpath = 4; } message modelChosen { @@ -25,6 +27,4 @@ message modelChosen { int32 first_byte_timeout = 2; int32 inference_timeout = 3; - - bool embedding = 4; } diff --git a/contrib/llm_inference/filters/http/source/config.cc b/contrib/llm_inference/filters/http/source/config.cc index d67c94a298331..811ff380b638e 100644 --- a/contrib/llm_inference/filters/http/source/config.cc +++ b/contrib/llm_inference/filters/http/source/config.cc @@ -14,24 +14,22 @@ class InferenceSingleton : public Envoy::Singleton::Instance { : inference_thread_(thread_factory) {} std::shared_ptr load(std::shared_ptr singleton, const ModelParameter& model_parameter, - const ModelChosen& model_chosen, const std::string& model_path) { + const std::string& model_name, const std::string& model_path, bool embedding) { std::shared_ptr ctx; - absl::MutexLock lock(&mu_); - auto it = ctx_.find(model_chosen.model_name); + std::string model = model_name + " " + std::to_string(model_parameter.n_threads) + " " + std::to_string(model_parameter.n_parallel); + auto it = ctx_.find(model); if (it != ctx_.end()) { ctx = it->second.lock(); } if (!ctx) { - ctx = std::make_shared(singleton, inference_thread_, model_parameter, model_path, model_chosen); - ctx_[model_chosen.model_name] = ctx; + ctx = std::make_shared(singleton, inference_thread_, model_parameter, model_name, model_path, embedding); } return ctx; } private: InferenceThread inference_thread_; - absl::Mutex mu_; - absl::flat_hash_map> ctx_ ABSL_GUARDED_BY(mu_); + absl::flat_hash_map> ctx_; }; SINGLETON_MANAGER_REGISTRATION(http_inference_singleton); @@ -49,14 +47,24 @@ Http::FilterFactoryCb LLMInferenceFilterConfigFactory::createFilterFactoryFromPr return std::make_shared(context.api().threadFactory()); }); - InferenceContextSharedPtr ctx; - auto modelpath = config->modelPath(); - if (modelpath.contains(model_Chosen_.model_name)) { - ctx = inference->load(inference, config->modelParameter(), model_Chosen_, modelpath[model_Chosen_.model_name]); + absl::flat_hash_map ctx; + + auto chat_modelpath = config->chatModelPath(); + + for (auto& model: chat_modelpath) { + ctx[model.first] = inference->load(inference, config->modelParameter(), model.first, model.second, false); + } + + auto embedding_modelpath = config->embeddingModelPath(); + + for (auto& model: embedding_modelpath) { + ctx[model.first] = inference->load(inference, config->modelParameter(), model.first, model.second, true); } + + InferenceContextHashMapSharedPtr ctx_map = std::make_shared>(ctx); - return [config, ctx](Http::FilterChainFactoryCallbacks& callbacks) -> void { - callbacks.addStreamDecoderFilter(std::make_shared(config, ctx)); + return [config, ctx_map](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(std::make_shared(config, ctx_map)); }; } @@ -66,8 +74,7 @@ Router::RouteSpecificFilterConfigConstSharedPtr LLMInferenceFilterConfigFactory: Server::Configuration::ServerFactoryContext&, ProtobufMessage::ValidationVisitor&) { LLMInferenceFilterConfigPerRouteSharedPtr config = std::make_shared(LLMInferenceFilterConfigPerRoute(proto_config)); - - model_Chosen_ = config->modelChosen(); + return config; } diff --git a/contrib/llm_inference/filters/http/source/config.h b/contrib/llm_inference/filters/http/source/config.h index 7a07f506f2db3..37fff3cd99138 100644 --- a/contrib/llm_inference/filters/http/source/config.h +++ b/contrib/llm_inference/filters/http/source/config.h @@ -2,7 +2,6 @@ #include "contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.pb.h" #include "contrib/envoy/extensions/filters/http/llm_inference/v3/llm_inference.pb.validate.h" -#include "contrib/llm_inference/filters/http/source/inference/inference_task.h" #include "source/extensions/filters/http/common/factory_base.h" @@ -30,7 +29,6 @@ class LLMInferenceFilterConfigFactory const envoy::extensions::filters::http::llm_inference::v3::modelChosen& proto_config, Server::Configuration::ServerFactoryContext&, ProtobufMessage::ValidationVisitor&) override; - ModelChosen model_Chosen_; }; } // namespace LLMInference diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.cc b/contrib/llm_inference/filters/http/source/inference/inference_context.cc index dd3c47688ec92..233bf5a2b7c55 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.cc +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.cc @@ -248,9 +248,9 @@ struct server_task { /* ================================================================= */ InferenceContext::InferenceContext(Singleton::InstanceSharedPtr owner, InferenceThread& inference_thread, - const ModelParameter& model_parameter, const std::string& model_path, const ModelChosen& model_chosen):owner_(owner), - inference_thread_(inference_thread), model_name_(model_chosen.model_name) { - loadModel(model_parameter, model_path, model_chosen); + const ModelParameter& model_parameter, const std::string& model_name,const std::string& model_path, bool embedding):owner_(owner), + inference_thread_(inference_thread), model_name_(model_name) { + loadModel(model_parameter, model_path, embedding); } /* ================================================================= */ @@ -291,10 +291,10 @@ int InferenceContext::getId() { /* load model */ /* ================================================================= */ -bool InferenceContext::loadModel(const ModelParameter& model_parameter, const std::string& model_path, const ModelChosen& model_chosen) { +bool InferenceContext::loadModel(const ModelParameter& model_parameter, const std::string& model_path, bool embedding) { params.n_threads = model_parameter.n_threads; params.n_parallel = model_parameter.n_parallel; - params.embedding = model_chosen.embedding; + params.embedding = embedding; params.model = model_path; diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.h b/contrib/llm_inference/filters/http/source/inference/inference_context.h index 7191b709be625..66be185f406e7 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.h +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.h @@ -30,9 +30,9 @@ using LookupBodyCallback = std::function; class InferenceContext { public: - InferenceContext(Envoy::Singleton::InstanceSharedPtr, InferenceThread&, const ModelParameter&, const std::string&, const ModelChosen&); + InferenceContext(Envoy::Singleton::InstanceSharedPtr, InferenceThread&, const ModelParameter&, const std::string&, const std::string&, bool); ~InferenceContext(); - bool loadModel(const ModelParameter&, const std::string&, const ModelChosen&); + bool loadModel(const ModelParameter&, const std::string&, bool); void modelInference(LookupBodyCallback&& cb, std::shared_ptr&&, int&); int getId(); @@ -78,6 +78,7 @@ class InferenceContext { }; using InferenceContextSharedPtr = std::shared_ptr; +using InferenceContextHashMapSharedPtr = std::shared_ptr>; } // namespace LLMInference } // namespace HttpFilters diff --git a/contrib/llm_inference/filters/http/source/inference/inference_task.h b/contrib/llm_inference/filters/http/source/inference/inference_task.h index f41c7c6dcda13..1c69f5e9b7021 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_task.h +++ b/contrib/llm_inference/filters/http/source/inference/inference_task.h @@ -15,7 +15,6 @@ struct ModelChosen { std::string model_name; int first_byte_timeout = 10; int inference_timeout = 90; - bool embedding = false; }; // https://community.openai.com/t/openai-chat-list-of-error-codes-and-types/357791/11 diff --git a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc index 54feee5d2db39..bdddb1a67bc82 100644 --- a/contrib/llm_inference/filters/http/source/llm_inference_filter.cc +++ b/contrib/llm_inference/filters/http/source/llm_inference_filter.cc @@ -1,6 +1,4 @@ #include "contrib/llm_inference/filters/http/source/llm_inference_filter.h" - -#include "inference/inference_context.h" #include "source/common/buffer/buffer_impl.h" #include "envoy/server/filter_config.h" @@ -9,9 +7,6 @@ #include "source/common/protobuf/utility.h" #include "source/common/http/headers.h" #include "source/common/http/header_map_impl.h" -#include -#include -#include namespace Envoy { namespace Extensions { @@ -20,33 +15,25 @@ namespace LLMInference { LLMInferenceFilterConfig::LLMInferenceFilterConfig( const envoy::extensions::filters::http::llm_inference::v3::modelParameter& proto_config) - : modelParameter_{proto_config.n_threads(), proto_config.n_parallel()}, - modelPath_(proto_config.modelpath()) {} + : model_parameter_{proto_config.n_threads(), proto_config.n_parallel()}, + chat_modelpath_(proto_config.chat_modelpath()), embedding_modelpath_(proto_config.embedding_modelpath()) {} LLMInferenceFilterConfigPerRoute::LLMInferenceFilterConfigPerRoute( const envoy::extensions::filters::http::llm_inference::v3::modelChosen& proto_config) - : modelChosen_{proto_config.usemodel() ,proto_config.first_byte_timeout(), proto_config.inference_timeout(), proto_config.embedding()} {} + : model_chosen_{proto_config.usemodel() ,proto_config.first_byte_timeout(), proto_config.inference_timeout()} {} -LLMInferenceFilter::LLMInferenceFilter(LLMInferenceFilterConfigSharedPtr config, InferenceContextSharedPtr ctx) +LLMInferenceFilter::LLMInferenceFilter(LLMInferenceFilterConfigSharedPtr config, InferenceContextHashMapSharedPtr ctx) : config_(config), ctx_(ctx) {} LLMInferenceFilter::~LLMInferenceFilter() {} void LLMInferenceFilter::onDestroy() { if (id_task_ != -1) { - ctx_->modelInference([](ModelInferenceResult&&) { - }, std::make_shared("{}", false, ctx_->getId(), InferencetasktypeTypeCancel, id_task_), inference_timeout_); + (*ctx_)[model_name_]->modelInference([](ModelInferenceResult&&) { + }, std::make_shared("{}", false, (*ctx_)[model_name_]->getId(), InferencetasktypeTypeCancel, id_task_), inference_timeout_); } } -const ModelParameter LLMInferenceFilter::modelParameter() const { - return config_->modelParameter(); -} - -const ModelPath LLMInferenceFilter::modelPath() const { - return config_->modelPath(); -} - Http::FilterHeadersStatus LLMInferenceFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { if (end_stream) { // If this is a header-only request, we don't need to do any inference. @@ -60,6 +47,7 @@ Http::FilterHeadersStatus LLMInferenceFilter::decodeHeaders(Http::RequestHeaderM return Http::FilterHeadersStatus::Continue; } else { auto per_route_config = per_route_inference_settings->modelChosen(); + model_name_ = per_route_config.model_name; first_byte_timeout_ = per_route_config.first_byte_timeout; inference_timeout_ = per_route_config.inference_timeout; } @@ -74,12 +62,17 @@ Http::FilterHeadersStatus LLMInferenceFilter::decodeHeaders(Http::RequestHeaderM return Http::FilterHeadersStatus::Continue; } + //check model + if (!ctx_->contains(model_name_)) { + return Http::FilterHeadersStatus::Continue; + } + return Http::FilterHeadersStatus::StopIteration; } Http::FilterDataStatus LLMInferenceFilter::decodeData(Buffer::Instance& data, bool end_stream) { if (!end_stream) { - id_task_ = ctx_->getId(); + id_task_ = (*ctx_)[model_name_]->getId(); getHeaders(std::make_shared(data.toString(), false, id_task_, task_type_, -1)); } return Http::FilterDataStatus::StopIterationNoBuffer; @@ -95,7 +88,7 @@ void LLMInferenceFilter::getHeaders(std::shared_ptr&& tas LLMInferenceFilterWeakPtr self = weak_from_this(); // The dispatcher needs to be captured because there's no guarantee that // decoder_callbacks_->dispatcher() is thread-safe. - ctx_->modelInference([self, &dispatcher = decoder_callbacks_->dispatcher()](ModelInferenceResult&& body) { + (*ctx_)[model_name_]->modelInference([self, &dispatcher = decoder_callbacks_->dispatcher()](ModelInferenceResult&& body) { // The callback is posted to the dispatcher to make sure it is called on the worker thread. dispatcher.post( [self, body = std::move(body)]() mutable { diff --git a/contrib/llm_inference/filters/http/source/llm_inference_filter.h b/contrib/llm_inference/filters/http/source/llm_inference_filter.h index bf9982df41571..d4072d8fbfe15 100644 --- a/contrib/llm_inference/filters/http/source/llm_inference_filter.h +++ b/contrib/llm_inference/filters/http/source/llm_inference_filter.h @@ -18,12 +18,14 @@ class LLMInferenceFilterConfig : public Router::RouteSpecificFilterConfig { public: LLMInferenceFilterConfig(const envoy::extensions::filters::http::llm_inference::v3::modelParameter& proto_config); - const ModelParameter& modelParameter() const {return modelParameter_;} - const ModelPath& modelPath() const {return modelPath_; } + const ModelParameter& modelParameter() const {return model_parameter_;} + const ModelPath& chatModelPath() const {return chat_modelpath_; } + const ModelPath& embeddingModelPath() const {return embedding_modelpath_; } private: - const ModelParameter modelParameter_; - const ModelPath modelPath_; + const ModelParameter model_parameter_; + const ModelPath chat_modelpath_; + const ModelPath embedding_modelpath_; }; using LLMInferenceFilterConfigSharedPtr = std::shared_ptr; @@ -32,10 +34,10 @@ class LLMInferenceFilterConfigPerRoute : public Router::RouteSpecificFilterConfi public: LLMInferenceFilterConfigPerRoute(const envoy::extensions::filters::http::llm_inference::v3::modelChosen& proto_config); - const ModelChosen& modelChosen() const {return modelChosen_;}; + const ModelChosen& modelChosen() const {return model_chosen_;}; private: - const ModelChosen modelChosen_; + const ModelChosen model_chosen_; }; using LLMInferenceFilterConfigPerRouteSharedPtr = std::shared_ptr; @@ -43,7 +45,7 @@ using LLMInferenceFilterConfigPerRouteSharedPtr = std::shared_ptr { public: - LLMInferenceFilter(LLMInferenceFilterConfigSharedPtr, InferenceContextSharedPtr); + LLMInferenceFilter(LLMInferenceFilterConfigSharedPtr, InferenceContextHashMapSharedPtr); ~LLMInferenceFilter(); // Http::StreamFilterBase @@ -63,18 +65,17 @@ class LLMInferenceFilter : public Http::PassThroughDecoderFilter, private: const LLMInferenceFilterConfigSharedPtr config_; - const InferenceContextSharedPtr ctx_; + const InferenceContextHashMapSharedPtr ctx_; Http::StreamDecoderFilterCallbacks* decoder_callbacks_; Event::TimerPtr timer_; InferenceTaskType task_type_; Buffer::InstancePtr request_data_; + std::string model_name_; int first_byte_timeout_ = 10; int inference_timeout_ = 90; int id_task_ = -1; bool header_ = false; - const ModelParameter modelParameter() const; - const ModelPath modelPath() const; }; using LLMInferenceFilterSharedPtr = std::shared_ptr; From c7fc2a7a214b4c6b6cff64a75939c6b4d9a4b746 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Sun, 22 Sep 2024 17:34:35 +0800 Subject: [PATCH 240/274] Remove locking operations, add use of system command log, fix a bug of request interrupt kvcache release twice --- .../http/source/inference/inference_context.cc | 15 ++++++++------- .../http/source/inference/inference_context.h | 6 ++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.cc b/contrib/llm_inference/filters/http/source/inference/inference_context.cc index 233bf5a2b7c55..2686eb2bdf90d 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.cc +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.cc @@ -4,9 +4,6 @@ #include "common/grammar-parser.cpp" #include "utils.hpp" #include "contrib/llm_inference/filters/http/source/inference/inference_context.h" -#include -#include -#include char const *LLAMA_COMMIT = ""; char const *LLAMA_COMPILER = ""; @@ -18,8 +15,6 @@ namespace Extensions { namespace HttpFilters { namespace LLMInference { -gpt_params params; - struct server_slot { int id; int id_task = -1; @@ -306,6 +301,13 @@ bool InferenceContext::loadModel(const ModelParameter& model_parameter, const st llama_backend_init(); llama_numa_init(params.numa); + LOG_INFO("system info", { + {"n_threads", params.n_threads}, + {"n_threads_batch", params.n_threads_batch}, + {"total_threads", std::thread::hardware_concurrency()}, + {"system_info", llama_print_system_info()}, + }); + // load the model { // dedicate one sequence to the system prompt @@ -490,7 +492,7 @@ void InferenceContext::processSingleTask(const server_task & task) { for (auto & use_slot : slots) { if (use_slot.id_task == task.id_target) { use_slot.release(); - break; + return; } } } break; @@ -936,7 +938,6 @@ void InferenceContext::updateSlots() { server_task task; task.type = SERVER_TASK_TYPE_NEXT_RESPONSE; task.id_target = -1; - task.id = getId(); inference_thread_.addTask([this, task](){ this->processSingleTask(task); }); diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.h b/contrib/llm_inference/filters/http/source/inference/inference_context.h index 66be185f406e7..9a5737b077f41 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.h +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.h @@ -3,10 +3,7 @@ #include "contrib/llm_inference/filters/http/source/inference/inference_thread.h" #include "contrib/llm_inference/filters/http/source/inference/inference_task.h" #include "source/extensions/filters/http/common/factory_base.h" - -#include -#include -#include +#include "common/common.h" #include "llama.h" namespace Envoy { @@ -60,6 +57,7 @@ class InferenceContext { bool add_bos_token = true; bool has_eos_token = true; int32_t n_ctx; // total context for all clients / slots + gpt_params params; // system prompt std::string system_prompt; From efc3e893a4bde3414bb0fe12ac1946e1d55c2ae2 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Thu, 26 Sep 2024 15:50:13 +0800 Subject: [PATCH 241/274] Improve inference performance, Divide the load function into loadLLM and LoadEmbedding, Change LOG_INFO to ENVOY_LOG --- bazel/foreign_cc/BUILD | 3 +- bazel/repository_locations.bzl | 6 +- contrib/llm_inference/filters/http/README.md | 146 ++++++++++ .../filters/http/source/config.cc | 28 +- .../source/inference/inference_context.cc | 256 +++++++++++++----- .../http/source/inference/inference_context.h | 10 +- .../filters/http/source/inference/utils.hpp | 50 +--- .../filters/http/test/{test.sh => test1.sh} | 2 +- .../filters/http/test/test_envoy.sh | 20 ++ .../filters/http/test/test_ollama.sh | 21 ++ source/common/common/logger.h | 3 +- 11 files changed, 421 insertions(+), 124 deletions(-) create mode 100644 contrib/llm_inference/filters/http/README.md rename contrib/llm_inference/filters/http/test/{test.sh => test1.sh} (96%) create mode 100755 contrib/llm_inference/filters/http/test/test_envoy.sh create mode 100755 contrib/llm_inference/filters/http/test/test_ollama.sh diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index edaf86d960dbb..fcdfee4e34a45 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -576,8 +576,9 @@ envoy_cmake( cache_entries = { "CMAKE_INSTALL_LIBDIR": "lib", "BUILD_SHARED_LIBS": "off", - "GGML_OPENMP": "off", + "CMAKE_BUILD_TYPE": "Release" }, + linkopts = ["-fopenmp"], lib_source = "@com_github_ggerganov_llama//:all", out_static_libs = select({ "//conditions:default": [ diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index e374478f0cd88..152ed8d83e306 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -362,12 +362,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "llama.cpp", project_desc = "LLM inference in C/C++", project_url = "https://github.com/ggerganov/llama.cpp", - version = "a07c32ea54850c989f0ef6989da5b955b77b7172", - sha256 = "4a5aaa9f4329dc5364ff6e4eea9ee977adce24051f5a6ba099faaaaa57a47149", + version = "947538acb8617756a092042ff7e58db18dde05ec", + sha256 = "566ec06009584be8303d5d4b0070ccb0b531695fef3008019e1db97bb7c427c4", strip_prefix = "llama.cpp-{version}", urls = ["https://github.com/ggerganov/llama.cpp/archive/{version}.zip"], use_category = ["dataplane_core"], - release_date = "2024-08-23", + release_date = "2024-09-06", cpe = "N/A", ), com_github_gperftools_gperftools = dict( diff --git a/contrib/llm_inference/filters/http/README.md b/contrib/llm_inference/filters/http/README.md new file mode 100644 index 0000000000000..594a89ae4af83 --- /dev/null +++ b/contrib/llm_inference/filters/http/README.md @@ -0,0 +1,146 @@ +# Filter 配置使用说明 + +## 概述 + +本项目实现了一个 HTTP Filter,该`filter`会解析推理请求,并调用异步推理线程实现推理过程,同时给该异步线程一个回调函数,实现流式传输的大模型推理过程。此文档将指导您如何配置和使用 `filter`,以及在性能方面与 Ollama 进行对比。 + +## 配置使用方式 + +### 配置 Filter + +1、在配置文件中,您需要首先设置filter级的配置,例如: + +```json +- name: envoy.filters.http.llm_inference + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelParameter + n_threads : 100 + n_parallel : 5 + chat_modelpath: { + "qwen2": "/home/yuanjq/model/qwen2-7b-instruct-q5_k_m.gguf", + "llama3": "/home/yuanjq/model/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf" + } + embedding_modelpath: { + "bge": "/home/yuanjq/model/bge-small-zh-v1.5-f32.gguf" + } +``` +其中 +n_threads: 表示推理线程能用的最大线程数 +n_parallel: 表示推理服务的最大并行请求数 +chat_modelpath: 表示chat模型本地路径 +embedding_modelpath: 表示embedding模型本地路径 + +2、在route_config中明确您对router级配置,即需要路由使用到的模型,例如: +``` +route_config: + name: route + virtual_hosts: + - name: llm_inference_service + domains: ["api.openai.com"] + routes: + - match: + prefix: "/v1/chat/completions" + typed_per_filter_config: + envoy.filters.http.llm_inference: + "@type": type.googleapis.com/envoy.extensions.filters.http.llm_inference.v3.modelChosen + usemodel: "qwen2" + first_byte_timeout : 4 + inference_timeout : 90 + direct_response: + status: 504 + body: + inline_string: "inference timeout" +``` +其中 +usemodel: 表示使用的模型,模型名字与modelpath里面设置的要对应 +first_byte_timeout: 表示首字节超时时间 +inference_timeout: 表示总推理超时时间 + +### 更新 Filter +本项目可以动态地加载和卸载使用模型,您只需添加或删除chat_modelpath、embedding_modelpath里面的模型文件路径,再更新配置文件,即可动态地加载和卸载模型。需要注意的是,卸载了模型之后要确保router级配置里面使用的模型没有被卸载。 + + +## 使用注意事项 + +1. **参数设置**:请根据具体场景调整 `n_threads` 、`n_parallel`的参数,以确保最佳性能。 +2. **模型选用**:确保模型在本地中的路径是正确的,否则加载模型的时候会报错;同时需要用户区分该模型是否是embedding模型。 +3. **并发处理**:确保服务器具有足够的内存和cpu资源,因为一般模型都有几个GB,同时模型推理是一个计算密集型任务,它需要在大量的数据上进行矩阵运算和张量操作。 + +## 性能对比与测试 + +为了评估 `filter` 的性能,现与 Ollama 进行以下对比: + +### 1. 相同模型与问题 + +确保在相同模型和问题的条件下进行测试,使用以下步骤: + +- **模型选择**:选择相同的预训练模型。 + 这里我们使用alibaba的**qwen2.5-7b-instruct-q3_k_m.gguf**模型 +- **输入问题**:使用相同的输入数据进行推理。 + 这里我们相同的请求,要求最多生成500个词: +``` +curl http://localhost:10000/v1/chat/completions \ + -H "host:api.openai.com" \ + -d '{ + "model": "qwen2.5", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Hello! Building a website can be done in 10 simple steps:" + } + ], + "stream": true, + "n_predict": 500 + }' + +``` +### 2. 并发测试 + +在不同的并发级别下(如 1、4、8 个请求)进行性能测试,并记录以下指标: + +- **资源开销**:内存使用情况。 +- **响应延迟**:每个请求的响应时间。 +- **推理延迟**:每个请求的推理时间。 + +其中,4、8个请求的时候,我们把内存使用、延迟时间求平均值作为指标 +### 3. 数据记录与分析 + +- 使用性能监控工具(htop)记录资源使用情况。 +- 记录时间并进行对比分析。 + +### 4. 对比结果 +- **内存资源开销** + +并发请求数 | 项目 | Ollama +-------- |-------- | ----- +1 | 7.1GB | 7.1GB +4 | 7.2GB| 7.2GB +8 | 7.2GB| 7.2GB + +- **响应延迟** + +并发请求数 | 项目 | Ollama +-------- |-------- | ----- +1 | 529.60 ms | 547.66 ms +4 | 638.31 ms| 1595.795 ms +8 | 708.50 ms| 3238.79 ms + +- **推理延迟** + +并发请求数 | 项目 | Ollama +-------- |-------- | ----- +1 | 20692.06 ms | 18716.46 ms +4 | 41225.08 ms| 41640.59ms +8 | 80240.99 ms | 94524.08ms + + +## 结论 + + + +通过上述方法,您可以有效地配置和使用 `filter`,并与 Ollama 在性能上进行对比。欢迎提交反馈和建议,以帮助我们持续改进项目。 + diff --git a/contrib/llm_inference/filters/http/source/config.cc b/contrib/llm_inference/filters/http/source/config.cc index 811ff380b638e..ae9938bfc4927 100644 --- a/contrib/llm_inference/filters/http/source/config.cc +++ b/contrib/llm_inference/filters/http/source/config.cc @@ -13,8 +13,8 @@ class InferenceSingleton : public Envoy::Singleton::Instance { InferenceSingleton(Thread::ThreadFactory& thread_factory) : inference_thread_(thread_factory) {} - std::shared_ptr load(std::shared_ptr singleton, const ModelParameter& model_parameter, - const std::string& model_name, const std::string& model_path, bool embedding) { + std::shared_ptr loadLLM(std::shared_ptr singleton, const ModelParameter& model_parameter, + const std::string& model_name, const std::string& model_path) { std::shared_ptr ctx; std::string model = model_name + " " + std::to_string(model_parameter.n_threads) + " " + std::to_string(model_parameter.n_parallel); auto it = ctx_.find(model); @@ -22,7 +22,25 @@ class InferenceSingleton : public Envoy::Singleton::Instance { ctx = it->second.lock(); } if (!ctx) { - ctx = std::make_shared(singleton, inference_thread_, model_parameter, model_name, model_path, embedding); + ctx = std::make_shared(singleton, inference_thread_, model_name); + ctx->loadLLM(model_parameter, model_path); + ctx_[model] = ctx; + } + return ctx; + } + + std::shared_ptr loadEmbedding(std::shared_ptr singleton, const ModelParameter& model_parameter, + const std::string& model_name, const std::string& model_path) { + std::shared_ptr ctx; + std::string model = model_name + " " + std::to_string(model_parameter.n_threads) + " " + std::to_string(model_parameter.n_parallel); + auto it = ctx_.find(model); + if (it != ctx_.end()) { + ctx = it->second.lock(); + } + if (!ctx) { + ctx = std::make_shared(singleton, inference_thread_, model_name); + ctx->loadEmbedding(model_parameter, model_path); + ctx_[model] = ctx; } return ctx; } @@ -52,13 +70,13 @@ Http::FilterFactoryCb LLMInferenceFilterConfigFactory::createFilterFactoryFromPr auto chat_modelpath = config->chatModelPath(); for (auto& model: chat_modelpath) { - ctx[model.first] = inference->load(inference, config->modelParameter(), model.first, model.second, false); + ctx[model.first] = inference->loadLLM(inference, config->modelParameter(), model.first, model.second); } auto embedding_modelpath = config->embeddingModelPath(); for (auto& model: embedding_modelpath) { - ctx[model.first] = inference->load(inference, config->modelParameter(), model.first, model.second, true); + ctx[model.first] = inference->loadEmbedding(inference, config->modelParameter(), model.first, model.second); } InferenceContextHashMapSharedPtr ctx_map = std::make_shared>(ctx); diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.cc b/contrib/llm_inference/filters/http/source/inference/inference_context.cc index 2686eb2bdf90d..66118e6e01580 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.cc +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.cc @@ -15,7 +15,8 @@ namespace Extensions { namespace HttpFilters { namespace LLMInference { -struct server_slot { +class server_slot: public Logger::Loggable { +public: int id; int id_task = -1; @@ -190,14 +191,14 @@ struct server_slot { t_prompt_processing, n_prompt_tokens_processed, t_token, n_tokens_second); - LOG_INFO(buffer, { + ENVOY_LOG(info, server_log(buffer, { {"id_slot", id}, {"id_task", id_task}, {"t_prompt_processing", t_prompt_processing}, {"n_prompt_tokens_processed", n_prompt_tokens_processed}, {"t_token", t_token}, {"n_tokens_second", n_tokens_second}, - }); + })); t_token = t_token_generation / n_decoded; n_tokens_second = 1e3 / t_token_generation * n_decoded; @@ -206,24 +207,24 @@ struct server_slot { t_token_generation, n_decoded, t_token, n_tokens_second); - LOG_INFO(buffer, { + ENVOY_LOG(info, server_log(buffer, { {"id_slot", id}, {"id_task", id_task}, {"t_token_generation", t_token_generation}, {"n_decoded", n_decoded}, {"t_token", t_token}, {"n_tokens_second", n_tokens_second}, - }); + })); snprintf(buffer, 512, " total time = %10.2f ms", t_prompt_processing + t_token_generation); - LOG_INFO(buffer, { + ENVOY_LOG(info, server_log(buffer, { {"id_slot", id}, {"id_task", id_task}, {"t_prompt_processing", t_prompt_processing}, {"t_token_generation", t_token_generation}, {"t_total", t_prompt_processing + t_token_generation}, - }); + })); } }; @@ -243,10 +244,7 @@ struct server_task { /* ================================================================= */ InferenceContext::InferenceContext(Singleton::InstanceSharedPtr owner, InferenceThread& inference_thread, - const ModelParameter& model_parameter, const std::string& model_name,const std::string& model_path, bool embedding):owner_(owner), - inference_thread_(inference_thread), model_name_(model_name) { - loadModel(model_parameter, model_path, embedding); -} + const std::string& model_name):owner_(owner), inference_thread_(inference_thread), model_name_(model_name){} /* ================================================================= */ /* Destructors */ @@ -286,10 +284,111 @@ int InferenceContext::getId() { /* load model */ /* ================================================================= */ -bool InferenceContext::loadModel(const ModelParameter& model_parameter, const std::string& model_path, bool embedding) { - params.n_threads = model_parameter.n_threads; +bool InferenceContext::loadLLM(const ModelParameter& model_parameter, const std::string& model_path) { + params.cpuparams.n_threads = model_parameter.n_threads; + params.n_parallel = model_parameter.n_parallel; + params.embedding = false; + params.use_mmap = false; + + params.model = model_path; + + gpt_params_handle_model_default(params); + + if (params.model_alias == "unknown") { + params.model_alias = params.model; + } + llama_backend_init(); + llama_numa_init(params.numa); + + ENVOY_LOG(info, server_log("system info",{ + {"n_threads", params.cpuparams.n_threads}, + {"total_threads", std::thread::hardware_concurrency()}, + {"system_info", llama_print_system_info()}, + })); + + // load the model + { + // dedicate one sequence to the system prompt + params.n_parallel += 1; + llama_init_result llama_init = llama_init_from_gpt_params(params); + model = llama_init.model; + ctx = llama_init.context; + params.n_parallel -= 1; // but be sneaky about it + if (model == nullptr) { + return false; + } + n_ctx = llama_n_ctx(ctx); + + add_bos_token = llama_add_bos_token(model); + has_eos_token = !llama_add_eos_token(model); + } + // init slot + { + const int32_t n_ctx_slot = n_ctx / params.n_parallel; + + ENVOY_LOG(info, server_log("initializing slots",{ + {"n_slots", params.n_parallel} + })); + + for (int i = 0; i < params.n_parallel; i++) { + server_slot slot; + + slot.id = i; + slot.n_ctx = n_ctx_slot; + slot.n_predict = params.n_predict; + + ENVOY_LOG(info, server_log("new slot",{ + {"id_slot", slot.id}, + {"n_ctx_slot", slot.n_ctx} + })); + + const int ga_n = params.grp_attn_n; + const int ga_w = params.grp_attn_w; + if (ga_n != 1) { + GGML_ASSERT(ga_n > 0 && "ga_n must be positive"); // NOLINT + GGML_ASSERT(ga_w % ga_n == 0 && "ga_w must be a multiple of ga_n"); // NOLINT + ENVOY_LOG(info, server_log("slot self-extend",{ + {"id_slot", slot.id}, + {"ga_n", ga_n}, + {"ga_w", ga_w} + })); + } + slot.ga_i = 0; + slot.ga_n = ga_n; + slot.ga_w = ga_w; + + slot.reset(); + + slots.push_back(slot); + + // the update_slots() logic will always submit a maximum of n_batch tokens + // note that n_batch can be > n_ctx (e.g. for non-causal attention models such as BERT where the KV cache is not used) + { + const int32_t n_batch = llama_n_batch(ctx); + + // only a single seq_id per token is needed + batch = llama_batch_init(std::max(n_batch, params.n_parallel), 0, 1); + } + } + } + + ENVOY_LOG(info, server_log("model loaded",{})); + // if a custom chat template is not supplied, we will use the one that comes with the model (if any) + { + llama_chat_message chat[] = {{"user", "test"}}; + + if (!(llama_chat_apply_template(model, nullptr, chat, 1, true, nullptr, 0) > 0)) { + chat_template_ = "chatml"; + } + } + return true; +} + +bool InferenceContext::loadEmbedding(const ModelParameter& model_parameter, const std::string& model_path) { + params.cpuparams.n_threads = model_parameter.n_threads; params.n_parallel = model_parameter.n_parallel; - params.embedding = embedding; + params.embedding = true; + params.use_mmap = false; params.model = model_path; @@ -301,12 +400,11 @@ bool InferenceContext::loadModel(const ModelParameter& model_parameter, const st llama_backend_init(); llama_numa_init(params.numa); - LOG_INFO("system info", { - {"n_threads", params.n_threads}, - {"n_threads_batch", params.n_threads_batch}, + ENVOY_LOG(info, server_log("system info",{ + {"n_threads", params.cpuparams.n_threads}, {"total_threads", std::thread::hardware_concurrency()}, {"system_info", llama_print_system_info()}, - }); + })); // load the model { @@ -328,7 +426,9 @@ bool InferenceContext::loadModel(const ModelParameter& model_parameter, const st { const int32_t n_ctx_slot = n_ctx / params.n_parallel; - LOG_INFO("initializing slots", {{"n_slots", params.n_parallel}}); + ENVOY_LOG(info, server_log("initializing slots",{ + {"n_slots", params.n_parallel} + })); for (int i = 0; i < params.n_parallel; i++) { server_slot slot; @@ -337,21 +437,21 @@ bool InferenceContext::loadModel(const ModelParameter& model_parameter, const st slot.n_ctx = n_ctx_slot; slot.n_predict = params.n_predict; - LOG_INFO("new slot", { + ENVOY_LOG(info, server_log("new slot",{ {"id_slot", slot.id}, {"n_ctx_slot", slot.n_ctx} - }); + })); const int ga_n = params.grp_attn_n; const int ga_w = params.grp_attn_w; if (ga_n != 1) { GGML_ASSERT(ga_n > 0 && "ga_n must be positive"); // NOLINT GGML_ASSERT(ga_w % ga_n == 0 && "ga_w must be a multiple of ga_n"); // NOLINT - LOG_INFO("slot self-extend", { + ENVOY_LOG(info, server_log("slot self-extend",{ {"id_slot", slot.id}, {"ga_n", ga_n}, {"ga_w", ga_w} - }); + })); } slot.ga_i = 0; slot.ga_n = ga_n; @@ -372,8 +472,7 @@ bool InferenceContext::loadModel(const ModelParameter& model_parameter, const st } } - LOG_INFO("model loaded", {}); - + ENVOY_LOG(info, server_log("model loaded",{})); // if a custom chat template is not supplied, we will use the one that comes with the model (if any) { llama_chat_message chat[] = {{"user", "test"}}; @@ -926,7 +1025,7 @@ void InferenceContext::updateSlots() { } if (all_idle) { - LOG_INFO("all slots are idle", {}); + ENVOY_LOG(info, server_log("all slots are idle",{})); if (system_prompt.empty() && clean_kv_cache) { // clear the entire KV cache llama_kv_cache_clear(ctx); @@ -952,18 +1051,17 @@ void InferenceContext::updateSlots() { const int n_keep = slot.params.n_keep + add_bos_token; const int n_left = static_cast(system_tokens.size()) + slot.n_past - n_keep; const int n_discard = slot.params.n_discard ? slot.params.n_discard : (n_left / 2); - - LOG_INFO("slot context shift", { - {"id_slot", slot.id}, - {"id_task", slot.id_task}, - {"n_keep", n_keep}, - {"n_left", n_left}, - {"n_discard", n_discard}, - {"n_ctx", n_ctx}, - {"n_past", slot.n_past}, - {"n_system_tokens", system_tokens.size()}, - {"n_cache_tokens", slot.cache_tokens.size()} - }); + ENVOY_LOG(info, server_log("slot context shift",{ + {"id_slot", slot.id}, + {"id_task", slot.id_task}, + {"n_keep", n_keep}, + {"n_left", n_left}, + {"n_discard", n_discard}, + {"n_ctx", n_ctx}, + {"n_past", slot.n_past}, + {"n_system_tokens", system_tokens.size()}, + {"n_cache_tokens", slot.cache_tokens.size()} + })); llama_kv_cache_seq_rm (ctx, slot.id + 1, n_keep , n_keep + n_discard); llama_kv_cache_seq_add(ctx, slot.id + 1, n_keep + n_discard, system_tokens.size() + slot.n_past, -n_discard); @@ -1010,7 +1108,13 @@ void InferenceContext::updateSlots() { int32_t n_batch = llama_n_batch(ctx); int32_t n_ubatch = llama_n_ubatch(ctx); + // track if this is an embedding or non-embedding batch + // if we've added sampled tokens above, we are in non-embedding mode + // -1: none, 0: non-embedding, 1: embedding + int32_t batch_type = batch.n_tokens > 0 ? 0 : -1; + // next, batch any pending prompts without exceeding n_batch + if (params.cont_batching || batch.n_tokens == 0) { for (auto & slot : slots) { // this slot still has a prompt to be processed if (slot.state == SLOT_STATE_IDLE && slot.command == SLOT_COMMAND_LOAD_PROMPT) { @@ -1023,11 +1127,12 @@ void InferenceContext::updateSlots() { slot.t_start_generation = 0; if (slot.infill) { + const bool add_bos = llama_add_bos_token(model); bool suff_rm_leading_spc = true; - // if (params.input_suffix.find_first_of(' ') == 0 && params.input_suffix.size() > 1) { - // params.input_suffix.erase(0, 1); - // suff_rm_leading_spc = false; - // } + if (params.input_suffix.find_first_of(' ') == 0 && params.input_suffix.size() > 1) { + params.input_suffix.erase(0, 1); + suff_rm_leading_spc = false; + } auto prefix_tokens = tokenize(ctx, slot.params.input_prefix, false); auto suffix_tokens = tokenize(ctx, slot.params.input_suffix, false); @@ -1038,11 +1143,21 @@ void InferenceContext::updateSlots() { } prefix_tokens.insert(prefix_tokens.begin(), llama_token_prefix(model)); - prefix_tokens.insert(prefix_tokens.begin(), llama_token_bos(model)); // always add BOS - prefix_tokens.insert(prefix_tokens.end(), llama_token_suffix(model)); - prefix_tokens.insert(prefix_tokens.end(), suffix_tokens.begin(), suffix_tokens.end()); - prefix_tokens.push_back(llama_token_middle(model)); - prompt_tokens = prefix_tokens; + suffix_tokens.insert(suffix_tokens.begin(), llama_token_suffix(model)); + + auto embd_inp = params.spm_infill ? suffix_tokens : prefix_tokens; + auto embd_end = params.spm_infill ? prefix_tokens : suffix_tokens; + if (add_bos) { + embd_inp.insert(embd_inp.begin(), llama_token_bos(model)); + } + embd_inp.insert(embd_inp.end(), embd_end.begin(), embd_end.end()); + + const llama_token middle_token = llama_token_middle(model); + if (middle_token >= 0) { + embd_inp.push_back(middle_token); + } + + prompt_tokens = embd_inp; } else { prompt_tokens = tokenize(ctx, slot.prompt, system_prompt.empty()); // add BOS if there isn't system prompt } @@ -1052,10 +1167,10 @@ void InferenceContext::updateSlots() { // empty prompt passed -> release the slot and send empty response if (prompt_tokens.empty()) { - LOG_INFO("empty prompt - releasing slot", { - {"id_slot", slot.id}, - {"id_task", slot.id_task} - }); + ENVOY_LOG(info, server_log("empty prompt - releasing slot",{ + {"id_slot", slot.id}, + {"id_task", slot.id_task} + })); slot.state = SLOT_STATE_PROCESSING; slot.command = SLOT_COMMAND_NONE; slot.release(); @@ -1123,11 +1238,10 @@ void InferenceContext::updateSlots() { if (slot.n_past == slot.n_prompt_tokens && slot.n_past > 0) { // we have to evaluate at least 1 token to generate logits. - LOG_INFO("we have to evaluate at least 1 token to generate logits", { - { "id_slot", slot.id }, - { "id_task", slot.id_task } - }); - + ENVOY_LOG(info, server_log("we have to evaluate at least 1 token to generate logits",{ + {"id_slot", slot.id}, + {"id_task", slot.id_task} + })); slot.n_past--; if (slot.ga_i > 0) { slot.n_past_se--; @@ -1144,6 +1258,14 @@ void InferenceContext::updateSlots() { } } + // check that we are in the right batch_type, if not defer the slot + bool slot_type = slot.embedding ? 1 : 0; + if (batch_type == -1) { + batch_type = slot_type; + } else if (batch_type != slot_type) { + continue; + } + // keep only the common part int p0 = static_cast(system_tokens.size()) + slot.n_past; if (!llama_kv_cache_seq_rm(ctx, slot.id + 1, p0, -1)) { @@ -1166,11 +1288,11 @@ void InferenceContext::updateSlots() { // remove the non-common part from the cache slot.cache_tokens.resize(slot.n_past); - LOG_INFO("kv cache rm [p0, end)", { - { "id_slot", slot.id }, - { "id_task", slot.id_task }, - { "p0", p0 } - }); + ENVOY_LOG(info, server_log("kv cache rm [p0, end)",{ + {"id_slot", slot.id}, + {"id_task", slot.id_task}, + { "p0", p0} + })); int32_t slot_npast = slot.n_past_se > 0 ? slot.n_past_se : slot.n_past; int32_t ga_i = slot.ga_i; @@ -1217,10 +1339,15 @@ void InferenceContext::updateSlots() { break; } } + } if (batch.n_tokens == 0) { return; } + + // make sure we're in the right embedding mode + llama_set_embeddings(ctx, batch_type == 1); + // process the created batch of tokens for (int32_t i = 0; i < batch.n_tokens; i += n_batch) { @@ -1234,11 +1361,6 @@ void InferenceContext::updateSlots() { const int bd = (slot.ga_w / slot.ga_n) * (slot.ga_n - 1); const int dd = (slot.ga_w / slot.ga_n) - ib * bd - slot.ga_w; - LOG_TEE("\n"); - LOG_TEE("shift: [%6d, %6d] + %6d -> [%6d, %6d]\n", slot.ga_i, slot.n_past_se, ib * bd, slot.ga_i + ib * bd, slot.n_past_se + ib * bd); - LOG_TEE("div: [%6d, %6d] / %6d -> [%6d, %6d]\n", slot.ga_i + ib * bd, slot.ga_i + ib * bd + slot.ga_w, slot.ga_n, (slot.ga_i + ib * bd) / slot.ga_n, (slot.ga_i + ib * bd + slot.ga_w) / slot.ga_n); - LOG_TEE("shift: [%6d, %6d] + %6d -> [%6d, %6d]\n", slot.ga_i + ib * bd + slot.ga_w, slot.n_past_se + ib * bd, dd, slot.ga_i + ib * bd + slot.ga_w + dd, slot.n_past_se + ib * bd + dd); - llama_kv_cache_seq_add(ctx, slot.id + 1, slot.ga_i, slot.n_past_se, ib * bd); llama_kv_cache_seq_div(ctx, slot.id + 1, slot.ga_i + ib * bd, slot.ga_i + ib * bd + slot.ga_w, slot.ga_n); llama_kv_cache_seq_add(ctx, slot.id + 1, slot.ga_i + ib * bd + slot.ga_w, slot.n_past_se + ib * bd, dd); @@ -1246,8 +1368,6 @@ void InferenceContext::updateSlots() { slot.n_past_se -= bd; slot.ga_i += slot.ga_w / slot.ga_n; - - LOG_TEE("\nn_past_old = %d, n_past = %d, ga_i = %d\n\n", slot.n_past_se + bd, slot.n_past_se, slot.ga_i); } slot.n_past_se += n_tokens; diff --git a/contrib/llm_inference/filters/http/source/inference/inference_context.h b/contrib/llm_inference/filters/http/source/inference/inference_context.h index 9a5737b077f41..2b8c8ae1ee3a7 100644 --- a/contrib/llm_inference/filters/http/source/inference/inference_context.h +++ b/contrib/llm_inference/filters/http/source/inference/inference_context.h @@ -3,6 +3,7 @@ #include "contrib/llm_inference/filters/http/source/inference/inference_thread.h" #include "contrib/llm_inference/filters/http/source/inference/inference_task.h" #include "source/extensions/filters/http/common/factory_base.h" +#include "source/common/common/logger.h" #include "common/common.h" #include "llama.h" @@ -12,7 +13,7 @@ namespace HttpFilters { namespace LLMInference { struct server_task; -struct server_slot; +class server_slot; struct completion_token_output; struct ModelInferenceResult { @@ -24,12 +25,13 @@ struct ModelInferenceResult { using LookupBodyCallback = std::function; -class InferenceContext { +class InferenceContext: public Logger::Loggable { public: - InferenceContext(Envoy::Singleton::InstanceSharedPtr, InferenceThread&, const ModelParameter&, const std::string&, const std::string&, bool); + InferenceContext(Envoy::Singleton::InstanceSharedPtr, InferenceThread&, const std::string&); ~InferenceContext(); - bool loadModel(const ModelParameter&, const std::string&, bool); + bool loadLLM(const ModelParameter&, const std::string&); + bool loadEmbedding(const ModelParameter&, const std::string&); void modelInference(LookupBodyCallback&& cb, std::shared_ptr&&, int&); int getId(); diff --git a/contrib/llm_inference/filters/http/source/inference/utils.hpp b/contrib/llm_inference/filters/http/source/inference/utils.hpp index 84b681817a84c..e7963c69dd0ec 100644 --- a/contrib/llm_inference/filters/http/source/inference/utils.hpp +++ b/contrib/llm_inference/filters/http/source/inference/utils.hpp @@ -65,48 +65,16 @@ struct completion_token_output { std::vector probs; }; -#define LOG_INFO( MSG, ...) server_log("INFO", __func__, __LINE__, MSG, __VA_ARGS__) - -static inline void server_log(const char * level, const char * function, int line, const char * message, const json & extra) { - std::stringstream ss_tid; - ss_tid << std::this_thread::get_id(); - json log = json{ - {"tid", ss_tid.str()}, - {"timestamp", time(nullptr)}, - }; - - if (1) { - log.merge_patch({ - {"level", level}, - {"function", function}, - {"line", line}, - {"msg", message}, - }); - - if (!extra.empty()) { - log.merge_patch(extra); - } - - printf("%s\n", log.dump(-1, ' ', false, json::error_handler_t::replace).c_str()); - } else { - char buf[1024]; - snprintf(buf, 1024, "%4s [%24s] %s", level, function, message); - - if (!extra.empty()) { - log.merge_patch(extra); - } - std::stringstream ss; - ss << buf << " |"; - for (const auto & el : log.items()) - { - const std::string value = el.value().dump(-1, ' ', false, json::error_handler_t::replace); - ss << " " << el.key() << "=" << value; - } - - const std::string str = ss.str(); - printf("%.*s\n", static_cast(str.size()), str.data()); +static inline std::string server_log(const char * message, const json & extra) { + json log; + log.merge_patch({ + {"msg", message}, + }); + + if (!extra.empty()) { + log.merge_patch(extra); } - fflush(stdout); + return log.dump(-1, ' ', false, json::error_handler_t::replace); } template diff --git a/contrib/llm_inference/filters/http/test/test.sh b/contrib/llm_inference/filters/http/test/test1.sh similarity index 96% rename from contrib/llm_inference/filters/http/test/test.sh rename to contrib/llm_inference/filters/http/test/test1.sh index 3843a67559565..f9241f22aa0af 100755 --- a/contrib/llm_inference/filters/http/test/test.sh +++ b/contrib/llm_inference/filters/http/test/test1.sh @@ -4,7 +4,7 @@ do curl -s http://localhost:10000/v1/chat/completions \ -H "host:api.openai.com" \ -d '{ - "model": "qwen2", + "model": "qwen2.5", "messages": [ { "role": "system", diff --git a/contrib/llm_inference/filters/http/test/test_envoy.sh b/contrib/llm_inference/filters/http/test/test_envoy.sh new file mode 100755 index 0000000000000..621903478506a --- /dev/null +++ b/contrib/llm_inference/filters/http/test/test_envoy.sh @@ -0,0 +1,20 @@ +for i in {1..9} +do + curl -s http://localhost:10000/v1/chat/completions \ + -H "host:api.openai.com" \ + -d '{ + "model": "qwen2.5", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Hello! Building a website can be done in 10 simple steps:" + } + ], + "stream": true, + "n_predict": 500 + }' > /dev/null & +done \ No newline at end of file diff --git a/contrib/llm_inference/filters/http/test/test_ollama.sh b/contrib/llm_inference/filters/http/test/test_ollama.sh new file mode 100755 index 0000000000000..8117309f4bcfa --- /dev/null +++ b/contrib/llm_inference/filters/http/test/test_ollama.sh @@ -0,0 +1,21 @@ +for i in {1..9} +do + curl -s http://localhost:8080/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer no-key" \ + -d '{ + "model": "qwen2.5", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Hello! Building a website can be done in 10 simple steps:" + } + ], + "stream": true, + "n_predict": 500 + }' > /dev/null & +done \ No newline at end of file diff --git a/source/common/common/logger.h b/source/common/common/logger.h index cab4e9731a18f..9eb222b926ce8 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -93,7 +93,8 @@ const static bool should_log = true; FUNCTION(udp) \ FUNCTION(wasm) \ FUNCTION(websocket) \ - FUNCTION(golang) + FUNCTION(golang) \ + FUNCTION(llm_inference) // clang-format off enum class Id { From 9e5aae5f851cb522db52b3cd9de1a8f9729a5b03 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Sat, 28 Sep 2024 17:07:23 +0800 Subject: [PATCH 242/274] update test data --- contrib/llm_inference/filters/http/README.md | 16 ++++++------- .../filters/http/test/test_envoy.sh | 2 +- .../filters/http/test/test_ollama.sh | 23 ++++--------------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/contrib/llm_inference/filters/http/README.md b/contrib/llm_inference/filters/http/README.md index 594a89ae4af83..23c25786bc47a 100644 --- a/contrib/llm_inference/filters/http/README.md +++ b/contrib/llm_inference/filters/http/README.md @@ -107,8 +107,8 @@ curl http://localhost:10000/v1/chat/completions \ - **推理延迟**:每个请求的推理时间。 其中,4、8个请求的时候,我们把内存使用、延迟时间求平均值作为指标 -### 3. 数据记录与分析 - +### 3. cpu核数设置与数据记录 +- cpu使用8核,即n_threads = 8 - 使用性能监控工具(htop)记录资源使用情况。 - 记录时间并进行对比分析。 @@ -125,17 +125,17 @@ curl http://localhost:10000/v1/chat/completions \ 并发请求数 | 项目 | Ollama -------- |-------- | ----- -1 | 529.60 ms | 547.66 ms -4 | 638.31 ms| 1595.795 ms -8 | 708.50 ms| 3238.79 ms +1 | 2633.20 ms / 34 tokens | 1336.57 ms / 15 tokens +4 | 2873.74 ms / 34 tokens | 2196.26 ms / 15 tokens +8 | 2969.98 ms / 34 tokens | 2077.51 ms / 15 tokens - **推理延迟** 并发请求数 | 项目 | Ollama -------- |-------- | ----- -1 | 20692.06 ms | 18716.46 ms -4 | 41225.08 ms| 41640.59ms -8 | 80240.99 ms | 94524.08ms +1 | 55543.16 ms | 62373.26 ms +4 | 169539.01 ms| 231860.54ms +8 | 316113.34 ms | 477764.59 ms ## 结论 diff --git a/contrib/llm_inference/filters/http/test/test_envoy.sh b/contrib/llm_inference/filters/http/test/test_envoy.sh index 621903478506a..08223bf7372b6 100755 --- a/contrib/llm_inference/filters/http/test/test_envoy.sh +++ b/contrib/llm_inference/filters/http/test/test_envoy.sh @@ -1,4 +1,4 @@ -for i in {1..9} +for i in {1..8} do curl -s http://localhost:10000/v1/chat/completions \ -H "host:api.openai.com" \ diff --git a/contrib/llm_inference/filters/http/test/test_ollama.sh b/contrib/llm_inference/filters/http/test/test_ollama.sh index 8117309f4bcfa..e86c6c27e303b 100755 --- a/contrib/llm_inference/filters/http/test/test_ollama.sh +++ b/contrib/llm_inference/filters/http/test/test_ollama.sh @@ -1,21 +1,6 @@ -for i in {1..9} +for i in {1..8} do - curl -s http://localhost:8080/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer no-key" \ - -d '{ - "model": "qwen2.5", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant." - }, - { - "role": "user", - "content": "Hello! Building a website can be done in 10 simple steps:" - } - ], - "stream": true, - "n_predict": 500 - }' > /dev/null & +curl -s localhost:11434/api/generate -d '{ +"model":"qwen2.5","options":{"num_thread":8,"num_predict":500},"prompt":"Hello! Building a website can be done in 10 simple steps:","stream":true}' > /dev/null & + done \ No newline at end of file From 6d84641516559c0bdc9849d44d6364190d112972 Mon Sep 17 00:00:00 2001 From: YJQ1101 <1315469765@qq.com> Date: Sat, 28 Sep 2024 18:21:26 +0800 Subject: [PATCH 243/274] update README.md --- contrib/llm_inference/filters/http/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/llm_inference/filters/http/README.md b/contrib/llm_inference/filters/http/README.md index 23c25786bc47a..6ccc5f6bcc163 100644 --- a/contrib/llm_inference/filters/http/README.md +++ b/contrib/llm_inference/filters/http/README.md @@ -115,7 +115,7 @@ curl http://localhost:10000/v1/chat/completions \ ### 4. 对比结果 - **内存资源开销** -并发请求数 | 项目 | Ollama +并发请求数 | LLM Inference Filter | Ollama -------- |-------- | ----- 1 | 7.1GB | 7.1GB 4 | 7.2GB| 7.2GB @@ -123,7 +123,7 @@ curl http://localhost:10000/v1/chat/completions \ - **响应延迟** -并发请求数 | 项目 | Ollama +并发请求数 | LLM Inference Filter | Ollama -------- |-------- | ----- 1 | 2633.20 ms / 34 tokens | 1336.57 ms / 15 tokens 4 | 2873.74 ms / 34 tokens | 2196.26 ms / 15 tokens @@ -131,7 +131,7 @@ curl http://localhost:10000/v1/chat/completions \ - **推理延迟** -并发请求数 | 项目 | Ollama +并发请求数 | LLM Inference Filter | Ollama -------- |-------- | ----- 1 | 55543.16 ms | 62373.26 ms 4 | 169539.01 ms| 231860.54ms From 825bcabac5640019e35a8cdb0ca6913d5aa36a84 Mon Sep 17 00:00:00 2001 From: CH3CHO Date: Wed, 16 Oct 2024 12:35:27 +0800 Subject: [PATCH 244/274] fix: Update envoy dependencies --- bazel/repository_locations.bzl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 1bfcd01175ead..defb0d288cd15 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -421,17 +421,17 @@ REPOSITORY_LOCATIONS_SPEC = dict( com_github_intel_ipp_crypto_crypto_mb = dict( project_name = "libipp-crypto", project_desc = "Intel® Integrated Performance Primitives Cryptography", - project_url = "https://github.com/intel/ipp-crypto", + project_url = "https://github.com/intel/cryptography-primitives", version = "2021.6", - sha256 = "632cc5ba54413eeab575682619c05d247e9b7f2fc58ea3e5f4a02bdcab3e6b78", - strip_prefix = "ipp-crypto-ippcp_{version}", - urls = ["https://github.com/intel/ipp-crypto/archive/ippcp_{version}.tar.gz"], + sha256 = "a52bf15208d493adb846994f2ce928bd02c74fd8ff3a2def2fca7b072d67e6bf", + strip_prefix = "cryptography-primitives-ippcp_{version}", + urls = ["https://github.com/intel/cryptography-primitives/archive/ippcp_{version}.tar.gz"], release_date = "2022-08-09", use_category = ["dataplane_ext"], extensions = ["envoy.tls.key_providers.cryptomb"], cpe = "cpe:2.3:a:intel:cryptography_for_intel_integrated_performance_primitives:*", license = "Apache-2.0", - license_url = "https://github.com/intel/ipp-crypto/blob/ippcp_{version}/LICENSE", + license_url = "https://github.com/intel/cryptography-primitives/blob/ippcp_{version}/LICENSE", ), com_github_intel_qatlib = dict( project_name = "qatlib", @@ -1165,7 +1165,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_url = "https://quiche.googlesource.com/googleurl", # Static snapshot of https://quiche.googlesource.com/googleurl/+archive/dd4080fec0b443296c0ed0036e1e776df8813aa7.tar.gz version = "dd4080fec0b443296c0ed0036e1e776df8813aa7", - sha256 = "59f14d4fb373083b9dc8d389f16bbb817b5f936d1d436aa67e16eb6936028a51", + sha256 = "fc694942e8a7491dcc1dde1bddf48a31370a1f46fef862bc17acf07c34dc6325", urls = ["https://storage.googleapis.com/quiche-envoy-integration/{version}.tar.gz"], use_category = ["controlplane", "dataplane_core"], extensions = [], From 0cf0aea91c39e41ca7fb645280623ce6418e6356 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 25 Nov 2024 09:52:04 +0800 Subject: [PATCH 245/274] optimize custom-response filter for ai-fallback --- .../redirect_policy/v3/redirect_policy.proto | 2 + source/common/http/conn_manager_impl.cc | 7 ++++ source/common/http/conn_manager_utility.cc | 3 ++ source/common/http/headers.h | 5 +++ .../custom_response/custom_response_filter.cc | 35 ++++++---------- .../custom_response/custom_response_filter.h | 3 -- .../redirect_policy/redirect_policy.cc | 40 ++++++++++++++----- .../redirect_policy/redirect_policy.h | 1 + 8 files changed, 61 insertions(+), 35 deletions(-) diff --git a/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto b/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto index c984b72ec2d5f..35d235f2df02c 100644 --- a/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto +++ b/api/envoy/extensions/http/custom_response/redirect_policy/v3/redirect_policy.proto @@ -51,6 +51,8 @@ message RedirectPolicy { // - `regex_rewrite` config.route.v3.RedirectAction redirect_action = 2; + string uri_from_response_header = 106 [(validate.rules).string = {min_len: 1}]; + google.protobuf.BoolValue use_original_request_uri = 107; } diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index bc78085466313..9830cf95c49c3 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1349,6 +1349,13 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt filter_manager_.setDownstreamRemoteAddress(mutate_result.final_remote_address); } +#if defined(HIGRESS) + else { + request_headers_->setReferenceKey( + Http::CustomHeaders::get().AliExtendedValues.XEnvoyInternalRoute, + Http::CustomHeaders::get().EnvoyIntenralRouteValues.True); + } +#endif ASSERT(filter_manager_.streamInfo().downstreamAddressProvider().remoteAddress() != nullptr); ASSERT(!cached_route_); diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 984b33ba96686..4a15928d5ee7c 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -325,6 +325,9 @@ void ConnectionManagerUtility::cleanInternalHeaders( request_headers.removeEnvoyIpTags(); request_headers.removeEnvoyOriginalUrl(); request_headers.removeEnvoyHedgeOnPerTryTimeout(); +#if defined(HIGRESS) + request_headers.remove(Http::CustomHeaders::get().AliExtendedValues.XEnvoyInternalRoute); +#endif for (const LowerCaseString& header : internal_only_headers) { request_headers.remove(header); diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 0ecf0c1fb2951..bcb50beb92094 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -138,7 +138,12 @@ class CustomHeaderValues { const LowerCaseString EnvoyOriginalHost{"original-host"}; const LowerCaseString XEnvoyOriginalHost{"x-envoy-original-host"}; const LowerCaseString XEnvoyRouteIdentifier{"x-envoy-route-identifier"}; + const LowerCaseString XEnvoyInternalRoute{"x-envoy-internal-route"}; } AliExtendedValues; + + struct { + const std::string True{"true"}; + } EnvoyIntenralRouteValues; #endif }; diff --git a/source/extensions/filters/http/custom_response/custom_response_filter.cc b/source/extensions/filters/http/custom_response/custom_response_filter.cc index caf9513c51109..d127f7425775b 100644 --- a/source/extensions/filters/http/custom_response/custom_response_filter.cc +++ b/source/extensions/filters/http/custom_response/custom_response_filter.cc @@ -20,11 +20,7 @@ Http::FilterHeadersStatus CustomResponseFilter::decodeHeaders(Http::RequestHeade downstream_headers_ = &header_map; const FilterConfig* config = nullptr; if (decoder_callbacks_ && decoder_callbacks_->route()) { - const auto* config = - Http::Utility::resolveMostSpecificPerFilterConfig(decoder_callbacks_); - if (config != nullptr) { - has_rules_ = true; - } + config = Http::Utility::resolveMostSpecificPerFilterConfig(decoder_callbacks_); } if (config == nullptr) { config = config_.get(); @@ -77,24 +73,19 @@ Http::FilterHeadersStatus CustomResponseFilter::encodeHeaders(Http::ResponseHead // policy. Note that since the traversal is least to most specific, we can't // return early when a match is found. PolicySharedPtr policy; -#if defined(HIGRESS) - if (has_rules_) { -#endif - decoder_callbacks_->traversePerFilterConfig( - [&policy, &headers, this](const Router::RouteSpecificFilterConfig& config) { - const FilterConfig* typed_config = dynamic_cast(&config); - if (typed_config) { - // Check if a match is found first to avoid overwriting policy with an - // empty shared_ptr. - auto maybe_policy = typed_config->getPolicy(headers, encoder_callbacks_->streamInfo()); - if (maybe_policy) { - policy = maybe_policy; - } + decoder_callbacks_->traversePerFilterConfig( + [&policy, &headers, this](const Router::RouteSpecificFilterConfig& config) { + const FilterConfig* typed_config = dynamic_cast(&config); + if (typed_config) { + // Check if a match is found first to avoid overwriting policy with an + // empty shared_ptr. + auto maybe_policy = typed_config->getPolicy(headers, encoder_callbacks_->streamInfo()); + if (maybe_policy) { + policy = maybe_policy; } - }); -#if defined(HIGRESS) - } -#endif + } + }); + if (!policy) { policy = config_->getPolicy(headers, encoder_callbacks_->streamInfo()); } diff --git a/source/extensions/filters/http/custom_response/custom_response_filter.h b/source/extensions/filters/http/custom_response/custom_response_filter.h index e9f8183e6480a..f06c475e3f257 100644 --- a/source/extensions/filters/http/custom_response/custom_response_filter.h +++ b/source/extensions/filters/http/custom_response/custom_response_filter.h @@ -50,9 +50,6 @@ class CustomResponseFilter : public ::Envoy::Http::PassThroughFilter, const std::shared_ptr config_; ::Envoy::Http::RequestHeaderMap* downstream_headers_ = nullptr; bool on_local_reply_called_ = false; -#if defined(HIGRESS) - bool has_rules_ = false; -#endif }; } // namespace CustomResponse diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc index 39066fc58d1cd..b1a71a7692edf 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc @@ -64,6 +64,9 @@ RedirectPolicy::RedirectPolicy( createRedirectConfig(config.redirect_action())) : nullptr}, #if defined(HIGRESS) + uri_from_response_header_{config.has_uri_from_response_header() + ? absl::optional(config.uri_from_response_header()) + : absl::nullopt}, use_original_request_uri_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_request_uri, false)), keep_original_response_code_( @@ -85,7 +88,8 @@ RedirectPolicy::RedirectPolicy( modify_request_headers_action_(createModifyRequestHeadersAction(config, context)) { #if defined(HIGRESS) // Ensure that exactly one of uri_ or redirect_action_ or use_original_request_uri_ is specified. - ASSERT(int(uri_ != nullptr) + int(redirect_action_ != nullptr) + int(use_original_request_uri_) == + ASSERT(int(uri_ != nullptr) + int(redirect_action_ != nullptr) + int(use_original_request_uri_) + + int(uri_from_response_header_.has_value()) == 1); #else // Ensure that exactly one of uri_ or redirect_action_ is specified. @@ -236,17 +240,33 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( downstream_headers->setHost(real_original_host); downstream_headers->setPath(real_original_path); } else { + std::string uri; + if (uri_from_response_header_.has_value()) { + auto custom_location = headers.get(Envoy::Http::LowerCaseString(*uri_from_response_header_)); + uri = custom_location.empty() ? "" : custom_location[0]->value().getStringView(); + if (uri == "" || !absolute_url.initialize(uri, false)) { + stats_.custom_response_invalid_uri_.inc(); + ENVOY_LOG(debug, "uri specified in response header is invalid"); + return ::Envoy::Http::FilterHeadersStatus::Continue; + } + } else { + uri = uri_ ? *uri_ : ::Envoy::Http::Utility::newUri(*redirect_action_, *downstream_headers); +#else + std::string uri(uri_ ? *uri_ + : ::Envoy::Http::Utility::newUri(*redirect_action_, *downstream_headers)); #endif - std::string uri(uri_ ? *uri_ - : ::Envoy::Http::Utility::newUri(*redirect_action_, *downstream_headers)); - if (!absolute_url.initialize(uri, false)) { - stats_.custom_response_invalid_uri_.inc(); - // We could potentially get an invalid url only if redirect_action_ was specified instead - // of uri_. Hence, assert that uri_ is not set. - ENVOY_BUG(!static_cast(uri_), - "uri should not be invalid as this was already validated during config load"); - return ::Envoy::Http::FilterHeadersStatus::Continue; + + if (!absolute_url.initialize(uri, false)) { + stats_.custom_response_invalid_uri_.inc(); + // We could potentially get an invalid url only if redirect_action_ was specified instead + // of uri_. Hence, assert that uri_ is not set. + ENVOY_BUG(!static_cast(uri_), + "uri should not be invalid as this was already validated during config load"); + return ::Envoy::Http::FilterHeadersStatus::Continue; + } +#if defined(HIGRESS) } +#endif downstream_headers->setScheme(absolute_url.scheme()); downstream_headers->setHost(absolute_url.hostAndPort()); diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.h b/source/extensions/http/custom_response/redirect_policy/redirect_policy.h index da399a98f6b1e..fdee2d6c23c05 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.h +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.h @@ -71,6 +71,7 @@ class RedirectPolicy : public Extensions::HttpFilters::CustomResponse::Policy, const std::unique_ptr uri_; const std::unique_ptr redirect_action_; #if defined(HIGRESS) + absl::optional uri_from_response_header_; bool use_original_request_uri_; bool keep_original_response_code_; uint32_t max_internal_redirects_; From 13aee61c753e4270477e95895f847efd1dbefcc0 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 25 Nov 2024 09:53:06 +0800 Subject: [PATCH 246/274] optimize srds for route fallback --- .../v3/http_connection_manager.proto | 2 + envoy/router/scopes.h | 12 ++-- source/common/http/conn_manager_config.h | 5 ++ source/common/http/conn_manager_impl.cc | 29 +++++++- source/common/http/conn_manager_impl.h | 3 + source/common/router/scoped_config_impl.cc | 34 ++++++--- source/common/router/scoped_config_impl.h | 9 +++ .../network/http_connection_manager/config.cc | 2 + .../network/http_connection_manager/config.h | 4 ++ source/server/admin/admin.h | 1 + .../http/conn_manager_impl_fuzz_test.cc | 2 + test/common/http/conn_manager_impl_test_2.cc | 26 +++---- .../common/http/conn_manager_impl_test_base.h | 2 + test/common/router/scoped_rds_test.cc | 71 +++++++++++++++++++ test/mocks/http/mocks.h | 1 + test/mocks/router/mocks.cc | 1 + test/mocks/router/mocks.h | 4 ++ 17 files changed, 181 insertions(+), 27 deletions(-) diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index b5c5ce51fcbcb..dadd19daa93cd 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -1125,6 +1125,8 @@ message ScopedRoutes { // in this message. ScopedRds scoped_rds = 5; } + + google.protobuf.BoolValue retry_other_scope_when_not_found = 101; } message ScopedRds { diff --git a/envoy/router/scopes.h b/envoy/router/scopes.h index 8e4dbb7a646b5..fa9cf737ffc56 100644 --- a/envoy/router/scopes.h +++ b/envoy/router/scopes.h @@ -123,11 +123,15 @@ class ScopedConfig : public Envoy::Config::ConfigProvider::Config { virtual ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr& scope_key) const PURE; #if defined(HIGRESS) - virtual ConfigConstSharedPtr - getRouteConfig(const ScopeKeyBuilder* builder, const Http::HeaderMap& headers, - const StreamInfo::StreamInfo* info = nullptr) const PURE; + virtual ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder* builder, + const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info) const PURE; + virtual ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder* builder, + const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info, + std::function& recompute) const PURE; virtual ScopeKeyPtr computeScopeKey(const ScopeKeyBuilder*, const Http::HeaderMap&, - const StreamInfo::StreamInfo* = nullptr) const { + const StreamInfo::StreamInfo*) const { return {}; }; #endif diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index ce2910e4182b7..f3652f7c64aa4 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -545,6 +545,11 @@ class ConnectionManagerConfig { * Zero indicates this behavior is disabled. */ virtual std::chrono::seconds keepaliveHeaderTimeout() const PURE; + /** + * @return whether to retry to other scoped routes when the target route is not found in the + * current scope, supported only when using scoped_routes. + */ + virtual bool retryOtherScopeWhenNotFound() const PURE; #endif }; } // namespace Http diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 9830cf95c49c3..fd4faff0e32f4 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1209,7 +1209,14 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt connection_manager_.config_.scopeKeyBuilder().has_value()) { snapped_scoped_routes_config_ = connection_manager_.config_.scopedRouteConfigProvider()->config(); +#if defined(HIGRESS) + // It is only used to determine whether to remove specific internal headers, but at the cost + // of an additional routing calculation. In our scenario, there is no removal of internal + // headers, so there is no need to calculate the route here. + snapped_route_config_ = std::make_shared(); +#else snapScopedRouteConfig(); +#endif } } else { snapped_route_config_ = connection_manager_.config_.routeConfigProvider()->configCast(); @@ -1524,9 +1531,10 @@ void ConnectionManagerImpl::startDrainSequence() { void ConnectionManagerImpl::ActiveStream::snapScopedRouteConfig() { #if defined(HIGRESS) + snapped_scoped_routes_recompute_ = nullptr; snapped_route_config_ = snapped_scoped_routes_config_->getRouteConfig( connection_manager_.config_.scopeKeyBuilder().ptr(), *request_headers_, - &connection()->streamInfo()); + &connection()->streamInfo(), snapped_scoped_routes_recompute_); #else // NOTE: if a RDS subscription hasn't got a RouteConfiguration back, a Router::NullConfigImpl is // returned, in that case we let it pass. @@ -1653,6 +1661,25 @@ void ConnectionManagerImpl::ActiveStream::refreshCachedRoute(const Router::Route } } +#if defined(HIGRESS) + if (connection_manager_.config_.retryOtherScopeWhenNotFound()) { + while (route == nullptr && snapped_scoped_routes_recompute_ != nullptr) { + ASSERT(snapped_scoped_routes_config_ != nullptr); + snapped_route_config_ = snapped_scoped_routes_config_->getRouteConfig( + connection_manager_.config_.scopeKeyBuilder().ptr(), *request_headers_, + &connection()->streamInfo(), snapped_scoped_routes_recompute_); + if (snapped_route_config_ == nullptr) { + break; + } + route = snapped_route_config_->route(cb, *request_headers_, filter_manager_.streamInfo(), + stream_id_); + ENVOY_STREAM_LOG(debug, + "after the route was not found, search again in other scopes and found:{}", + *this, route != nullptr); + } + } +#endif + setRoute(route); } diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 859fd18a546f2..f18177a21824d 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -486,6 +486,9 @@ class ConnectionManagerImpl : Logger::Loggable, // route configuration is updated frequently and the request is long-lived. Router::ConfigConstSharedPtr snapped_route_config_; Router::ScopedConfigConstSharedPtr snapped_scoped_routes_config_; +#if defined(HIGRESS) + std::function snapped_scoped_routes_recompute_; +#endif // This is used to track the route that has been cached in the request. And we will keep this // route alive until the request is finished. absl::optional cached_route_; diff --git a/source/common/router/scoped_config_impl.cc b/source/common/router/scoped_config_impl.cc index bd4a6412a3183..0ad3aabff5ef5 100644 --- a/source/common/router/scoped_config_impl.cc +++ b/source/common/router/scoped_config_impl.cc @@ -91,9 +91,10 @@ HostValueExtractorImpl::computeFragment(const Http::HeaderMap& headers, if (port_start != absl::string_view::npos) { host = host.substr(0, port_start); } - *recompute = [this, host, weak_recompute = ReComputeCbWeakPtr(recompute)]() mutable - -> std::unique_ptr { - return reComputeHelper(std::string(host), weak_recompute, 0); + *recompute = [this, host_str = std::string(host), + weak_recompute = ReComputeCbWeakPtr( + recompute)]() mutable -> std::unique_ptr { + return reComputeHelper(host_str, weak_recompute, 0); }; return std::make_unique(host); } @@ -136,13 +137,14 @@ ScopeKeyPtr ScopeKeyBuilderImpl::computeScopeKey(const Http::HeaderMap& headers, recompute = [&recompute, recompute_cbs]() mutable -> ScopeKeyPtr { ScopeKey new_key; for (auto& cb : *recompute_cbs) { + if (*cb == nullptr) { + recompute = nullptr; + return nullptr; + } auto new_fragment = (*cb)(); if (new_fragment == nullptr) { return nullptr; } - if (*cb == nullptr) { - recompute = nullptr; - } new_key.addFragment(std::move(new_fragment)); } return std::make_unique(std::move(new_key)); @@ -171,10 +173,14 @@ ScopeKeyPtr ScopedConfigImpl::computeScopeKey(const ScopeKeyBuilder* scope_key_b Router::ConfigConstSharedPtr ScopedConfigImpl::getRouteConfig(const ScopeKeyBuilder* scope_key_builder, - const Http::HeaderMap& headers, - const StreamInfo::StreamInfo* info) const { - std::function recompute; - ScopeKeyPtr scope_key = scope_key_builder->computeScopeKey(headers, info, recompute); + const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info, + std::function& recompute) const { + ScopeKeyPtr scope_key = nullptr; + if (recompute == nullptr) { + scope_key = scope_key_builder->computeScopeKey(headers, info, recompute); + } else { + scope_key = recompute(); + } if (scope_key == nullptr) { return nullptr; } @@ -189,6 +195,14 @@ ScopedConfigImpl::getRouteConfig(const ScopeKeyBuilder* scope_key_builder, return nullptr; } +Router::ConfigConstSharedPtr +ScopedConfigImpl::getRouteConfig(const ScopeKeyBuilder* scope_key_builder, + const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info) const { + std::function recompute; + return getRouteConfig(scope_key_builder, headers, info, recompute); +} + #endif bool ScopeKey::operator!=(const ScopeKey& other) const { return !(*this == other); } diff --git a/source/common/router/scoped_config_impl.h b/source/common/router/scoped_config_impl.h index 96425a4c74a2d..a71de917a91bb 100644 --- a/source/common/router/scoped_config_impl.h +++ b/source/common/router/scoped_config_impl.h @@ -178,6 +178,10 @@ class ScopedConfigImpl : public ScopedConfig { Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyPtr& scope_key) const override; #if defined(HIGRESS) + Router::ConfigConstSharedPtr + getRouteConfig(const ScopeKeyBuilder* scope_key_builder, const Http::HeaderMap& headers, + const StreamInfo::StreamInfo* info, + std::function& recompute) const override; Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder* scope_key_builder, const Http::HeaderMap& headers, const StreamInfo::StreamInfo* info) const override; @@ -202,6 +206,11 @@ class NullScopedConfigImpl : public ScopedConfig { return std::make_shared(); } #if defined(HIGRESS) + Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder*, const Http::HeaderMap&, + const StreamInfo::StreamInfo*, + std::function&) const override { + return std::make_shared(); + } Router::ConfigConstSharedPtr getRouteConfig(const ScopeKeyBuilder*, const Http::HeaderMap&, const StreamInfo::StreamInfo*) const override { return std::make_shared(); diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 1d6ce67d75c68..640f00d325960 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -498,6 +498,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( config, context_.getServerFactoryContext(), context_.initManager(), stats_prefix_, scoped_routes_config_provider_manager_); scope_key_builder_ = Router::ScopedRoutesConfigProviderUtil::createScopeKeyBuilder(config); + retry_other_scope_when_not_found_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config.scoped_routes(), retry_other_scope_when_not_found, true); break; case envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: RouteSpecifierCase::ROUTE_SPECIFIER_NOT_SET: diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 39c77861d6ddf..c105d807623a9 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -269,6 +269,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, } #if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } + bool retryOtherScopeWhenNotFound() const override { return retry_other_scope_when_not_found_; } #endif private: @@ -332,6 +333,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, // routes Router::ScopeKeyBuilderPtr scope_key_builder_; Config::ConfigProviderPtr scoped_routes_config_provider_; +#if defined(HIGRESS) + bool retry_other_scope_when_not_found_; +#endif std::chrono::milliseconds drain_timeout_; bool generate_request_id_; const bool preserve_external_request_id_; diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index 6f4233d575bc5..77d1778d2c524 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -229,6 +229,7 @@ class AdminImpl : public Admin, bool addProxyProtocolConnectionState() const override { return true; } #if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return {}; } + bool retryOtherScopeWhenNotFound() const override { return false; } #endif private: diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 0ec5bf4855998..d7ae59ec6877d 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -243,6 +243,7 @@ class FuzzConfig : public ConnectionManagerConfig { bool addProxyProtocolConnectionState() const override { return true; } #if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } + bool retryOtherScopeWhenNotFound() const override { return retry_other_scope_when_not_found_; } #endif const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager @@ -299,6 +300,7 @@ class FuzzConfig : public ConnectionManagerConfig { std::unique_ptr proxy_status_config_; #if defined(HIGRESS) std::chrono::seconds keepalive_header_timeout_{}; + bool retry_other_scope_when_not_found_ = false; #endif }; diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 93c71e44bb2d7..910164d4f2a1f 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2753,7 +2753,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteNotFound) { #if defined(HIGRESS) EXPECT_CALL(*static_cast( scopedRouteConfigProvider()->config().get()), - getRouteConfig(_, _, _)) + getRouteConfig(_, _, _, _)) .Times(2) .WillRepeatedly(Return(nullptr)); #else @@ -2796,7 +2796,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsUpdate) { #if defined(HIGRESS) EXPECT_CALL(*static_cast( scopedRouteConfigProvider()->config().get()), - getRouteConfig(_, _, _)) + getRouteConfig(_, _, _, _)) .Times(3) .WillOnce(Return(nullptr)) .WillOnce(Return(nullptr)) // refreshCachedRoute first time. @@ -2870,19 +2870,21 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsCrossScopeReroute) { #if defined(HIGRESS) EXPECT_CALL(*static_cast( scopedRouteConfigProvider()->config().get()), - getRouteConfig(_, _, _)) + getRouteConfig(_, _, _, _)) // 1. Snap scoped route config; // 2. refreshCachedRoute (both in decodeHeaders(headers,end_stream); // 3. then refreshCachedRoute triggered by decoder_filters_[1]->callbacks_->route(). .Times(3) - .WillRepeatedly(Invoke([&](const Router::ScopeKeyBuilder*, const Http::HeaderMap& headers, - const StreamInfo::StreamInfo*) -> Router::ConfigConstSharedPtr { - auto& test_headers = dynamic_cast(headers); - if (test_headers.get_("scope_key") == "foo") { - return route_config1; - } - return route_config2; - })); + .WillRepeatedly( + Invoke([&](const Router::ScopeKeyBuilder*, const Http::HeaderMap& headers, + const StreamInfo::StreamInfo*, + std::function&) -> Router::ConfigConstSharedPtr { + auto& test_headers = dynamic_cast(headers); + if (test_headers.get_("scope_key") == "foo") { + return route_config1; + } + return route_config2; + })); #else EXPECT_CALL(*static_cast(scopeKeyBuilder().ptr()), computeScopeKey(_)) @@ -2959,7 +2961,7 @@ TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteFound) { EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(fake_cluster1.get())); #if defined(HIGRESS) EXPECT_CALL(*scopedRouteConfigProvider()->config(), - getRouteConfig(_, _, _)) + getRouteConfig(_, _, _, _)) // 1. decodeHeaders() snapping route config. // 2. refreshCachedRoute() later in the same decodeHeaders(). .Times(2); diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 5472fb7a81394..44f5b20f2a05a 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -178,6 +178,7 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { #if defined(HIGRESS) std::chrono::seconds keepaliveHeaderTimeout() const override { return keepalive_header_timeout_; } + bool retryOtherScopeWhenNotFound() const override { return retry_other_scope_when_not_found_; } #endif // Simple helper to wrapper filter to the factory function. FilterFactoryCb createDecoderFilterFactoryCb(StreamDecoderFilterSharedPtr filter) { @@ -282,6 +283,7 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { bool add_proxy_protocol_connection_state_ = true; #if defined(HIGRESS) std::chrono::seconds keepalive_header_timeout_{}; + bool retry_other_scope_when_not_found_ = true; #endif const LocalReply::LocalReplyPtr local_reply_; diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index 510f59965d17a..fa16e2d523654 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -253,6 +253,77 @@ TEST_F(InlineScopedRoutesTest, InlineRouteConfigurations) { "foo"); } +#if defined(HIGRESS) +TEST_F(InlineScopedRoutesTest, InlineWildcardDomainFallback) { + server_factory_context_.cluster_manager_.initializeClusters({"baz"}, {}); + const std::string hcm_config = absl::StrCat(hcm_config_base, R"EOF( +scoped_routes: + name: $0 + scope_key_builder: + fragments: + - local_port_value_extractor: {} + - host_value_extractor: {} + scoped_route_configurations_list: + scoped_route_configurations: + - name: foo-scope + route_configuration: + name: foo + virtual_hosts: + - name: bar + domains: ["www.example.com"] + routes: + - match: { path: "/" } + route: { cluster: baz } + key: + fragments: + - string_key: "80" + - string_key: www.example.com + - name: foo2-scope + route_configuration: + name: foo2 + virtual_hosts: + - name: bar + domains: ["*.example.com"] + routes: + - match: { path: "/foo" } + route: { cluster: baz } + key: + fragments: + - string_key: "80" + - string_key: "*.example.com" +)EOF"); + const auto config = + parseHttpConnectionManagerFromYaml(absl::Substitute(hcm_config, "foo-scoped-routes")); + Envoy::Config::ConfigProviderPtr provider = ScopedRoutesConfigProviderUtil::create( + config, server_factory_context_, context_init_manager_, "foo.", *config_provider_manager_); + Envoy::Router::ScopeKeyBuilderPtr scope_key_builder = + ScopedRoutesConfigProviderUtil::createScopeKeyBuilder(config); + ASSERT_THAT(provider->config(), Not(IsNull())); + + NiceMock stream_info; + auto downstream_connection_info_provider = std::make_shared( + std::make_shared("127.0.0.1", 80), + std::make_shared("127.0.0.2", 1000)); + ON_CALL(stream_info, downstreamAddressProvider()) + .WillByDefault(ReturnPointee(downstream_connection_info_provider)); + + std::function recompute; + Http::TestRequestHeaderMapImpl headers = {{":authority", "www.example.com"}, + {":path", "/foo"}, + {":method", "GET"}, + {":scheme", "http"}, + {"x-forwarded-proto", "http"}}; + auto route_config = provider->config()->getRouteConfig( + scope_key_builder.get(), headers, &stream_info, recompute); + EXPECT_EQ(route_config->name(), "foo"); + EXPECT_EQ(route_config->route(headers, stream_info, 0), nullptr); + route_config = provider->config()->getRouteConfig( + scope_key_builder.get(), headers, &stream_info, recompute); + EXPECT_EQ(route_config->name(), "foo2"); + EXPECT_NE(route_config->route(headers, stream_info, 0), nullptr); +} +#endif + TEST_F(InlineScopedRoutesTest, ConfigLoadAndDump) { server_factory_context_.cluster_manager_.initializeClusters({"baz"}, {}); timeSystem().setSystemTime(std::chrono::milliseconds(1234567891234)); diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index b52be457f564a..41743445b1337 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -680,6 +680,7 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(bool, addProxyProtocolConnectionState, (), (const)); #if defined(HIGRESS) MOCK_METHOD(std::chrono::seconds, keepaliveHeaderTimeout, (), (const)); + MOCK_METHOD(bool, retryOtherScopeWhenNotFound, (), (const)); #endif std::unique_ptr internal_address_config_ = diff --git a/test/mocks/router/mocks.cc b/test/mocks/router/mocks.cc index 72a6f6cb13814..a89cba4e458cc 100644 --- a/test/mocks/router/mocks.cc +++ b/test/mocks/router/mocks.cc @@ -175,6 +175,7 @@ MockScopedConfig::MockScopedConfig() { ON_CALL(*this, getRouteConfig(_)).WillByDefault(Return(route_config_)); #if defined(HIGRESS) ON_CALL(*this, getRouteConfig(_, _, _)).WillByDefault(Return(route_config_)); + ON_CALL(*this, getRouteConfig(_, _, _, _)).WillByDefault(Return(route_config_)); #endif } MockScopedConfig::~MockScopedConfig() = default; diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index 84807f146e45e..de385fe274083 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -619,6 +619,10 @@ class MockScopedConfig : public ScopedConfig { MOCK_METHOD(ConfigConstSharedPtr, getRouteConfig, (const ScopeKeyBuilder*, const Http::HeaderMap&, const StreamInfo::StreamInfo*), (const)); + MOCK_METHOD(ConfigConstSharedPtr, getRouteConfig, + (const ScopeKeyBuilder*, const Http::HeaderMap&, const StreamInfo::StreamInfo*, + std::function&), + (const)); #endif std::shared_ptr route_config_{new NiceMock()}; From 440fb1b0f31f06b9efa5506c9670fb7cae385fbf Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 25 Nov 2024 10:01:04 +0800 Subject: [PATCH 247/274] fix data buffer of wasm filter --- source/extensions/common/wasm/context.cc | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 616119cf7b85e..4c1a4ae1c0f2a 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1934,6 +1934,17 @@ Http::FilterDataStatus Context::decodeData(::Envoy::Buffer::Instance& data, bool #endif return Http::FilterDataStatus::Continue; } + if (buffering_request_body_) { + decoder_callbacks_->addDecodedData(data, false); + if (destroyed_) { + // The data adding have triggered a local reply (413) and we needn't to continue to + // call the VM. + // Note this is not perfect way. If the local reply processing is stopped by other + // filters, this filter will still try to call the VM. But at least we can ensure + // the VM has valid context. + return Http::FilterDataStatus::StopIterationAndBuffer; + } + } request_body_buffer_ = &data; end_of_stream_ = end_stream; const auto buffer = getBuffer(WasmBufferType::HttpRequestBody); @@ -2020,6 +2031,17 @@ Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool #endif return Http::FilterDataStatus::Continue; } + if (buffering_response_body_) { + encoder_callbacks_->addEncodedData(data, false); + if (destroyed_) { + // The data adding have triggered a local reply (413) and we needn't to continue to + // call the VM. + // Note this is not perfect way. If the local reply processing is stopped by other + // filters, this filter will still try to call the VM. But at least we can ensure + // the VM has valid context. + return Http::FilterDataStatus::StopIterationAndBuffer; + } + } response_body_buffer_ = &data; end_of_stream_ = end_stream; const auto buffer = getBuffer(WasmBufferType::HttpResponseBody); @@ -2028,7 +2050,7 @@ Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool buffering_response_body_ = false; switch (result) { case Http::FilterDataStatus::Continue: - request_body_buffer_ = nullptr; + response_body_buffer_ = nullptr; break; case Http::FilterDataStatus::StopIterationAndBuffer: buffering_response_body_ = true; From c2471f1247916012561120b3d31ff99c38ea01ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 18 Feb 2025 19:30:03 +0800 Subject: [PATCH 248/274] Some fixes (#3) --- source/common/http/conn_manager_impl.cc | 3 + source/common/http/filter_manager.cc | 8 ++ source/common/http/filter_manager.h | 8 ++ source/common/upstream/upstream_impl.cc | 5 + source/common/upstream/upstream_impl.h | 4 + source/extensions/common/wasm/context.cc | 27 ++++-- .../common/health_checker_base_impl.cc | 30 ++++++ .../common/health_checker_base_impl.h | 3 + test/common/http/conn_manager_impl_test_2.cc | 95 +++++++++++++++++++ test/common/upstream/upstream_impl_test.cc | 88 +++++++++++++++++ test/extensions/common/wasm/wasm_test.cc | 5 + 11 files changed, 270 insertions(+), 6 deletions(-) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index fd4faff0e32f4..4fa358b16db40 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -2216,6 +2216,9 @@ void ConnectionManagerImpl::ActiveStream::recreateStream( request_data->move(*buffered_request_data); } } + // Prevent the stream from being used through the commonContinue process of + // ActiveStreamDecoderFilter or ActiveStreamEncoderFilter. + filter_manager_.interruptContinue(); #else const auto& buffered_request_data = filter_manager_.bufferedRequestData(); const bool proxy_body = buffered_request_data != nullptr && buffered_request_data->length() > 0; diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 3ff524e04b060..621bf948e2467 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -333,13 +333,21 @@ bool ActiveStreamDecoderFilter::canContinue() { // continue to further filters. A concrete example of this is a filter buffering data, the // last data frame comes in and the filter continues, but the final buffering takes the stream // over the high watermark such that a 413 is returned. +#if defined(HIGRESS) + return !parent_.state_.local_complete_ && !parent_.state_.continue_interrupted_; +#else return !parent_.state_.local_complete_; +#endif } bool ActiveStreamEncoderFilter::canContinue() { // As with ActiveStreamDecoderFilter::canContinue() make sure we do not // continue if a local reply has been sent. +#if defined(HIGRESS) + return !parent_.state_.remote_encode_complete_ && !parent_.state_.continue_interrupted_; +#else return !parent_.state_.remote_encode_complete_; +#endif } Buffer::InstancePtr ActiveStreamDecoderFilter::createBuffer() { diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 5f3a4d99526db..3f9cb804cc23e 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -805,6 +805,10 @@ class FilterManager : public ScopeTrackedObject, */ void setLocalComplete() { state_.local_complete_ = true; } +#if defined(HIGRESS) + void interruptContinue() { state_.continue_interrupted_ = true; } +#endif + /** * Whether the filters have been destroyed. */ @@ -885,6 +889,10 @@ class FilterManager : public ScopeTrackedObject, bool decoder_filters_streaming_{true}; bool destroyed_{false}; +#if defined(HIGRESS) + bool continue_interrupted_{false}; +#endif + // Used to track which filter is the latest filter that has received data. ActiveStreamEncoderFilter* latest_data_encoding_filter_{}; ActiveStreamDecoderFilter* latest_data_decoding_filter_{}; diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 09b52f04bc458..72bcc23c607cd 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -1550,6 +1550,9 @@ void ClusterImplBase::onPreInitComplete() { void ClusterImplBase::onInitDone() { info()->configUpdateStats().warming_state_.set(0); if (health_checker_ && pending_initialize_health_checks_ == 0) { +#if defined(HIGRESS) + health_checker_->start(); +#endif for (auto& host_set : prioritySet().hostSetsPerPriority()) { for (auto& host : host_set->hosts()) { if (host->disableActiveHealthCheck()) { @@ -1595,7 +1598,9 @@ void ClusterImplBase::finishInitialization() { void ClusterImplBase::setHealthChecker(const HealthCheckerSharedPtr& health_checker) { ASSERT(!health_checker_); health_checker_ = health_checker; +#if !defined(HIGRESS) health_checker_->start(); +#endif health_checker_->addHostCheckCompleteCb( [this](const HostSharedPtr& host, HealthTransition changed_state) -> void { // If we get a health check completion that resulted in a state change, signal to diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 8cbf785d5599c..3bcdf86ef941a 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -1188,6 +1188,10 @@ class ClusterImplBase : public Cluster, protected Logger::Loggable callback) override; +#if defined(HIGRESS) + // only for test + Init::ManagerImpl& initManager() { return init_manager_; } +#endif protected: ClusterImplBase(const envoy::config::cluster::v3::Cluster& cluster, diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 4c1a4ae1c0f2a..6cb577789d534 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -209,7 +209,11 @@ void Context::onCloseTCP() { void Context::onResolveDns(uint32_t token, Envoy::Network::DnsResolver::ResolutionStatus status, std::list&& response) { proxy_wasm::DeferAfterCallActions actions(this); +#if defined(HIGRESS) + if (isFailed() || !wasm()->on_resolve_dns_) { +#else if (wasm()->isFailed() || !wasm()->on_resolve_dns_) { +#endif return; } if (status != Network::DnsResolver::ResolutionStatus::Success) { @@ -257,7 +261,11 @@ template inline char* align(char* p) { void Context::onStatsUpdate(Envoy::Stats::MetricSnapshot& snapshot) { proxy_wasm::DeferAfterCallActions actions(this); +#if defined(HIGRESS) + if (isFailed() || !wasm()->on_stats_update_) { +#else if (wasm()->isFailed() || !wasm()->on_stats_update_) { +#endif return; } // buffer format: @@ -1120,7 +1128,7 @@ WasmResult Context::redisCall(std::string_view cluster, std::string_view query, } void Context::onRedisCallSuccess(uint32_t token, std::string&& response) { - if (wasm()->isFailed()) { + if (isFailed()) { redis_request_.erase(token); return; } @@ -1150,7 +1158,7 @@ void Context::onRedisCallSuccess(uint32_t token, std::string&& response) { } void Context::onRedisCallFailure(uint32_t token) { - if (wasm()->isFailed()) { + if (isFailed()) { redis_request_.erase(token); return; } @@ -1906,8 +1914,15 @@ WasmResult Context::sendLocalResponse(uint32_t response_code, std::string_view b if (local_reply_sent_) { return; } +#if defined(HIGRESS) + auto wasm_details = absl::StrFormat("via_wasm%s%s", plugin_ ? "::" + plugin()->name_ : "", + details.empty() ? "" : "::" + details); + decoder_callbacks_->sendLocalReply(static_cast(response_code), body_text, + modify_headers, grpc_status, wasm_details); +#else decoder_callbacks_->sendLocalReply(static_cast(response_code), body_text, modify_headers, grpc_status, details); +#endif local_reply_sent_ = true; }); } @@ -2103,7 +2118,7 @@ void Context::setEncoderFilterCallbacks(Envoy::Http::StreamEncoderFilterCallback void Context::onHttpCallSuccess(uint32_t token, Envoy::Http::ResponseMessagePtr&& response) { // TODO: convert this into a function in proxy-wasm-cpp-host and use here. #if defined(HIGRESS) - if (wasm()->isFailed()) { + if (isFailed()) { http_request_.erase(token); return; } @@ -2133,7 +2148,7 @@ void Context::onHttpCallSuccess(uint32_t token, Envoy::Http::ResponseMessagePtr& void Context::onHttpCallFailure(uint32_t token, Http::AsyncClient::FailureReason reason) { #if defined(HIGRESS) - if (wasm()->isFailed()) { + if (isFailed()) { http_request_.erase(token); return; } @@ -2169,7 +2184,7 @@ void Context::onGrpcReceiveWrapper(uint32_t token, ::Envoy::Buffer::InstancePtr } }; #if defined(HIGRESS) - if (wasm()->isFailed()) { + if (isFailed()) { cleanup(); return; } @@ -2211,7 +2226,7 @@ void Context::onGrpcCloseWrapper(uint32_t token, const Grpc::Status::GrpcStatus& } }; #if defined(HIGRESS) - if (wasm()->isFailed()) { + if (isFailed()) { cleanup(); return; } diff --git a/source/extensions/health_checkers/common/health_checker_base_impl.cc b/source/extensions/health_checkers/common/health_checker_base_impl.cc index f774af6f9aca5..963930829f00a 100644 --- a/source/extensions/health_checkers/common/health_checker_base_impl.cc +++ b/source/extensions/health_checkers/common/health_checker_base_impl.cc @@ -163,10 +163,24 @@ void HealthCheckerImplBase::addHosts(const HostVector& hosts) { if (host->disableActiveHealthCheck()) { continue; } +#if defined(HIGRESS) + if (active_sessions_.find(host) != active_sessions_.end()) { + continue; + } + active_sessions_[host] = makeSession(host); + host->setHealthChecker( + HealthCheckHostMonitorPtr{new HealthCheckHostMonitorImpl(shared_from_this(), host)}); + if (started_) { + // Because EDS and SDS are not synchronized, if SDS has not yet completed when it is started, + // it will cause the health check to fail. + active_sessions_[host]->start(); + } +#else active_sessions_[host] = makeSession(host); host->setHealthChecker( HealthCheckHostMonitorPtr{new HealthCheckHostMonitorImpl(shared_from_this(), host)}); active_sessions_[host]->start(); +#endif } } @@ -230,9 +244,25 @@ void HealthCheckerImplBase::setUnhealthyCrossThread(const HostSharedPtr& host, } void HealthCheckerImplBase::start() { +#if defined(HIGRESS) + if (started_) { + return; + } + for (auto& host_set : cluster_.prioritySet().hostSetsPerPriority()) { + // It appears to be a duplicate addition since onClusterMemberUpdate has already been added + // once. However, considering the case of HDS, which does not call onClusterMemberUpdate, it + // needs to be added here. + addHosts(host_set->hosts()); + } + for (auto& session_iter : active_sessions_) { + session_iter.second->start(); + } + started_ = true; +#else for (auto& host_set : cluster_.prioritySet().hostSetsPerPriority()) { addHosts(host_set->hosts()); } +#endif } HealthCheckerImplBase::ActiveHealthCheckSession::ActiveHealthCheckSession( diff --git a/source/extensions/health_checkers/common/health_checker_base_impl.h b/source/extensions/health_checkers/common/health_checker_base_impl.h index 1e7e308348cd1..3eed7583c9e0f 100644 --- a/source/extensions/health_checkers/common/health_checker_base_impl.h +++ b/source/extensions/health_checkers/common/health_checker_base_impl.h @@ -160,6 +160,9 @@ class HealthCheckerImplBase : public HealthChecker, const std::shared_ptr transport_socket_options_; const MetadataConstSharedPtr transport_socket_match_metadata_; const Common::CallbackHandlePtr member_update_cb_; +#if defined(HIGRESS) + bool started_{false}; +#endif }; } // namespace Upstream diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 910164d4f2a1f..91cff4b801003 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2119,6 +2119,101 @@ TEST_F(HttpConnectionManagerImplTest, AddDataWithStopAndContinue) { encoder_filters_[2]->callbacks_->continueEncoding(); } +#if defined(HIGRESS) +TEST_F(HttpConnectionManagerImplTest, CannotContinueDecodingAfterRecreateStream) { + setup(false, ""); + decoder_filters_.push_back(new NiceMock()); + decoder_filters_.push_back(new NiceMock()); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillOnce(Invoke([this](FilterChainManager& manager) -> bool { + bool applied_filters = false; + if (log_handler_.get()) { + auto factory = createLogHandlerFactoryCb(log_handler_); + manager.applyFilterFactoryCb({}, factory); + applied_filters = true; + } + for (int i = 0; i < 2; i++) { + auto factory = + createDecoderFilterFactoryCb(StreamDecoderFilterSharedPtr{decoder_filters_[i]}); + manager.applyFilterFactoryCb({}, factory); + applied_filters = true; + } + return applied_filters; + })) + .WillOnce(Return(true)); + + EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) + .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { + decoder_filters_[0]->callbacks_->recreateStream(nullptr); + return FilterHeadersStatus::StopIteration; + })); + + // Kick off the request. + startRequest(true); + + // Should not continue headers of filter 1. + EXPECT_CALL(*decoder_filters_[1], decodeHeaders(_, true)).Times(0); + decoder_filters_[0]->callbacks_->continueDecoding(); + filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); +} + +TEST_F(HttpConnectionManagerImplTest, CannotContinueEncodingAfterRecreateStream) { + setup(false, ""); + decoder_filters_.push_back(new NiceMock()); + decoder_filters_.push_back(new NiceMock()); + encoder_filters_.push_back(new NiceMock()); + encoder_filters_.push_back(new NiceMock()); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillOnce(Invoke([this](FilterChainManager& manager) -> bool { + bool applied_filters = false; + if (log_handler_.get()) { + auto factory = createLogHandlerFactoryCb(log_handler_); + manager.applyFilterFactoryCb({}, factory); + applied_filters = true; + } + for (int i = 0; i < 2; i++) { + auto factory = + createDecoderFilterFactoryCb(StreamDecoderFilterSharedPtr{decoder_filters_[i]}); + manager.applyFilterFactoryCb({}, factory); + applied_filters = true; + } + for (int i = 0; i < 2; i++) { + auto factory = + createEncoderFilterFactoryCb(StreamEncoderFilterSharedPtr{encoder_filters_[i]}); + manager.applyFilterFactoryCb({}, factory); + applied_filters = true; + } + return applied_filters; + })) + .WillOnce(Return(true)); + + EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) + .WillOnce(Return(FilterHeadersStatus::Continue)); + EXPECT_CALL(*decoder_filters_[1], decodeHeaders(_, true)) + .WillOnce(Return(FilterHeadersStatus::StopIteration)); + + // Kick off the request. + startRequest(true); + + EXPECT_CALL(*encoder_filters_[1], encodeHeaders(_, true)) + .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { + decoder_filters_[1]->callbacks_->recreateStream(nullptr); + return FilterHeadersStatus::StopIteration; + })); + + decoder_filters_[1]->callbacks_->streamInfo().setResponseCodeDetails(""); + decoder_filters_[1]->callbacks_->encodeHeaders( + ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true, "details"); + + // Should not continue headers of filter 0. + EXPECT_CALL(*encoder_filters_[0], encodeHeaders(_, true)).Times(0); + encoder_filters_[1]->callbacks_->continueEncoding(); + filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); +} +#endif + // Use filter direct decode/encodeData() calls without trailers. TEST_F(HttpConnectionManagerImplTest, FilterDirectDecodeEncodeDataNoTrailers) { setup(false, ""); diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 17b9f0bfb5fab..9d52979c345c6 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -270,6 +270,94 @@ TEST_F(StrictDnsClusterImplTest, ZeroHostsHealthChecker) { EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); } +#if defined(HIGRESS) +TEST_F(StrictDnsClusterImplTest, HealthCheckNotStartBeforeInitTargetDone) { + ReadyWatcher initialized; + + const std::string yaml = R"EOF( + name: name + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + )EOF"; + + ResolverData resolver(*dns_resolver_, server_context_.dispatcher_); + envoy::config::cluster::v3::Cluster cluster_config = parseClusterFromV3Yaml(yaml); + + Envoy::Upstream::ClusterFactoryContextImpl factory_context( + server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, + false); + StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto& init_manager = cluster.initManager(); + Init::ExpectableTargetImpl target("mock_sds_api"); + init_manager.add(target); + target.expectInitialize(); + std::shared_ptr health_checker(new MockHealthChecker()); + EXPECT_CALL(*health_checker, start()).Times(0); + EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); + cluster.setHealthChecker(health_checker); + cluster.initialize([&]() -> void { initialized.ready(); }); + + EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)).Times(0); + EXPECT_CALL(initialized, ready()).Times(0); + EXPECT_CALL(*resolver.timer_, enableTimer(_, _)); + resolver.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, {}); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +} + +TEST_F(StrictDnsClusterImplTest, HealthCheckStartAfterInitTargetDone) { + ReadyWatcher initialized; + + const std::string yaml = R"EOF( + name: name + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + )EOF"; + + ResolverData resolver(*dns_resolver_, server_context_.dispatcher_); + envoy::config::cluster::v3::Cluster cluster_config = parseClusterFromV3Yaml(yaml); + + Envoy::Upstream::ClusterFactoryContextImpl factory_context( + server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, + false); + StrictDnsClusterImpl cluster(cluster_config, factory_context, dns_resolver_); + auto& init_manager = cluster.initManager(); + Init::ExpectableTargetImpl target("mock_sds_api"); + init_manager.add(target); + target.expectInitializeWillCallReady(); + std::shared_ptr health_checker(new MockHealthChecker()); + EXPECT_CALL(*health_checker, start()); + EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); + cluster.setHealthChecker(health_checker); + cluster.initialize([&]() -> void { initialized.ready(); }); + + EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); + EXPECT_CALL(initialized, ready()); + EXPECT_CALL(*resolver.timer_, enableTimer(_, _)); + resolver.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, {}); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +} +#endif + TEST_F(StrictDnsClusterImplTest, DontWaitForDNSOnInit) { ResolverData resolver(*dns_resolver_, server_context_.dispatcher_); diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 8e8b7fed1101e..44e81c228be3b 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -1442,8 +1442,13 @@ TEST_P(WasmCommonContextTest, DuplicateLocalReply) { setupContext(); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) .WillOnce([this](Http::ResponseHeaderMap&, bool) { context().onResponseHeaders(0, false); }); +#if defined(HIGRESS) + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::OK, testing::Eq("body"), _, _, + testing::Eq("via_wasm::plugin_name::ok"))); +#else EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::OK, testing::Eq("body"), _, _, testing::Eq("ok"))); +#endif // Create in-VM context. context().onCreate(); From 6d786e29d9d24a92c0d84f0b4a03cdee3057818e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 25 Mar 2025 21:31:54 +0800 Subject: [PATCH 249/274] Update go filter (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yongjie.yyj Signed-off-by: spacewander Signed-off-by: wangkai19 Signed-off-by: doujiang24 Signed-off-by: Meshkati Signed-off-by: Michael Sauter Signed-off-by: Ryan Northey Signed-off-by: Braden Bassingthwaite Signed-off-by: Willem Veerman <6502426+willemveerman@users.noreply.github.com> Signed-off-by: duxin Co-authored-by: antJack <52443884+antJack@users.noreply.github.com> Co-authored-by: 罗泽轩 Co-authored-by: StarryNight Co-authored-by: doujiang24 Co-authored-by: Seyed Mostafa Meshkati Co-authored-by: phlax Co-authored-by: Braden Bassingthwaite Co-authored-by: Braden Bassingthwaite Co-authored-by: Willem Veerman <6502426+willemveerman@users.noreply.github.com> Co-authored-by: duxin40 <33946910+duxin40@users.noreply.github.com> --- .github/dependabot.yml | 42 +- .gitignore | 2 + bazel/dependency_imports.bzl | 2 +- contrib/golang/common/dso/dso.cc | 47 +- contrib/golang/common/dso/dso.h | 85 +- contrib/golang/common/dso/libgolang.h | 47 +- contrib/golang/common/dso/test/dso_test.cc | 22 +- contrib/golang/common/dso/test/mocks.h | 20 +- .../common/dso/test/test_data/simple.go | 47 +- contrib/golang/common/go/api/BUILD | 24 +- contrib/golang/common/go/api/api.h | 131 +- contrib/golang/common/go/api/capi.go | 68 +- contrib/golang/common/go/api/cgocheck.go | 32 - contrib/golang/common/go/api/filter.go | 155 ++- contrib/golang/common/go/api/logger.go | 118 ++ contrib/golang/common/go/api/type.go | 55 +- contrib/golang/common/go/api_impl/BUILD | 32 + contrib/golang/common/go/api_impl/api.h | 1 + .../golang/common/go/api_impl/capi_impl.go | 86 ++ contrib/golang/common/log/BUILD | 22 + contrib/golang/common/log/cgo.cc | 68 + contrib/golang/common/log/cgo.h | 23 + contrib/golang/filters/http/source/BUILD | 17 + contrib/golang/filters/http/source/cgo.cc | 414 ++++-- contrib/golang/filters/http/source/config.cc | 13 +- .../filters/http/source/go/pkg/http/BUILD | 2 + .../filters/http/source/go/pkg/http/asan.go | 34 + .../http/source/go/pkg/http/capi_impl.go | 377 +++-- .../filters/http/source/go/pkg/http/config.go | 100 +- .../filters/http/source/go/pkg/http/filter.go | 267 +++- .../http/source/go/pkg/http/filtermanager.go | 62 +- .../http/source/go/pkg/http/passthrough.go | 8 +- .../filters/http/source/go/pkg/http/shim.go | 257 +++- .../filters/http/source/go/pkg/http/type.go | 153 ++- .../filters/http/source/golang_filter.cc | 1212 +++++++++++------ .../filters/http/source/golang_filter.h | 252 ++-- .../filters/http/source/processor_state.cc | 141 +- .../filters/http/source/processor_state.h | 105 +- contrib/golang/filters/http/test/BUILD | 36 +- .../golang/filters/http/test/config_test.cc | 25 +- .../http/test/golang_filter_fuzz_test.cc | 12 +- .../filters/http/test/golang_filter_test.cc | 19 +- .../http/test/golang_integration_test.cc | 889 +++++++++++- .../golang/filters/http/test/test_data/BUILD | 33 + .../http/test/test_data/access_log/BUILD | 21 + .../http/test/test_data/access_log/config.go | 40 + .../http/test/test_data/access_log/filter.go | 173 +++ .../filters/http/test/test_data/action/BUILD | 21 + .../http/test/test_data/action/config.go | 18 + .../http/test/test_data/action/filter.go | 72 + .../http/test/test_data/add_data/BUILD | 21 + .../http/test/test_data/add_data/config.go | 39 + .../http/test/test_data/add_data/filter.go | 92 ++ .../filters/http/test/test_data/basic/BUILD | 12 +- .../http/test/test_data/basic/config.go | 18 +- .../http/test/test_data/basic/filter.go | 199 ++- .../filters/http/test/test_data/basic/go.mod | 9 - .../filters/http/test/test_data/buffer/BUILD | 21 + .../http/test/test_data/buffer/config.go | 40 + .../http/test/test_data/buffer/filter.go | 138 ++ .../test/test_data/bufferinjectdata/BUILD | 21 + .../test/test_data/bufferinjectdata/config.go | 39 + .../test/test_data/bufferinjectdata/filter.go | 152 +++ .../filters/http/test/test_data/dummy/go.mod | 4 +- .../http/test/test_data/dummy/plugin.go | 2 +- .../filters/http/test/test_data/echo/BUILD | 10 +- .../http/test/test_data/echo/config.go | 18 +- .../http/test/test_data/echo/filter.go | 4 +- .../filters/http/test/test_data/echo/go.mod | 18 - .../golang/filters/http/test/test_data/go.mod | 19 + .../filters/http/test/test_data/metric/BUILD | 20 + .../http/test/test_data/metric/config.go | 49 + .../http/test/test_data/metric/filter.go | 76 ++ .../http/test/test_data/passthrough/BUILD | 10 +- .../http/test/test_data/passthrough/filter.go | 7 +- .../http/test/test_data/passthrough/go.mod | 9 - .../http/test/test_data/passthrough/go.sum | 8 - .../filters/http/test/test_data/plugins.go | 18 + .../http/test/test_data/property/BUILD | 21 + .../http/test/test_data/property/config.go | 40 + .../http/test/test_data/property/filter.go | 137 ++ .../http/test/test_data/routeconfig/BUILD | 10 +- .../http/test/test_data/routeconfig/config.go | 19 +- .../http/test/test_data/routeconfig/filter.go | 2 +- .../http/test/test_data/routeconfig/go.mod | 18 - .../http/test/test_data/websocket/BUILD | 20 + .../http/test/test_data/websocket/config.go | 18 + .../http/test/test_data/websocket/filter.go | 39 + .../http/test/websocket_integration_test.cc | 121 ++ contrib/golang/filters/network/source/BUILD | 9 +- contrib/golang/filters/network/source/cgo.cc | 14 +- .../network/source/go/pkg/network/capi.go | 10 +- .../network/source/go/pkg/network/filter.go | 34 +- .../network/source/go/pkg/network/shim.go | 91 +- .../golang/filters/network/source/golang.cc | 19 +- .../golang/filters/network/source/golang.h | 14 +- .../golang/filters/network/source/upstream.cc | 34 +- .../golang/filters/network/source/upstream.h | 12 +- contrib/golang/filters/network/test/BUILD | 1 + .../filters/network/test/filter_test.cc | 2 +- .../filters/network/test/test_data/filter.go | 18 +- .../filters/network/test/upstream_test.cc | 20 +- .../go/pkg/cluster_specifier/capi_impl.go | 1 - .../http/http_filters/golang_filter.rst | 7 - docs/root/start/sandboxes/golang-http.rst | 9 - examples/golang-http/simple/config.go | 18 +- examples/golang-http/simple/filter.go | 86 +- examples/golang-http/simple/go.mod | 2 +- examples/golang-network/envoy.yaml | 2 - examples/golang-network/simple/filter.go | 5 + go.mod | 6 +- 111 files changed, 6167 insertions(+), 1667 deletions(-) delete mode 100644 contrib/golang/common/go/api/cgocheck.go create mode 100644 contrib/golang/common/go/api/logger.go create mode 100644 contrib/golang/common/go/api_impl/BUILD create mode 120000 contrib/golang/common/go/api_impl/api.h create mode 100644 contrib/golang/common/go/api_impl/capi_impl.go create mode 100644 contrib/golang/common/log/BUILD create mode 100644 contrib/golang/common/log/cgo.cc create mode 100644 contrib/golang/common/log/cgo.h create mode 100644 contrib/golang/filters/http/source/go/pkg/http/asan.go create mode 100644 contrib/golang/filters/http/test/test_data/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/access_log/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/access_log/config.go create mode 100644 contrib/golang/filters/http/test/test_data/access_log/filter.go create mode 100644 contrib/golang/filters/http/test/test_data/action/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/action/config.go create mode 100644 contrib/golang/filters/http/test/test_data/action/filter.go create mode 100644 contrib/golang/filters/http/test/test_data/add_data/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/add_data/config.go create mode 100644 contrib/golang/filters/http/test/test_data/add_data/filter.go delete mode 100644 contrib/golang/filters/http/test/test_data/basic/go.mod create mode 100644 contrib/golang/filters/http/test/test_data/buffer/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/buffer/config.go create mode 100644 contrib/golang/filters/http/test/test_data/buffer/filter.go create mode 100644 contrib/golang/filters/http/test/test_data/bufferinjectdata/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/bufferinjectdata/config.go create mode 100644 contrib/golang/filters/http/test/test_data/bufferinjectdata/filter.go delete mode 100644 contrib/golang/filters/http/test/test_data/echo/go.mod create mode 100644 contrib/golang/filters/http/test/test_data/go.mod create mode 100644 contrib/golang/filters/http/test/test_data/metric/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/metric/config.go create mode 100644 contrib/golang/filters/http/test/test_data/metric/filter.go delete mode 100644 contrib/golang/filters/http/test/test_data/passthrough/go.mod delete mode 100644 contrib/golang/filters/http/test/test_data/passthrough/go.sum create mode 100644 contrib/golang/filters/http/test/test_data/plugins.go create mode 100644 contrib/golang/filters/http/test/test_data/property/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/property/config.go create mode 100644 contrib/golang/filters/http/test/test_data/property/filter.go delete mode 100644 contrib/golang/filters/http/test/test_data/routeconfig/go.mod create mode 100644 contrib/golang/filters/http/test/test_data/websocket/BUILD create mode 100644 contrib/golang/filters/http/test/test_data/websocket/config.go create mode 100644 contrib/golang/filters/http/test/test_data/websocket/filter.go create mode 100644 contrib/golang/filters/http/test/websocket_integration_test.cc diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c1952f33f039b..265bea2d2c706 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -197,49 +197,27 @@ updates: time: "06:00" - package-ecosystem: "gomod" - directory: "/contrib/golang/filters/http/test/test_data/basic" + directory: "/contrib/golang/filters/http/test/test_data" + groups: + contrib-golang: + patterns: + - "*" schedule: interval: daily time: "06:00" - package-ecosystem: "gomod" - directory: "/contrib/golang/filters/http/test/test_data/dummy" - schedule: - interval: daily - time: "06:00" - -- package-ecosystem: "gomod" - directory: "/contrib/golang/filters/http/test/test_data/echo" - schedule: - interval: daily - time: "06:00" - -- package-ecosystem: "gomod" - directory: "/contrib/golang/filters/http/test/test_data/passthrough" - schedule: - interval: daily - time: "06:00" - -- package-ecosystem: "gomod" - directory: "/contrib/golang/filters/http/test/test_data/routeconfig" - schedule: - interval: daily - time: "06:00" - -- package-ecosystem: "gomod" - directory: "/contrib/golang/router/cluster_specifier/test/test_data/simple" + directory: "/contrib/golang/filters/http/test/test_data/access_log" schedule: interval: daily time: "06:00" - package-ecosystem: "gomod" directory: "/contrib/golang/filters/network/test/test_data" - schedule: - interval: daily - time: "06:00" - -- package-ecosystem: "gomod" - directory: "/examples/ext_authz/auth/grpc-service" + groups: + contrib-golang: + patterns: + - "*" schedule: interval: daily time: "06:00" diff --git a/.gitignore b/.gitignore index de313efb4416e..6aad749804db6 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ bazel.output.txt tools/dev/src distribution/custom examples/websocket/certs +/contrib/golang/**/test_data/go.sum +/contrib/golang/**/test_data/*/go.sum diff --git a/bazel/dependency_imports.bzl b/bazel/dependency_imports.bzl index b743a1936d0d8..21ff0abc420c8 100644 --- a/bazel/dependency_imports.bzl +++ b/bazel/dependency_imports.bzl @@ -18,7 +18,7 @@ load("@com_google_cel_cpp//bazel:deps.bzl", "parser_deps") load("@com_github_chrusty_protoc_gen_jsonschema//:deps.bzl", protoc_gen_jsonschema_go_dependencies = "go_dependencies") # go version for rules_go -GO_VERSION = "1.18" +GO_VERSION = "1.20" JQ_VERSION = "1.6" YQ_VERSION = "4.24.4" diff --git a/contrib/golang/common/dso/dso.cc b/contrib/golang/common/dso/dso.cc index b9463e19caeb6..2580204d446ad 100644 --- a/contrib/golang/common/dso/dso.cc +++ b/contrib/golang/common/dso/dso.cc @@ -57,16 +57,22 @@ HttpFilterDsoImpl::HttpFilterDsoImpl(const std::string dso_name) : HttpFilterDso envoy_go_filter_on_http_header_, handler_, dso_name, "envoyGoFilterOnHttpHeader"); loaded_ &= dlsymInternal( envoy_go_filter_on_http_data_, handler_, dso_name, "envoyGoFilterOnHttpData"); + loaded_ &= dlsymInternal( + envoy_go_filter_on_http_log_, handler_, dso_name, "envoyGoFilterOnHttpLog"); + loaded_ &= dlsymInternal( + envoy_go_filter_on_http_stream_complete_, handler_, dso_name, + "envoyGoFilterOnHttpStreamComplete"); loaded_ &= dlsymInternal( envoy_go_filter_on_http_destroy_, handler_, dso_name, "envoyGoFilterOnHttpDestroy"); loaded_ &= dlsymInternal( envoy_go_filter_go_request_sema_dec_, handler_, dso_name, "envoyGoRequestSemaDec"); + loaded_ &= dlsymInternal(envoy_go_filter_cleanup_, handler_, + dso_name, "envoyGoFilterCleanUp"); } -GoUint64 HttpFilterDsoImpl::envoyGoFilterNewHttpPluginConfig(GoUint64 p0, GoUint64 p1, GoUint64 p2, - GoUint64 p3) { +GoUint64 HttpFilterDsoImpl::envoyGoFilterNewHttpPluginConfig(httpConfig* p0) { ASSERT(envoy_go_filter_new_http_plugin_config_ != nullptr); - return envoy_go_filter_new_http_plugin_config_(p0, p1, p2, p3); + return envoy_go_filter_new_http_plugin_config_(p0); } GoUint64 HttpFilterDsoImpl::envoyGoFilterMergeHttpPluginConfig(GoUint64 p0, GoUint64 p1, @@ -75,23 +81,36 @@ GoUint64 HttpFilterDsoImpl::envoyGoFilterMergeHttpPluginConfig(GoUint64 p0, GoUi return envoy_go_filter_merge_http_plugin_config_(p0, p1, p2, p3); } -void HttpFilterDsoImpl::envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0) { +void HttpFilterDsoImpl::envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0, GoInt p1) { ASSERT(envoy_go_filter_destroy_http_plugin_config_ != nullptr); - return envoy_go_filter_destroy_http_plugin_config_(p0); + return envoy_go_filter_destroy_http_plugin_config_(p0, p1); } -GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpHeader(httpRequest* p0, GoUint64 p1, GoUint64 p2, +GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpHeader(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) { ASSERT(envoy_go_filter_on_http_header_ != nullptr); return envoy_go_filter_on_http_header_(p0, p1, p2, p3); } -GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpData(httpRequest* p0, GoUint64 p1, GoUint64 p2, +GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpData(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) { ASSERT(envoy_go_filter_on_http_data_ != nullptr); return envoy_go_filter_on_http_data_(p0, p1, p2, p3); } +void HttpFilterDsoImpl::envoyGoFilterOnHttpLog(httpRequest* p0, int p1, processState* p2, + processState* p3, GoUint64 p4, GoUint64 p5, + GoUint64 p6, GoUint64 p7, GoUint64 p8, GoUint64 p9, + GoUint64 p10, GoUint64 p11) { + ASSERT(envoy_go_filter_on_http_log_ != nullptr); + envoy_go_filter_on_http_log_(p0, GoUint64(p1), p2, p3, p4, p5, p6, p7, p8, p9, p10, p11); +} + +void HttpFilterDsoImpl::envoyGoFilterOnHttpStreamComplete(httpRequest* p0) { + ASSERT(envoy_go_filter_on_http_stream_complete_ != nullptr); + envoy_go_filter_on_http_stream_complete_(p0); +} + void HttpFilterDsoImpl::envoyGoFilterOnHttpDestroy(httpRequest* p0, int p1) { ASSERT(envoy_go_filter_on_http_destroy_ != nullptr); envoy_go_filter_on_http_destroy_(p0, GoUint64(p1)); @@ -102,6 +121,11 @@ void HttpFilterDsoImpl::envoyGoRequestSemaDec(httpRequest* p0) { envoy_go_filter_go_request_sema_dec_(p0); } +void HttpFilterDsoImpl::cleanup() { + ASSERT(envoy_go_filter_cleanup_ != nullptr); + envoy_go_filter_cleanup_(); +} + ClusterSpecifierDsoImpl::ClusterSpecifierDsoImpl(const std::string dso_name) : ClusterSpecifierDso(dso_name) { loaded_ &= dlsymInternal( @@ -190,14 +214,15 @@ GoUint64 NetworkFilterDsoImpl::envoyGoFilterOnDownstreamWrite(void* w, GoUint64 return envoy_go_filter_on_downstream_write_(w, data_size, data_ptr, slice_num, end_of_stream); } -void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionReady(void* w) { +void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionReady(void* w, GoUint64 connID) { ASSERT(envoy_go_filter_on_upstream_connection_ready_ != nullptr); - envoy_go_filter_on_upstream_connection_ready_(w); + envoy_go_filter_on_upstream_connection_ready_(w, connID); } -void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason) { +void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason, + GoUint64 connID) { ASSERT(envoy_go_filter_on_upstream_connection_failure_ != nullptr); - envoy_go_filter_on_upstream_connection_failure_(w, reason); + envoy_go_filter_on_upstream_connection_failure_(w, reason, connID); } void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamData(void* w, GoUint64 data_size, diff --git a/contrib/golang/common/dso/dso.h b/contrib/golang/common/dso/dso.h index a2cc8aa4b72f4..5c33f8f0ad1e0 100644 --- a/contrib/golang/common/dso/dso.h +++ b/contrib/golang/common/dso/dso.h @@ -17,8 +17,12 @@ class Dso { public: Dso() = default; Dso(const std::string dso_name); - ~Dso(); + virtual ~Dso(); bool loaded() { return loaded_; } + /* + * Clean up resources that are referenced on the Golang side. + */ + virtual void cleanup(){}; protected: const std::string dso_name_; @@ -29,17 +33,20 @@ class Dso { class HttpFilterDso : public Dso { public: HttpFilterDso(const std::string dso_name) : Dso(dso_name){}; - virtual ~HttpFilterDso() = default; + ~HttpFilterDso() override = default; - virtual GoUint64 envoyGoFilterNewHttpPluginConfig(GoUint64 p0, GoUint64 p1, GoUint64 p2, - GoUint64 p3) PURE; + virtual GoUint64 envoyGoFilterNewHttpPluginConfig(httpConfig* p0) PURE; virtual GoUint64 envoyGoFilterMergeHttpPluginConfig(GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) PURE; - virtual void envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0) PURE; - virtual GoUint64 envoyGoFilterOnHttpHeader(httpRequest* p0, GoUint64 p1, GoUint64 p2, + virtual void envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0, GoInt p1) PURE; + virtual GoUint64 envoyGoFilterOnHttpHeader(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) PURE; - virtual GoUint64 envoyGoFilterOnHttpData(httpRequest* p0, GoUint64 p1, GoUint64 p2, + virtual GoUint64 envoyGoFilterOnHttpData(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) PURE; + virtual void envoyGoFilterOnHttpLog(httpRequest* p0, int p1, processState* p2, processState* p3, + GoUint64 p4, GoUint64 p5, GoUint64 p6, GoUint64 p7, + GoUint64 p8, GoUint64 p9, GoUint64 p10, GoUint64 p11) PURE; + virtual void envoyGoFilterOnHttpStreamComplete(httpRequest* p0) PURE; virtual void envoyGoFilterOnHttpDestroy(httpRequest* p0, int p1) PURE; virtual void envoyGoRequestSemaDec(httpRequest* p0) PURE; }; @@ -49,35 +56,45 @@ class HttpFilterDsoImpl : public HttpFilterDso { HttpFilterDsoImpl(const std::string dso_name); ~HttpFilterDsoImpl() override = default; - GoUint64 envoyGoFilterNewHttpPluginConfig(GoUint64 p0, GoUint64 p1, GoUint64 p2, - GoUint64 p3) override; + GoUint64 envoyGoFilterNewHttpPluginConfig(httpConfig* p0) override; GoUint64 envoyGoFilterMergeHttpPluginConfig(GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) override; - void envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0) override; - GoUint64 envoyGoFilterOnHttpHeader(httpRequest* p0, GoUint64 p1, GoUint64 p2, + void envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0, GoInt p1) override; + GoUint64 envoyGoFilterOnHttpHeader(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) override; - GoUint64 envoyGoFilterOnHttpData(httpRequest* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) override; + GoUint64 envoyGoFilterOnHttpData(processState* p0, GoUint64 p1, GoUint64 p2, + GoUint64 p3) override; + void envoyGoFilterOnHttpLog(httpRequest* p0, int p1, processState* p2, processState* p3, + GoUint64 p4, GoUint64 p5, GoUint64 p6, GoUint64 p7, GoUint64 p8, + GoUint64 p9, GoUint64 p10, GoUint64 p11) override; + void envoyGoFilterOnHttpStreamComplete(httpRequest* p0) override; void envoyGoFilterOnHttpDestroy(httpRequest* p0, int p1) override; void envoyGoRequestSemaDec(httpRequest* p0) override; + void cleanup() override; private: - GoUint64 (*envoy_go_filter_new_http_plugin_config_)(GoUint64 p0, GoUint64 p1, GoUint64 p2, - GoUint64 p3) = {nullptr}; + GoUint64 (*envoy_go_filter_new_http_plugin_config_)(httpConfig* p0) = {nullptr}; GoUint64 (*envoy_go_filter_merge_http_plugin_config_)(GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) = {nullptr}; - void (*envoy_go_filter_destroy_http_plugin_config_)(GoUint64 p0) = {nullptr}; - GoUint64 (*envoy_go_filter_on_http_header_)(httpRequest* p0, GoUint64 p1, GoUint64 p2, + void (*envoy_go_filter_destroy_http_plugin_config_)(GoUint64 p0, GoInt p1) = {nullptr}; + GoUint64 (*envoy_go_filter_on_http_header_)(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) = {nullptr}; - GoUint64 (*envoy_go_filter_on_http_data_)(httpRequest* p0, GoUint64 p1, GoUint64 p2, + GoUint64 (*envoy_go_filter_on_http_data_)(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) = {nullptr}; + void (*envoy_go_filter_on_http_log_)(httpRequest* p0, int p1, processState* p2, processState* p3, + GoUint64 p4, GoUint64 p5, GoUint64 p6, GoUint64 p7, + GoUint64 p8, GoUint64 p9, GoUint64 p10, + GoUint64 p11) = {nullptr}; + void (*envoy_go_filter_on_http_stream_complete_)(httpRequest* p0) = {nullptr}; void (*envoy_go_filter_on_http_destroy_)(httpRequest* p0, GoUint64 p1) = {nullptr}; void (*envoy_go_filter_go_request_sema_dec_)(httpRequest* p0) = {nullptr}; + void (*envoy_go_filter_cleanup_)() = {nullptr}; }; class ClusterSpecifierDso : public Dso { public: ClusterSpecifierDso(const std::string dso_name) : Dso(dso_name){}; - virtual ~ClusterSpecifierDso() = default; + ~ClusterSpecifierDso() override = default; virtual GoInt64 envoyGoOnClusterSpecify(GoUint64 plugin_ptr, GoUint64 header_ptr, GoUint64 plugin_id, GoUint64 buffer_ptr, @@ -109,7 +126,7 @@ class NetworkFilterDso : public Dso { public: NetworkFilterDso() = default; NetworkFilterDso(const std::string dso_name) : Dso(dso_name){}; - virtual ~NetworkFilterDso() = default; + ~NetworkFilterDso() override = default; virtual GoUint64 envoyGoFilterOnNetworkFilterConfig(GoUint64 library_id_ptr, GoUint64 library_id_len, GoUint64 config_ptr, @@ -123,8 +140,9 @@ class NetworkFilterDso : public Dso { virtual GoUint64 envoyGoFilterOnDownstreamWrite(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) PURE; - virtual void envoyGoFilterOnUpstreamConnectionReady(void* w) PURE; - virtual void envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason) PURE; + virtual void envoyGoFilterOnUpstreamConnectionReady(void* w, GoUint64 connID) PURE; + virtual void envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason, + GoUint64 connID) PURE; virtual void envoyGoFilterOnUpstreamData(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) PURE; virtual void envoyGoFilterOnUpstreamEvent(void* w, GoInt event) PURE; @@ -148,8 +166,8 @@ class NetworkFilterDsoImpl : public NetworkFilterDso { GoUint64 envoyGoFilterOnDownstreamWrite(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) override; - void envoyGoFilterOnUpstreamConnectionReady(void* w) override; - void envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason) override; + void envoyGoFilterOnUpstreamConnectionReady(void* w, GoUint64 connID) override; + void envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason, GoUint64 connID) override; void envoyGoFilterOnUpstreamData(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) override; void envoyGoFilterOnUpstreamEvent(void* w, GoInt event) override; @@ -171,8 +189,9 @@ class NetworkFilterDsoImpl : public NetworkFilterDso { GoInt slice_num, GoInt end_of_stream) = {nullptr}; - void (*envoy_go_filter_on_upstream_connection_ready_)(void* w) = {nullptr}; - void (*envoy_go_filter_on_upstream_connection_failure_)(void* w, GoInt reason) = {nullptr}; + void (*envoy_go_filter_on_upstream_connection_ready_)(void* w, GoUint64 connID) = {nullptr}; + void (*envoy_go_filter_on_upstream_connection_failure_)(void* w, GoInt reason, + GoUint64 connID) = {nullptr}; void (*envoy_go_filter_on_upstream_data_)(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) = {nullptr}; void (*envoy_go_filter_on_upstream_event_)(void* w, GoInt event) = {nullptr}; @@ -262,6 +281,22 @@ template class DsoManager { return nullptr; }; + /** + * Clean up all golang runtime to make asan happy in testing. + */ + static void cleanUpForTest() { + DsoStoreType& dsoStore = getDsoStore(); + absl::WriterMutexLock lock(&dsoStore.mutex_); + for (auto it = dsoStore.id_to_dso_.begin(); it != dsoStore.id_to_dso_.end(); it++) { + auto dso = it->second; + if (dso != nullptr) { + dso->cleanup(); + } + } + dsoStore.id_to_dso_.clear(); + dsoStore.plugin_name_to_dso_.clear(); + }; + private: using DsoMapType = absl::flat_hash_map>; struct DsoStoreType { diff --git a/contrib/golang/common/dso/libgolang.h b/contrib/golang/common/dso/libgolang.h index d9be431248ba3..1ef3520530223 100644 --- a/contrib/golang/common/dso/libgolang.h +++ b/contrib/golang/common/dso/libgolang.h @@ -96,14 +96,11 @@ extern "C" { // go:linkname envoyGoFilterNewHttpPluginConfig // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterNewHttpPluginConfig extern GoUint64 -envoyGoFilterNewHttpPluginConfig(GoUint64 namePtr, // NOLINT(readability-identifier-naming) - GoUint64 nameLen, // NOLINT(readability-identifier-naming) - GoUint64 configPtr, // NOLINT(readability-identifier-naming) - GoUint64 configLen); // NOLINT(readability-identifier-naming) +envoyGoFilterNewHttpPluginConfig(httpConfig* p0); // NOLINT(readability-identifier-naming) // go:linkname envoyGoFilterDestroyHttpPluginConfig // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterDestroyHttpPluginConfig -extern void envoyGoFilterDestroyHttpPluginConfig(GoUint64 id); +extern void envoyGoFilterDestroyHttpPluginConfig(GoUint64 id, GoInt need_delay); // go:linkname envoyGoFilterMergeHttpPluginConfig // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterMergeHttpPluginConfig @@ -115,18 +112,26 @@ envoyGoFilterMergeHttpPluginConfig(GoUint64 namePtr, // NOLINT(readability-iden // go:linkname envoyGoFilterOnHttpHeader // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpHeader -extern GoUint64 -envoyGoFilterOnHttpHeader(httpRequest* r, - GoUint64 endStream, // NOLINT(readability-identifier-naming) - GoUint64 headerNum, // NOLINT(readability-identifier-naming) - GoUint64 headerBytes); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnHttpHeader(processState* r, GoUint64 end_stream, GoUint64 header_num, + GoUint64 header_bytes); // go:linkname envoyGoFilterOnHttpData // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpData -extern GoUint64 envoyGoFilterOnHttpData(httpRequest* r, - GoUint64 endStream, // NOLINT(readability-identifier-naming) - GoUint64 buffer, - GoUint64 length); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnHttpData(processState* s, GoUint64 end_stream, GoUint64 buffer, + GoUint64 length); + +// go:linkname envoyGoFilterOnHttpLog +// github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpLog +extern void envoyGoFilterOnHttpLog(httpRequest* r, GoUint64 type, processState* decoding_state, + processState* encoding_state, GoUint64 req_header_num, + GoUint64 req_header_bytes, GoUint64 req_trailer_num, + GoUint64 req_trailer_bytes, GoUint64 resp_header_num, + GoUint64 resp_header_bytes, GoUint64 resp_trailer_num, + GoUint64 resp_trailer_bytes); + +// go:linkname envoyGoFilterOnHttpStreamComplete +// github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpStreamComplete +extern void envoyGoFilterOnHttpStreamComplete(httpRequest* r); // go:linkname envoyGoFilterOnHttpDestroy // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpDestroy @@ -136,6 +141,10 @@ extern void envoyGoFilterOnHttpDestroy(httpRequest* r, GoUint64 reason); // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoRequestSemaDec extern void envoyGoRequestSemaDec(httpRequest* r); +// go:linkname envoyGoFilterCleanUp +// github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterCleanUp +extern void envoyGoFilterCleanUp(); + // go:linkname envoyGoOnClusterSpecify // github.com/envoyproxy/envoy/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier.envoyGoOnClusterSpecify extern GoInt64 envoyGoOnClusterSpecify(GoUint64 pluginPtr, // NOLINT(readability-identifier-naming) @@ -191,16 +200,16 @@ extern void envoyGoFilterOnDownstreamEvent(void* f, // go:linkname envoyGoFilterOnUpstreamConnectionReady // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnUpstreamConnectionReady -extern void envoyGoFilterOnUpstreamConnectionReady( - void* f, - GoUint64 envoyConnID, // NOLINT(readability-identifier-naming) - GoUint64 configID); // NOLINT(readability-identifier-naming) +extern void +envoyGoFilterOnUpstreamConnectionReady(void* f, + GoUint64 connID); // NOLINT(readability-identifier-naming) // go:linkname envoyGoFilterOnUpstreamConnectionFailure // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnUpstreamConnectionFailure extern void envoyGoFilterOnUpstreamConnectionFailure(void* f, - GoInt reason); // NOLINT(readability-identifier-naming) + GoInt reason, // NOLINT(readability-identifier-naming) + GoUint64 connID); // NOLINT(readability-identifier-naming) // go:linkname envoyGoFilterOnUpstreamData // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnUpstreamData diff --git a/contrib/golang/common/dso/test/dso_test.cc b/contrib/golang/common/dso/test/dso_test.cc index 6471b62ac46e6..2954e20da6d28 100644 --- a/contrib/golang/common/dso/test/dso_test.cc +++ b/contrib/golang/common/dso/test/dso_test.cc @@ -21,7 +21,9 @@ std::string genSoPath(std::string name) { TEST(DsoInstanceTest, SimpleAPI) { auto path = genSoPath("simple.so"); HttpFilterDsoPtr dso(new HttpFilterDsoImpl(path)); - EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(0, 0, 0, 100), 100); + httpConfig* config = new httpConfig(); + config->config_len = 100; + EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(config), 100); } TEST(DsoManagerTest, Pub) { @@ -41,7 +43,9 @@ TEST(DsoManagerTest, Pub) { // get after load http filter dso dso = DsoManager::getDsoByPluginName(plugin_name); EXPECT_NE(dso, nullptr); - EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(0, 0, 0, 200), 200); + httpConfig* config = new httpConfig(); + config->config_len = 200; + EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(config), 200); // second time load http filter dso dso = DsoManager::load(id, path, plugin_name); @@ -87,18 +91,20 @@ TEST(DsoInstanceTest, BadSo) { TEST(DsoInstanceTest, RemovePluginConfig) { auto path = genSoPath("simple.so"); HttpFilterDsoPtr dso(new HttpFilterDsoImpl(path)); - EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(0, 0, 0, 300), 300); + httpConfig* config = new httpConfig(); + config->config_len = 300; + EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(config), 300); // new again, return 0, since it's already existing - EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(0, 0, 0, 300), 0); + EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(config), 0); // remove it - dso->envoyGoFilterDestroyHttpPluginConfig(300); + dso->envoyGoFilterDestroyHttpPluginConfig(300, 0); // new again, after removed. - EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(0, 0, 0, 300), 300); + EXPECT_EQ(dso->envoyGoFilterNewHttpPluginConfig(config), 300); // remove twice should be ok - dso->envoyGoFilterDestroyHttpPluginConfig(300); - dso->envoyGoFilterDestroyHttpPluginConfig(300); + dso->envoyGoFilterDestroyHttpPluginConfig(300, 0); + dso->envoyGoFilterDestroyHttpPluginConfig(300, 0); } } // namespace diff --git a/contrib/golang/common/dso/test/mocks.h b/contrib/golang/common/dso/test/mocks.h index 6aa459971cdd1..2c1952b50883b 100644 --- a/contrib/golang/common/dso/test/mocks.h +++ b/contrib/golang/common/dso/test/mocks.h @@ -11,17 +11,22 @@ class MockHttpFilterDsoImpl : public HttpFilterDso { MockHttpFilterDsoImpl(); ~MockHttpFilterDsoImpl() override; - MOCK_METHOD(GoUint64, envoyGoFilterNewHttpPluginConfig, - (GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); + MOCK_METHOD(GoUint64, envoyGoFilterNewHttpPluginConfig, (httpConfig * p0)); MOCK_METHOD(GoUint64, envoyGoFilterMergeHttpPluginConfig, (GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); - MOCK_METHOD(void, envoyGoFilterDestroyHttpPluginConfig, (GoUint64 p0)); + MOCK_METHOD(void, envoyGoFilterDestroyHttpPluginConfig, (GoUint64 p0, GoInt p1)); MOCK_METHOD(GoUint64, envoyGoFilterOnHttpHeader, - (httpRequest * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); + (processState * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); MOCK_METHOD(GoUint64, envoyGoFilterOnHttpData, - (httpRequest * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); + (processState * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); + MOCK_METHOD(void, envoyGoFilterOnHttpLog, + (httpRequest * p0, int p1, processState* p2, processState* p3, GoUint64 p4, + GoUint64 p5, GoUint64 p6, GoUint64 p7, GoUint64 p8, GoUint64 p9, GoUint64 p10, + GoUint64 p11)); + MOCK_METHOD(void, envoyGoFilterOnHttpStreamComplete, (httpRequest * p0)); MOCK_METHOD(void, envoyGoFilterOnHttpDestroy, (httpRequest * p0, int p1)); MOCK_METHOD(void, envoyGoRequestSemaDec, (httpRequest * p0)); + MOCK_METHOD(void, envoyGoFilterCleanUp, ()); }; class MockNetworkFilterDsoImpl : public NetworkFilterDso { @@ -40,8 +45,9 @@ class MockNetworkFilterDsoImpl : public NetworkFilterDso { MOCK_METHOD(GoUint64, envoyGoFilterOnDownstreamWrite, (void* w, GoUint64 dataSize, GoUint64 dataPtr, GoInt sliceNum, GoInt endOfStream)); - MOCK_METHOD(void, envoyGoFilterOnUpstreamConnectionReady, (void* w)); - MOCK_METHOD(void, envoyGoFilterOnUpstreamConnectionFailure, (void* w, GoInt reason)); + MOCK_METHOD(void, envoyGoFilterOnUpstreamConnectionReady, (void* w, GoUint64 connID)); + MOCK_METHOD(void, envoyGoFilterOnUpstreamConnectionFailure, + (void* w, GoInt reason, GoUint64 connID)); MOCK_METHOD(void, envoyGoFilterOnUpstreamData, (void* w, GoUint64 dataSize, GoUint64 dataPtr, GoInt sliceNum, GoInt endOfStream)); MOCK_METHOD(void, envoyGoFilterOnUpstreamEvent, (void* w, GoInt event)); diff --git a/contrib/golang/common/dso/test/test_data/simple.go b/contrib/golang/common/dso/test/test_data/simple.go index 028321c7c8b15..22d36a1cbd3ea 100644 --- a/contrib/golang/common/dso/test/test_data/simple.go +++ b/contrib/golang/common/dso/test/test_data/simple.go @@ -4,29 +4,40 @@ package main typedef struct { int foo; } httpRequest; + +typedef struct { + int state; +} processState; + +typedef struct { + unsigned long long int plugin_name_ptr; + unsigned long long int plugin_name_len; + unsigned long long int config_ptr; + unsigned long long int config_len; + int is_route_config; +} httpConfig; */ import "C" -import "unsafe" - import ( "sync" + "unsafe" ) var configCache = &sync.Map{} //export envoyGoFilterNewHttpPluginConfig -func envoyGoFilterNewHttpPluginConfig(namePtr, nameLen, configPtr, configLen uint64) uint64 { +func envoyGoFilterNewHttpPluginConfig(c *C.httpConfig) uint64 { // already existing return 0, just for testing the destroy api. - if _, ok := configCache.Load(configLen); ok { + if _, ok := configCache.Load(uint64(c.config_len)); ok { return 0 } // mark this configLen already existing - configCache.Store(configLen, configLen) - return configLen + configCache.Store(uint64(c.config_len), uint64(c.config_len)) + return uint64(c.config_len) } //export envoyGoFilterDestroyHttpPluginConfig -func envoyGoFilterDestroyHttpPluginConfig(id uint64) { +func envoyGoFilterDestroyHttpPluginConfig(id uint64, needDelay int) { configCache.Delete(id) } @@ -36,15 +47,25 @@ func envoyGoFilterMergeHttpPluginConfig(namePtr, nameLen, parentId, childId uint } //export envoyGoFilterOnHttpHeader -func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerBytes uint64) uint64 { +func envoyGoFilterOnHttpHeader(s *C.processState, endStream, headerNum, headerBytes uint64) uint64 { return 0 } //export envoyGoFilterOnHttpData -func envoyGoFilterOnHttpData(r *C.httpRequest, endStream, buffer, length uint64) uint64 { +func envoyGoFilterOnHttpData(s *C.processState, endStream, buffer, length uint64) uint64 { return 0 } +//export envoyGoFilterOnHttpLog +func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64, decodingState *C.processState, encodingState *C.processState, + reqHeaderNum, reqHeaderBytes, reqTrailerNum, reqTrailerBytes, + respHeaderNum, respHeaderBytes, respTrailerNum, respTrailerBytes uint64) { +} + +//export envoyGoFilterOnHttpStreamComplete +func envoyGoFilterOnHttpStreamComplete(r *C.httpRequest) { +} + //export envoyGoFilterOnHttpDestroy func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) { } @@ -84,10 +105,10 @@ func envoyGoFilterOnDownstreamWrite(wrapper unsafe.Pointer, dataSize uint64, dat } //export envoyGoFilterOnUpstreamConnectionReady -func envoyGoFilterOnUpstreamConnectionReady(wrapper unsafe.Pointer) {} +func envoyGoFilterOnUpstreamConnectionReady(wrapper unsafe.Pointer, connID uint64) {} //export envoyGoFilterOnUpstreamConnectionFailure -func envoyGoFilterOnUpstreamConnectionFailure(wrapper unsafe.Pointer, reason int) {} +func envoyGoFilterOnUpstreamConnectionFailure(wrapper unsafe.Pointer, reason int, connID uint64) {} //export envoyGoFilterOnUpstreamData func envoyGoFilterOnUpstreamData(wrapper unsafe.Pointer, dataSize uint64, dataPtr uint64, sliceNum int, endOfStream int) { @@ -104,5 +125,9 @@ func envoyGoFilterOnSemaDec(wrapper unsafe.Pointer) { func envoyGoRequestSemaDec(r *C.httpRequest) { } +//export envoyGoFilterCleanUp +func envoyGoFilterCleanUp() { +} + func main() { } diff --git a/contrib/golang/common/go/api/BUILD b/contrib/golang/common/go/api/BUILD index f77587290be12..9d3d70db763ed 100644 --- a/contrib/golang/common/go/api/BUILD +++ b/contrib/golang/common/go/api/BUILD @@ -1,15 +1,35 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) licenses(["notice"]) # Apache 2 go_library( name = "api", srcs = [ + "api.h", "capi.go", - "cgocheck.go", "filter.go", + "logger.go", "type.go", ], + cgo = True, + clinkopts = select({ + "@io_bazel_rules_go//go/platform:android": [ + "-Wl,-unresolved-symbols=ignore-all", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "-Wl,-undefined,dynamic_lookup", + ], + "@io_bazel_rules_go//go/platform:ios": [ + "-Wl,-undefined,dynamic_lookup", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "-Wl,-unresolved-symbols=ignore-all", + ], + "//conditions:default": [], + }), importpath = "github.com/envoyproxy/envoy/contrib/golang/common/go/api", visibility = ["//visibility:public"], deps = [ diff --git a/contrib/golang/common/go/api/api.h b/contrib/golang/common/go/api/api.h index 11a266e4e1e23..0e681bbe042c7 100644 --- a/contrib/golang/common/go/api/api.h +++ b/contrib/golang/common/go/api/api.h @@ -3,22 +3,50 @@ // NOLINT(namespace-envoy) #ifdef __cplusplus +#include + +#define _Atomic(X) std::atomic + extern "C" { +#else +#include // NOLINT(modernize-deprecated-headers) #endif -#include +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) typedef struct { // NOLINT(modernize-use-using) const char* data; - unsigned long long int len; + uint64_t len; } Cstring; +struct httpRequest; + typedef struct { // NOLINT(modernize-use-using) + struct httpRequest* req; + int is_encoding; + int state; +} processState; + +typedef struct httpRequest { // NOLINT(modernize-use-using) Cstring plugin_name; - unsigned long long int configId; - int phase; + uint64_t configId; + // The ID of the worker that is processing this request, this enables the go filter to dedicate + // memory to each worker and not require locks + uint32_t worker_id; + // This flag will be read & written by different threads, so it need to be atomic + _Atomic(int) is_golang_processing_log; } httpRequest; +typedef struct { // NOLINT(modernize-use-using) + uint64_t plugin_name_ptr; + uint64_t plugin_name_len; + uint64_t config_ptr; + uint64_t config_len; + int is_route_config; + uint32_t concurrency; +} httpConfig; + typedef enum { // NOLINT(modernize-use-using) Set, Append, @@ -39,40 +67,69 @@ typedef enum { // NOLINT(modernize-use-using) CAPIInvalidPhase = -4, CAPIValueNotFound = -5, CAPIYield = -6, + CAPIInternalFailure = -7, + CAPISerializationFailure = -8, + CAPIInvalidScene = -9, } CAPIStatus; -CAPIStatus envoyGoFilterHttpContinue(void* r, int status); -CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text, - void* headers, long long int grpc_status, void* details); -CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details); - -CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key, void* value); -CAPIStatus envoyGoFilterHttpCopyHeaders(void* r, void* strs, void* buf); -CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key, void* value, headerAction action); -CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key); - -CAPIStatus envoyGoFilterHttpGetBuffer(void* r, unsigned long long int buffer, void* value); -CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, unsigned long long int buffer, void* data, - int length, bufferAction action); - -CAPIStatus envoyGoFilterHttpCopyTrailers(void* r, void* strs, void* buf); -CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key, void* value, headerAction action); -CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key); - -CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, void* value); -CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, void* value); - -CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name, void* hand); -CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name, void* key, void* buf); - -void envoyGoFilterHttpLog(uint32_t level, void* message); -uint32_t envoyGoFilterHttpLogLevel(); - +/* These APIs are related to the decode/encode phase, use the pointer of processState. */ +CAPIStatus envoyGoFilterHttpContinue(void* s, int status); +CAPIStatus envoyGoFilterHttpSendLocalReply(void* s, int response_code, void* body_text_data, + int body_text_len, void* headers, int headers_num, + long long int grpc_status, void* details_data, + int details_len); +CAPIStatus envoyGoFilterHttpSendPanicReply(void* s, void* details_data, int details_len); +CAPIStatus envoyGoFilterHttpAddData(void* s, void* data, int data_len, bool is_streaming); +CAPIStatus envoyGoFilterHttpInjectData(void* s, void* data, int data_len); + +CAPIStatus envoyGoFilterHttpGetHeader(void* s, void* key_data, int key_len, uint64_t* value_data, + int* value_len); +CAPIStatus envoyGoFilterHttpCopyHeaders(void* s, void* strs, void* buf); +CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* s, void* key_data, int key_len, void* value_data, + int value_len, headerAction action); +CAPIStatus envoyGoFilterHttpRemoveHeader(void* s, void* key_data, int key_len); + +CAPIStatus envoyGoFilterHttpGetBuffer(void* s, uint64_t buffer, void* value); +CAPIStatus envoyGoFilterHttpDrainBuffer(void* s, uint64_t buffer, uint64_t length); +CAPIStatus envoyGoFilterHttpSetBufferHelper(void* s, uint64_t buffer, void* data, int length, + bufferAction action); + +CAPIStatus envoyGoFilterHttpCopyTrailers(void* s, void* strs, void* buf); +CAPIStatus envoyGoFilterHttpSetTrailer(void* s, void* key_data, int key_len, void* value, + int value_len, headerAction action); +CAPIStatus envoyGoFilterHttpRemoveTrailer(void* s, void* key_data, int key_len); + +/* These APIs have nothing to do with the decode/encode phase, use the pointer of httpRequest. */ +CAPIStatus envoyGoFilterHttpClearRouteCache(void* r, bool refresh); +CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, uint64_t* value_data, int* value_len); +CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, uint64_t* value); + +CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name_data, int name_len, + uint64_t* value_data, int* value_len); +CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name_data, int name_len, + void* key_data, int key_len, void* buf_data, + int buf_len); void envoyGoFilterHttpFinalize(void* r, int reason); -CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key, void* value, int state_type, +CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key_data, int key_len, + void* value_data, int value_len, int state_type, int life_span, int stream_sharing); -CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key, void* value); +CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len); +CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len, int* rc); + +/* These APIs have nothing to do with request */ +void envoyGoFilterLog(uint32_t level, void* message_data, int message_len); +uint32_t envoyGoFilterLogLevel(); + +/* These APIs are related to config, use the pointer of config. */ +void envoyGoConfigHttpFinalize(void* c); +CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name_data, + int name_len, uint32_t* metric_id); +CAPIStatus envoyGoFilterHttpIncrementMetric(void* c, uint32_t metric_id, int64_t offset); +CAPIStatus envoyGoFilterHttpGetMetric(void* c, uint32_t metric_id, uint64_t* value); +CAPIStatus envoyGoFilterHttpRecordMetric(void* c, uint32_t metric_id, uint64_t value); // downstream CAPIStatus envoyGoFilterDownstreamClose(void* wrapper, int closeType); @@ -81,10 +138,10 @@ CAPIStatus envoyGoFilterDownstreamWrite(void* wrapper, void* buffers, int buffer void envoyGoFilterDownstreamFinalize(void* wrapper, int reason); CAPIStatus envoyGoFilterDownstreamInfo(void* wrapper, int t, void* ret); -// upstream -void* envoyGoFilterUpstreamConnect(void* libraryID, void* addr); -CAPIStatus envoyGoFilterUpstreamWrite(void* wrapper, void* buffers, int buffersNum, int endStream); -CAPIStatus envoyGoFilterUpstreamClose(void* wrapper, int closeType); +void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, uint64_t conn_id); +CAPIStatus envoyGoFilterUpstreamConnEnableHalfClose(void* u, int enable_half_close); +CAPIStatus envoyGoFilterUpstreamWrite(void* u, void* buffer_ptr, int buffer_len, int end_stream); +CAPIStatus envoyGoFilterUpstreamClose(void* wrapper, int close_type); void envoyGoFilterUpstreamFinalize(void* wrapper, int reason); CAPIStatus envoyGoFilterUpstreamInfo(void* wrapper, int t, void* ret); diff --git a/contrib/golang/common/go/api/capi.go b/contrib/golang/common/go/api/capi.go index 9d7cc069a8da2..f8367a10533f6 100644 --- a/contrib/golang/common/go/api/capi.go +++ b/contrib/golang/common/go/api/capi.go @@ -20,24 +20,32 @@ package api import "unsafe" type HttpCAPI interface { - HttpContinue(r unsafe.Pointer, status uint64) - HttpSendLocalReply(r unsafe.Pointer, responseCode int, bodyText string, headers map[string]string, grpcStatus int64, details string) + /* These APIs are related to the decode/encode phase, use the pointer of processState. */ + HttpContinue(s unsafe.Pointer, status uint64) + HttpSendLocalReply(s unsafe.Pointer, responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) + HttpAddData(s unsafe.Pointer, data []byte, isStreaming bool) + HttpInjectData(s unsafe.Pointer, data []byte) // Send a specialized reply that indicates that the filter has failed on the go side. Internally this is used for // when unhandled panics are detected. - HttpSendPanicReply(r unsafe.Pointer, details string) + HttpSendPanicReply(s unsafe.Pointer, details string) // experience api, memory unsafe - HttpGetHeader(r unsafe.Pointer, key *string, value *string) - HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string - HttpSetHeader(r unsafe.Pointer, key *string, value *string, add bool) - HttpRemoveHeader(r unsafe.Pointer, key *string) + HttpGetHeader(s unsafe.Pointer, key string) string + HttpCopyHeaders(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string + HttpSetHeader(s unsafe.Pointer, key string, value string, add bool) + HttpRemoveHeader(s unsafe.Pointer, key string) - HttpGetBuffer(r unsafe.Pointer, bufferPtr uint64, value *string, length uint64) - HttpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, value string, action BufferAction) + HttpGetBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) []byte + HttpDrainBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) + HttpSetBufferHelper(s unsafe.Pointer, bufferPtr uint64, value string, action BufferAction) + HttpSetBytesBufferHelper(s unsafe.Pointer, bufferPtr uint64, value []byte, action BufferAction) - HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string - HttpSetTrailer(r unsafe.Pointer, key *string, value *string, add bool) - HttpRemoveTrailer(r unsafe.Pointer, key *string) + HttpCopyTrailers(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string + HttpSetTrailer(s unsafe.Pointer, key string, value string, add bool) + HttpRemoveTrailer(s unsafe.Pointer, key string) + + /* These APIs have nothing to do with the decode/encode phase, use the pointer of httpRequest. */ + ClearRouteCache(r unsafe.Pointer, refresh bool) HttpGetStringValue(r unsafe.Pointer, id int) (string, bool) HttpGetIntegerValue(r unsafe.Pointer, id int) (uint64, bool) @@ -45,13 +53,23 @@ type HttpCAPI interface { HttpGetDynamicMetadata(r unsafe.Pointer, filterName string) map[string]interface{} HttpSetDynamicMetadata(r unsafe.Pointer, filterName string, key string, value interface{}) - HttpLog(level LogType, message string) - HttpLogLevel() LogType + HttpSetStringFilterState(r unsafe.Pointer, key string, value string, stateType StateType, lifeSpan LifeSpan, streamSharing StreamSharing) + HttpGetStringFilterState(r unsafe.Pointer, key string) string + + HttpGetStringProperty(r unsafe.Pointer, key string) (string, error) HttpFinalize(r unsafe.Pointer, reason int) - HttpSetStringFilterState(r unsafe.Pointer, key string, value string, stateType StateType, lifeSpan LifeSpan, streamSharing StreamSharing) - HttpGetStringFilterState(r unsafe.Pointer, key string) string + /* These APIs are related to config, use the pointer of config. */ + HttpDefineMetric(c unsafe.Pointer, metricType MetricType, name string) uint32 + HttpIncrementMetric(c unsafe.Pointer, metricId uint32, offset int64) + HttpGetMetric(c unsafe.Pointer, metricId uint32) uint64 + HttpRecordMetric(c unsafe.Pointer, metricId uint32, value uint64) + HttpConfigFinalize(c unsafe.Pointer) + + /* These APIs have nothing to do with request */ + HttpLog(level LogType, message string) + HttpLogLevel() LogType } type NetworkCAPI interface { @@ -69,7 +87,9 @@ type NetworkCAPI interface { SetFilterState(f unsafe.Pointer, key string, value string, stateType StateType, lifeSpan LifeSpan, streamSharing StreamSharing) // UpstreamConnect creates an envoy upstream connection to address - UpstreamConnect(libraryID string, addr string) unsafe.Pointer + UpstreamConnect(libraryID string, addr string, connID uint64) unsafe.Pointer + // UpstreamConnEnableHalfClose upstream conn EnableHalfClose + UpstreamConnEnableHalfClose(f unsafe.Pointer, enableHalfClose int) // UpstreamWrite writes buffer data into upstream connection. UpstreamWrite(f unsafe.Pointer, bufferPtr unsafe.Pointer, bufferLen int, endStream int) // UpstreamClose closes the upstream connection @@ -79,3 +99,17 @@ type NetworkCAPI interface { // UpstreamInfo gets the upstream connection info of infoType UpstreamInfo(f unsafe.Pointer, infoType int) string } + +type CommonCAPI interface { + Log(level LogType, message string) + LogLevel() LogType +} + +type commonCApiImpl struct{} + +var cAPI CommonCAPI = &commonCApiImpl{} + +// SetCommonCAPI for mock cAPI +func SetCommonCAPI(api CommonCAPI) { + cAPI = api +} diff --git a/contrib/golang/common/go/api/cgocheck.go b/contrib/golang/common/go/api/cgocheck.go deleted file mode 100644 index 01c6f84c8c408..0000000000000 --- a/contrib/golang/common/go/api/cgocheck.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package api - -import ( - "os" - "strings" -) - -func CgoCheckDisabled() bool { - env := os.Getenv("GODEBUG") - // TODO: handle compile-time GODEBUG var after Go 1.21 is released - if strings.Index(env, "cgocheck=0") != -1 { - return true - } - return false -} diff --git a/contrib/golang/common/go/api/filter.go b/contrib/golang/common/go/api/filter.go index 2bae0600c1766..569d48b7ac603 100644 --- a/contrib/golang/common/go/api/filter.go +++ b/contrib/golang/common/go/api/filter.go @@ -29,6 +29,11 @@ type ( PassThroughStreamDecoderFilter PassThroughStreamEncoderFilter } + + // EmptyDownstreamFilter provides the no-op implementation of the DownstreamFilter interface + EmptyDownstreamFilter struct{} + // EmptyUpstreamFilter provides the no-op implementation of the UpstreamFilter interface + EmptyUpstreamFilter struct{} ) // request @@ -74,21 +79,44 @@ type StreamFilter interface { StreamDecoderFilter // response stream StreamEncoderFilter + + // log + OnLog(RequestHeaderMap, RequestTrailerMap, ResponseHeaderMap, ResponseTrailerMap) + OnLogDownstreamStart(RequestHeaderMap) + OnLogDownstreamPeriodic(RequestHeaderMap, RequestTrailerMap, ResponseHeaderMap, ResponseTrailerMap) + // destroy filter OnDestroy(DestroyReason) - // TODO add more for stream complete and log phase + OnStreamComplete() +} + +func (*PassThroughStreamFilter) OnLog(RequestHeaderMap, RequestTrailerMap, ResponseHeaderMap, ResponseTrailerMap) { +} + +func (*PassThroughStreamFilter) OnLogDownstreamStart(RequestHeaderMap) { +} + +func (*PassThroughStreamFilter) OnLogDownstreamPeriodic(RequestHeaderMap, RequestTrailerMap, ResponseHeaderMap, ResponseTrailerMap) { } func (*PassThroughStreamFilter) OnDestroy(DestroyReason) { } +func (*PassThroughStreamFilter) OnStreamComplete() { +} + type StreamFilterConfigParser interface { - Parse(any *anypb.Any) (interface{}, error) + // Parse the proto message to any Go value, and return error to reject the config. + // This is called when Envoy receives the config from the control plane. + // Also, you can define Metrics through the callbacks, and the callbacks will be nil when parsing the route config. + Parse(any *anypb.Any, callbacks ConfigCallbackHandler) (interface{}, error) + // Merge the two configs(filter level config or route level config) into one. + // May merge multi-level configurations, i.e. filter level, virtualhost level, router level and weighted cluster level, + // into a single one recursively, by invoking this method multiple times. Merge(parentConfig interface{}, childConfig interface{}) interface{} } -type StreamFilterConfigFactory func(config interface{}) StreamFilterFactory -type StreamFilterFactory func(callbacks FilterCallbackHandler) StreamFilter +type StreamFilterFactory func(config interface{}, callbacks FilterCallbackHandler) StreamFilter // stream info // refer https://github.com/envoyproxy/envoy/blob/main/envoy/stream_info/stream_info.h @@ -119,26 +147,65 @@ type StreamInfo interface { FilterState() FilterState // VirtualClusterName returns the name of the virtual cluster which got matched VirtualClusterName() (string, bool) + // WorkerID returns the ID of the Envoy worker thread + WorkerID() uint32 + // Some fields in stream info can be fetched via GetProperty + // For example, startTime() is equal to GetProperty("request.time") } type StreamFilterCallbacks interface { StreamInfo() StreamInfo + + // ClearRouteCache clears the route cache for the current request, and filtermanager will re-fetch the route in the next filter. + // Please be careful to invoke it, since filtermanager will raise an 404 route_not_found response when failed to re-fetch a route. + ClearRouteCache() + // RefreshRouteCache works like ClearRouteCache, but it will re-fetch the route immediately. + RefreshRouteCache() + Log(level LogType, msg string) + LogLevel() LogType + // GetProperty fetch Envoy attribute and return the value as a string. + // The list of attributes can be found in https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes. + // If the fetch succeeded, a string will be returned. + // If the value is a timestamp, it is returned as a timestamp string like "2023-07-31T07:21:40.695646+00:00". + // If the fetch failed (including the value is not found), an error will be returned. + // + // The error can be one of: + // * ErrInternalFailure + // * ErrSerializationFailure (Currently, fetching attributes in List/Map type are unsupported) + // * ErrValueNotFound + GetProperty(key string) (string, error) + // TODO add more for filter callbacks } -type FilterCallbacks interface { - StreamFilterCallbacks +// FilterProcessCallbacks is the interface for filter to process request/response in decode/encode phase. +type FilterProcessCallbacks interface { // Continue or SendLocalReply should be last API invoked, no more code after them. Continue(StatusType) - SendLocalReply(responseCode int, bodyText string, headers map[string]string, grpcStatus int64, details string) + SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) // RecoverPanic recover panic in defer and terminate the request by SendLocalReply with 500 status code. RecoverPanic() - Log(level LogType, msg string) - LogLevel() LogType - // TODO add more for filter callbacks + // AddData add extra data when processing headers/trailers. + // For example, turn a headers only request into a request with a body, add more body when processing trailers, and so on. + // The second argument isStreaming supplies if this caller streams data or buffers the full body. + AddData(data []byte, isStreaming bool) + // InjectData inject the content of slice data via Envoy StreamXXFilterCallbacks's injectXXDataToFilterChaininjectData. + InjectData(data []byte) +} + +type DecoderFilterCallbacks interface { + FilterProcessCallbacks +} + +type EncoderFilterCallbacks interface { + FilterProcessCallbacks } type FilterCallbackHandler interface { - FilterCallbacks + StreamFilterCallbacks + // DecoderFilterCallbacks could only be used in DecodeXXX phases. + DecoderFilterCallbacks() DecoderFilterCallbacks + // EncoderFilterCallbacks could only be used in EncodeXXX phases. + EncoderFilterCallbacks() EncoderFilterCallbacks } type DynamicMetadata interface { @@ -157,6 +224,21 @@ type DownstreamFilter interface { OnWrite(buffer []byte, endOfStream bool) FilterStatus } +func (*EmptyDownstreamFilter) OnNewConnection() FilterStatus { + return NetworkFilterContinue +} + +func (*EmptyDownstreamFilter) OnData(buffer []byte, endOfStream bool) FilterStatus { + return NetworkFilterContinue +} + +func (*EmptyDownstreamFilter) OnEvent(event ConnectionEvent) { +} + +func (*EmptyDownstreamFilter) OnWrite(buffer []byte, endOfStream bool) FilterStatus { + return NetworkFilterContinue +} + type UpstreamFilter interface { // Called when a connection is available to process a request/response. OnPoolReady(cb ConnectionCallback) @@ -168,6 +250,19 @@ type UpstreamFilter interface { OnEvent(event ConnectionEvent) } +func (*EmptyUpstreamFilter) OnPoolReady(cb ConnectionCallback) { +} + +func (*EmptyUpstreamFilter) OnPoolFailure(poolFailureReason PoolFailureReason, transportFailureReason string) { +} + +func (*EmptyUpstreamFilter) OnData(buffer []byte, endOfStream bool) FilterStatus { + return NetworkFilterContinue +} + +func (*EmptyUpstreamFilter) OnEvent(event ConnectionEvent) { +} + type ConnectionCallback interface { // StreamInfo returns the stream info of the connection StreamInfo() StreamInfo @@ -175,6 +270,8 @@ type ConnectionCallback interface { Write(buffer []byte, endStream bool) // Close the connection. Close(closeType ConnectionCloseType) + // EnableHalfClose only for upstream connection + EnableHalfClose(enabled bool) } type StateType int @@ -205,3 +302,39 @@ type FilterState interface { SetString(key, value string, stateType StateType, lifeSpan LifeSpan, streamSharing StreamSharing) GetString(key string) string } + +type MetricType uint32 + +const ( + Counter MetricType = 0 + Gauge MetricType = 1 + Histogram MetricType = 2 +) + +type ConfigCallbacks interface { + // Define a metric, for different MetricType, name must be different, + // for same MetricType, the same name will share a metric. + DefineCounterMetric(name string) CounterMetric + DefineGaugeMetric(name string) GaugeMetric + // TODO Histogram +} + +type ConfigCallbackHandler interface { + ConfigCallbacks +} + +type CounterMetric interface { + Increment(offset int64) + Get() uint64 + Record(value uint64) +} + +type GaugeMetric interface { + Increment(offset int64) + Get() uint64 + Record(value uint64) +} + +// TODO +type HistogramMetric interface { +} diff --git a/contrib/golang/common/go/api/logger.go b/contrib/golang/common/go/api/logger.go new file mode 100644 index 0000000000000..a9223ba60611c --- /dev/null +++ b/contrib/golang/common/go/api/logger.go @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "fmt" +) + +func (c *commonCApiImpl) Log(level LogType, message string) { + panic("To implement") +} + +func (c *commonCApiImpl) LogLevel() LogType { + panic("To implement") +} + +func LogTrace(message string) { + if cAPI.LogLevel() > Trace { + return + } + cAPI.Log(Trace, message) +} + +func LogDebug(message string) { + if cAPI.LogLevel() > Debug { + return + } + cAPI.Log(Debug, message) +} + +func LogInfo(message string) { + if cAPI.LogLevel() > Info { + return + } + cAPI.Log(Info, message) +} + +func LogWarn(message string) { + if cAPI.LogLevel() > Warn { + return + } + cAPI.Log(Warn, message) +} + +func LogError(message string) { + if cAPI.LogLevel() > Error { + return + } + cAPI.Log(Error, message) +} + +func LogCritical(message string) { + if cAPI.LogLevel() > Critical { + return + } + cAPI.Log(Critical, message) +} + +func LogTracef(format string, v ...any) { + if cAPI.LogLevel() > Trace { + return + } + cAPI.Log(Trace, fmt.Sprintf(format, v...)) +} + +func LogDebugf(format string, v ...any) { + if cAPI.LogLevel() > Debug { + return + } + cAPI.Log(Debug, fmt.Sprintf(format, v...)) +} + +func LogInfof(format string, v ...any) { + if cAPI.LogLevel() > Info { + return + } + cAPI.Log(Info, fmt.Sprintf(format, v...)) +} + +func LogWarnf(format string, v ...any) { + if cAPI.LogLevel() > Warn { + return + } + cAPI.Log(Warn, fmt.Sprintf(format, v...)) +} + +func LogErrorf(format string, v ...any) { + if cAPI.LogLevel() > Error { + return + } + cAPI.Log(Error, fmt.Sprintf(format, v...)) +} + +func LogCriticalf(format string, v ...any) { + if cAPI.LogLevel() > Critical { + return + } + cAPI.Log(Critical, fmt.Sprintf(format, v...)) +} + +func GetLogLevel() LogType { + return cAPI.LogLevel() +} diff --git a/contrib/golang/common/go/api/type.go b/contrib/golang/common/go/api/type.go index f2a0fa7dfb113..7aceebd9e7b1f 100644 --- a/contrib/golang/common/go/api/type.go +++ b/contrib/golang/common/go/api/type.go @@ -17,6 +17,8 @@ package api +import "errors" + // ****************** filter status start ******************// type StatusType int @@ -107,16 +109,19 @@ type HeaderMap interface { // Set key-value pair in header map, the previous pair will be replaced if exists. // It may not take affects immediately in the Envoy thread side when it's invoked in a Go thread. + // This won't refresh route cache, please invoke ClearRouteCache if needed. Set(key, value string) // Add value for given key. // Multiple headers with the same key may be added with this function. // Use Set for setting a single header for the given key. // It may not take affects immediately in the Envoy thread side when it's invoked in a Go thread. + // This won't refresh route cache, please invoke ClearRouteCache if needed. Add(key, value string) // Del delete pair of specified key // It may not take affects immediately in the Envoy thread side when it's invoked in a Go thread. + // This won't refresh route cache, please invoke ClearRouteCache if needed. Del(key string) // Range calls f sequentially for each key and value present in the map. @@ -127,17 +132,26 @@ type HeaderMap interface { // RangeWithCopy calls f sequentially for each key and value copied from the map. RangeWithCopy(f func(key, value string) bool) - // ByteSize return size of HeaderMap - ByteSize() uint64 + // GetAllHeaders returns all the headers. + GetAllHeaders() map[string][]string } type RequestHeaderMap interface { HeaderMap - Protocol() string Scheme() string Method() string Host() string Path() string + // SetMethod set method in header map + // This won't refresh route cache, please invoke ClearRouteCache if needed. + SetMethod(method string) + // SetHost set host in header map + // This won't refresh route cache, please invoke ClearRouteCache if needed. + SetHost(host string) + // SetPath set path in header map + // This won't refresh route cache, please invoke ClearRouteCache if needed. + SetPath(path string) + // Note: Scheme is the downstream protocol, we'd better not override it. } type RequestTrailerMap interface { @@ -200,12 +214,6 @@ type DataBufferBase interface { // buffer becomes too large, Write will panic with ErrTooLarge. WriteUint64(p uint64) error - // Peek returns n bytes from buffer, without draining any buffered data. - // If n > readable buffer, nil will be returned. - // It can be used in codec to check first-n-bytes magic bytes - // Note: do not change content in return bytes, use write instead - Peek(n int) []byte - // Bytes returns all bytes from buffer, without draining any buffered data. // It can be used to get fixed-length content, such as headers, body. // Note: do not change content in return bytes, use write instead @@ -257,6 +265,25 @@ const ( Terminate DestroyReason = 1 ) +// For each AccessLogType's meaning, see +// https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage +// Currently, only some downstream access log types are supported +type AccessLogType int + +const ( + AccessLogNotSet AccessLogType = 0 + AccessLogTcpUpstreamConnected AccessLogType = 1 + AccessLogTcpPeriodic AccessLogType = 2 + AccessLogTcpConnectionEnd AccessLogType = 3 + AccessLogDownstreamStart AccessLogType = 4 + AccessLogDownstreamPeriodic AccessLogType = 5 + AccessLogDownstreamEnd AccessLogType = 6 + AccessLogUpstreamPoolReady AccessLogType = 7 + AccessLogUpstreamPeriodic AccessLogType = 8 + AccessLogUpstreamEnd AccessLogType = 9 + AccessLogDownstreamTunnelSuccessfullyEstablished AccessLogType = 10 +) + const ( NormalFinalize int = 0 // normal, finalize on destroy GCFinalize int = 1 // finalize in GC sweep @@ -408,3 +435,13 @@ func (t ConnectionInfoType) String() string { } return "unknown" } + +// *************** errors start **************// +var ( + ErrInternalFailure = errors.New("internal failure") + ErrValueNotFound = errors.New("value not found") + // Failed to serialize the value when we fetch the value as string + ErrSerializationFailure = errors.New("serialization failure") +) + +// *************** errors end **************// diff --git a/contrib/golang/common/go/api_impl/BUILD b/contrib/golang/common/go/api_impl/BUILD new file mode 100644 index 0000000000000..3813bf2577934 --- /dev/null +++ b/contrib/golang/common/go/api_impl/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "api_impl", + srcs = [ + "api.h", + "capi_impl.go", + ], + cgo = True, + clinkopts = select({ + "@io_bazel_rules_go//go/platform:android": [ + "-Wl,-unresolved-symbols=ignore-all", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "-Wl,-undefined,dynamic_lookup", + ], + "@io_bazel_rules_go//go/platform:ios": [ + "-Wl,-undefined,dynamic_lookup", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "-Wl,-unresolved-symbols=ignore-all", + ], + "//conditions:default": [], + }), + importpath = "github.com/envoyproxy/envoy/contrib/golang/common/go/api_impl", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + ], +) diff --git a/contrib/golang/common/go/api_impl/api.h b/contrib/golang/common/go/api_impl/api.h new file mode 120000 index 0000000000000..7ccbc18e959f7 --- /dev/null +++ b/contrib/golang/common/go/api_impl/api.h @@ -0,0 +1 @@ +../api/api.h \ No newline at end of file diff --git a/contrib/golang/common/go/api_impl/capi_impl.go b/contrib/golang/common/go/api_impl/capi_impl.go new file mode 100644 index 0000000000000..7bfaf9230d980 --- /dev/null +++ b/contrib/golang/common/go/api_impl/capi_impl.go @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api_impl + +/* +// ref https://github.com/golang/go/issues/25832 + +#cgo CFLAGS: -I../api +#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all +#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup + +#include +#include + +#include "api.h" + +*/ +import "C" +import ( + "os" + "sync/atomic" + "time" + "unsafe" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +var ( + currLogLevel atomic.Int32 +) + +type commonCApiImpl struct{} + +// The default log format is: +// [2023-08-09 03:04:15.985][1390][critical][golang] [contrib/golang/common/log/cgo.cc:27] msg + +func (c *commonCApiImpl) Log(level api.LogType, message string) { + C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) +} + +func (c *commonCApiImpl) LogLevel() api.LogType { + lv := currLogLevel.Load() + return api.LogType(lv) +} + +func init() { + api.SetCommonCAPI(&commonCApiImpl{}) + + interval := time.Second + envInterval := os.Getenv("ENVOY_GOLANG_LOG_LEVEL_SYNC_INTERVAL") + if envInterval != "" { + dur, err := time.ParseDuration(envInterval) + if err == nil && dur >= time.Millisecond { + // protect against too frequent sync + interval = dur + } else { + api.LogErrorf("invalid env var ENVOY_GOLANG_LOG_LEVEL_SYNC_INTERVAL: %s", envInterval) + } + } + + currLogLevel.Store(int32(C.envoyGoFilterLogLevel())) + ticker := time.NewTicker(interval) + go func() { + for { + select { + case <-ticker.C: + currLogLevel.Store(int32(C.envoyGoFilterLogLevel())) + } + } + }() +} diff --git a/contrib/golang/common/log/BUILD b/contrib/golang/common/log/BUILD new file mode 100644 index 0000000000000..afd36321b4b27 --- /dev/null +++ b/contrib/golang/common/log/BUILD @@ -0,0 +1,22 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "log_lib", + srcs = ["cgo.cc"], + hdrs = [ + "cgo.h", + ], + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/dso:dso_lib", + "//source/common/common:utility_lib", + ], +) diff --git a/contrib/golang/common/log/cgo.cc b/contrib/golang/common/log/cgo.cc new file mode 100644 index 0000000000000..0aa799b3ea7f9 --- /dev/null +++ b/contrib/golang/common/log/cgo.cc @@ -0,0 +1,68 @@ +#include "contrib/golang/common/log/cgo.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Golang { + +/* FilterLogger */ +void FilterLogger::log(uint32_t level, absl::string_view message) const { + switch (static_cast(level)) { + case spdlog::level::trace: + ENVOY_LOG(trace, "{}", message); + return; + case spdlog::level::debug: + ENVOY_LOG(debug, "{}", message); + return; + case spdlog::level::info: + ENVOY_LOG(info, "{}", message); + return; + case spdlog::level::warn: + ENVOY_LOG(warn, "{}", message); + return; + case spdlog::level::err: + ENVOY_LOG(error, "{}", message); + return; + case spdlog::level::critical: + ENVOY_LOG(critical, "{}", message); + return; + case spdlog::level::off: + // means not logging + return; + case spdlog::level::n_levels: + PANIC("not implemented"); + } + + ENVOY_LOG(error, "undefined log level {} with message '{}'", level, message); + + PANIC_DUE_TO_CORRUPT_ENUM; +} + +uint32_t FilterLogger::level() const { return static_cast(ENVOY_LOGGER().level()); } + +const FilterLogger& getFilterLogger() { CONSTRUCT_ON_FIRST_USE(FilterLogger); } + +// The returned absl::string_view only refer to Go memory, +// should not use it after the current cgo call returns. +absl::string_view stringViewFromGoPointer(void* p, int len) { + return {static_cast(p), static_cast(len)}; +} + +#ifdef __cplusplus +extern "C" { +#endif + +void envoyGoFilterLog(uint32_t level, void* message_data, int message_len) { + auto mesg = stringViewFromGoPointer(message_data, message_len); + getFilterLogger().log(level, mesg); +} + +uint32_t envoyGoFilterLogLevel() { return getFilterLogger().level(); } + +#ifdef __cplusplus +} +#endif +} // namespace Golang +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/golang/common/log/cgo.h b/contrib/golang/common/log/cgo.h new file mode 100644 index 0000000000000..1ec4e87133013 --- /dev/null +++ b/contrib/golang/common/log/cgo.h @@ -0,0 +1,23 @@ +#pragma once + +#include "source/common/common/utility.h" + +#include "contrib/golang/common/dso/dso.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Golang { + +class FilterLogger : Logger::Loggable { +public: + FilterLogger() = default; + + void log(uint32_t level, absl::string_view message) const; + uint32_t level() const; +}; + +} // namespace Golang +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/golang/filters/http/source/BUILD b/contrib/golang/filters/http/source/BUILD index 5b89151eb067a..6e5e85d57dcc7 100644 --- a/contrib/golang/filters/http/source/BUILD +++ b/contrib/golang/filters/http/source/BUILD @@ -37,6 +37,19 @@ envoy_cc_library( "//source/common/http:headers_lib", "//source/common/http:utility_lib", "//source/common/http/http1:codec_lib", + "//source/common/protobuf:utility_lib", + "//source/common/router:string_accessor_lib", + "//source/extensions/filters/common/expr:cel_state_lib", + "//source/extensions/filters/common/expr:evaluator_lib", + "@com_google_cel_cpp//eval/public:activation", + "@com_google_cel_cpp//eval/public:builtin_func_registrar", + "@com_google_cel_cpp//eval/public:cel_expr_builder_factory", + "@com_google_cel_cpp//eval/public:cel_value", + "@com_google_cel_cpp//eval/public:value_export_util", + "@com_google_cel_cpp//eval/public/containers:field_access", + "@com_google_cel_cpp//eval/public/containers:field_backed_list_impl", + "@com_google_cel_cpp//eval/public/containers:field_backed_map_impl", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@envoy_api//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg_cc_proto", ], ) @@ -66,6 +79,7 @@ envoy_cc_library( ], deps = [ "//contrib/golang/common/dso:dso_lib", + "//contrib/golang/common/log:log_lib", "//envoy/http:codes_interface", "//envoy/http:filter_interface", "//source/common/buffer:watermark_buffer_lib", @@ -76,6 +90,9 @@ envoy_cc_library( "//source/common/http:headers_lib", "//source/common/http:utility_lib", "//source/common/http/http1:codec_lib", + "//source/common/protobuf:utility_lib", + "//source/extensions/filters/common/expr:cel_state_lib", + "//source/extensions/filters/common/expr:evaluator_lib", "@envoy_api//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg_cc_proto", ], ) diff --git a/contrib/golang/filters/http/source/cgo.cc b/contrib/golang/filters/http/source/cgo.cc index 8cf85a9fd2a4b..330f1ba9f9d84 100644 --- a/contrib/golang/filters/http/source/cgo.cc +++ b/contrib/golang/filters/http/source/cgo.cc @@ -12,24 +12,16 @@ namespace Golang { // thread. // -// Deep copy GoString into std::string, including the string content, +// Deep copy Go memory into std::string, // it's safe to use it after the current cgo call returns. -std::string copyGoString(void* str) { - if (str == nullptr) { - return ""; - } - auto go_str = reinterpret_cast(str); - return {go_str->p, static_cast(go_str->n)}; +std::string copyStringFromGoPointer(void* p, int len) { + return {static_cast(p), static_cast(len)}; } -// The returned absl::string_view only refer to the GoString, won't copy the string content into -// C++, should not use it after the current cgo call returns. -absl::string_view referGoString(void* str) { - if (str == nullptr) { - return ""; - } - auto go_str = reinterpret_cast(str); - return {go_str->p, static_cast(go_str->n)}; +// The returned absl::string_view only refer to Go memory, +// should not use it after the current cgo call returns. +absl::string_view stringViewFromGoPointer(void* p, int len) { + return {static_cast(p), static_cast(len)}; } absl::string_view stringViewFromGoSlice(void* slice) { @@ -40,61 +32,89 @@ absl::string_view stringViewFromGoSlice(void* slice) { return {static_cast(go_slice->data), static_cast(go_slice->len)}; } -std::vector stringsFromGoSlice(void* slice) { +std::vector stringsFromGoSlice(void* slice_data, int slice_len) { std::vector list; - if (slice == nullptr) { + if (slice_len == 0) { return list; } - auto go_slice = reinterpret_cast(slice); - auto str = reinterpret_cast(go_slice->data); - for (auto i = 0; i < go_slice->len; i += 2) { - auto key = std::string(static_cast(str->p), str->n); - str++; - auto value = std::string(static_cast(str->p), str->n); - str++; + auto strs = reinterpret_cast(slice_data); + for (auto i = 0; i < slice_len; i += 2) { + auto key = std::string(strs[i + 0]); + auto value = std::string(strs[i + 1]); list.push_back(key); list.push_back(value); } return list; } -const FilterLogger& getFilterLogger() { CONSTRUCT_ON_FIRST_USE(FilterLogger); } - #ifdef __cplusplus extern "C" { #endif +CAPIStatus envoyGoFilterProcessStateHandlerWrapper( + void* s, std::function&, ProcessorState&)> f) { + auto state = static_cast(reinterpret_cast(s)); + if (!state->isProcessingInGo()) { + return CAPIStatus::CAPINotInGo; + } + auto req = static_cast(state->req); + auto weak_filter = req->weakFilter(); + if (auto filter = weak_filter.lock()) { + return f(filter, *state); + } + return CAPIStatus::CAPIFilterIsGone; +} + CAPIStatus envoyGoFilterHandlerWrapper(void* r, std::function&)> f) { - auto req = reinterpret_cast(r); + auto req = reinterpret_cast(r); auto weak_filter = req->weakFilter(); if (auto filter = weak_filter.lock()) { + // Though it's memory safe without this limitation. + // But it's not a good idea to run Go code after continue back to Envoy C++, + // so, add this limitation. + if (!filter->isProcessingInGo()) { + return CAPIStatus::CAPINotInGo; + } return f(filter); } return CAPIStatus::CAPIFilterIsGone; } -CAPIStatus envoyGoFilterHttpContinue(void* r, int status) { - return envoyGoFilterHandlerWrapper(r, [status](std::shared_ptr& filter) -> CAPIStatus { - return filter->continueStatus(static_cast(status)); - }); +CAPIStatus +envoyGoConfigHandlerWrapper(void* c, std::function&)> fc) { + auto config = reinterpret_cast(c); + auto weak_filter_config = config->weakFilterConfig(); + if (auto filter_config = weak_filter_config.lock()) { + return fc(filter_config); + } + return CAPIStatus::CAPIFilterIsGone; } -CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text, - void* headers, long long int grpc_status, - void* details) { - return envoyGoFilterHandlerWrapper( - r, - [response_code, body_text, headers, grpc_status, - details](std::shared_ptr& filter) -> CAPIStatus { - auto header_values = stringsFromGoSlice(headers); +CAPIStatus envoyGoFilterHttpContinue(void* s, int status) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [status](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + return filter->continueStatus(state, static_cast(status)); + }); +} + +CAPIStatus envoyGoFilterHttpSendLocalReply(void* s, int response_code, void* body_text_data, + int body_text_len, void* headers, int headers_num, + long long int grpc_status, void* details_data, + int details_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [response_code, body_text_data, body_text_len, headers, headers_num, grpc_status, + details_data, + details_len](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto header_values = stringsFromGoSlice(headers, headers_num); std::function modify_headers = [header_values](Http::ResponseHeaderMap& headers) -> void { for (size_t i = 0; i < header_values.size(); i += 2) { const auto& key = header_values[i]; const auto& value = header_values[i + 1]; if (value.length() > 0) { - headers.setCopy(Http::LowerCaseString(key), value); + headers.addCopy(Http::LowerCaseString(key), value); } } }; @@ -102,163 +122,277 @@ CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* bod // Deep clone the GoString into C++, since the GoString may be freed after the function // returns, while they may still be used in the callback. - return filter->sendLocalReply(static_cast(response_code), - copyGoString(body_text), modify_headers, status, - copyGoString(details)); + return filter->sendLocalReply(state, static_cast(response_code), + copyStringFromGoPointer(body_text_data, body_text_len), + modify_headers, status, + copyStringFromGoPointer(details_data, details_len)); + }); +} + +CAPIStatus envoyGoFilterHttpSendPanicReply(void* s, void* details_data, int details_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [details_data, details_len](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + // Since this is only used for logs we don't need to deep copy. + auto details = stringViewFromGoPointer(details_data, details_len); + return filter->sendPanicReply(state, details); + }); +} + +CAPIStatus envoyGoFilterHttpAddData(void* s, void* data, int data_len, bool is_streaming) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [data, data_len, is_streaming](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + // Since this is only used for logs we don't need to deep copy. + auto dataView = stringViewFromGoPointer(data, data_len); + return filter->addData(state, dataView, is_streaming); + }); +} + +CAPIStatus envoyGoFilterHttpInjectData(void* s, void* data, int data_length) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [data, data_length](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto value = stringViewFromGoPointer(data, data_length); + return filter->injectData(state, value); }); } // unsafe API, without copy memory from c to go. -CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key, void* value) { - return envoyGoFilterHandlerWrapper(r, - [key, value](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto go_value = reinterpret_cast(value); - return filter->getHeader(key_str, go_value); - }); +CAPIStatus envoyGoFilterHttpGetHeader(void* s, void* key_data, int key_len, uint64_t* value_data, + int* value_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [key_data, key_len, value_data, value_len](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->getHeader(state, key_str, value_data, value_len); + }); } -CAPIStatus envoyGoFilterHttpCopyHeaders(void* r, void* strs, void* buf) { - return envoyGoFilterHandlerWrapper(r, [strs, buf](std::shared_ptr& filter) -> CAPIStatus { - auto go_strs = reinterpret_cast(strs); - auto go_buf = reinterpret_cast(buf); - return filter->copyHeaders(go_strs, go_buf); - }); +CAPIStatus envoyGoFilterHttpCopyHeaders(void* s, void* strs, void* buf) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [strs, buf](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto go_strs = reinterpret_cast(strs); + auto go_buf = reinterpret_cast(buf); + return filter->copyHeaders(state, go_strs, go_buf); + }); } -CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key, void* value, headerAction act) { - return envoyGoFilterHandlerWrapper( - r, [key, value, act](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = referGoString(value); - return filter->setHeader(key_str, value_str, act); +CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* s, void* key_data, int key_len, void* value_data, + int value_len, headerAction act) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [key_data, key_len, value_data, value_len, act](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = stringViewFromGoPointer(value_data, value_len); + return filter->setHeader(state, key_str, value_str, act); }); } -CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key) { - return envoyGoFilterHandlerWrapper(r, [key](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - return filter->removeHeader(key_str); - }); +CAPIStatus envoyGoFilterHttpRemoveHeader(void* s, void* key_data, int key_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [key_data, key_len](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->removeHeader(state, key_str); + }); } -CAPIStatus envoyGoFilterHttpGetBuffer(void* r, unsigned long long int buffer_ptr, void* data) { - return envoyGoFilterHandlerWrapper( - r, [buffer_ptr, data](std::shared_ptr& filter) -> CAPIStatus { +CAPIStatus envoyGoFilterHttpGetBuffer(void* s, uint64_t buffer_ptr, void* data) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [buffer_ptr, data](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); - return filter->copyBuffer(buffer, reinterpret_cast(data)); + return filter->copyBuffer(state, buffer, reinterpret_cast(data)); }); } -CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, unsigned long long int buffer_ptr, void* data, - int length, bufferAction action) { - return envoyGoFilterHandlerWrapper( - r, [buffer_ptr, data, length, action](std::shared_ptr& filter) -> CAPIStatus { +CAPIStatus envoyGoFilterHttpDrainBuffer(void* s, uint64_t buffer_ptr, uint64_t length) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [buffer_ptr, length](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); - auto value = absl::string_view(reinterpret_cast(data), length); - return filter->setBufferHelper(buffer, value, action); + return filter->drainBuffer(state, buffer, length); }); } -CAPIStatus envoyGoFilterHttpCopyTrailers(void* r, void* strs, void* buf) { - return envoyGoFilterHandlerWrapper(r, [strs, buf](std::shared_ptr& filter) -> CAPIStatus { - auto go_strs = reinterpret_cast(strs); - auto go_buf = reinterpret_cast(buf); - return filter->copyTrailers(go_strs, go_buf); - }); +CAPIStatus envoyGoFilterHttpSetBufferHelper(void* s, uint64_t buffer_ptr, void* data, int length, + bufferAction action) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [buffer_ptr, data, length, action](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + auto buffer = reinterpret_cast(buffer_ptr); + auto value = stringViewFromGoPointer(data, length); + return filter->setBufferHelper(state, buffer, value, action); + }); } -CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key, void* value, headerAction act) { - return envoyGoFilterHandlerWrapper( - r, [key, value, act](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = referGoString(value); - return filter->setTrailer(key_str, value_str, act); +CAPIStatus envoyGoFilterHttpCopyTrailers(void* s, void* strs, void* buf) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [strs, buf](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto go_strs = reinterpret_cast(strs); + auto go_buf = reinterpret_cast(buf); + return filter->copyTrailers(state, go_strs, go_buf); }); } -CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key) { - return envoyGoFilterHandlerWrapper(r, [key](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - return filter->removeTrailer(key_str); - }); +CAPIStatus envoyGoFilterHttpSetTrailer(void* s, void* key_data, int key_len, void* value_data, + int value_len, headerAction act) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [key_data, key_len, value_data, value_len, act](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = stringViewFromGoPointer(value_data, value_len); + return filter->setTrailer(state, key_str, value_str, act); + }); } -CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, void* value) { - return envoyGoFilterHandlerWrapper(r, [id, value](std::shared_ptr& filter) -> CAPIStatus { - auto value_str = reinterpret_cast(value); - return filter->getStringValue(id, value_str); - }); +CAPIStatus envoyGoFilterHttpRemoveTrailer(void* s, void* key_data, int key_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [key_data, key_len](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->removeTrailer(state, key_str); + }); } -CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, void* value) { - return envoyGoFilterHandlerWrapper(r, [id, value](std::shared_ptr& filter) -> CAPIStatus { - auto value_int = reinterpret_cast(value); - return filter->getIntegerValue(id, value_int); +CAPIStatus envoyGoFilterHttpClearRouteCache(void* r, bool refresh) { + return envoyGoFilterHandlerWrapper(r, [refresh](std::shared_ptr& filter) -> CAPIStatus { + return filter->clearRouteCache(refresh); }); } -void envoyGoFilterHttpLog(uint32_t level, void* message) { - auto mesg = referGoString(message); - getFilterLogger().log(level, mesg); +CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, uint64_t* value_data, int* value_len) { + return envoyGoFilterHandlerWrapper( + r, [id, value_data, value_len](std::shared_ptr& filter) -> CAPIStatus { + return filter->getStringValue(id, value_data, value_len); + }); } -CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name, void* buf) { - return envoyGoFilterHandlerWrapper(r, [name, buf](std::shared_ptr& filter) -> CAPIStatus { - auto name_str = copyGoString(name); - auto buf_slice = reinterpret_cast(buf); - return filter->getDynamicMetadata(name_str, buf_slice); +CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, uint64_t* value) { + return envoyGoFilterHandlerWrapper(r, [id, value](std::shared_ptr& filter) -> CAPIStatus { + return filter->getIntegerValue(id, value); }); } -uint32_t envoyGoFilterHttpLogLevel() { return getFilterLogger().level(); } -CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name, void* key, void* buf) { +CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name_data, int name_len, + uint64_t* buf_data, int* buf_len) { return envoyGoFilterHandlerWrapper( - r, [name, key, buf](std::shared_ptr& filter) -> CAPIStatus { - auto name_str = copyGoString(name); - auto key_str = copyGoString(key); - auto buf_str = stringViewFromGoSlice(buf); - return filter->setDynamicMetadata(name_str, key_str, buf_str); + r, [name_data, name_len, buf_data, buf_len](std::shared_ptr& filter) -> CAPIStatus { + auto name_str = copyStringFromGoPointer(name_data, name_len); + return filter->getDynamicMetadata(name_str, buf_data, buf_len); }); } +CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name_data, int name_len, + void* key_data, int key_len, void* buf_data, + int buf_len) { + return envoyGoFilterHandlerWrapper(r, + [name_data, name_len, key_data, key_len, buf_data, + buf_len](std::shared_ptr& filter) -> CAPIStatus { + auto name_str = copyStringFromGoPointer(name_data, name_len); + auto key_str = copyStringFromGoPointer(key_data, key_len); + auto buf_str = stringViewFromGoPointer(buf_data, buf_len); + return filter->setDynamicMetadata(name_str, key_str, + buf_str); + }); +} + void envoyGoFilterHttpFinalize(void* r, int reason) { UNREFERENCED_PARAMETER(reason); // req is used by go, so need to use raw memory and then it is safe to release at the gc finalize // phase of the go object. - auto req = reinterpret_cast(r); - delete req; + auto req = reinterpret_cast(r); + auto weak_filter = req->weakFilter(); + if (auto filter = weak_filter.lock()) { + // Finalize must happens after onDestory, that means Filter is marked as destroyed. + // When filter is still existing, it could happens in very low rate, since Golang GC + // finalizer delays execution. + // Now, the race is there might be filter method running, i.e. continueStatusInternal may invoke + // onDestroy, and check state in request after it. + // So, we'd better to defer delete the request. + filter->deferredDeleteRequest(req); + } else { + // It's safe to delete directly since filter is not existing. + delete req; + } } -CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details) { - return envoyGoFilterHandlerWrapper(r, [details](std::shared_ptr& filter) -> CAPIStatus { - // Since this is only used for logs we don't need to deep copy. - return filter->sendPanicReply(referGoString(details)); - }); +void envoyGoConfigHttpFinalize(void* c) { + // config is used by go, so need to use raw memory and then it is safe to release at the gc + // finalize phase of the go object. + auto config = reinterpret_cast(c); + delete config; } -CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key, void* value, int state_type, +CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key_data, int key_len, + void* value_data, int value_len, int state_type, int life_span, int stream_sharing) { - return envoyGoFilterHandlerWrapper(r, - [key, value, state_type, life_span, stream_sharing]( - std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = referGoString(value); - return filter->setStringFilterState(key_str, value_str, - state_type, life_span, - stream_sharing); - }); + return envoyGoFilterHandlerWrapper( + r, + [key_data, key_len, value_data, value_len, state_type, life_span, + stream_sharing](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = stringViewFromGoPointer(value_data, value_len); + return filter->setStringFilterState(key_str, value_str, state_type, life_span, + stream_sharing); + }); +} + +CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len) { + return envoyGoFilterHandlerWrapper( + r, [key_data, key_len, value_data, value_len](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->getStringFilterState(key_str, value_data, value_len); + }); } -CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key, void* value) { +CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len, int* rc) { return envoyGoFilterHandlerWrapper(r, - [key, value](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = reinterpret_cast(value); - return filter->getStringFilterState(key_str, value_str); + [key_data, key_len, value_data, value_len, + rc](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->getStringProperty(key_str, value_data, + value_len, rc); }); } +CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name_data, + int name_len, uint32_t* metric_id) { + return envoyGoConfigHandlerWrapper( + c, + [metric_type, name_data, name_len, + metric_id](std::shared_ptr& filter_config) -> CAPIStatus { + auto name_str = stringViewFromGoPointer(name_data, name_len); + return filter_config->defineMetric(metric_type, name_str, metric_id); + }); +} + +CAPIStatus envoyGoFilterHttpIncrementMetric(void* c, uint32_t metric_id, int64_t offset) { + return envoyGoConfigHandlerWrapper( + c, [metric_id, offset](std::shared_ptr& filter_config) -> CAPIStatus { + return filter_config->incrementMetric(metric_id, offset); + }); +} + +CAPIStatus envoyGoFilterHttpGetMetric(void* c, uint32_t metric_id, uint64_t* value) { + return envoyGoConfigHandlerWrapper( + c, [metric_id, value](std::shared_ptr& filter_config) -> CAPIStatus { + return filter_config->getMetric(metric_id, value); + }); +} + +CAPIStatus envoyGoFilterHttpRecordMetric(void* c, uint32_t metric_id, uint64_t value) { + return envoyGoConfigHandlerWrapper( + c, [metric_id, value](std::shared_ptr& filter_config) -> CAPIStatus { + return filter_config->recordMetric(metric_id, value); + }); +} + #ifdef __cplusplus } #endif diff --git a/contrib/golang/filters/http/source/config.cc b/contrib/golang/filters/http/source/config.cc index 3b27e08beaa0f..91d7eddc32c70 100644 --- a/contrib/golang/filters/http/source/config.cc +++ b/contrib/golang/filters/http/source/config.cc @@ -1,5 +1,7 @@ #include "contrib/golang/filters/http/source/config.h" +#include + #include "envoy/registry/registry.h" #include "source/common/common/fmt.h" @@ -31,9 +33,16 @@ Http::FilterFactoryCb GolangFilterConfig::createFilterFactoryFromProtoTyped( FilterConfigSharedPtr config = std::make_shared( proto_config, dso_lib, fmt::format("{}golang.", stats_prefix), context); - + config->newGoPluginConfig(); return [config, dso_lib](Http::FilterChainFactoryCallbacks& callbacks) { - auto filter = std::make_shared(config, dso_lib); + const std::string& worker_name = callbacks.dispatcher().name(); + auto pos = worker_name.find_first_of('_'); + ENVOY_BUG(pos != std::string::npos, "worker name is not in expected format worker_{id}"); + uint32_t worker_id; + if (!absl::SimpleAtoi(worker_name.substr(pos + 1), &worker_id)) { + IS_ENVOY_BUG("failed to parse worker id from name"); + } + auto filter = std::make_shared(config, dso_lib, worker_id); callbacks.addStreamFilter(filter); callbacks.addAccessLogHandler(filter); }; diff --git a/contrib/golang/filters/http/source/go/pkg/http/BUILD b/contrib/golang/filters/http/source/go/pkg/http/BUILD index 0c1b726ce3185..f8a616992f72b 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/BUILD +++ b/contrib/golang/filters/http/source/go/pkg/http/BUILD @@ -6,6 +6,7 @@ go_library( name = "http", srcs = [ "api.h", + "asan.go", "capi_impl.go", "config.go", "filter.go", @@ -34,6 +35,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//contrib/golang/common/go/api", + "//contrib/golang/common/go/api_impl", "//contrib/golang/common/go/utils", "@org_golang_google_protobuf//proto", "@org_golang_google_protobuf//types/known/anypb", diff --git a/contrib/golang/filters/http/source/go/pkg/http/asan.go b/contrib/golang/filters/http/source/go/pkg/http/asan.go new file mode 100644 index 0000000000000..fb659ee0debe5 --- /dev/null +++ b/contrib/golang/filters/http/source/go/pkg/http/asan.go @@ -0,0 +1,34 @@ +package http + +import ( + "runtime" + "sync" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +var ( + asanTestEnabled = false +) + +// forceGCFinalizer enforce GC and wait GC finalizer finished. +// Just for testing, to make asan happy. +func forceGCFinalizer() { + var wg sync.WaitGroup + wg.Add(1) + + { + // create a fake httpRequest to trigger GC finalizer. + fake := &httpRequest{} + runtime.SetFinalizer(fake, func(*httpRequest) { + wg.Done() + }) + } + + api.LogWarn("golang filter enforcing GC") + // enforce a GC cycle. + runtime.GC() + + // wait GC finalizers finished. + wg.Wait() +} diff --git a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go index 6aa9dc401dc28..6af9224ffa5f7 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go +++ b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go @@ -32,16 +32,16 @@ package http */ import "C" import ( - "reflect" + "errors" "runtime" "strings" - "sync/atomic" "unsafe" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + _ "github.com/envoyproxy/envoy/contrib/golang/common/go/api_impl" ) const ( @@ -57,67 +57,139 @@ const ( ValueUpstreamRemoteAddress = 10 ValueUpstreamClusterName = 11 ValueVirtualClusterName = 12 + + // NOTE: this is a trade-off value. + // When the number of header is less this value, we could use the slice on the stack, + // otherwise, we have to allocate a new slice on the heap, + // and the slice on the stack will be wasted. + // So, we choose a value that many requests' number of header is less than this value. + // But also, it should not be too large, otherwise it might be waste stack memory. + maxStackAllocedHeaderSize = 16 + maxStackAllocedSliceLen = maxStackAllocedHeaderSize * 2 ) type httpCApiImpl struct{} -// Only CAPIOK is expected, otherwise, it means unexpected stage when invoke C API, +// When the status means unexpected stage when invoke C API, // panic here and it will be recover in the Go entry function. func handleCApiStatus(status C.CAPIStatus) { switch status { - case C.CAPIOK: - return + case C.CAPIFilterIsGone, + C.CAPIFilterIsDestroy, + C.CAPINotInGo, + C.CAPIInvalidPhase, + C.CAPIInvalidScene: + panic(capiStatusToStr(status)) + } +} + +func capiStatusToStr(status C.CAPIStatus) string { + switch status { case C.CAPIFilterIsGone: - panic(errRequestFinished) + return errRequestFinished case C.CAPIFilterIsDestroy: - panic(errFilterDestroyed) + return errFilterDestroyed case C.CAPINotInGo: - panic(errNotInGo) + return errNotInGo case C.CAPIInvalidPhase: - panic(errInvalidPhase) + return errInvalidPhase + case C.CAPIInvalidScene: + return errInvalidScene } + + return "unknown status" +} + +func capiStatusToErr(status C.CAPIStatus) error { + switch status { + case C.CAPIValueNotFound: + return api.ErrValueNotFound + case C.CAPIInternalFailure: + return api.ErrInternalFailure + case C.CAPISerializationFailure: + return api.ErrSerializationFailure + } + + return errors.New("unknown status") } -func (c *httpCApiImpl) HttpContinue(r unsafe.Pointer, status uint64) { - res := C.envoyGoFilterHttpContinue(r, C.int(status)) +func (c *httpCApiImpl) HttpContinue(s unsafe.Pointer, status uint64) { + state := (*processState)(s) + res := C.envoyGoFilterHttpContinue(unsafe.Pointer(state.processState), C.int(status)) handleCApiStatus(res) } // Only may panic with errRequestFinished, errFilterDestroyed or errNotInGo, // won't panic with errInvalidPhase and others, otherwise will cause deadloop, see RecoverPanic for the details. -func (c *httpCApiImpl) HttpSendLocalReply(r unsafe.Pointer, response_code int, body_text string, headers map[string]string, grpc_status int64, details string) { +func (c *httpCApiImpl) HttpSendLocalReply(s unsafe.Pointer, responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) { + state := (*processState)(s) hLen := len(headers) - strs := make([]string, 0, hLen) - for k, v := range headers { - strs = append(strs, k, v) + strs := make([]*C.char, 0, hLen*2) + defer func() { + for _, s := range strs { + C.free(unsafe.Pointer(s)) + } + }() + // TODO: use runtime.Pinner after go1.22 release for better performance. + for k, h := range headers { + for _, v := range h { + keyStr := C.CString(k) + valueStr := C.CString(v) + strs = append(strs, keyStr, valueStr) + } } - res := C.envoyGoFilterHttpSendLocalReply(r, C.int(response_code), unsafe.Pointer(&body_text), unsafe.Pointer(&strs), C.longlong(grpc_status), unsafe.Pointer(&details)) + res := C.envoyGoFilterHttpSendLocalReply(unsafe.Pointer(state.processState), C.int(responseCode), + unsafe.Pointer(unsafe.StringData(bodyText)), C.int(len(bodyText)), + unsafe.Pointer(unsafe.SliceData(strs)), C.int(len(strs)), + C.longlong(grpcStatus), unsafe.Pointer(unsafe.StringData(details)), C.int(len(details))) + handleCApiStatus(res) +} + +func (c *httpCApiImpl) HttpSendPanicReply(s unsafe.Pointer, details string) { + state := (*processState)(s) + res := C.envoyGoFilterHttpSendPanicReply(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(details)), C.int(len(details))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpSendPanicReply(r unsafe.Pointer, details string) { - res := C.envoyGoFilterHttpSendPanicReply(r, unsafe.Pointer(&details)) +func (c *httpCApiImpl) HttpAddData(s unsafe.Pointer, data []byte, isStreaming bool) { + state := (*processState)(s) + res := C.envoyGoFilterHttpAddData(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.SliceData(data)), C.int(len(data)), C.bool(isStreaming)) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetHeader(r unsafe.Pointer, key *string, value *string) { - res := C.envoyGoFilterHttpGetHeader(r, unsafe.Pointer(key), unsafe.Pointer(value)) +func (c *httpCApiImpl) HttpInjectData(s unsafe.Pointer, data []byte) { + state := (*processState)(s) + res := C.envoyGoFilterHttpInjectData(unsafe.Pointer(state.processState), + unsafe.Pointer(unsafe.SliceData(data)), C.int(len(data))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string { - // TODO: use a memory pool for better performance, - // since these go strings in strs, will be copied into the following map. - strs := make([]string, num*2) - // but, this buffer can not be reused safely, +func (c *httpCApiImpl) HttpGetHeader(s unsafe.Pointer, key string) string { + state := (*processState)(s) + var valueData C.uint64_t + var valueLen C.int + res := C.envoyGoFilterHttpGetHeader(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen) + handleCApiStatus(res) + return unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) +} + +func (c *httpCApiImpl) HttpCopyHeaders(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string { + state := (*processState)(s) + var strs []string + if num <= maxStackAllocedHeaderSize { + // NOTE: only const length slice may be allocated on stack. + strs = make([]string, maxStackAllocedSliceLen) + } else { + // TODO: maybe we could use a memory pool for better performance, + // since these go strings in strs, will be copied into the following map. + strs = make([]string, num*2) + } + // NOTE: this buffer can not be reused safely, // since strings may refer to this buffer as string data, and string is const in go. // we have to make sure the all strings is not using before reusing, // but strings may be alive beyond the request life. buf := make([]byte, bytes) - sHeader := (*reflect.SliceHeader)(unsafe.Pointer(&strs)) - bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) - - res := C.envoyGoFilterHttpCopyHeaders(r, unsafe.Pointer(sHeader.Data), unsafe.Pointer(bHeader.Data)) + res := C.envoyGoFilterHttpCopyHeaders(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) m := make(map[string][]string, num) @@ -135,34 +207,50 @@ func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint6 return m } -func (c *httpCApiImpl) HttpSetHeader(r unsafe.Pointer, key *string, value *string, add bool) { +func (c *httpCApiImpl) HttpSetHeader(s unsafe.Pointer, key string, value string, add bool) { + state := (*processState)(s) var act C.headerAction if add { act = C.HeaderAdd } else { act = C.HeaderSet } - res := C.envoyGoFilterHttpSetHeaderHelper(r, unsafe.Pointer(key), unsafe.Pointer(value), act) + res := C.envoyGoFilterHttpSetHeaderHelper(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpRemoveHeader(r unsafe.Pointer, key *string) { - res := C.envoyGoFilterHttpRemoveHeader(r, unsafe.Pointer(key)) +func (c *httpCApiImpl) HttpRemoveHeader(s unsafe.Pointer, key string) { + state := (*processState)(s) + res := C.envoyGoFilterHttpRemoveHeader(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetBuffer(r unsafe.Pointer, bufferPtr uint64, value *string, length uint64) { +func (c *httpCApiImpl) HttpGetBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) []byte { + state := (*processState)(s) buf := make([]byte, length) - bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) - sHeader := (*reflect.StringHeader)(unsafe.Pointer(value)) - sHeader.Data = bHeader.Data - sHeader.Len = int(length) - res := C.envoyGoFilterHttpGetBuffer(r, C.ulonglong(bufferPtr), unsafe.Pointer(bHeader.Data)) + res := C.envoyGoFilterHttpGetBuffer(unsafe.Pointer(state.processState), C.uint64_t(bufferPtr), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) + return unsafe.Slice(unsafe.SliceData(buf), length) +} + +func (c *httpCApiImpl) HttpDrainBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) { + state := (*processState)(s) + res := C.envoyGoFilterHttpDrainBuffer(unsafe.Pointer(state.processState), C.uint64_t(bufferPtr), C.uint64_t(length)) + handleCApiStatus(res) +} + +func (c *httpCApiImpl) HttpSetBufferHelper(s unsafe.Pointer, bufferPtr uint64, value string, action api.BufferAction) { + state := (*processState)(s) + c.httpSetBufferHelper(state, bufferPtr, unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), action) +} + +func (c *httpCApiImpl) HttpSetBytesBufferHelper(s unsafe.Pointer, bufferPtr uint64, value []byte, action api.BufferAction) { + state := (*processState)(s) + c.httpSetBufferHelper(state, bufferPtr, unsafe.Pointer(unsafe.SliceData(value)), C.int(len(value)), action) } -func (c *httpCApiImpl) HttpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, value string, action api.BufferAction) { - sHeader := (*reflect.StringHeader)(unsafe.Pointer(&value)) +func (c *httpCApiImpl) httpSetBufferHelper(state *processState, bufferPtr uint64, data unsafe.Pointer, length C.int, action api.BufferAction) { var act C.bufferAction switch action { case api.SetBuffer: @@ -172,21 +260,27 @@ func (c *httpCApiImpl) HttpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, v case api.PrependBuffer: act = C.Prepend } - res := C.envoyGoFilterHttpSetBufferHelper(r, C.ulonglong(bufferPtr), unsafe.Pointer(sHeader.Data), C.int(sHeader.Len), act) + res := C.envoyGoFilterHttpSetBufferHelper(unsafe.Pointer(state.processState), C.uint64_t(bufferPtr), data, length, act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string { - // TODO: use a memory pool for better performance, - // but, should be very careful, since string is const in go, - // and we have to make sure the strings is not using before reusing, - // strings may be alive beyond the request life. - strs := make([]string, num*2) +func (c *httpCApiImpl) HttpCopyTrailers(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string { + state := (*processState)(s) + var strs []string + if num <= maxStackAllocedHeaderSize { + // NOTE: only const length slice may be allocated on stack. + strs = make([]string, maxStackAllocedSliceLen) + } else { + // TODO: maybe we could use a memory pool for better performance, + // since these go strings in strs, will be copied into the following map. + strs = make([]string, num*2) + } + // NOTE: this buffer can not be reused safely, + // since strings may refer to this buffer as string data, and string is const in go. + // we have to make sure the all strings is not using before reusing, + // but strings may be alive beyond the request life. buf := make([]byte, bytes) - sHeader := (*reflect.SliceHeader)(unsafe.Pointer(&strs)) - bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) - - res := C.envoyGoFilterHttpCopyTrailers(r, unsafe.Pointer(sHeader.Data), unsafe.Pointer(bHeader.Data)) + res := C.envoyGoFilterHttpCopyTrailers(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) m := make(map[string][]string, num) @@ -200,65 +294,82 @@ func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint m[key] = append(v, value) } } + runtime.KeepAlive(buf) return m } -func (c *httpCApiImpl) HttpSetTrailer(r unsafe.Pointer, key *string, value *string, add bool) { +func (c *httpCApiImpl) HttpSetTrailer(s unsafe.Pointer, key string, value string, add bool) { + state := (*processState)(s) var act C.headerAction if add { act = C.HeaderAdd } else { act = C.HeaderSet } - res := C.envoyGoFilterHttpSetTrailer(r, unsafe.Pointer(key), unsafe.Pointer(value), act) + res := C.envoyGoFilterHttpSetTrailer(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpRemoveTrailer(r unsafe.Pointer, key *string) { - res := C.envoyGoFilterHttpRemoveTrailer(r, unsafe.Pointer(key)) +func (c *httpCApiImpl) HttpRemoveTrailer(s unsafe.Pointer, key string) { + state := (*processState)(s) + res := C.envoyGoFilterHttpRemoveTrailer(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetStringValue(rr unsafe.Pointer, id int) (string, bool) { - r := (*httpRequest)(rr) - var value string +func (c *httpCApiImpl) ClearRouteCache(r unsafe.Pointer, refresh bool) { + req := (*httpRequest)(r) + res := C.envoyGoFilterHttpClearRouteCache(unsafe.Pointer(req.req), C.bool(refresh)) + handleCApiStatus(res) +} + +func (c *httpCApiImpl) HttpGetStringValue(r unsafe.Pointer, id int) (string, bool) { + req := (*httpRequest)(r) // add a lock to protect filter->req_->strValue field in the Envoy side, from being writing concurrency, // since there might be multiple concurrency goroutines invoking this API on the Go side. - r.mutex.Lock() - defer r.mutex.Unlock() - res := C.envoyGoFilterHttpGetStringValue(unsafe.Pointer(r.req), C.int(id), unsafe.Pointer(&value)) + req.mutex.Lock() + defer req.mutex.Unlock() + + var valueData C.uint64_t + var valueLen C.int + res := C.envoyGoFilterHttpGetStringValue(unsafe.Pointer(req.req), C.int(id), &valueData, &valueLen) if res == C.CAPIValueNotFound { return "", false } handleCApiStatus(res) + value := unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) // copy the memory from c to Go. return strings.Clone(value), true } func (c *httpCApiImpl) HttpGetIntegerValue(r unsafe.Pointer, id int) (uint64, bool) { - var value uint64 - res := C.envoyGoFilterHttpGetIntegerValue(r, C.int(id), unsafe.Pointer(&value)) + req := (*httpRequest)(r) + var value C.uint64_t + res := C.envoyGoFilterHttpGetIntegerValue(unsafe.Pointer(req.req), C.int(id), &value) if res == C.CAPIValueNotFound { return 0, false } handleCApiStatus(res) - return value, true + return uint64(value), true } -func (c *httpCApiImpl) HttpGetDynamicMetadata(rr unsafe.Pointer, filterName string) map[string]interface{} { - r := (*httpRequest)(rr) - var buf []byte - r.mutex.Lock() - defer r.mutex.Unlock() - r.sema.Add(1) - res := C.envoyGoFilterHttpGetDynamicMetadata(unsafe.Pointer(r.req), unsafe.Pointer(&filterName), unsafe.Pointer(&buf)) +func (c *httpCApiImpl) HttpGetDynamicMetadata(r unsafe.Pointer, filterName string) map[string]interface{} { + req := (*httpRequest)(r) + req.mutex.Lock() + defer req.mutex.Unlock() + req.markMayWaitingCallback() + + var valueData C.uint64_t + var valueLen C.int + res := C.envoyGoFilterHttpGetDynamicMetadata(unsafe.Pointer(req.req), + unsafe.Pointer(unsafe.StringData(filterName)), C.int(len(filterName)), &valueData, &valueLen) if res == C.CAPIYield { - atomic.AddInt32(&r.waitingOnEnvoy, 1) - r.sema.Wait() + req.checkOrWaitCallback() } else { - r.sema.Done() + req.markNoWaitingCallback() handleCApiStatus(res) } + buf := unsafe.Slice((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) // copy the memory from c to Go. var meta structpb.Struct proto.Unmarshal(buf, &meta) @@ -266,6 +377,7 @@ func (c *httpCApiImpl) HttpGetDynamicMetadata(rr unsafe.Pointer, filterName stri } func (c *httpCApiImpl) HttpSetDynamicMetadata(r unsafe.Pointer, filterName string, key string, value interface{}) { + req := (*httpRequest)(r) v, err := structpb.NewValue(value) if err != nil { panic(err) @@ -274,20 +386,79 @@ func (c *httpCApiImpl) HttpSetDynamicMetadata(r unsafe.Pointer, filterName strin if err != nil { panic(err) } - res := C.envoyGoFilterHttpSetDynamicMetadata(r, unsafe.Pointer(&filterName), unsafe.Pointer(&key), unsafe.Pointer(&buf)) + res := C.envoyGoFilterHttpSetDynamicMetadata(unsafe.Pointer(req.req), + unsafe.Pointer(unsafe.StringData(filterName)), C.int(len(filterName)), + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.SliceData(buf)), C.int(len(buf))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpLog(level api.LogType, message string) { - C.envoyGoFilterHttpLog(C.uint32_t(level), unsafe.Pointer(&message)) +func (c *httpCApiImpl) HttpFinalize(r unsafe.Pointer, reason int) { + req := (*httpRequest)(r) + C.envoyGoFilterHttpFinalize(unsafe.Pointer(req.req), C.int(reason)) } -func (c *httpCApiImpl) HttpLogLevel() api.LogType { - return api.LogType(C.envoyGoFilterHttpLogLevel()) +func (c *httpCApiImpl) HttpSetStringFilterState(r unsafe.Pointer, key string, value string, stateType api.StateType, lifeSpan api.LifeSpan, streamSharing api.StreamSharing) { + req := (*httpRequest)(r) + res := C.envoyGoFilterHttpSetStringFilterState(unsafe.Pointer(req.req), + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), + C.int(stateType), C.int(lifeSpan), C.int(streamSharing)) + handleCApiStatus(res) } -func (c *httpCApiImpl) HttpFinalize(r unsafe.Pointer, reason int) { - C.envoyGoFilterHttpFinalize(r, C.int(reason)) +func (c *httpCApiImpl) HttpGetStringFilterState(r unsafe.Pointer, key string) string { + req := (*httpRequest)(r) + var valueData C.uint64_t + var valueLen C.int + req.mutex.Lock() + defer req.mutex.Unlock() + req.markMayWaitingCallback() + res := C.envoyGoFilterHttpGetStringFilterState(unsafe.Pointer(req.req), + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen) + if res == C.CAPIYield { + req.checkOrWaitCallback() + } else { + req.markNoWaitingCallback() + handleCApiStatus(res) + } + + value := unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) + return strings.Clone(value) +} + +func (c *httpCApiImpl) HttpGetStringProperty(r unsafe.Pointer, key string) (string, error) { + req := (*httpRequest)(r) + var valueData C.uint64_t + var valueLen C.int + var rc C.int + req.mutex.Lock() + defer req.mutex.Unlock() + req.markMayWaitingCallback() + res := C.envoyGoFilterHttpGetStringProperty(unsafe.Pointer(req.req), + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen, &rc) + if res == C.CAPIYield { + req.checkOrWaitCallback() + res = C.CAPIStatus(rc) + } else { + req.markNoWaitingCallback() + handleCApiStatus(res) + } + + if res == C.CAPIOK { + value := unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) + return strings.Clone(value), nil + } + + return "", capiStatusToErr(res) +} + +func (c *httpCApiImpl) HttpLog(level api.LogType, message string) { + C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) +} + +func (c *httpCApiImpl) HttpLogLevel() api.LogType { + return api.GetLogLevel() } var cAPI api.HttpCAPI = &httpCApiImpl{} @@ -297,25 +468,33 @@ func SetHttpCAPI(api api.HttpCAPI) { cAPI = api } -func (c *httpCApiImpl) HttpSetStringFilterState(r unsafe.Pointer, key string, value string, stateType api.StateType, lifeSpan api.LifeSpan, streamSharing api.StreamSharing) { - res := C.envoyGoFilterHttpSetStringFilterState(r, unsafe.Pointer(&key), unsafe.Pointer(&value), C.int(stateType), C.int(lifeSpan), C.int(streamSharing)) +func (c *httpCApiImpl) HttpConfigFinalize(cfg unsafe.Pointer) { + C.envoyGoConfigHttpFinalize(cfg) +} + +func (c *httpCApiImpl) HttpDefineMetric(cfg unsafe.Pointer, metricType api.MetricType, name string) uint32 { + var value C.uint32_t + res := C.envoyGoFilterHttpDefineMetric(cfg, C.uint32_t(metricType), unsafe.Pointer(unsafe.StringData(name)), C.int(len(name)), &value) handleCApiStatus(res) + return uint32(value) } -func (c *httpCApiImpl) HttpGetStringFilterState(rr unsafe.Pointer, key string) string { - r := (*httpRequest)(rr) - var value string - r.mutex.Lock() - defer r.mutex.Unlock() - r.sema.Add(1) - res := C.envoyGoFilterHttpGetStringFilterState(unsafe.Pointer(r.req), unsafe.Pointer(&key), unsafe.Pointer(&value)) - if res == C.CAPIYield { - atomic.AddInt32(&r.waitingOnEnvoy, 1) - r.sema.Wait() - } else { - r.sema.Done() - handleCApiStatus(res) - } +func (c *httpCApiImpl) HttpIncrementMetric(cc unsafe.Pointer, metricId uint32, offset int64) { + cfg := (*httpConfig)(cc) + res := C.envoyGoFilterHttpIncrementMetric(unsafe.Pointer(cfg.config), C.uint32_t(metricId), C.int64_t(offset)) + handleCApiStatus(res) +} - return strings.Clone(value) +func (c *httpCApiImpl) HttpGetMetric(cc unsafe.Pointer, metricId uint32) uint64 { + cfg := (*httpConfig)(cc) + var value C.uint64_t + res := C.envoyGoFilterHttpGetMetric(unsafe.Pointer(cfg.config), C.uint32_t(metricId), &value) + handleCApiStatus(res) + return uint64(value) +} + +func (c *httpCApiImpl) HttpRecordMetric(cc unsafe.Pointer, metricId uint32, value uint64) { + cfg := (*httpConfig)(cc) + res := C.envoyGoFilterHttpRecordMetric(unsafe.Pointer(cfg.config), C.uint32_t(metricId), C.uint64_t(value)) + handleCApiStatus(res) } diff --git a/contrib/golang/filters/http/source/go/pkg/http/config.go b/contrib/golang/filters/http/source/go/pkg/http/config.go index 1727370f69105..96df664579615 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/config.go +++ b/contrib/golang/filters/http/source/go/pkg/http/config.go @@ -33,8 +33,10 @@ import "C" import ( "fmt" + "runtime" "sync" "sync/atomic" + "time" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" @@ -45,41 +47,73 @@ import ( var ( configNumGenerator uint64 - configCache = &sync.Map{} // uint64 -> *anypb.Any + configCache = &sync.Map{} // uint64 -> config(interface{}) + // From get a cached merged_config_id_ in getMergedConfigId on the C++ side, + // to get the merged config by the id on the Go side, 2 seconds should be long enough. + delayDeleteTime = time.Second * 2 // 2s ) -//export envoyGoFilterNewHttpPluginConfig -func envoyGoFilterNewHttpPluginConfig(namePtr, nameLen, configPtr, configLen uint64) uint64 { - if !api.CgoCheckDisabled() { - cAPI.HttpLog(api.Error, "The Envoy Golang filter requires the `GODEBUG=cgocheck=0` environment variable set.") - return 0 +func configFinalize(c *httpConfig) { + c.Finalize() +} + +func createConfig(c *C.httpConfig) *httpConfig { + config := &httpConfig{ + config: c, } + // NP: make sure httpConfig will be deleted. + runtime.SetFinalizer(config, configFinalize) + + return config +} - buf := utils.BytesToSlice(configPtr, configLen) +//export envoyGoFilterNewHttpPluginConfig +func envoyGoFilterNewHttpPluginConfig(c *C.httpConfig) uint64 { + buf := utils.BytesToSlice(uint64(c.config_ptr), uint64(c.config_len)) var any anypb.Any proto.Unmarshal(buf, &any) + Requests.initialize(uint32(c.concurrency)) + configNum := atomic.AddUint64(&configNumGenerator, 1) - name := utils.BytesToString(namePtr, nameLen) + name := utils.BytesToString(uint64(c.plugin_name_ptr), uint64(c.plugin_name_len)) configParser := getHttpFilterConfigParser(name) - if configParser != nil { - parsedConfig, err := configParser.Parse(&any) - if err != nil { - cAPI.HttpLog(api.Error, fmt.Sprintf("failed to parse golang plugin config: %v", err)) - return 0 - } - configCache.Store(configNum, parsedConfig) + + var parsedConfig interface{} + var err error + if c.is_route_config == 1 { + parsedConfig, err = configParser.Parse(&any, nil) } else { - configCache.Store(configNum, &any) + config := createConfig(c) + parsedConfig, err = configParser.Parse(&any, config) } + if err != nil { + cAPI.HttpLog(api.Error, fmt.Sprintf("failed to parse golang plugin config: %v", err)) + return 0 + } + configCache.Store(configNum, parsedConfig) return configNum } //export envoyGoFilterDestroyHttpPluginConfig -func envoyGoFilterDestroyHttpPluginConfig(id uint64) { - configCache.Delete(id) +func envoyGoFilterDestroyHttpPluginConfig(id uint64, needDelay int) { + if needDelay == 1 { + // there is a concurrency race in the c++ side: + // 1. when A envoy worker thread is using the cached merged_config_id_ and it will call into Go after some time. + // 2. while B envoy worker thread may update the merged_config_id_ in getMergedConfigId, that will delete the id. + // so, we delay deleting the id in the Go side. + time.AfterFunc(delayDeleteTime, func() { + configCache.Delete(id) + }) + } else { + // there is no race for non-merged config. + configCache.Delete(id) + } + if asanTestEnabled { + forceGCFinalizer() + } } //export envoyGoFilterMergeHttpPluginConfig @@ -87,23 +121,17 @@ func envoyGoFilterMergeHttpPluginConfig(namePtr, nameLen, parentId, childId uint name := utils.BytesToString(namePtr, nameLen) configParser := getHttpFilterConfigParser(name) - if configParser != nil { - parent, ok := configCache.Load(parentId) - if !ok { - panic(fmt.Sprintf("merge config: get parentId: %d config failed", parentId)) - } - child, ok := configCache.Load(childId) - if !ok { - panic(fmt.Sprintf("merge config: get childId: %d config failed", childId)) - } - - new := configParser.Merge(parent, child) - configNum := atomic.AddUint64(&configNumGenerator, 1) - configCache.Store(configNum, new) - return configNum - - } else { - // child override parent by default - return childId + parent, ok := configCache.Load(parentId) + if !ok { + panic(fmt.Sprintf("merge config: get parentId: %d config failed", parentId)) + } + child, ok := configCache.Load(childId) + if !ok { + panic(fmt.Sprintf("merge config: get childId: %d config failed", childId)) } + + new := configParser.Merge(parent, child) + configNum := atomic.AddUint64(&configNumGenerator, 1) + configCache.Store(configNum, new) + return configNum } diff --git a/contrib/golang/filters/http/source/go/pkg/http/filter.go b/contrib/golang/filters/http/source/go/pkg/http/filter.go index ecaccab8bf578..cceecc466256b 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/filter.go +++ b/contrib/golang/filters/http/source/go/pkg/http/filter.go @@ -33,7 +33,9 @@ package http import "C" import ( "fmt" + "runtime/debug" "sync" + "sync/atomic" "unsafe" "github.com/envoyproxy/envoy/contrib/golang/common/go/api" @@ -46,6 +48,11 @@ const ( HTTP30 = "HTTP/3.0" ) +const ( + NoWaitingCallback = 0 + MayWaitingCallback = 1 +) + var protocolsIdToName = map[uint64]string{ 0: HTTP10, 1: HTTP11, @@ -57,27 +64,86 @@ type panicInfo struct { paniced bool details string } + type httpRequest struct { - req *C.httpRequest - httpFilter api.StreamFilter - pInfo panicInfo - sema sync.WaitGroup - waitingOnEnvoy int32 - mutex sync.Mutex + req *C.httpRequest + httpFilter api.StreamFilter + pInfo panicInfo + waitingLock sync.Mutex // protect waitingCallback + cond sync.Cond + waitingCallback int32 + + // protect multiple cases: + // 1. protect req_->strValue in the C++ side from being used concurrently. + // 2. protect waitingCallback from being modified in markMayWaitingCallback concurrently. + mutex sync.Mutex + + // decodingState and encodingState are part of httpRequest, not another GC object. + // So, no cycle reference, GC finalizer could work well. + decodingState processState + encodingState processState + streamInfo streamInfo } -func (r *httpRequest) pluginName() string { - return C.GoStringN(r.req.plugin_name.data, C.int(r.req.plugin_name.len)) +// processState implements the FilterCallbacks interface. +type processState struct { + request *httpRequest + processState *C.processState } -func (r *httpRequest) sendPanicReply(details string) { - defer r.RecoverPanic() - cAPI.HttpSendPanicReply(unsafe.Pointer(r.req), details) +const ( + // Values align with "enum class FilterState" in C++ + ProcessingHeader = 1 + ProcessingData = 4 + ProcessingTrailer = 6 +) + +func (s *processState) Phase() api.EnvoyRequestPhase { + if s.processState.is_encoding == 0 { + switch int(s.processState.state) { + case ProcessingHeader: + return api.DecodeHeaderPhase + case ProcessingData: + return api.DecodeDataPhase + case ProcessingTrailer: + return api.DecodeTrailerPhase + } + } + // s.processState.is_encoding == 1 + switch int(s.processState.state) { + case ProcessingHeader: + return api.EncodeHeaderPhase + case ProcessingData: + return api.EncodeDataPhase + case ProcessingTrailer: + return api.EncodeTrailerPhase + } + panic(fmt.Errorf("unexpected state, is_encoding: %d, state: %d", s.processState.is_encoding, s.processState.state)) +} + +func (s *processState) Continue(status api.StatusType) { + cAPI.HttpContinue(unsafe.Pointer(s), uint64(status)) +} + +func (s *processState) SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) { + cAPI.HttpSendLocalReply(unsafe.Pointer(s), responseCode, bodyText, headers, grpcStatus, details) } -func (r *httpRequest) RecoverPanic() { +func (s *processState) sendPanicReply(details string) { + defer s.RecoverPanic() + cAPI.HttpSendPanicReply(unsafe.Pointer(s), details) +} + +func (s *processState) RecoverPanic() { if e := recover(); e != nil { - // TODO: print an error message to Envoy error log. + buf := debug.Stack() + + if e == errRequestFinished || e == errFilterDestroyed { + api.LogInfof("http: panic serving: %v (Client may cancel the request prematurely)\n%s", e, buf) + } else { + api.LogErrorf("http: panic serving: %v\n%s", e, buf) + } + switch e { case errRequestFinished, errFilterDestroyed: // do nothing @@ -85,7 +151,7 @@ func (r *httpRequest) RecoverPanic() { case errNotInGo: // We can not send local reply now, since not in go now, // will delay to the next time entering Go. - r.pInfo = panicInfo{ + s.request.pInfo = panicInfo{ paniced: true, details: fmt.Sprint(e), } @@ -96,11 +162,96 @@ func (r *httpRequest) RecoverPanic() { // errInvalidPhase, or other panic, not from not-ok C return status. // It's safe to try send a local reply with 500 status. - r.sendPanicReply(fmt.Sprint(e)) + s.sendPanicReply(fmt.Sprint(e)) + } + } +} + +func (s *processState) AddData(data []byte, isStreaming bool) { + cAPI.HttpAddData(unsafe.Pointer(s), data, isStreaming) +} + +func (s *processState) InjectData(data []byte) { + cAPI.HttpInjectData(unsafe.Pointer(s), data) +} + +func (r *httpRequest) StreamInfo() api.StreamInfo { + return &r.streamInfo +} + +func (r *httpRequest) DecoderFilterCallbacks() api.DecoderFilterCallbacks { + return &r.decodingState +} + +func (r *httpRequest) EncoderFilterCallbacks() api.EncoderFilterCallbacks { + return &r.encodingState +} + +// markWaitingOnEnvoy marks the request may be waiting a callback from envoy. +// Must be the NoWaitingCallback state since it's invoked under the r.mutex lock. +// We do not do lock waitingCallback here, to reduce lock contention. +func (r *httpRequest) markMayWaitingCallback() { + if !atomic.CompareAndSwapInt32(&r.waitingCallback, NoWaitingCallback, MayWaitingCallback) { + panic("markWaitingCallback: unexpected state") + } +} + +// markNoWaitingOnEnvoy marks the request is not waiting a callback from envoy. +// Can not make sure it's in the MayWaitingCallback state, since the state maybe changed by OnDestroy. +func (r *httpRequest) markNoWaitingCallback() { + atomic.StoreInt32(&r.waitingCallback, NoWaitingCallback) +} + +// checkOrWaitCallback checks if we need to wait a callback from envoy, and wait it. +func (r *httpRequest) checkOrWaitCallback() { + // need acquire the lock, since there might be concurrency race with resumeWaitCallback. + r.cond.L.Lock() + defer r.cond.L.Unlock() + + // callback or OnDestroy already called, no need to wait. + if atomic.LoadInt32(&r.waitingCallback) == NoWaitingCallback { + return + } + r.cond.Wait() +} + +// resumeWaitCallback resumes the goroutine that waiting for the callback from envoy. +func (r *httpRequest) resumeWaitCallback() { + // need acquire the lock, since there might be concurrency race with checkOrWaitCallback. + r.cond.L.Lock() + defer r.cond.L.Unlock() + + if atomic.CompareAndSwapInt32(&r.waitingCallback, MayWaitingCallback, NoWaitingCallback) { + // Broadcast is safe even there is no waiters. + r.cond.Broadcast() + } +} + +func (r *httpRequest) pluginName() string { + return C.GoStringN(r.req.plugin_name.data, C.int(r.req.plugin_name.len)) +} + +// recover goroutine to stop Envoy process crashing when panic happens +func (r *httpRequest) recoverPanic() { + if e := recover(); e != nil { + buf := debug.Stack() + + if e == errRequestFinished || e == errFilterDestroyed { + api.LogInfof("http: panic serving: %v (Client may cancel the request prematurely)\n%s", e, buf) + } else { + api.LogErrorf("http: panic serving: %v\n%s", e, buf) } } } +func (r *httpRequest) ClearRouteCache() { + cAPI.ClearRouteCache(unsafe.Pointer(r), false) +} + +func (r *httpRequest) RefreshRouteCache() { + cAPI.ClearRouteCache(unsafe.Pointer(r), true) +} + func (r *httpRequest) Continue(status api.StatusType) { if status == api.LocalReply { fmt.Printf("warning: LocalReply status is useless after sendLocalReply, ignoring") @@ -109,28 +260,28 @@ func (r *httpRequest) Continue(status api.StatusType) { cAPI.HttpContinue(unsafe.Pointer(r.req), uint64(status)) } -func (r *httpRequest) SendLocalReply(responseCode int, bodyText string, headers map[string]string, grpcStatus int64, details string) { +func (r *httpRequest) SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) { cAPI.HttpSendLocalReply(unsafe.Pointer(r.req), responseCode, bodyText, headers, grpcStatus, details) } func (r *httpRequest) Log(level api.LogType, message string) { // TODO performance optimization points: // Add a new goroutine to write logs asynchronously and avoid frequent cgo calls - cAPI.HttpLog(level, fmt.Sprintf("[go_plugin_http][%v] %v", r.pluginName(), message)) + cAPI.HttpLog(level, fmt.Sprintf("[http][%v] %v", r.pluginName(), message)) + // The default log format is: + // [2023-08-09 03:04:16.179][1390][error][golang] [contrib/golang/common/log/cgo.cc:24] [http][plugin_name] msg } func (r *httpRequest) LogLevel() api.LogType { return cAPI.HttpLogLevel() } -func (r *httpRequest) StreamInfo() api.StreamInfo { - return &streamInfo{ - request: r, - } +func (r *httpRequest) GetProperty(key string) (string, error) { + return cAPI.HttpGetStringProperty(unsafe.Pointer(r), key) } func (r *httpRequest) Finalize(reason int) { - cAPI.HttpFinalize(unsafe.Pointer(r.req), reason) + cAPI.HttpFinalize(unsafe.Pointer(r), reason) } type streamInfo struct { @@ -148,7 +299,7 @@ func (s *streamInfo) FilterChainName() string { } func (s *streamInfo) Protocol() (string, bool) { - if protocol, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request.req), ValueProtocol); ok { + if protocol, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request), ValueProtocol); ok { if name, ok := protocolsIdToName[protocol]; ok { return name, true } @@ -158,7 +309,7 @@ func (s *streamInfo) Protocol() (string, bool) { } func (s *streamInfo) ResponseCode() (uint32, bool) { - if code, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request.req), ValueResponseCode); ok { + if code, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request), ValueResponseCode); ok { return uint32(code), true } return 0, false @@ -169,7 +320,7 @@ func (s *streamInfo) ResponseCodeDetails() (string, bool) { } func (s *streamInfo) AttemptCount() uint32 { - count, _ := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request.req), ValueAttemptCount) + count, _ := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request), ValueAttemptCount) return uint32(count) } @@ -188,7 +339,7 @@ func (d *dynamicMetadata) Get(filterName string) map[string]interface{} { } func (d *dynamicMetadata) Set(filterName string, key string, value interface{}) { - cAPI.HttpSetDynamicMetadata(unsafe.Pointer(d.request.req), filterName, key, value) + cAPI.HttpSetDynamicMetadata(unsafe.Pointer(d.request), filterName, key, value) } func (s *streamInfo) DownstreamLocalAddress() string { @@ -219,6 +370,10 @@ func (s *streamInfo) VirtualClusterName() (string, bool) { return cAPI.HttpGetStringValue(unsafe.Pointer(s.request), ValueVirtualClusterName) } +func (s *streamInfo) WorkerID() uint32 { + return uint32(s.request.req.worker_id) +} + type filterState struct { request *httpRequest } @@ -230,9 +385,67 @@ func (s *streamInfo) FilterState() api.FilterState { } func (f *filterState) SetString(key, value string, stateType api.StateType, lifeSpan api.LifeSpan, streamSharing api.StreamSharing) { - cAPI.HttpSetStringFilterState(unsafe.Pointer(f.request.req), key, value, stateType, lifeSpan, streamSharing) + cAPI.HttpSetStringFilterState(unsafe.Pointer(f.request), key, value, stateType, lifeSpan, streamSharing) } func (f *filterState) GetString(key string) string { return cAPI.HttpGetStringFilterState(unsafe.Pointer(f.request), key) } + +type httpConfig struct { + config *C.httpConfig +} + +func (c *httpConfig) DefineCounterMetric(name string) api.CounterMetric { + id := cAPI.HttpDefineMetric(unsafe.Pointer(c.config), api.Counter, name) + return &counterMetric{ + config: c, + metricId: id, + } +} + +func (c *httpConfig) DefineGaugeMetric(name string) api.GaugeMetric { + id := cAPI.HttpDefineMetric(unsafe.Pointer(c.config), api.Gauge, name) + return &gaugeMetric{ + config: c, + metricId: id, + } +} + +func (c *httpConfig) Finalize() { + cAPI.HttpConfigFinalize(unsafe.Pointer(c.config)) +} + +type counterMetric struct { + config *httpConfig + metricId uint32 +} + +func (m *counterMetric) Increment(offset int64) { + cAPI.HttpIncrementMetric(unsafe.Pointer(m.config), m.metricId, offset) +} + +func (m *counterMetric) Get() uint64 { + return cAPI.HttpGetMetric(unsafe.Pointer(m.config), m.metricId) +} + +func (m *counterMetric) Record(value uint64) { + cAPI.HttpRecordMetric(unsafe.Pointer(m.config), m.metricId, value) +} + +type gaugeMetric struct { + config *httpConfig + metricId uint32 +} + +func (m *gaugeMetric) Increment(offset int64) { + cAPI.HttpIncrementMetric(unsafe.Pointer(m.config), m.metricId, offset) +} + +func (m *gaugeMetric) Get() uint64 { + return cAPI.HttpGetMetric(unsafe.Pointer(m.config), m.metricId) +} + +func (m *gaugeMetric) Record(value uint64) { + cAPI.HttpRecordMetric(unsafe.Pointer(m.config), m.metricId, value) +} diff --git a/contrib/golang/filters/http/source/go/pkg/http/filtermanager.go b/contrib/golang/filters/http/source/go/pkg/http/filtermanager.go index a790a90070429..550cc53264f34 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/filtermanager.go +++ b/contrib/golang/filters/http/source/go/pkg/http/filtermanager.go @@ -22,42 +22,68 @@ import ( "sync" "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "google.golang.org/protobuf/types/known/anypb" ) -var httpFilterConfigFactoryAndParser = sync.Map{} +var httpFilterFactoryAndParser = sync.Map{} -type filterConfigFactoryAndParser struct { - configFactory api.StreamFilterConfigFactory +type filterFactoryAndParser struct { + filterFactory api.StreamFilterFactory configParser api.StreamFilterConfigParser } -// Register config factory and config parser for the specified plugin. -// The "factory" parameter is required, should not be nil, -// and the "parser" parameter is optional, could be nil. -func RegisterHttpFilterConfigFactoryAndParser(name string, factory api.StreamFilterConfigFactory, parser api.StreamFilterConfigParser) { +// nullParser is a no-op implementation of the StreamFilterConfigParser interface. +type nullParser struct{} + +// Parse does nothing, returns the input `any` as is. +func (p *nullParser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + return any, nil +} + +// Merge only uses the childConfig, ignore the parentConfig. +func (p *nullParser) Merge(parentConfig interface{}, childConfig interface{}) interface{} { + return childConfig +} + +var NullParser api.StreamFilterConfigParser = &nullParser{} + +// RegisterHttpFilterFactoryAndConfigParser registers the http filter factory and config parser for the specified plugin. +// The factory and parser should not be nil. +// Use the NullParser if the plugin does not care about config. +func RegisterHttpFilterFactoryAndConfigParser(name string, factory api.StreamFilterFactory, parser api.StreamFilterConfigParser) { if factory == nil { - panic("config factory should not be nil") + panic("filter factory should not be nil") } - httpFilterConfigFactoryAndParser.Store(name, &filterConfigFactoryAndParser{factory, parser}) + if parser == nil { + panic("config parser should not be nil") + } + httpFilterFactoryAndParser.Store(name, &filterFactoryAndParser{factory, parser}) } -func getOrCreateHttpFilterFactory(name string, configId uint64) api.StreamFilterFactory { +func getHttpFilterFactoryAndConfig(name string, configId uint64) (api.StreamFilterFactory, interface{}) { config, ok := configCache.Load(configId) if !ok { - panic(fmt.Sprintf("get config failed, plugin: %s, configId: %d", name, configId)) + panic(fmt.Sprintf("config not found, plugin: %s, configId: %d", name, configId)) } - if v, ok := httpFilterConfigFactoryAndParser.Load(name); ok { - return (v.(*filterConfigFactoryAndParser)).configFactory(config) + if v, ok := httpFilterFactoryAndParser.Load(name); ok { + return (v.(*filterFactoryAndParser)).filterFactory, config } - // pass through by default - return PassThroughFactory(config) + api.LogErrorf("plugin %s not found, pass through by default", name) + + // return PassThroughFactory when no factory found + return PassThroughFactory, config } func getHttpFilterConfigParser(name string) api.StreamFilterConfigParser { - if v, ok := httpFilterConfigFactoryAndParser.Load(name); ok { - return (v.(*filterConfigFactoryAndParser)).configParser + if v, ok := httpFilterFactoryAndParser.Load(name); ok { + parser := (v.(*filterFactoryAndParser)).configParser + if parser == nil { + panic(fmt.Sprintf("config parser not found, plugin: %s", name)) + } + return parser } - return nil + // return NullParser when no parser found + return NullParser } diff --git a/contrib/golang/filters/http/source/go/pkg/http/passthrough.go b/contrib/golang/filters/http/source/go/pkg/http/passthrough.go index 2cffbd5c8a8db..6d000e37f79db 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/passthrough.go +++ b/contrib/golang/filters/http/source/go/pkg/http/passthrough.go @@ -26,10 +26,8 @@ type passThroughFilter struct { callbacks api.FilterCallbackHandler } -func PassThroughFactory(interface{}) api.StreamFilterFactory { - return func(callbacks api.FilterCallbackHandler) api.StreamFilter { - return &passThroughFilter{ - callbacks: callbacks, - } +func PassThroughFactory(config interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + return &passThroughFilter{ + callbacks: callbacks, } } diff --git a/contrib/golang/filters/http/source/go/pkg/http/shim.go b/contrib/golang/filters/http/source/go/pkg/http/shim.go index 1493430bc86fe..8b2ffc1a88d70 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/shim.go +++ b/contrib/golang/filters/http/source/go/pkg/http/shim.go @@ -37,7 +37,6 @@ import ( "fmt" "runtime" "sync" - "sync/atomic" "github.com/envoyproxy/envoy/contrib/golang/common/go/api" ) @@ -46,43 +45,95 @@ var ErrDupRequestKey = errors.New("dup request key") var Requests = &requestMap{} +var ( + initialized = false + envoyConcurrency uint32 +) + +// EnvoyConcurrency returns the concurrency Envoy was set to run at. This can be used to optimize HTTP filters that need +// memory per worker thread to avoid locks. +// +// Note: Do not use inside of an `init()` function, the value will not be populated yet. Use within the filters +// `StreamFilterFactory` or `StreamFilterConfigParser` +func EnvoyConcurrency() uint32 { + if !initialized { + panic("concurrency has not yet been initialized, do not access within an init()") + } + return envoyConcurrency +} + type requestMap struct { - m sync.Map // *C.httpRequest -> *httpRequest + initOnce sync.Once + requests []map[*C.httpRequest]*httpRequest +} + +func (f *requestMap) initialize(concurrency uint32) { + f.initOnce.Do(func() { + initialized = true + envoyConcurrency = concurrency + f.requests = make([]map[*C.httpRequest]*httpRequest, concurrency) + for i := uint32(0); i < concurrency; i++ { + f.requests[i] = map[*C.httpRequest]*httpRequest{} + } + }) } func (f *requestMap) StoreReq(key *C.httpRequest, req *httpRequest) error { - if _, loaded := f.m.LoadOrStore(key, req); loaded { + m := f.requests[key.worker_id] + if _, ok := m[key]; ok { return ErrDupRequestKey } + m[key] = req return nil } func (f *requestMap) GetReq(key *C.httpRequest) *httpRequest { - if v, ok := f.m.Load(key); ok { - return v.(*httpRequest) - } - return nil + return f.requests[key.worker_id][key] } func (f *requestMap) DeleteReq(key *C.httpRequest) { - f.m.Delete(key) + delete(f.requests[key.worker_id], key) } func (f *requestMap) Clear() { - f.m.Range(func(key, _ interface{}) bool { - f.m.Delete(key) - return true - }) + for idx := range f.requests { + f.requests[idx] = map[*C.httpRequest]*httpRequest{} + } } func requestFinalize(r *httpRequest) { r.Finalize(api.NormalFinalize) } +func getOrCreateState(s *C.processState) *processState { + r := s.req + req := getRequest(r) + if req == nil { + req = createRequest(r) + } + if s.is_encoding == 0 { + if req.decodingState.processState == nil { + req.decodingState.processState = s + } + return &req.decodingState + } + + // s.is_encoding == 1 + if req.encodingState.processState == nil { + req.encodingState.processState = s + } + return &req.encodingState +} + func createRequest(r *C.httpRequest) *httpRequest { req := &httpRequest{ req: r, } + req.decodingState.request = req + req.encodingState.request = req + req.streamInfo.request = req + + req.cond.L = &req.waitingLock // NP: make sure filter will be deleted. runtime.SetFinalizer(req, requestFinalize) @@ -92,8 +143,8 @@ func createRequest(r *C.httpRequest) *httpRequest { } configId := uint64(r.configId) - filterFactory := getOrCreateHttpFilterFactory(req.pluginName(), configId) - f := filterFactory(req) + filterFactory, config := getHttpFilterFactoryAndConfig(req.pluginName(), configId) + f := filterFactory(config, req) req.httpFilter = f return req @@ -103,35 +154,38 @@ func getRequest(r *C.httpRequest) *httpRequest { return Requests.GetReq(r) } -//export envoyGoFilterOnHttpHeader -func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerBytes uint64) uint64 { - var req *httpRequest - phase := api.EnvoyRequestPhase(r.phase) - if phase == api.DecodeHeaderPhase { - req = createRequest(r) - } else { - req = getRequest(r) - // early sendLocalReply may skip the whole decode phase - if req == nil { - req = createRequest(r) - } +func getState(s *C.processState) *processState { + r := s.req + req := getRequest(r) + if s.is_encoding == 0 { + return &req.decodingState } + // s.is_encoding == 1 + return &req.encodingState +} + +//export envoyGoFilterOnHttpHeader +func envoyGoFilterOnHttpHeader(s *C.processState, endStream, headerNum, headerBytes uint64) uint64 { + // early SendLocalReply or OnLogDownstreamStart may run before the header handling + state := getOrCreateState(s) + + req := state.request if req.pInfo.paniced { // goroutine panic in the previous state that could not sendLocalReply, delay terminating the request here, // to prevent error from spreading. - req.sendPanicReply(req.pInfo.details) + state.sendPanicReply(req.pInfo.details) return uint64(api.LocalReply) } - defer req.RecoverPanic() + defer state.RecoverPanic() f := req.httpFilter var status api.StatusType - switch phase { + switch state.Phase() { case api.DecodeHeaderPhase: header := &requestHeaderMapImpl{ requestOrResponseHeaderMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -142,7 +196,7 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt header := &requestTrailerMapImpl{ requestOrResponseTrailerMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -153,7 +207,7 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt header := &responseHeaderMapImpl{ requestOrResponseHeaderMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -164,7 +218,7 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt header := &responseTrailerMapImpl{ requestOrResponseTrailerMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -172,25 +226,32 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt } status = f.EncodeTrailers(header) } + + if endStream == 1 && (status == api.StopAndBuffer || status == api.StopAndBufferWatermark) { + panic("received wait data status when there is no data, please fix the returned status") + } + return uint64(status) } //export envoyGoFilterOnHttpData -func envoyGoFilterOnHttpData(r *C.httpRequest, endStream, buffer, length uint64) uint64 { - req := getRequest(r) +func envoyGoFilterOnHttpData(s *C.processState, endStream, buffer, length uint64) uint64 { + state := getState(s) + + req := state.request if req.pInfo.paniced { // goroutine panic in the previous state that could not sendLocalReply, delay terminating the request here, // to prevent error from spreading. - req.sendPanicReply(req.pInfo.details) + state.sendPanicReply(req.pInfo.details) return uint64(api.LocalReply) } - defer req.RecoverPanic() + defer state.RecoverPanic() f := req.httpFilter - isDecode := api.EnvoyRequestPhase(r.phase) == api.DecodeDataPhase + isDecode := state.Phase() == api.DecodeDataPhase buf := &httpBuffer{ - request: req, + state: state, envoyBufferInstance: buffer, length: length, } @@ -204,28 +265,130 @@ func envoyGoFilterOnHttpData(r *C.httpRequest, endStream, buffer, length uint64) return uint64(status) } +//export envoyGoFilterOnHttpLog +func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64, + decodingStateWrapper *C.processState, encodingStateWrapper *C.processState, + reqHeaderNum, reqHeaderBytes, reqTrailerNum, reqTrailerBytes, + respHeaderNum, respHeaderBytes, respTrailerNum, respTrailerBytes uint64) { + + decodingState := getOrCreateState(decodingStateWrapper) + encodingState := getOrCreateState(encodingStateWrapper) + req := getRequest(r) + if req == nil { + req = createRequest(r) + } + + defer req.recoverPanic() + + v := api.AccessLogType(logType) + + // Request headers must exist because the HTTP filter won't be run if the headers are + // not sent yet. + // TODO: make the headers/trailers read-only + reqHeader := &requestHeaderMapImpl{ + requestOrResponseHeaderMapImpl{ + headerMapImpl{ + state: decodingState, + headerNum: reqHeaderNum, + headerBytes: reqHeaderBytes, + }, + }, + } + + var reqTrailer api.RequestTrailerMap + if reqTrailerNum != 0 { + reqTrailer = &requestTrailerMapImpl{ + requestOrResponseTrailerMapImpl{ + headerMapImpl{ + state: decodingState, + headerNum: reqTrailerNum, + headerBytes: reqTrailerBytes, + }, + }, + } + } + + var respHeader api.ResponseHeaderMap + if respHeaderNum != 0 { + respHeader = &responseHeaderMapImpl{ + requestOrResponseHeaderMapImpl{ + headerMapImpl{ + state: encodingState, + headerNum: respHeaderNum, + headerBytes: respHeaderBytes, + }, + }, + } + } + + var respTrailer api.ResponseTrailerMap + if respTrailerNum != 0 { + respTrailer = &responseTrailerMapImpl{ + requestOrResponseTrailerMapImpl{ + headerMapImpl{ + state: encodingState, + headerNum: respTrailerNum, + headerBytes: respTrailerBytes, + }, + }, + } + } + + f := req.httpFilter + + switch v { + case api.AccessLogDownstreamEnd: + f.OnLog(reqHeader, reqTrailer, respHeader, respTrailer) + case api.AccessLogDownstreamPeriodic: + f.OnLogDownstreamPeriodic(reqHeader, reqTrailer, respHeader, respTrailer) + case api.AccessLogDownstreamStart: + f.OnLogDownstreamStart(reqHeader) + default: + api.LogErrorf("access log type %d is not supported yet", logType) + } +} + +//export envoyGoFilterOnHttpStreamComplete +func envoyGoFilterOnHttpStreamComplete(r *C.httpRequest) { + req := getRequest(r) + defer req.recoverPanic() + + f := req.httpFilter + f.OnStreamComplete() +} + //export envoyGoFilterOnHttpDestroy func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) { req := getRequest(r) // do nothing even when req.panic is true, since filter is already destroying. - defer req.RecoverPanic() - if atomic.CompareAndSwapInt32(&req.waitingOnEnvoy, 1, 0) { - req.sema.Done() - } + defer req.recoverPanic() + + req.resumeWaitCallback() v := api.DestroyReason(reason) f := req.httpFilter f.OnDestroy(v) + // Break circular references between httpRequest and StreamFilter, + // since Finalizers don't work with circular references, + // otherwise, it will leads to memory leaking. + req.httpFilter = nil + Requests.DeleteReq(r) } //export envoyGoRequestSemaDec func envoyGoRequestSemaDec(r *C.httpRequest) { req := getRequest(r) - defer req.RecoverPanic() - if atomic.CompareAndSwapInt32(&req.waitingOnEnvoy, 1, 0) { - req.sema.Done() - } + defer req.recoverPanic() + req.resumeWaitCallback() +} + +// This is unsafe, just for asan testing. +// +//export envoyGoFilterCleanUp +func envoyGoFilterCleanUp() { + asanTestEnabled = true + forceGCFinalizer() } diff --git a/contrib/golang/filters/http/source/go/pkg/http/type.go b/contrib/golang/filters/http/source/go/pkg/http/type.go index 8b975cb263682..a893e8cbdcc01 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/type.go +++ b/contrib/golang/filters/http/source/go/pkg/http/type.go @@ -19,6 +19,7 @@ package http import ( "strconv" + "strings" "sync" "unsafe" @@ -31,39 +32,35 @@ const ( errFilterDestroyed = "golang filter has been destroyed" errNotInGo = "not proccessing Go" errInvalidPhase = "invalid phase, maybe headers/buffer already continued" + errInvalidScene = "invalid scene for this API" ) // api.HeaderMap type headerMapImpl struct { - request *httpRequest + state *processState headers map[string][]string headerNum uint64 headerBytes uint64 mutex sync.Mutex } -// ByteSize return size of HeaderMap -func (h *headerMapImpl) ByteSize() uint64 { - return h.headerBytes -} - type requestOrResponseHeaderMapImpl struct { headerMapImpl } func (h *requestOrResponseHeaderMapImpl) initHeaders() { if h.headers == nil { - h.headers = cAPI.HttpCopyHeaders(unsafe.Pointer(h.request.req), h.headerNum, h.headerBytes) + h.headers = cAPI.HttpCopyHeaders(unsafe.Pointer(h.state), h.headerNum, h.headerBytes) } } func (h *requestOrResponseHeaderMapImpl) GetRaw(key string) string { - var value string - cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), &key, &value) - return value + // GetRaw is case-sensitive + return cAPI.HttpGetHeader(unsafe.Pointer(h.state), key) } func (h *requestOrResponseHeaderMapImpl) Get(key string) (string, bool) { + key = strings.ToLower(key) h.mutex.Lock() defer h.mutex.Unlock() h.initHeaders() @@ -75,6 +72,7 @@ func (h *requestOrResponseHeaderMapImpl) Get(key string) (string, bool) { } func (h *requestOrResponseHeaderMapImpl) Values(key string) []string { + key = strings.ToLower(key) h.mutex.Lock() defer h.mutex.Unlock() h.initHeaders() @@ -86,6 +84,7 @@ func (h *requestOrResponseHeaderMapImpl) Values(key string) []string { } func (h *requestOrResponseHeaderMapImpl) Set(key, value string) { + key = strings.ToLower(key) // Get all header values first before setting a value, since the set operation may not take affects immediately // when it's invoked in a Go thread, instead, it will post a callback to run in the envoy worker thread. // Otherwise, we may get outdated values in a following Get call. @@ -95,10 +94,11 @@ func (h *requestOrResponseHeaderMapImpl) Set(key, value string) { if h.headers != nil { h.headers[key] = []string{value} } - cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), &key, &value, false) + cAPI.HttpSetHeader(unsafe.Pointer(h.state), key, value, false) } func (h *requestOrResponseHeaderMapImpl) Add(key, value string) { + key = strings.ToLower(key) h.mutex.Lock() defer h.mutex.Unlock() h.initHeaders() @@ -109,10 +109,11 @@ func (h *requestOrResponseHeaderMapImpl) Add(key, value string) { h.headers[key] = []string{value} } } - cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), &key, &value, true) + cAPI.HttpSetHeader(unsafe.Pointer(h.state), key, value, true) } func (h *requestOrResponseHeaderMapImpl) Del(key string) { + key = strings.ToLower(key) // Get all header values first before removing a key, since the del operation may not take affects immediately // when it's invoked in a Go thread, instead, it will post a callback to run in the envoy worker thread. // Otherwise, we may get outdated values in a following Get call. @@ -120,7 +121,7 @@ func (h *requestOrResponseHeaderMapImpl) Del(key string) { defer h.mutex.Unlock() h.initHeaders() delete(h.headers, key) - cAPI.HttpRemoveHeader(unsafe.Pointer(h.request.req), &key) + cAPI.HttpRemoveHeader(unsafe.Pointer(h.state), key) } func (h *requestOrResponseHeaderMapImpl) Range(f func(key, value string) bool) { @@ -155,6 +156,18 @@ func (h *requestOrResponseHeaderMapImpl) RangeWithCopy(f func(key, value string) } } +func (h *requestOrResponseHeaderMapImpl) GetAllHeaders() map[string][]string { + h.mutex.Lock() + defer h.mutex.Unlock() + h.initHeaders() + copiedHeaders := make(map[string][]string) + for key, value := range h.headers { + copiedHeaders[key] = make([]string, len(value)) + copy(copiedHeaders[key], value) + } + return copiedHeaders +} + // api.RequestHeaderMap type requestHeaderMapImpl struct { requestOrResponseHeaderMapImpl @@ -162,11 +175,6 @@ type requestHeaderMapImpl struct { var _ api.RequestHeaderMap = (*requestHeaderMapImpl)(nil) -func (h *requestHeaderMapImpl) Protocol() string { - v, _ := h.Get(":protocol") - return v -} - func (h *requestHeaderMapImpl) Scheme() string { v, _ := h.Get(":scheme") return v @@ -187,6 +195,18 @@ func (h *requestHeaderMapImpl) Host() string { return v } +func (h *requestHeaderMapImpl) SetMethod(method string) { + h.Set(":method", method) +} + +func (h *requestHeaderMapImpl) SetPath(path string) { + h.Set(":path", path) +} + +func (h *requestHeaderMapImpl) SetHost(host string) { + h.Set(":authority", host) +} + // api.ResponseHeaderMap type responseHeaderMapImpl struct { requestOrResponseHeaderMapImpl @@ -208,17 +228,16 @@ type requestOrResponseTrailerMapImpl struct { func (h *requestOrResponseTrailerMapImpl) initTrailers() { if h.headers == nil { - h.headers = cAPI.HttpCopyTrailers(unsafe.Pointer(h.request.req), h.headerNum, h.headerBytes) + h.headers = cAPI.HttpCopyTrailers(unsafe.Pointer(h.state), h.headerNum, h.headerBytes) } } func (h *requestOrResponseTrailerMapImpl) GetRaw(key string) string { - var value string - cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), &key, &value) - return value + return cAPI.HttpGetHeader(unsafe.Pointer(h.state), key) } func (h *requestOrResponseTrailerMapImpl) Get(key string) (string, bool) { + key = strings.ToLower(key) h.mutex.Lock() defer h.mutex.Unlock() h.initTrailers() @@ -230,6 +249,7 @@ func (h *requestOrResponseTrailerMapImpl) Get(key string) (string, bool) { } func (h *requestOrResponseTrailerMapImpl) Values(key string) []string { + key = strings.ToLower(key) h.mutex.Lock() defer h.mutex.Unlock() h.initTrailers() @@ -241,6 +261,7 @@ func (h *requestOrResponseTrailerMapImpl) Values(key string) []string { } func (h *requestOrResponseTrailerMapImpl) Set(key, value string) { + key = strings.ToLower(key) // Get all header values first before setting a value, since the set operation may not take affects immediately // when it's invoked in a Go thread, instead, it will post a callback to run in the envoy worker thread. // Otherwise, we may get outdated values in a following Get call. @@ -251,10 +272,11 @@ func (h *requestOrResponseTrailerMapImpl) Set(key, value string) { h.headers[key] = []string{value} } - cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), &key, &value, false) + cAPI.HttpSetTrailer(unsafe.Pointer(h.state), key, value, false) } func (h *requestOrResponseTrailerMapImpl) Add(key, value string) { + key = strings.ToLower(key) h.mutex.Lock() defer h.mutex.Unlock() h.initTrailers() @@ -265,15 +287,16 @@ func (h *requestOrResponseTrailerMapImpl) Add(key, value string) { h.headers[key] = []string{value} } } - cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), &key, &value, true) + cAPI.HttpSetTrailer(unsafe.Pointer(h.state), key, value, true) } func (h *requestOrResponseTrailerMapImpl) Del(key string) { + key = strings.ToLower(key) h.mutex.Lock() defer h.mutex.Unlock() h.initTrailers() delete(h.headers, key) - cAPI.HttpRemoveTrailer(unsafe.Pointer(h.request.req), &key) + cAPI.HttpRemoveTrailer(unsafe.Pointer(h.state), key) } func (h *requestOrResponseTrailerMapImpl) Range(f func(key, value string) bool) { @@ -308,6 +331,18 @@ func (h *requestOrResponseTrailerMapImpl) RangeWithCopy(f func(key, value string } } +func (h *requestOrResponseTrailerMapImpl) GetAllHeaders() map[string][]string { + h.mutex.Lock() + defer h.mutex.Unlock() + h.initTrailers() + copiedHeaders := make(map[string][]string) + for key, value := range h.headers { + copiedHeaders[key] = make([]string, len(value)) + copy(copiedHeaders[key], value) + } + return copiedHeaders +} + // api.RequestTrailerMap type requestTrailerMapImpl struct { requestOrResponseTrailerMapImpl @@ -324,26 +359,31 @@ var _ api.ResponseTrailerMap = (*responseTrailerMapImpl)(nil) // api.BufferInstance type httpBuffer struct { - request *httpRequest + state *processState envoyBufferInstance uint64 length uint64 - value string + value []byte } var _ api.BufferInstance = (*httpBuffer)(nil) func (b *httpBuffer) Write(p []byte) (n int, err error) { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, string(p), api.AppendBuffer) - return len(p), nil + cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, p, api.AppendBuffer) + n = len(p) + b.length += uint64(n) + return n, nil } func (b *httpBuffer) WriteString(s string) (n int, err error) { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, s, api.AppendBuffer) - return len(s), nil + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, s, api.AppendBuffer) + n = len(s) + b.length += uint64(n) + return n, nil } func (b *httpBuffer) WriteByte(p byte) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, string(p), api.AppendBuffer) + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, string(p), api.AppendBuffer) + b.length++ return nil } @@ -360,25 +400,32 @@ func (b *httpBuffer) WriteUint32(p uint32) error { } func (b *httpBuffer) WriteUint64(p uint64) error { - s := strconv.FormatUint(uint64(p), 10) + s := strconv.FormatUint(p, 10) _, err := b.WriteString(s) return err } -func (b *httpBuffer) Peek(n int) []byte { - panic("implement me") -} - func (b *httpBuffer) Bytes() []byte { if b.length == 0 { return nil } - cAPI.HttpGetBuffer(unsafe.Pointer(b.request.req), b.envoyBufferInstance, &b.value, b.length) - return []byte(b.value) + b.value = cAPI.HttpGetBuffer(unsafe.Pointer(b.state), b.envoyBufferInstance, b.length) + return b.value } func (b *httpBuffer) Drain(offset int) { - panic("implement me") + if offset <= 0 || b.length == 0 { + return + } + + size := uint64(offset) + if size > b.length { + size = b.length + } + + cAPI.HttpDrainBuffer(unsafe.Pointer(b.state), b.envoyBufferInstance, size) + + b.length -= size } func (b *httpBuffer) Len() int { @@ -386,43 +433,47 @@ func (b *httpBuffer) Len() int { } func (b *httpBuffer) Reset() { - panic("implement me") + b.Drain(b.Len()) } func (b *httpBuffer) String() string { if b.length == 0 { return "" } - cAPI.HttpGetBuffer(unsafe.Pointer(b.request.req), b.envoyBufferInstance, &b.value, b.length) - return b.value + b.value = cAPI.HttpGetBuffer(unsafe.Pointer(b.state), b.envoyBufferInstance, b.length) + return string(b.value) } func (b *httpBuffer) Append(data []byte) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, string(data), api.AppendBuffer) - return nil + _, err := b.Write(data) + return err } func (b *httpBuffer) Prepend(data []byte) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, string(data), api.PrependBuffer) + cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, data, api.PrependBuffer) + b.length += uint64(len(data)) return nil } func (b *httpBuffer) AppendString(s string) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, s, api.AppendBuffer) - return nil + _, err := b.WriteString(s) + return err } func (b *httpBuffer) PrependString(s string) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, s, api.PrependBuffer) + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, s, api.PrependBuffer) + b.length += uint64(len(s)) return nil } func (b *httpBuffer) Set(data []byte) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, string(data), api.SetBuffer) + cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, data, api.SetBuffer) + b.length = uint64(len(data)) return nil } func (b *httpBuffer) SetString(s string) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, s, api.SetBuffer) + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, s, api.SetBuffer) + b.length = uint64(len(s)) return nil } diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index f1b4e99b77c26..f2ff52a33e12b 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -1,10 +1,13 @@ #include "contrib/golang/filters/http/source/golang_filter.h" +#include #include +#include #include #include #include "envoy/http/codes.h" +#include "envoy/router/string_accessor.h" #include "source/common/buffer/buffer_impl.h" #include "source/common/common/base64.h" @@ -16,24 +19,24 @@ #include "source/common/grpc/status.h" #include "source/common/http/headers.h" #include "source/common/http/http1/codec_impl.h" +#include "source/common/router/string_accessor_impl.h" +#include "source/extensions/filters/common/expr/context.h" + +#include "eval/public/cel_value.h" +#include "eval/public/containers/field_access.h" +#include "eval/public/containers/field_backed_list_impl.h" +#include "eval/public/containers/field_backed_map_impl.h" +#include "eval/public/structs/cel_proto_wrapper.h" namespace Envoy { namespace Extensions { namespace HttpFilters { namespace Golang { -void Filter::onHeadersModified() { - // Any changes to request headers can affect how the request is going to be - // routed. If we are changing the headers we also need to clear the route - // cache. - decoding_state_.getFilterCallbacks()->downstreamCallbacks()->clearRouteCache(); -} - Http::LocalErrorStatus Filter::onLocalReply(const LocalReplyData& data) { - auto& state = getProcessorState(); - ASSERT(state.isThreadSafe()); - ENVOY_LOG(debug, "golang filter onLocalReply, state: {}, phase: {}, code: {}", state.stateStr(), - state.phaseStr(), int(data.code_)); + ASSERT(isThreadSafe()); + ENVOY_LOG(debug, "golang filter onLocalReply, decoding state: {}, encoding state: {}, code: {}", + decoding_state_.stateStr(), encoding_state_.stateStr(), int(data.code_)); return Http::LocalErrorStatus::Continue; } @@ -41,8 +44,10 @@ Http::LocalErrorStatus Filter::onLocalReply(const LocalReplyData& data) { Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { ProcessorState& state = decoding_state_; - ENVOY_LOG(debug, "golang filter decodeHeaders, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter decodeHeaders, decoding state: {}, end_stream: {}", + state.stateStr(), end_stream); + + request_headers_ = &headers; state.setEndStream(end_stream); @@ -53,9 +58,8 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { ProcessorState& state = decoding_state_; - ENVOY_LOG(debug, - "golang filter decodeData, state: {}, phase: {}, data length: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), data.length(), end_stream); + ENVOY_LOG(debug, "golang filter decodeData, decoding state: {}, data length: {}, end_stream: {}", + state.stateStr(), data.length(), end_stream); state.setEndStream(end_stream); @@ -71,10 +75,9 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trailers) { ProcessorState& state = decoding_state_; - ENVOY_LOG(debug, "golang filter decodeTrailers, state: {}, phase: {}", state.stateStr(), - state.phaseStr()); + ENVOY_LOG(debug, "golang filter decodeTrailers, decoding state: {}", state.stateStr()); - state.setSeenTrailers(); + request_trailers_ = &trailers; bool done = doTrailer(state, trailers); @@ -82,51 +85,18 @@ Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trail } Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { - ProcessorState& state = getProcessorState(); - ENVOY_LOG(debug, "golang filter encodeHeaders, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); - - encoding_state_.setEndStream(end_stream); - - // NP: may enter encodeHeaders in any phase & any state_, - // since other filters or filtermanager could call encodeHeaders or sendLocalReply in any time. - // eg. filtermanager may invoke sendLocalReply, when scheme is invalid, - // with "Sending local reply with details // http1.invalid_scheme" details. - if (state.state() != FilterState::Done) { - ENVOY_LOG(debug, - "golang filter enter encodeHeaders early, maybe sendLocalReply or encodeHeaders " - "happened, current state: {}, phase: {}", - state.stateStr(), state.phaseStr()); - - ENVOY_LOG(debug, "golang filter drain data buffer since enter encodeHeaders early"); - // NP: is safe to overwrite it since go code won't read it directly - // need drain buffer to enable read when it's high watermark - state.drainBufferData(); - - // get the state before changing it. - bool in_go = state.isProcessingInGo(); - - if (in_go) { - // NP: wait go returns to avoid concurrency conflict in go side. - local_reply_waiting_go_ = true; - ENVOY_LOG(debug, "waiting go returns before handle the local reply from other filter"); - - // NP: save to another local_headers_ variable to avoid conflict, - // since the headers_ may be used in Go side. - local_headers_ = &headers; - - // can not use "StopAllIterationAndWatermark" here, since Go decodeHeaders may return - // stopAndBuffer, that means it need data buffer and not continue header. - return Http::FilterHeadersStatus::StopIteration; + ProcessorState& state = encoding_state_; + ENVOY_LOG(debug, "golang filter encodeHeaders, encoding state: {}, end_stream: {}", + state.stateStr(), end_stream); - } else { - ENVOY_LOG(debug, "golang filter clear do data buffer before continue encodeHeader, " - "since no go code is running"); - state.doDataList.clearAll(); - } - } + state.setEndStream(end_stream); + activation_response_headers_ = dynamic_cast(&headers); - enter_encoding_ = true; + // NP: may enter encodeHeaders in any state, + // since other filters or filtermanager could call encodeHeaders or sendLocalReply in any + // time. eg. filtermanager may invoke sendLocalReply, when scheme is invalid, with "Sending + // local reply with details // http1.invalid_scheme" details. This means DecodeXXX & EncodeXXX + // may run concurrently in Golang side. bool done = doHeaders(encoding_state_, headers, end_stream); @@ -134,20 +104,13 @@ Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers } Http::FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_stream) { - ProcessorState& state = getProcessorState(); - ENVOY_LOG(debug, - "golang filter encodeData, state: {}, phase: {}, data length: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), data.length(), end_stream); - - encoding_state_.setEndStream(end_stream); + ProcessorState& state = encoding_state_; + ENVOY_LOG(debug, "golang filter encodeData, encoding state: {}, data length: {}, end_stream: {}", + state.stateStr(), data.length(), end_stream); - if (local_reply_waiting_go_) { - ENVOY_LOG(debug, "golang filter appending data to buffer"); - encoding_state_.addBufferData(data); - return Http::FilterDataStatus::StopIterationNoBuffer; - } + state.setEndStream(end_stream); - bool done = doData(encoding_state_, data, end_stream); + bool done = doData(state, data, end_stream); if (done) { state.doDataList.moveOut(data); @@ -158,29 +121,32 @@ Http::FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_strea } Http::FilterTrailersStatus Filter::encodeTrailers(Http::ResponseTrailerMap& trailers) { - ProcessorState& state = getProcessorState(); - ENVOY_LOG(debug, "golang filter encodeTrailers, state: {}, phase: {}", state.stateStr(), - state.phaseStr()); + ProcessorState& state = encoding_state_; + ENVOY_LOG(debug, "golang filter encodeTrailers, encoding state: {}", state.stateStr()); - encoding_state_.setSeenTrailers(); - - if (local_reply_waiting_go_) { - // NP: save to another local_trailers_ variable to avoid conflict, - // since the trailers_ may be used in Go side. - local_trailers_ = &trailers; - return Http::FilterTrailersStatus::StopIteration; - } + activation_response_trailers_ = dynamic_cast(&trailers); bool done = doTrailer(encoding_state_, trailers); return done ? Http::FilterTrailersStatus::Continue : Http::FilterTrailersStatus::StopIteration; } +void Filter::onStreamComplete() { + // We reuse the same flag for both onStreamComplete & log to save the space, + // since they are exclusive and serve for the access log purpose. + req_->is_golang_processing_log = 1; + dynamic_lib_->envoyGoFilterOnHttpStreamComplete(req_); + req_->is_golang_processing_log = 0; +} + void Filter::onDestroy() { ENVOY_LOG(debug, "golang filter on destroy"); - // do nothing, stream reset may happen before entering this filter. - if (req_ == nullptr) { + // initRequest haven't be called yet, which mean haven't called into Go. + if (req_->configId == 0) { + // should release the req object, since stream reset may happen before calling into Go side, + // which means no GC finializer will be invoked to release this C++ object. + delete req_; return; } @@ -193,84 +159,132 @@ void Filter::onDestroy() { has_destroyed_ = true; } - auto& state = getProcessorState(); - auto reason = state.isProcessingInGo() ? DestroyReason::Terminate : DestroyReason::Normal; + auto reason = (decoding_state_.isProcessingInGo() || encoding_state_.isProcessingInGo()) + ? DestroyReason::Terminate + : DestroyReason::Normal; dynamic_lib_->envoyGoFilterOnHttpDestroy(req_, int(reason)); } // access_log is executed before the log of the stream filter -void Filter::log(const Http::RequestHeaderMap*, const Http::ResponseHeaderMap*, - const Http::ResponseTrailerMap*, const StreamInfo::StreamInfo&, - Envoy::AccessLog::AccessLogType) { - // Todo log phase of stream filter +void Filter::log(const Http::RequestHeaderMap* headers, + const Http::ResponseHeaderMap* responseHeaders, + const Http::ResponseTrailerMap* responseTrailers, const StreamInfo::StreamInfo&, + Envoy::AccessLog::AccessLogType type) { + uint64_t req_header_num = 0; + uint64_t req_header_bytes = 0; + uint64_t req_trailer_num = 0; + uint64_t req_trailer_bytes = 0; + uint64_t resp_header_num = 0; + uint64_t resp_header_bytes = 0; + uint64_t resp_trailer_num = 0; + uint64_t resp_trailer_bytes = 0; + + auto decoding_state = dynamic_cast(&decoding_state_); + auto encoding_state = dynamic_cast(&encoding_state_); + + // `log` may be called multiple times with different log type + switch (type) { + case Envoy::AccessLog::AccessLogType::DownstreamStart: + case Envoy::AccessLog::AccessLogType::DownstreamPeriodic: + case Envoy::AccessLog::AccessLogType::DownstreamEnd: + // log called by AccessLogDownstreamStart will happen before doHeaders + if (initRequest()) { + request_headers_ = const_cast(headers); + } + + if (request_headers_ != nullptr) { + req_header_num = request_headers_->size(); + req_header_bytes = request_headers_->byteSize(); + decoding_state_.headers = request_headers_; + } + + if (request_trailers_ != nullptr) { + req_trailer_num = request_trailers_->size(); + req_trailer_bytes = request_trailers_->byteSize(); + decoding_state_.trailers = request_trailers_; + } + + activation_response_headers_ = responseHeaders; + if (activation_response_headers_ != nullptr) { + resp_header_num = activation_response_headers_->size(); + resp_header_bytes = activation_response_headers_->byteSize(); + encoding_state_.headers = const_cast(activation_response_headers_); + } + + activation_response_trailers_ = responseTrailers; + if (activation_response_trailers_ != nullptr) { + resp_trailer_num = activation_response_trailers_->size(); + resp_trailer_bytes = activation_response_trailers_->byteSize(); + encoding_state_.trailers = + const_cast(activation_response_trailers_); + } + + req_->is_golang_processing_log = 1; + dynamic_lib_->envoyGoFilterOnHttpLog(req_, int(type), decoding_state, encoding_state, + req_header_num, req_header_bytes, req_trailer_num, + req_trailer_bytes, resp_header_num, resp_header_bytes, + resp_trailer_num, resp_trailer_bytes); + req_->is_golang_processing_log = 0; + break; + default: + // skip calling with unsupported log types + break; + } } /*** common APIs for filter, both decode and encode ***/ GolangStatus Filter::doHeadersGo(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, bool end_stream) { - ENVOY_LOG(debug, "golang filter passing data to golang, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter passing header to golang, state: {}, end_stream: {}", + state.stateStr(), end_stream); - if (req_ == nullptr) { - // req is used by go, so need to use raw memory and then it is safe to release at the gc - // finalize phase of the go object. - req_ = new httpRequestInternal(weak_from_this()); - req_->configId = getMergedConfigId(state); - req_->plugin_name.data = config_->pluginName().data(); - req_->plugin_name.len = config_->pluginName().length(); - } + initRequest(); - req_->phase = static_cast(state.phase()); - { - Thread::LockGuard lock(mutex_); - headers_ = &headers; - } - auto status = dynamic_lib_->envoyGoFilterOnHttpHeader(req_, end_stream ? 1 : 0, headers.size(), + auto s = dynamic_cast(&state); + auto status = dynamic_lib_->envoyGoFilterOnHttpHeader(s, end_stream ? 1 : 0, headers.size(), headers.byteSize()); return static_cast(status); } bool Filter::doHeaders(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, bool end_stream) { - ENVOY_LOG(debug, "golang filter doHeaders, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter doHeaders, state: {}, end_stream: {}", state.stateStr(), + end_stream); ASSERT(state.isBufferDataEmpty()); + state.headers = &headers; state.processHeader(end_stream); auto status = doHeadersGo(state, headers, end_stream); auto done = state.handleHeaderGolangStatus(status); if (done) { - Thread::LockGuard lock(mutex_); - headers_ = nullptr; + state.headers = nullptr; } return done; } bool Filter::doDataGo(ProcessorState& state, Buffer::Instance& data, bool end_stream) { - ENVOY_LOG(debug, "golang filter passing data to golang, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter passing data to golang, state: {}, end_stream: {}", + state.stateStr(), end_stream); state.processData(end_stream); Buffer::Instance& buffer = state.doDataList.push(data); - ASSERT(req_ != nullptr); - req_->phase = static_cast(state.phase()); + auto s = dynamic_cast(&state); auto status = dynamic_lib_->envoyGoFilterOnHttpData( - req_, end_stream ? 1 : 0, reinterpret_cast(&buffer), buffer.length()); + s, end_stream ? 1 : 0, reinterpret_cast(&buffer), buffer.length()); return state.handleDataGolangStatus(static_cast(status)); } bool Filter::doData(ProcessorState& state, Buffer::Instance& data, bool end_stream) { - ENVOY_LOG(debug, "golang filter doData, state: {}, phase: {}, end_stream: {}", state.stateStr(), - state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter doData, state: {}, end_stream: {}", state.stateStr(), end_stream); bool done = false; - switch (state.state()) { + switch (state.filterState()) { case FilterState::WaitingData: done = doDataGo(state, data, end_stream); break; @@ -283,7 +297,7 @@ bool Filter::doData(ProcessorState& state, Buffer::Instance& data, bool end_stre } // check state again since data_buffer may be full and sendLocalReply with 413. // TODO: better not trigger 413 here. - if (state.state() == FilterState::WaitingAllData) { + if (state.filterState() == FilterState::WaitingAllData) { done = doDataGo(state, data, end_stream); } break; @@ -307,33 +321,26 @@ bool Filter::doData(ProcessorState& state, Buffer::Instance& data, bool end_stre } bool Filter::doTrailerGo(ProcessorState& state, Http::HeaderMap& trailers) { - ENVOY_LOG(debug, "golang filter passing trailers to golang, state: {}, phase: {}", - state.stateStr(), state.phaseStr()); + ENVOY_LOG(debug, "golang filter passing trailers to golang, state: {}", state.stateStr()); state.processTrailer(); - ASSERT(req_ != nullptr); - req_->phase = static_cast(state.phase()); - auto status = - dynamic_lib_->envoyGoFilterOnHttpHeader(req_, 1, trailers.size(), trailers.byteSize()); + auto s = dynamic_cast(&state); + auto status = dynamic_lib_->envoyGoFilterOnHttpHeader(s, 1, trailers.size(), trailers.byteSize()); return state.handleTrailerGolangStatus(static_cast(status)); } bool Filter::doTrailer(ProcessorState& state, Http::HeaderMap& trailers) { - ENVOY_LOG(debug, "golang filter doTrailer, state: {}, phase: {}", state.stateStr(), - state.phaseStr()); + ENVOY_LOG(debug, "golang filter doTrailer, state: {}", state.stateStr()); ASSERT(!state.getEndStream() && !state.isProcessingEndStream()); - { - Thread::LockGuard lock(mutex_); - trailers_ = &trailers; - } + state.trailers = &trailers; bool done = false; Buffer::OwnedImpl body; - switch (state.state()) { + switch (state.filterState()) { case FilterState::WaitingTrailer: done = doTrailerGo(state, trailers); break; @@ -346,9 +353,9 @@ bool Filter::doTrailer(ProcessorState& state, Http::HeaderMap& trailers) { if (!state.isBufferDataEmpty()) { done = doDataGo(state, state.getBufferData(), false); // NP: can not use done as condition here, since done will be false - // maybe we can remove the done variable totally? by using state_ only? + // maybe we can remove the done variable totally? by using state only? // continue trailers - if (state.state() == FilterState::WaitingTrailer) { + if (state.filterState() == FilterState::WaitingTrailer) { state.continueDoData(); done = doTrailerGo(state, trailers); } @@ -367,55 +374,17 @@ bool Filter::doTrailer(ProcessorState& state, Http::HeaderMap& trailers) { break; } - ENVOY_LOG(debug, "golang filter doTrailer, return: {}", done); + ENVOY_LOG(debug, "golang filter doTrailer, return: {}, seen trailers: {}", done, + state.trailers != nullptr); return done; } /*** APIs for go call C ***/ -void Filter::continueEncodeLocalReply(ProcessorState& state) { - ENVOY_LOG(debug, - "golang filter continue encodeHeader(local reply from other filters) after return from " - "go, current state: {}, phase: {}", - state.stateStr(), state.phaseStr()); - - ENVOY_LOG(debug, "golang filter drain do data buffer before continueEncodeLocalReply"); - state.doDataList.clearAll(); - - local_reply_waiting_go_ = false; - // should use encoding_state_ now - enter_encoding_ = true; - - auto header_end_stream = encoding_state_.getEndStream(); - if (local_trailers_ != nullptr) { - Thread::LockGuard lock(mutex_); - trailers_ = local_trailers_; - header_end_stream = false; - } - if (!encoding_state_.isBufferDataEmpty()) { - header_end_stream = false; - } - // NP: we not overwrite state end_stream in doHeadersGo - encoding_state_.processHeader(header_end_stream); - auto status = doHeadersGo(encoding_state_, *local_headers_, header_end_stream); - continueStatusInternal(status); -} - -void Filter::continueStatusInternal(GolangStatus status) { - ProcessorState& state = getProcessorState(); +void Filter::continueStatusInternal(ProcessorState& state, GolangStatus status) { ASSERT(state.isThreadSafe()); - auto saved_state = state.state(); - - if (local_reply_waiting_go_) { - ENVOY_LOG(debug, - "other filter already trigger sendLocalReply, ignoring the continue status: {}, " - "state: {}, phase: {}", - int(status), state.stateStr(), state.phaseStr()); - - continueEncodeLocalReply(state); - return; - } + auto saved_state = state.filterState(); auto done = state.handleGolangStatus(status); if (done) { @@ -445,10 +414,14 @@ void Filter::continueStatusInternal(GolangStatus status) { } } + ENVOY_LOG(debug, + "after done handle golang status, status: {}, state: {}, done: {}, seen trailers: {}", + int(status), state.stateStr(), done, state.trailers != nullptr); + // TODO: state should also grow in this case // state == WaitingData && bufferData is empty && seen trailers - auto current_state = state.state(); + auto current_state = state.filterState(); if ((current_state == FilterState::WaitingData && (!state.isBufferDataEmpty() || state.getEndStream())) || (current_state == FilterState::WaitingAllData && state.isStreamEnd())) { @@ -461,10 +434,8 @@ void Filter::continueStatusInternal(GolangStatus status) { } } - Thread::ReleasableLockGuard lock(mutex_); - if (state.state() == FilterState::WaitingTrailer && trailers_ != nullptr) { - auto trailers = trailers_; - lock.release(); + if (state.filterState() == FilterState::WaitingTrailer && state.trailers != nullptr) { + auto trailers = state.trailers; auto done = doTrailerGo(state, *trailers); if (done) { state.continueProcessing(); @@ -473,22 +444,11 @@ void Filter::continueStatusInternal(GolangStatus status) { } void Filter::sendLocalReplyInternal( - Http::Code response_code, absl::string_view body_text, + ProcessorState& state, Http::Code response_code, absl::string_view body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details) { - ENVOY_LOG(debug, "sendLocalReply Internal, response code: {}", int(response_code)); - - ProcessorState& state = getProcessorState(); - - if (local_reply_waiting_go_) { - ENVOY_LOG(debug, - "other filter already invoked sendLocalReply or encodeHeaders, ignoring the local " - "reply from go, code: {}, body: {}, details: {}", - int(response_code), body_text, details); - - continueEncodeLocalReply(state); - return; - } + ENVOY_LOG(debug, "sendLocalReply Internal, state: {}, response code: {}", state.stateStr(), + int(response_code)); ENVOY_LOG(debug, "golang filter drain do data buffer before sendLocalReply"); state.doDataList.clearAll(); @@ -500,7 +460,7 @@ void Filter::sendLocalReplyInternal( } CAPIStatus -Filter::sendLocalReply(Http::Code response_code, std::string body_text, +Filter::sendLocalReply(ProcessorState& state, Http::Code response_code, std::string body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, std::string details) { // lock until this function return since it may running in a Go thread. @@ -509,7 +469,6 @@ Filter::sendLocalReply(Http::Code response_code, std::string body_text, ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; @@ -517,19 +476,19 @@ Filter::sendLocalReply(Http::Code response_code, std::string body_text, ENVOY_LOG(debug, "sendLocalReply, response code: {}", int(response_code)); auto weak_ptr = weak_from_this(); - state.getDispatcher().post( - [this, &state, weak_ptr, response_code, body_text, modify_headers, grpc_status, details] { - if (!weak_ptr.expired() && !hasDestroyed()) { - ASSERT(state.isThreadSafe()); - sendLocalReplyInternal(response_code, body_text, modify_headers, grpc_status, details); - } else { - ENVOY_LOG(debug, "golang filter has gone or destroyed in sendLocalReply"); - } - }); + state.getDispatcher().post([this, &state, weak_ptr, response_code, body_text, modify_headers, + grpc_status, details] { + if (!weak_ptr.expired() && !hasDestroyed()) { + ASSERT(state.isThreadSafe()); + sendLocalReplyInternal(state, response_code, body_text, modify_headers, grpc_status, details); + } else { + ENVOY_LOG(debug, "golang filter has gone or destroyed in sendLocalReply"); + } + }); return CAPIStatus::CAPIOK; }; -CAPIStatus Filter::sendPanicReply(absl::string_view details) { +CAPIStatus Filter::sendPanicReply(ProcessorState& state, absl::string_view details) { config_->stats().panic_error_.inc(); ENVOY_LOG(error, "[go_plugin_http][{}] {}", config_->pluginName(), absl::StrCat("filter paniced with error details: ", details)); @@ -537,24 +496,23 @@ CAPIStatus Filter::sendPanicReply(absl::string_view details) { // we don't want to leak the operational details of the service for security reasons. // Operators should be able to view the details via the log message above // and use the stats for o11y - return sendLocalReply(Http::Code::InternalServerError, "error happened in filter\r\n", nullptr, - Grpc::Status::WellKnownGrpcStatus::Ok, ""); + return sendLocalReply(state, Http::Code::InternalServerError, "error happened in filter\r\n", + nullptr, Grpc::Status::WellKnownGrpcStatus::Ok, ""); } -CAPIStatus Filter::continueStatus(GolangStatus status) { +CAPIStatus Filter::continueStatus(ProcessorState& state, GolangStatus status) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}, phase: {}", int(status), - state.stateStr(), state.phaseStr()); + ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}", int(status), + state.stateStr()); auto weak_ptr = weak_from_this(); // TODO: skip post event to dispatcher, and return continue in the caller, @@ -562,7 +520,7 @@ CAPIStatus Filter::continueStatus(GolangStatus status) { state.getDispatcher().post([this, &state, weak_ptr, status] { if (!weak_ptr.expired() && !hasDestroyed()) { ASSERT(state.isThreadSafe()); - continueStatusInternal(status); + continueStatusInternal(state, status); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in continueStatus event"); } @@ -570,28 +528,105 @@ CAPIStatus Filter::continueStatus(GolangStatus status) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getHeader(absl::string_view key, GoString* go_value) { +CAPIStatus Filter::addData(ProcessorState& state, absl::string_view data, bool is_streaming) { + if (state.filterState() == FilterState::ProcessingData) { + // Calling add{Decoded,Encoded}Data when processing data will mess up the buffer management + // in Golang filter. And more importantly, there is no need to use it to add data for now. + ENVOY_LOG(error, "golang filter calls addData when processing data is not supported, use " + "`BufferInstance.Append` instead."); + return CAPIStatus::CAPIInvalidPhase; + } + Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - auto m = state.isProcessingHeader() ? headers_ : trailers_; + + if (state.isThreadSafe()) { + Buffer::OwnedImpl buffer; + buffer.add(data); + state.addData(buffer, is_streaming); + return CAPIStatus::CAPIOK; + } + + auto weak_ptr = weak_from_this(); + auto data_str = std::string(data); + state.getDispatcher().post([this, weak_ptr, &state, data_str, is_streaming] { + if (!weak_ptr.expired() && !hasDestroyed()) { + Buffer::OwnedImpl buffer; + buffer.add(data_str); + state.addData(buffer, is_streaming); + } else { + ENVOY_LOG(debug, "golang filter has gone or destroyed in addData"); + } + }); + return CAPIStatus::CAPIYield; +} + +CAPIStatus Filter::injectData(ProcessorState& state, absl::string_view data) { + // lock until this function return since it may running in a Go thread. + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (!state.isProcessingInGo()) { + ENVOY_LOG(debug, "golang filter is not processing Go"); + return CAPIStatus::CAPINotInGo; + } + if (state.filterState() != FilterState::ProcessingData) { + ENVOY_LOG(error, "injectData is not supported when calling without processing data, use " + "`addData` instead."); + return CAPIStatus::CAPIInvalidPhase; + } + + if (state.isThreadSafe()) { + ENVOY_LOG(error, "injectData is not supported when calling inside the callback context"); + return CAPIStatus::CAPIInvalidScene; + } + + auto data_to_write = std::make_shared(data); + auto weak_ptr = weak_from_this(); + state.getDispatcher().post([this, &state, weak_ptr, data_to_write] { + if (!weak_ptr.expired() && !hasDestroyed()) { + ENVOY_LOG(debug, "golang filter inject data to filter chain, length: {}", + data_to_write->length()); + state.injectDataToFilterChain(*data_to_write.get(), false); + } else { + ENVOY_LOG(debug, "golang filter has gone or destroyed in injectData event"); + } + }); + + return CAPIStatus::CAPIOK; +} + +CAPIStatus Filter::getHeader(ProcessorState& state, absl::string_view key, uint64_t* value_data, + int* value_len) { + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (!state.isProcessingInGo()) { + ENVOY_LOG(debug, "golang filter is not processing Go"); + return CAPIStatus::CAPINotInGo; + } + auto m = state.headers; if (m == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } auto result = m->get(Http::LowerCaseString(key)); if (!result.empty()) { auto str = result[0]->value().getStringView(); - go_value->p = str.data(); - go_value->n = str.length(); + *value_data = reinterpret_cast(str.data()); + *value_len = str.length(); } return CAPIStatus::CAPIOK; } @@ -599,8 +634,9 @@ CAPIStatus Filter::getHeader(absl::string_view key, GoString* go_value) { void copyHeaderMapToGo(Http::HeaderMap& m, GoString* go_strs, char* go_buf) { auto i = 0; m.iterate([&i, &go_strs, &go_buf](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { - auto key = std::string(header.key().getStringView()); - auto value = std::string(header.value().getStringView()); + // It's safe to use StringView here, since we will copy them into Golang. + auto key = header.key().getStringView(); + auto value = header.value().getStringView(); auto len = key.length(); // go_strs is the heap memory of go, and the length is twice the number of headers. So range it @@ -615,48 +651,52 @@ void copyHeaderMapToGo(Http::HeaderMap& m, GoString* go_strs, char* go_buf) { len = value.length(); go_strs[i].n = len; - go_strs[i].p = go_buf; - memcpy(go_buf, value.data(), len); // NOLINT(safe-memcpy) - go_buf += len; + // go_buf may be an invalid pointer in Golang side when len is 0. + if (len > 0) { + go_strs[i].p = go_buf; + memcpy(go_buf, value.data(), len); // NOLINT(safe-memcpy) + go_buf += len; + } i++; return Http::HeaderMap::Iterate::Continue; }); } -CAPIStatus Filter::copyHeaders(GoString* go_strs, char* go_buf) { +CAPIStatus Filter::copyHeaders(ProcessorState& state, GoString* go_strs, char* go_buf) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (headers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto headers = state.headers; + if (headers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } - copyHeaderMapToGo(*headers_, go_strs, go_buf); + copyHeaderMapToGo(*headers, go_strs, go_buf); return CAPIStatus::CAPIOK; } // It won't take affect immidiately while it's invoked from a Go thread, instead, it will post a // callback to run in the envoy worker thread. -CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, headerAction act) { +CAPIStatus Filter::setHeader(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (headers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto headers = state.headers; + if (headers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } @@ -664,18 +704,16 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea // it's safe to write header in the safe thread. switch (act) { case HeaderAdd: - headers_->addCopy(Http::LowerCaseString(key), value); + headers->addCopy(Http::LowerCaseString(key), value); break; case HeaderSet: - headers_->setCopy(Http::LowerCaseString(key), value); + headers->setCopy(Http::LowerCaseString(key), value); break; default: RELEASE_ASSERT(false, absl::StrCat("unknown header action: ", act)); } - - onHeadersModified(); } else { // should deep copy the string_view before post to dipatcher callback. auto key_str = std::string(key); @@ -685,23 +723,20 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea // dispatch a callback to write header in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and writing // in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str, value_str, act] { + state.getDispatcher().post([this, headers, weak_ptr, key_str, value_str, act] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); switch (act) { case HeaderAdd: - headers_->addCopy(Http::LowerCaseString(key_str), value_str); + headers->addCopy(Http::LowerCaseString(key_str), value_str); break; case HeaderSet: - headers_->setCopy(Http::LowerCaseString(key_str), value_str); + headers->setCopy(Http::LowerCaseString(key_str), value_str); break; default: RELEASE_ASSERT(false, absl::StrCat("unknown header action: ", act)); } - - onHeadersModified(); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in setHeader"); } @@ -713,25 +748,24 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea // It won't take affect immidiately while it's invoked from a Go thread, instead, it will post a // callback to run in the envoy worker thread. -CAPIStatus Filter::removeHeader(absl::string_view key) { +CAPIStatus Filter::removeHeader(ProcessorState& state, absl::string_view key) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (headers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto headers = state.headers; + if (headers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (state.isThreadSafe()) { // it's safe to write header in the safe thread. - headers_->remove(Http::LowerCaseString(key)); - onHeadersModified(); + headers->remove(Http::LowerCaseString(key)); } else { // should deep copy the string_view before post to dipatcher callback. auto key_str = std::string(key); @@ -740,11 +774,9 @@ CAPIStatus Filter::removeHeader(absl::string_view key) { // dispatch a callback to write header in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and writing // in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str] { + state.getDispatcher().post([this, weak_ptr, headers, key_str] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); - headers_->remove(Http::LowerCaseString(key_str)); - onHeadersModified(); + headers->remove(Http::LowerCaseString(key_str)); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in removeHeader"); } @@ -753,20 +785,19 @@ CAPIStatus Filter::removeHeader(absl::string_view key) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::copyBuffer(Buffer::Instance* buffer, char* data) { +CAPIStatus Filter::copyBuffer(ProcessorState& state, Buffer::Instance* buffer, char* data) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } if (!state.doDataList.checkExisting(buffer)) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } for (const Buffer::RawSlice& slice : buffer->getRawSlices()) { @@ -778,21 +809,40 @@ CAPIStatus Filter::copyBuffer(Buffer::Instance* buffer, char* data) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::setBufferHelper(Buffer::Instance* buffer, absl::string_view& value, - bufferAction action) { +CAPIStatus Filter::drainBuffer(ProcessorState& state, Buffer::Instance* buffer, uint64_t length) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } if (!state.doDataList.checkExisting(buffer)) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); + return CAPIStatus::CAPIInvalidPhase; + } + + buffer->drain(length); + return CAPIStatus::CAPIOK; +} + +CAPIStatus Filter::setBufferHelper(ProcessorState& state, Buffer::Instance* buffer, + absl::string_view& value, bufferAction action) { + // lock until this function return since it may running in a Go thread. + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (!state.isProcessingInGo()) { + ENVOY_LOG(debug, "golang filter is not processing Go"); + return CAPIStatus::CAPINotInGo; + } + if (!state.doDataList.checkExisting(buffer)) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (action == bufferAction::Set) { @@ -806,48 +856,49 @@ CAPIStatus Filter::setBufferHelper(Buffer::Instance* buffer, absl::string_view& return CAPIStatus::CAPIOK; } -CAPIStatus Filter::copyTrailers(GoString* go_strs, char* go_buf) { +CAPIStatus Filter::copyTrailers(ProcessorState& state, GoString* go_strs, char* go_buf) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (trailers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto trailers = state.trailers; + if (trailers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } - copyHeaderMapToGo(*trailers_, go_strs, go_buf); + copyHeaderMapToGo(*trailers, go_strs, go_buf); return CAPIStatus::CAPIOK; } -CAPIStatus Filter::setTrailer(absl::string_view key, absl::string_view value, headerAction act) { +CAPIStatus Filter::setTrailer(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (trailers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto trailers = state.trailers; + if (trailers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (state.isThreadSafe()) { switch (act) { case HeaderAdd: - trailers_->addCopy(Http::LowerCaseString(key), value); + trailers->addCopy(Http::LowerCaseString(key), value); break; case HeaderSet: - trailers_->setCopy(Http::LowerCaseString(key), value); + trailers->setCopy(Http::LowerCaseString(key), value); break; default: @@ -862,16 +913,15 @@ CAPIStatus Filter::setTrailer(absl::string_view key, absl::string_view value, he // dispatch a callback to write trailer in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and // writing in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str, value_str, act] { + state.getDispatcher().post([this, trailers, weak_ptr, key_str, value_str, act] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); switch (act) { case HeaderAdd: - trailers_->addCopy(Http::LowerCaseString(key_str), value_str); + trailers->addCopy(Http::LowerCaseString(key_str), value_str); break; case HeaderSet: - trailers_->setCopy(Http::LowerCaseString(key_str), value_str); + trailers->setCopy(Http::LowerCaseString(key_str), value_str); break; default: @@ -885,23 +935,23 @@ CAPIStatus Filter::setTrailer(absl::string_view key, absl::string_view value, he return CAPIStatus::CAPIOK; } -CAPIStatus Filter::removeTrailer(absl::string_view key) { +CAPIStatus Filter::removeTrailer(ProcessorState& state, absl::string_view key) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (trailers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto trailers = state.trailers; + if (trailers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (state.isThreadSafe()) { - trailers_->remove(Http::LowerCaseString(key)); + trailers->remove(Http::LowerCaseString(key)); } else { // should deep copy the string_view before post to dipatcher callback. auto key_str = std::string(key); @@ -910,10 +960,9 @@ CAPIStatus Filter::removeTrailer(absl::string_view key) { // dispatch a callback to write trailer in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and writing // in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str] { + state.getDispatcher().post([this, trailers, weak_ptr, key_str] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); - trailers_->remove(Http::LowerCaseString(key_str)); + trailers->remove(Http::LowerCaseString(key_str)); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in removeTrailer"); } @@ -922,6 +971,38 @@ CAPIStatus Filter::removeTrailer(absl::string_view key) { return CAPIStatus::CAPIOK; } +CAPIStatus Filter::clearRouteCache(bool refresh) { + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (isThreadSafe()) { + clearRouteCacheInternal(refresh); + } else { + ENVOY_LOG(debug, "golang filter posting clear route cache callback"); + auto weak_ptr = weak_from_this(); + getDispatcher().post([this, weak_ptr, refresh] { + if (!weak_ptr.expired() && !hasDestroyed()) { + clearRouteCacheInternal(refresh); + } else { + ENVOY_LOG(info, "golang filter has gone or destroyed in clearRouteCache"); + } + }); + } + return CAPIStatus::CAPIOK; +} + +void Filter::clearRouteCacheInternal(bool refresh) { + ENVOY_LOG(debug, "golang filter clearing route cache, refresh: {}", refresh); + decoding_state_.getFilterCallbacks()->downstreamCallbacks()->clearRouteCache(); + if (refresh) { + // When the route cache is clear, the next call to route() will refresh the cache and return the + // pointer to the latest matched route. We don't need the returned pointer. + decoding_state_.getFilterCallbacks()->route(); + } +} + CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); @@ -929,30 +1010,25 @@ CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } switch (static_cast(id)) { case EnvoyValue::Protocol: - if (!state.streamInfo().protocol().has_value()) { + if (!streamInfo().protocol().has_value()) { return CAPIStatus::CAPIValueNotFound; } - *value = static_cast(state.streamInfo().protocol().value()); + *value = static_cast(streamInfo().protocol().value()); break; case EnvoyValue::ResponseCode: - if (!state.streamInfo().responseCode().has_value()) { + if (!streamInfo().responseCode().has_value()) { return CAPIStatus::CAPIValueNotFound; } - *value = state.streamInfo().responseCode().value(); + *value = streamInfo().responseCode().value(); break; case EnvoyValue::AttemptCount: - if (!state.streamInfo().attemptCount().has_value()) { + if (!streamInfo().attemptCount().has_value()) { return CAPIStatus::CAPIValueNotFound; } - *value = state.streamInfo().attemptCount().value(); + *value = streamInfo().attemptCount().value(); break; default: RELEASE_ASSERT(false, absl::StrCat("invalid integer value id: ", id)); @@ -960,99 +1036,87 @@ CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getStringValue(int id, GoString* value_str) { +CAPIStatus Filter::getStringValue(int id, uint64_t* value_data, int* value_len) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } // refer the string to req_->strValue, not deep clone, make sure it won't be freed while reading // it on the Go side. switch (static_cast(id)) { case EnvoyValue::RouteName: - req_->strValue = state.streamInfo().getRouteName(); + req_->strValue = streamInfo().getRouteName(); break; case EnvoyValue::FilterChainName: - req_->strValue = state.streamInfo().filterChainName(); + req_->strValue = streamInfo().filterChainName(); break; case EnvoyValue::ResponseCodeDetails: - if (!state.streamInfo().responseCodeDetails().has_value()) { + if (!streamInfo().responseCodeDetails().has_value()) { return CAPIStatus::CAPIValueNotFound; } - req_->strValue = state.streamInfo().responseCodeDetails().value(); + req_->strValue = streamInfo().responseCodeDetails().value(); break; case EnvoyValue::DownstreamLocalAddress: - req_->strValue = state.streamInfo().downstreamAddressProvider().localAddress()->asString(); + req_->strValue = streamInfo().downstreamAddressProvider().localAddress()->asString(); break; case EnvoyValue::DownstreamRemoteAddress: - req_->strValue = state.streamInfo().downstreamAddressProvider().remoteAddress()->asString(); + req_->strValue = streamInfo().downstreamAddressProvider().remoteAddress()->asString(); break; case EnvoyValue::UpstreamLocalAddress: - if (state.streamInfo().upstreamInfo() && - state.streamInfo().upstreamInfo()->upstreamLocalAddress()) { - req_->strValue = state.streamInfo().upstreamInfo()->upstreamLocalAddress()->asString(); + if (streamInfo().upstreamInfo() && streamInfo().upstreamInfo()->upstreamLocalAddress()) { + req_->strValue = streamInfo().upstreamInfo()->upstreamLocalAddress()->asString(); } else { return CAPIStatus::CAPIValueNotFound; } break; case EnvoyValue::UpstreamRemoteAddress: - if (state.streamInfo().upstreamInfo() && - state.streamInfo().upstreamInfo()->upstreamRemoteAddress()) { - req_->strValue = state.streamInfo().upstreamInfo()->upstreamRemoteAddress()->asString(); + if (streamInfo().upstreamInfo() && streamInfo().upstreamInfo()->upstreamRemoteAddress()) { + req_->strValue = streamInfo().upstreamInfo()->upstreamRemoteAddress()->asString(); } else { return CAPIStatus::CAPIValueNotFound; } break; case EnvoyValue::UpstreamClusterName: - if (state.streamInfo().upstreamClusterInfo().has_value() && - state.streamInfo().upstreamClusterInfo().value()) { - req_->strValue = state.streamInfo().upstreamClusterInfo().value()->name(); + if (streamInfo().upstreamClusterInfo().has_value() && + streamInfo().upstreamClusterInfo().value()) { + req_->strValue = streamInfo().upstreamClusterInfo().value()->name(); } else { return CAPIStatus::CAPIValueNotFound; } break; case EnvoyValue::VirtualClusterName: - if (!state.streamInfo().virtualClusterName().has_value()) { + if (!streamInfo().virtualClusterName().has_value()) { return CAPIStatus::CAPIValueNotFound; } - req_->strValue = state.streamInfo().virtualClusterName().value(); + req_->strValue = streamInfo().virtualClusterName().value(); break; default: RELEASE_ASSERT(false, absl::StrCat("invalid string value id: ", id)); } - value_str->p = req_->strValue.data(); - value_str->n = req_->strValue.length(); + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, GoSlice* buf_slice) { +CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, uint64_t* buf_data, + int* buf_len) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (!state.isThreadSafe()) { + if (!isThreadSafe()) { auto weak_ptr = weak_from_this(); ENVOY_LOG(debug, "golang filter getDynamicMetadata posting request to dispatcher"); - state.getDispatcher().post([this, &state, weak_ptr, filter_name, buf_slice] { + getDispatcher().post([this, weak_ptr, filter_name, buf_data, buf_len] { ENVOY_LOG(debug, "golang filter getDynamicMetadata request in worker thread"); if (!weak_ptr.expired() && !hasDestroyed()) { - populateSliceWithMetadata(state, filter_name, buf_slice); + populateSliceWithMetadata(filter_name, buf_data, buf_len); dynamic_lib_->envoyGoRequestSemaDec(req_); } else { ENVOY_LOG(info, "golang filter has gone or destroyed in getDynamicMetadata"); @@ -1061,21 +1125,20 @@ CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, GoSlice* b return CAPIStatus::CAPIYield; } else { ENVOY_LOG(debug, "golang filter getDynamicMetadata replying directly"); - populateSliceWithMetadata(state, filter_name, buf_slice); + populateSliceWithMetadata(filter_name, buf_data, buf_len); } return CAPIStatus::CAPIOK; } -void Filter::populateSliceWithMetadata(ProcessorState& state, const std::string& filter_name, - GoSlice* buf_slice) { - const auto& metadata = state.streamInfo().dynamicMetadata().filter_metadata(); +void Filter::populateSliceWithMetadata(const std::string& filter_name, uint64_t* buf_data, + int* buf_len) { + const auto& metadata = streamInfo().dynamicMetadata().filter_metadata(); const auto filter_it = metadata.find(filter_name); if (filter_it != metadata.end()) { filter_it->second.SerializeToString(&req_->strValue); - buf_slice->data = req_->strValue.data(); - buf_slice->len = req_->strValue.length(); - buf_slice->cap = req_->strValue.length(); + *buf_data = reinterpret_cast(req_->strValue.data()); + *buf_len = req_->strValue.length(); } } @@ -1088,21 +1151,15 @@ CAPIStatus Filter::setDynamicMetadata(std::string filter_name, std::string key, return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (!state.isThreadSafe()) { + if (!isThreadSafe()) { auto weak_ptr = weak_from_this(); // Since go only waits for the CAPI return code we need to create a deep copy // of the buffer slice and pass that to the dispatcher. auto buff_copy = std::string(buf); - state.getDispatcher().post([this, &state, weak_ptr, filter_name, key, buff_copy] { + getDispatcher().post([this, weak_ptr, filter_name, key, buff_copy] { if (!weak_ptr.expired() && !hasDestroyed()) { - ASSERT(state.isThreadSafe()); - setDynamicMetadataInternal(state, filter_name, key, buff_copy); + ASSERT(isThreadSafe()); + setDynamicMetadataInternal(filter_name, key, buff_copy); } else { ENVOY_LOG(info, "golang filter has gone or destroyed in setDynamicMetadata"); } @@ -1111,19 +1168,19 @@ CAPIStatus Filter::setDynamicMetadata(std::string filter_name, std::string key, } // it's safe to do it here since we are in the safe envoy worker thread now. - setDynamicMetadataInternal(state, filter_name, key, buf); + setDynamicMetadataInternal(filter_name, key, buf); return CAPIStatus::CAPIOK; } -void Filter::setDynamicMetadataInternal(ProcessorState& state, std::string filter_name, - std::string key, const absl::string_view& buf) { +void Filter::setDynamicMetadataInternal(std::string filter_name, std::string key, + const absl::string_view& buf) { ProtobufWkt::Struct value; ProtobufWkt::Value v; v.ParseFromArray(buf.data(), buf.length()); (*value.mutable_fields())[key] = v; - state.streamInfo().setDynamicMetadata(filter_name, value); + streamInfo().setDynamicMetadata(filter_name, value); } CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view value, @@ -1135,27 +1192,20 @@ CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (state.isThreadSafe()) { - state.streamInfo().filterState()->setData( - key, std::make_shared(value), + if (isThreadSafe()) { + streamInfo().filterState()->setData( + key, std::make_shared(value), static_cast(state_type), static_cast(life_span), static_cast(stream_sharing)); } else { auto key_str = std::string(key); - auto filter_state = std::make_shared(value); + auto filter_state = std::make_shared(value); auto weak_ptr = weak_from_this(); - state.getDispatcher().post( - [this, &state, weak_ptr, key_str, filter_state, state_type, life_span, stream_sharing] { + getDispatcher().post( + [this, weak_ptr, key_str, filter_state, state_type, life_span, stream_sharing] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); - state.streamInfo().filterState()->setData( + streamInfo().filterState()->setData( key_str, filter_state, static_cast(state_type), static_cast(life_span), static_cast(stream_sharing)); @@ -1167,7 +1217,8 @@ CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getStringFilterState(absl::string_view key, GoString* value_str) { +CAPIStatus Filter::getStringFilterState(absl::string_view key, uint64_t* value_data, + int* value_len) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { @@ -1175,31 +1226,24 @@ CAPIStatus Filter::getStringFilterState(absl::string_view key, GoString* value_s return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (state.isThreadSafe()) { - auto go_filter_state = - state.streamInfo().filterState()->getDataReadOnly(key); + if (isThreadSafe()) { + auto go_filter_state = streamInfo().filterState()->getDataReadOnly(key); if (go_filter_state) { - req_->strValue = go_filter_state->value(); - value_str->p = req_->strValue.data(); - value_str->n = req_->strValue.length(); + req_->strValue = go_filter_state->asString(); + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); } } else { auto key_str = std::string(key); auto weak_ptr = weak_from_this(); - state.getDispatcher().post([this, &state, weak_ptr, key_str, value_str] { + getDispatcher().post([this, weak_ptr, key_str, value_data, value_len] { if (!weak_ptr.expired() && !hasDestroyed()) { auto go_filter_state = - state.streamInfo().filterState()->getDataReadOnly(key_str); + streamInfo().filterState()->getDataReadOnly(key_str); if (go_filter_state) { - req_->strValue = go_filter_state->value(); - value_str->p = req_->strValue.data(); - value_str->n = req_->strValue.length(); + req_->strValue = go_filter_state->asString(); + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); } dynamic_lib_->envoyGoRequestSemaDec(req_); } else { @@ -1211,10 +1255,210 @@ CAPIStatus Filter::getStringFilterState(absl::string_view key, GoString* value_s return CAPIStatus::CAPIOK; } +CAPIStatus Filter::getStringProperty(absl::string_view path, uint64_t* value_data, int* value_len, + int* rc) { + // lock until this function return since it may running in a Go thread. + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + + // to access the headers_ and its friends we need to hold the lock + activation_request_headers_ = request_headers_; + + if (isThreadSafe()) { + return getStringPropertyCommon(path, value_data, value_len); + } + + auto weak_ptr = weak_from_this(); + getDispatcher().post([this, weak_ptr, path, value_data, value_len, rc] { + if (!weak_ptr.expired() && !hasDestroyed()) { + *rc = getStringPropertyCommon(path, value_data, value_len); + dynamic_lib_->envoyGoRequestSemaDec(req_); + } else { + ENVOY_LOG(info, "golang filter has gone or destroyed in getStringProperty"); + } + }); + return CAPIStatus::CAPIYield; +} + +CAPIStatus Filter::getStringPropertyCommon(absl::string_view path, uint64_t* value_data, + int* value_len) { + activation_info_ = &streamInfo(); + CAPIStatus status = getStringPropertyInternal(path, &req_->strValue); + if (status == CAPIStatus::CAPIOK) { + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); + } + return status; +} + +absl::optional Filter::findValue(absl::string_view name, + Protobuf::Arena* arena) { + // as we already support getting/setting FilterState, we don't need to implement + // getProperty with non-attribute name & setProperty which actually work on FilterState + return StreamActivation::FindValue(name, arena); + // we don't need to call resetActivation as activation_xx_ is overridden when we get property +} + +CAPIStatus Filter::getStringPropertyInternal(absl::string_view path, std::string* result) { + using google::api::expr::runtime::CelValue; + + bool first = true; + CelValue value; + Protobuf::Arena arena; + + size_t start = 0; + while (true) { + if (start >= path.size()) { + break; + } + + size_t end = path.find('.', start); + if (end == absl::string_view::npos) { + end = start + path.size(); + } + auto part = path.substr(start, end - start); + start = end + 1; + + if (first) { + // top-level identifier + first = false; + auto top_value = findValue(toAbslStringView(part), &arena); + if (!top_value.has_value()) { + return CAPIStatus::CAPIValueNotFound; + } + value = top_value.value(); + } else if (value.IsMap()) { + auto& map = *value.MapOrDie(); + auto field = map[CelValue::CreateStringView(toAbslStringView(part))]; + if (!field.has_value()) { + return CAPIStatus::CAPIValueNotFound; + } + value = field.value(); + } else if (value.IsMessage()) { + auto msg = value.MessageOrDie(); + if (msg == nullptr) { + return CAPIStatus::CAPIValueNotFound; + } + const Protobuf::Descriptor* desc = msg->GetDescriptor(); + const Protobuf::FieldDescriptor* field_desc = desc->FindFieldByName(std::string(part)); + if (field_desc == nullptr) { + return CAPIStatus::CAPIValueNotFound; + } + if (field_desc->is_map()) { + value = CelValue::CreateMap( + Protobuf::Arena::Create( + &arena, msg, field_desc, &arena)); + } else if (field_desc->is_repeated()) { + value = CelValue::CreateList( + Protobuf::Arena::Create( + &arena, msg, field_desc, &arena)); + } else { + auto status = + google::api::expr::runtime::CreateValueFromSingleField(msg, field_desc, &arena, &value); + if (!status.ok()) { + return CAPIStatus::CAPIInternalFailure; + } + } + } else if (value.IsList()) { + auto& list = *value.ListOrDie(); + int idx = 0; + if (!absl::SimpleAtoi(toAbslStringView(part), &idx)) { + return CAPIStatus::CAPIValueNotFound; + } + if (idx < 0 || idx >= list.size()) { + return CAPIStatus::CAPIValueNotFound; + } + value = list[idx]; + } else { + return CAPIStatus::CAPIValueNotFound; + } + } + + return serializeStringValue(value, result); +} + +CAPIStatus Filter::serializeStringValue(Filters::Common::Expr::CelValue value, + std::string* result) { + using Filters::Common::Expr::CelValue; + const Protobuf::Message* out_message; + + switch (value.type()) { + case CelValue::Type::kString: + result->assign(value.StringOrDie().value().data(), value.StringOrDie().value().size()); + return CAPIStatus::CAPIOK; + case CelValue::Type::kBytes: + result->assign(value.BytesOrDie().value().data(), value.BytesOrDie().value().size()); + return CAPIStatus::CAPIOK; + case CelValue::Type::kInt64: + result->assign(absl::StrCat(value.Int64OrDie())); + return CAPIStatus::CAPIOK; + case CelValue::Type::kUint64: + result->assign(absl::StrCat(value.Uint64OrDie())); + return CAPIStatus::CAPIOK; + case CelValue::Type::kDouble: + result->assign(absl::StrCat(value.DoubleOrDie())); + return CAPIStatus::CAPIOK; + case CelValue::Type::kBool: + result->assign(value.BoolOrDie() ? "true" : "false"); + return CAPIStatus::CAPIOK; + case CelValue::Type::kDuration: + result->assign(absl::FormatDuration(value.DurationOrDie())); + return CAPIStatus::CAPIOK; + case CelValue::Type::kTimestamp: + result->assign(absl::FormatTime(value.TimestampOrDie(), absl::UTCTimeZone())); + return CAPIStatus::CAPIOK; + case CelValue::Type::kMessage: + out_message = value.MessageOrDie(); + result->clear(); + if (!out_message || out_message->SerializeToString(result)) { + return CAPIStatus::CAPIOK; + } + return CAPIStatus::CAPISerializationFailure; + case CelValue::Type::kMap: { + // so far, only headers/trailers/filter state are in Map format, and we already have API to + // fetch them + ENVOY_LOG(error, "map type property result is not supported yet"); + return CAPIStatus::CAPISerializationFailure; + } + case CelValue::Type::kList: { + ENVOY_LOG(error, "list type property result is not supported yet"); + return CAPIStatus::CAPISerializationFailure; + } + default: + return CAPIStatus::CAPISerializationFailure; + } +} + +bool Filter::initRequest() { + if (req_->configId == 0) { + req_->setWeakFilter(weak_from_this()); + req_->configId = getMergedConfigId(); + return true; + } + return false; +} + +void Filter::deferredDeleteRequest(HttpRequestInternal* req) { + ASSERT(req == req_, "invalid request pointer"); + auto& dispatcher = getDispatcher(); + if (dispatcher.isThreadSafe()) { + auto r = std::make_unique(req); + dispatcher.deferredDelete(std::move(r)); + } else { + dispatcher.post([&dispatcher, req] { + auto r = std::make_unique(req); + dispatcher.deferredDelete(std::move(r)); + }); + } +} + /* ConfigId */ -uint64_t Filter::getMergedConfigId(ProcessorState& state) { - Http::StreamFilterCallbacks* callbacks = state.getFilterCallbacks(); +uint64_t Filter::getMergedConfigId() { + Http::StreamFilterCallbacks* callbacks = decoding_state_.getFilterCallbacks(); // get all of the per route config std::list route_config_list; @@ -1242,29 +1486,139 @@ FilterConfig::FilterConfig( Server::Configuration::FactoryContext& context) : plugin_name_(proto_config.plugin_name()), so_id_(proto_config.library_id()), so_path_(proto_config.library_path()), plugin_config_(proto_config.plugin_config()), - stats_(GolangFilterStats::generateStats(stats_prefix, context.scope())), dso_lib_(dso_lib) { - ENVOY_LOG(debug, "initializing golang filter config"); + concurrency_(context.getServerFactoryContext().options().concurrency()), + stats_(GolangFilterStats::generateStats(stats_prefix, context.scope())), dso_lib_(dso_lib), + metric_store_(std::make_shared(context.scope().createScope(""))){}; +void FilterConfig::newGoPluginConfig() { + ENVOY_LOG(debug, "initializing golang filter config"); std::string buf; auto res = plugin_config_.SerializeToString(&buf); ASSERT(res, "SerializeToString should always successful"); auto buf_ptr = reinterpret_cast(buf.data()); auto name_ptr = reinterpret_cast(plugin_name_.data()); - config_id_ = dso_lib_->envoyGoFilterNewHttpPluginConfig(name_ptr, plugin_name_.length(), buf_ptr, - buf.length()); + + config_ = new httpConfigInternal(weak_from_this()); + config_->plugin_name_ptr = name_ptr; + config_->plugin_name_len = plugin_name_.length(); + config_->config_ptr = buf_ptr; + config_->config_len = buf.length(); + config_->is_route_config = 0; + config_->concurrency = concurrency_; + + config_id_ = dso_lib_->envoyGoFilterNewHttpPluginConfig(config_); + if (config_id_ == 0) { - throw EnvoyException(fmt::format("golang filter failed to parse plugin config: {} {}", - proto_config.library_id(), proto_config.library_path())); + throw EnvoyException( + fmt::format("golang filter failed to parse plugin config: {} {}", so_id_, so_path_)); } + ENVOY_LOG(debug, "golang filter new plugin config, id: {}", config_id_); -}; +} FilterConfig::~FilterConfig() { if (config_id_ > 0) { - dso_lib_->envoyGoFilterDestroyHttpPluginConfig(config_id_); + dso_lib_->envoyGoFilterDestroyHttpPluginConfig(config_id_, 0); } } +CAPIStatus FilterConfig::defineMetric(uint32_t metric_type, absl::string_view name, + uint32_t* metric_id) { + Thread::LockGuard lock(mutex_); + if (metric_type > static_cast(MetricType::Max)) { + return CAPIStatus::CAPIValueNotFound; + } + + auto type = static_cast(metric_type); + + Stats::StatNameManagedStorage storage(name, metric_store_->scope_->symbolTable()); + Stats::StatName stat_name = storage.statName(); + if (type == MetricType::Counter) { + auto id = metric_store_->nextCounterMetricId(); + auto c = &metric_store_->scope_->counterFromStatName(stat_name); + metric_store_->counters_.emplace(id, c); + *metric_id = id; + } else if (type == MetricType::Gauge) { + auto id = metric_store_->nextGaugeMetricId(); + auto g = + &metric_store_->scope_->gaugeFromStatName(stat_name, Stats::Gauge::ImportMode::Accumulate); + metric_store_->gauges_.emplace(id, g); + *metric_id = id; + } else { // (type == MetricType::Histogram) + ASSERT(type == MetricType::Histogram); + auto id = metric_store_->nextHistogramMetricId(); + auto h = &metric_store_->scope_->histogramFromStatName(stat_name, + Stats::Histogram::Unit::Unspecified); + metric_store_->histograms_.emplace(id, h); + *metric_id = id; + } + + return CAPIStatus::CAPIOK; +} + +CAPIStatus FilterConfig::incrementMetric(uint32_t metric_id, int64_t offset) { + Thread::LockGuard lock(mutex_); + auto type = static_cast(metric_id & MetricStore::kMetricTypeMask); + if (type == MetricType::Counter) { + auto it = metric_store_->counters_.find(metric_id); + if (it != metric_store_->counters_.end()) { + if (offset > 0) { + it->second->add(offset); + } + } + } else if (type == MetricType::Gauge) { + auto it = metric_store_->gauges_.find(metric_id); + if (it != metric_store_->gauges_.end()) { + if (offset > 0) { + it->second->add(offset); + } else { + it->second->sub(-offset); + } + } + } + return CAPIStatus::CAPIOK; +} + +CAPIStatus FilterConfig::getMetric(uint32_t metric_id, uint64_t* value) { + Thread::LockGuard lock(mutex_); + auto type = static_cast(metric_id & MetricStore::kMetricTypeMask); + if (type == MetricType::Counter) { + auto it = metric_store_->counters_.find(metric_id); + if (it != metric_store_->counters_.end()) { + *value = it->second->value(); + } + } else if (type == MetricType::Gauge) { + auto it = metric_store_->gauges_.find(metric_id); + if (it != metric_store_->gauges_.end()) { + *value = it->second->value(); + } + } + return CAPIStatus::CAPIOK; +} + +CAPIStatus FilterConfig::recordMetric(uint32_t metric_id, uint64_t value) { + Thread::LockGuard lock(mutex_); + auto type = static_cast(metric_id & MetricStore::kMetricTypeMask); + if (type == MetricType::Counter) { + auto it = metric_store_->counters_.find(metric_id); + if (it != metric_store_->counters_.end()) { + it->second->add(value); + } + } else if (type == MetricType::Gauge) { + auto it = metric_store_->gauges_.find(metric_id); + if (it != metric_store_->gauges_.end()) { + it->second->set(value); + } + } else { + ASSERT(type == MetricType::Histogram); + auto it = metric_store_->histograms_.find(metric_id); + if (it != metric_store_->histograms_.end()) { + it->second->recordValue(value); + } + } + return CAPIStatus::CAPIOK; +} + uint64_t FilterConfig::getConfigId() { return config_id_; } FilterConfigPerRoute::FilterConfigPerRoute( @@ -1276,7 +1630,7 @@ FilterConfigPerRoute::FilterConfigPerRoute( for (const auto& it : config.plugins_config()) { auto plugin_name = it.first; auto route_plugin = it.second; - RoutePluginConfigPtr conf(new RoutePluginConfig(plugin_name, route_plugin)); + RoutePluginConfigPtr conf = std::make_shared(plugin_name, route_plugin); ENVOY_LOG(debug, "per route golang filter config, type_url: {}", route_plugin.config().type_url()); plugins_config_.insert({plugin_name, std::move(conf)}); @@ -1321,10 +1675,10 @@ RoutePluginConfig::RoutePluginConfig( RoutePluginConfig::~RoutePluginConfig() { absl::WriterMutexLock lock(&mutex_); if (config_id_ > 0) { - dso_lib_->envoyGoFilterDestroyHttpPluginConfig(config_id_); + dso_lib_->envoyGoFilterDestroyHttpPluginConfig(config_id_, 0); } - if (merged_config_id_ > 0) { - dso_lib_->envoyGoFilterDestroyHttpPluginConfig(merged_config_id_); + if (merged_config_id_ > 0 && config_id_ != merged_config_id_) { + dso_lib_->envoyGoFilterDestroyHttpPluginConfig(merged_config_id_, 0); } } @@ -1339,8 +1693,13 @@ uint64_t RoutePluginConfig::getConfigId() { ASSERT(res, "SerializeToString is always successful"); auto buf_ptr = reinterpret_cast(buf.data()); auto name_ptr = reinterpret_cast(plugin_name_.data()); - return dso_lib_->envoyGoFilterNewHttpPluginConfig(name_ptr, plugin_name_.length(), buf_ptr, - buf.length()); + + config_.plugin_name_ptr = name_ptr; + config_.plugin_name_len = plugin_name_.length(); + config_.config_ptr = buf_ptr; + config_.config_len = buf.length(); + config_.is_route_config = 1; + return dso_lib_->envoyGoFilterNewHttpPluginConfig(&config_); }; uint64_t RoutePluginConfig::getMergedConfigId(uint64_t parent_id) { @@ -1357,7 +1716,13 @@ uint64_t RoutePluginConfig::getMergedConfigId(uint64_t parent_id) { return merged_config_id_; } // upper level config changed, merged_config_id_ is outdated. - dso_lib_->envoyGoFilterDestroyHttpPluginConfig(merged_config_id_); + // there is a concurrency race: + // 1. when A envoy worker thread is using the cached merged_config_id_ and it will call into Go + // after some time. + // 2. while B envoy worker thread may update the merged_config_id_ in getMergedConfigId, that + // will delete the id. + // so, we delay deleting the id in the Go side. + dso_lib_->envoyGoFilterDestroyHttpPluginConfig(merged_config_id_, 1); } if (config_id_ == 0) { @@ -1376,47 +1741,6 @@ uint64_t RoutePluginConfig::getMergedConfigId(uint64_t parent_id) { return merged_config_id_; }; -/* ProcessorState */ -ProcessorState& Filter::getProcessorState() { - return enter_encoding_ ? dynamic_cast(encoding_state_) - : dynamic_cast(decoding_state_); -}; - -/* FilterLogger */ -void FilterLogger::log(uint32_t level, absl::string_view message) const { - switch (static_cast(level)) { - case spdlog::level::trace: - ENVOY_LOG(trace, "{}", message); - return; - case spdlog::level::debug: - ENVOY_LOG(debug, "{}", message); - return; - case spdlog::level::info: - ENVOY_LOG(info, "{}", message); - return; - case spdlog::level::warn: - ENVOY_LOG(warn, "{}", message); - return; - case spdlog::level::err: - ENVOY_LOG(error, "{}", message); - return; - case spdlog::level::critical: - ENVOY_LOG(critical, "{}", message); - return; - case spdlog::level::off: - // means not logging - return; - case spdlog::level::n_levels: - PANIC("not implemented"); - } - - ENVOY_LOG(error, "undefined log level {} with message '{}'", level, message); - - PANIC_DUE_TO_CORRUPT_ENUM; -} - -uint32_t FilterLogger::level() const { return static_cast(ENVOY_LOGGER().level()); } - } // namespace Golang } // namespace HttpFilters } // namespace Extensions diff --git a/contrib/golang/filters/http/source/golang_filter.h b/contrib/golang/filters/http/source/golang_filter.h index a965c53fd5256..44f406c4f03f6 100644 --- a/contrib/golang/filters/http/source/golang_filter.h +++ b/contrib/golang/filters/http/source/golang_filter.h @@ -11,9 +11,9 @@ #include "source/common/common/thread.h" #include "source/common/grpc/context_impl.h" #include "source/common/http/utility.h" +#include "source/extensions/filters/common/expr/evaluator.h" #include "contrib/envoy/extensions/filters/http/golang/v3alpha/golang.pb.h" -#include "contrib/golang/common/dso/dso.h" #include "contrib/golang/filters/http/source/processor_state.h" #include "contrib/golang/filters/http/source/stats.h" @@ -22,10 +22,45 @@ namespace Extensions { namespace HttpFilters { namespace Golang { +enum class MetricType { + Counter = 0, + Gauge = 1, + Histogram = 2, + Max = 2, +}; + +class MetricStore { +public: + MetricStore(Stats::ScopeSharedPtr scope) : scope_(scope) {} + + static constexpr uint32_t kMetricTypeMask = 0x3; + static constexpr uint32_t kMetricIdIncrement = 0x4; + + uint32_t nextCounterMetricId() { return next_counter_metric_id_ += kMetricIdIncrement; } + uint32_t nextGaugeMetricId() { return next_gauge_metric_id_ += kMetricIdIncrement; } + uint32_t nextHistogramMetricId() { return next_histogram_metric_id_ += kMetricIdIncrement; } + + absl::flat_hash_map counters_; + absl::flat_hash_map gauges_; + absl::flat_hash_map histograms_; + + Stats::ScopeSharedPtr scope_; + +private: + uint32_t next_counter_metric_id_ = static_cast(MetricType::Counter); + uint32_t next_gauge_metric_id_ = static_cast(MetricType::Gauge); + uint32_t next_histogram_metric_id_ = static_cast(MetricType::Histogram); +}; + +using MetricStoreSharedPtr = std::shared_ptr; + +struct httpConfigInternal; + /** * Configuration for the HTTP golang extension filter. */ -class FilterConfig : Logger::Loggable { +class FilterConfig : public std::enable_shared_from_this, + Logger::Loggable { public: FilterConfig(const envoy::extensions::filters::http::golang::v3alpha::Config& proto_config, Dso::HttpFilterDsoPtr dso_lib, const std::string& stats_prefix, @@ -38,21 +73,34 @@ class FilterConfig : Logger::Loggable { uint64_t getConfigId(); GolangFilterStats& stats() { return stats_; } + void newGoPluginConfig(); + CAPIStatus defineMetric(uint32_t metric_type, absl::string_view name, uint32_t* metric_id); + CAPIStatus incrementMetric(uint32_t metric_id, int64_t offset); + CAPIStatus getMetric(uint32_t metric_id, uint64_t* value); + CAPIStatus recordMetric(uint32_t metric_id, uint64_t value); + private: const std::string plugin_name_; const std::string so_id_; const std::string so_path_; const ProtobufWkt::Any plugin_config_; + uint32_t concurrency_; GolangFilterStats stats_; Dso::HttpFilterDsoPtr dso_lib_; uint64_t config_id_{0}; + // TODO(StarryVae): use rwlock. + Thread::MutexBasicLockable mutex_{}; + MetricStoreSharedPtr metric_store_ ABSL_GUARDED_BY(mutex_); + // filter level config is created in C++ side, and freed by Golang GC finalizer. + httpConfigInternal* config_{nullptr}; }; using FilterConfigSharedPtr = std::shared_ptr; -class RoutePluginConfig : Logger::Loggable { +class RoutePluginConfig : public std::enable_shared_from_this, + Logger::Loggable { public: RoutePluginConfig(const std::string plugin_name, const envoy::extensions::filters::http::golang::v3alpha::RouterPlugin& config); @@ -71,6 +119,8 @@ class RoutePluginConfig : Logger::Loggable { uint64_t cached_parent_id_ ABSL_GUARDED_BY(mutex_){0}; absl::Mutex mutex_; + // route level config, no Golang GC finalizer. + httpConfig config_; }; using RoutePluginConfigPtr = std::shared_ptr; @@ -111,21 +161,69 @@ enum class EnvoyValue { VirtualClusterName, }; -struct httpRequestInternal; +class Filter; + +// Go code only touch the fields in httpRequest +class HttpRequestInternal : public httpRequest { +public: + HttpRequestInternal(Filter& filter) + : decoding_state_(filter, this), encoding_state_(filter, this) { + configId = 0; + } + + void setWeakFilter(std::weak_ptr f) { filter_ = f; } + std::weak_ptr weakFilter() { return filter_; } + + DecodingProcessorState& decodingState() { return decoding_state_; } + EncodingProcessorState& encodingState() { return encoding_state_; } + + // anchor a string temporarily, make sure it won't be freed before copied to Go. + std::string strValue; + +private: + std::weak_ptr filter_; + + // The state of the filter on both the encoding and decoding side. + DecodingProcessorState decoding_state_; + EncodingProcessorState encoding_state_; +}; + +// Wrapper HttpRequestInternal to DeferredDeletable. +// Since we want keep httpRequest at the top of the HttpRequestInternal, +// so, HttpRequestInternal can not inherit the virtual class DeferredDeletable. +class HttpRequestInternalWrapper : public Envoy::Event::DeferredDeletable { +public: + HttpRequestInternalWrapper(HttpRequestInternal* req) : req_(req) {} + ~HttpRequestInternalWrapper() override { delete req_; } + +private: + HttpRequestInternal* req_; +}; /** * See docs/configuration/http_filters/golang_extension_filter.rst */ class Filter : public Http::StreamFilter, public std::enable_shared_from_this, + public Filters::Common::Expr::StreamActivation, Logger::Loggable, public AccessLog::Instance { public: - explicit Filter(FilterConfigSharedPtr config, Dso::HttpFilterDsoPtr dynamic_lib) - : config_(config), dynamic_lib_(dynamic_lib), decoding_state_(*this), encoding_state_(*this) { + explicit Filter(FilterConfigSharedPtr config, Dso::HttpFilterDsoPtr dynamic_lib, + uint32_t worker_id) + : config_(config), dynamic_lib_(dynamic_lib), req_(new HttpRequestInternal(*this)), + decoding_state_(req_->decodingState()), encoding_state_(req_->encodingState()) { + // req is used by go, so need to use raw memory and then it is safe to release at the gc + // finalize phase of the go object. + req_->plugin_name.data = config_->pluginName().data(); + req_->plugin_name.len = config_->pluginName().length(); + req_->worker_id = worker_id; + ENVOY_LOG(debug, "initilizing Golang Filter, decode state: {}, encode state: {}", + decoding_state_.stateStr(), encoding_state_.stateStr()); } // Http::StreamFilterBase + void onStreamComplete() override; void onDestroy() ABSL_LOCKS_EXCLUDED(mutex_) override; Http::LocalErrorStatus onLocalReply(const LocalReplyData&) override; @@ -136,6 +234,8 @@ class Filter : public Http::StreamFilter, Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap&) override; void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override { decoding_state_.setDecoderFilterCallbacks(callbacks); + // We initilizes dispatcher as soon as it is available. + dispatcher_ = &callbacks.dispatcher(); } // Http::StreamEncoderFilter @@ -161,41 +261,59 @@ class Filter : public Http::StreamFilter, const StreamInfo::StreamInfo& stream_info, Envoy::AccessLog::AccessLogType access_log_type) override; - void onStreamComplete() override {} - - CAPIStatus continueStatus(GolangStatus status); + CAPIStatus clearRouteCache(bool refresh); + void clearRouteCacheInternal(bool refresh); + CAPIStatus continueStatus(ProcessorState& state, GolangStatus status); - CAPIStatus sendLocalReply(Http::Code response_code, std::string body_text, + CAPIStatus sendLocalReply(ProcessorState& state, Http::Code response_code, std::string body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, std::string details); - CAPIStatus sendPanicReply(absl::string_view details); - - CAPIStatus getHeader(absl::string_view key, GoString* go_value); - CAPIStatus copyHeaders(GoString* go_strs, char* go_buf); - CAPIStatus setHeader(absl::string_view key, absl::string_view value, headerAction act); - CAPIStatus removeHeader(absl::string_view key); - CAPIStatus copyBuffer(Buffer::Instance* buffer, char* data); - CAPIStatus setBufferHelper(Buffer::Instance* buffer, absl::string_view& value, - bufferAction action); - CAPIStatus copyTrailers(GoString* go_strs, char* go_buf); - CAPIStatus setTrailer(absl::string_view key, absl::string_view value, headerAction act); - CAPIStatus removeTrailer(absl::string_view key); - CAPIStatus getStringValue(int id, GoString* value_str); + CAPIStatus sendPanicReply(ProcessorState& state, absl::string_view details); + + CAPIStatus addData(ProcessorState& state, absl::string_view data, bool is_streaming); + CAPIStatus injectData(ProcessorState& state, absl::string_view data); + + CAPIStatus getHeader(ProcessorState& state, absl::string_view key, uint64_t* value_data, + int* value_len); + CAPIStatus copyHeaders(ProcessorState& state, GoString* go_strs, char* go_buf); + CAPIStatus setHeader(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act); + CAPIStatus removeHeader(ProcessorState& state, absl::string_view key); + CAPIStatus copyBuffer(ProcessorState& state, Buffer::Instance* buffer, char* data); + CAPIStatus drainBuffer(ProcessorState& state, Buffer::Instance* buffer, uint64_t length); + CAPIStatus setBufferHelper(ProcessorState& state, Buffer::Instance* buffer, + absl::string_view& value, bufferAction action); + CAPIStatus copyTrailers(ProcessorState& state, GoString* go_strs, char* go_buf); + CAPIStatus setTrailer(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act); + CAPIStatus removeTrailer(ProcessorState& state, absl::string_view key); + + CAPIStatus getStringValue(int id, uint64_t* value_data, int* value_len); CAPIStatus getIntegerValue(int id, uint64_t* value); - CAPIStatus getDynamicMetadata(const std::string& filter_name, GoSlice* buf_slice); + CAPIStatus getDynamicMetadata(const std::string& filter_name, uint64_t* buf_data, int* buf_len); CAPIStatus setDynamicMetadata(std::string filter_name, std::string key, absl::string_view buf); CAPIStatus setStringFilterState(absl::string_view key, absl::string_view value, int state_type, int life_span, int stream_sharing); - CAPIStatus getStringFilterState(absl::string_view key, GoString* value_str); + CAPIStatus getStringFilterState(absl::string_view key, uint64_t* value_data, int* value_len); + CAPIStatus getStringProperty(absl::string_view path, uint64_t* value_data, int* value_len, + GoInt32* rc); + + bool isProcessingInGo() { + return decoding_state_.isProcessingInGo() || encoding_state_.isProcessingInGo(); + } + void deferredDeleteRequest(HttpRequestInternal* req); private: bool hasDestroyed() { Thread::LockGuard lock(mutex_); return has_destroyed_; }; - ProcessorState& getProcessorState(); + const StreamInfo::StreamInfo& streamInfo() const { return decoding_state_.streamInfo(); } + StreamInfo::StreamInfo& streamInfo() { return decoding_state_.streamInfo(); } + bool isThreadSafe() { return decoding_state_.isThreadSafe(); }; + Event::Dispatcher& getDispatcher() { return *dispatcher_; } bool doHeaders(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, bool end_stream); GolangStatus doHeadersGo(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, @@ -205,79 +323,59 @@ class Filter : public Http::StreamFilter, bool doTrailer(ProcessorState& state, Http::HeaderMap& trailers); bool doTrailerGo(ProcessorState& state, Http::HeaderMap& trailers); - uint64_t getMergedConfigId(ProcessorState& state); + // return true when it is first inited. + bool initRequest(); + + uint64_t getMergedConfigId(); void continueEncodeLocalReply(ProcessorState& state); - void continueStatusInternal(GolangStatus status); + void continueStatusInternal(ProcessorState& state, GolangStatus status); void continueData(ProcessorState& state); - void onHeadersModified(); - - void sendLocalReplyInternal(Http::Code response_code, absl::string_view body_text, + void sendLocalReplyInternal(ProcessorState& state, Http::Code response_code, + absl::string_view body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details); - void setDynamicMetadataInternal(ProcessorState& state, std::string filter_name, std::string key, + void setDynamicMetadataInternal(std::string filter_name, std::string key, const absl::string_view& buf); - void populateSliceWithMetadata(ProcessorState& state, const std::string& filter_name, - GoSlice* buf_slice); + void populateSliceWithMetadata(const std::string& filter_name, uint64_t* buf_data, int* buf_len); + + CAPIStatus getStringPropertyCommon(absl::string_view path, uint64_t* value_data, int* value_len); + CAPIStatus getStringPropertyInternal(absl::string_view path, std::string* result); + absl::optional findValue(absl::string_view name, + Protobuf::Arena* arena); + CAPIStatus serializeStringValue(Filters::Common::Expr::CelValue value, std::string* result); const FilterConfigSharedPtr config_; Dso::HttpFilterDsoPtr dynamic_lib_; - Http::RequestOrResponseHeaderMap* headers_ ABSL_GUARDED_BY(mutex_){nullptr}; - Http::HeaderMap* trailers_ ABSL_GUARDED_BY(mutex_){nullptr}; + // save temp values for fetching request attributes in the later phase, + // like getting request size + Http::RequestHeaderMap* request_headers_{nullptr}; + Http::RequestTrailerMap* request_trailers_{nullptr}; - // save temp values from local reply - Http::RequestOrResponseHeaderMap* local_headers_{nullptr}; - Http::HeaderMap* local_trailers_{nullptr}; + HttpRequestInternal* req_{nullptr}; // The state of the filter on both the encoding and decoding side. - DecodingProcessorState decoding_state_; - EncodingProcessorState encoding_state_; + // They are stored in HttpRequestInternal since Go need to read them, + // And it's safe to read them before onDestroy in C++ side. + DecodingProcessorState& decoding_state_; + EncodingProcessorState& encoding_state_; - httpRequestInternal* req_{nullptr}; + Event::Dispatcher* dispatcher_; - // lock for has_destroyed_ and the functions get/set/copy/remove/etc that operate on the - // headers_/trailers_/etc, to avoid race between envoy c thread and go thread (when calling back - // from go). it should also be okay without this lock in most cases, just for extreme case. + // lock for has_destroyed_/etc, to avoid race between envoy c thread and go thread (when calling + // back from go). Thread::MutexBasicLockable mutex_{}; bool has_destroyed_ ABSL_GUARDED_BY(mutex_){false}; - - // other filter trigger sendLocalReply during go processing in async. - // will wait go return before continue. - // this variable is read/write in safe thread, do no need lock. - bool local_reply_waiting_go_{false}; - - // the filter enter encoding phase - bool enter_encoding_{false}; -}; - -// Go code only touch the fields in httpRequest -struct httpRequestInternal : httpRequest { - std::weak_ptr filter_; - // anchor a string temporarily, make sure it won't be freed before copied to Go. - std::string strValue; - httpRequestInternal(std::weak_ptr f) { filter_ = f; } - std::weak_ptr weakFilter() { return filter_; } }; -class FilterLogger : Logger::Loggable { -public: - FilterLogger() = default; - - void log(uint32_t level, absl::string_view message) const; - uint32_t level() const; -}; - -class GoStringFilterState : public StreamInfo::FilterState::Object { -public: - GoStringFilterState(absl::string_view value) : value_(value) {} - const std::string& value() const { return value_; } - -private: - const std::string value_; +struct httpConfigInternal : httpConfig { + std::weak_ptr config_; + httpConfigInternal(std::weak_ptr c) { config_ = c; } + std::weak_ptr weakFilterConfig() { return config_; } }; } // namespace Golang diff --git a/contrib/golang/filters/http/source/processor_state.cc b/contrib/golang/filters/http/source/processor_state.cc index 29a658069e39a..44059e006e88a 100644 --- a/contrib/golang/filters/http/source/processor_state.cc +++ b/contrib/golang/filters/http/source/processor_state.cc @@ -12,7 +12,7 @@ Buffer::Instance& BufferList::push(Buffer::Instance& data) { bytes_ += data.length(); auto ptr = std::make_unique(); - Buffer::Instance& buffer = *ptr.get(); + Buffer::Instance& buffer = *ptr; buffer.move(data); queue_.push_back(std::move(ptr)); @@ -47,11 +47,11 @@ bool BufferList::checkExisting(Buffer::Instance* data) { }; // headers_ should set to nullptr when return true. -bool ProcessorState::handleHeaderGolangStatus(const GolangStatus status) { - ENVOY_LOG(debug, "golang filter handle header status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); +bool ProcessorState::handleHeaderGolangStatus(GolangStatus status) { + ENVOY_LOG(debug, "golang filter handle header status, state: {}, status: {}", stateStr(), + int(status)); - ASSERT(state_ == FilterState::ProcessingHeader); + ASSERT(filterState() == FilterState::ProcessingHeader); bool done = false; switch (status) { @@ -65,19 +65,19 @@ bool ProcessorState::handleHeaderGolangStatus(const GolangStatus status) { case GolangStatus::Continue: if (do_end_stream_) { - state_ = FilterState::Done; + setFilterState(FilterState::Done); } else { - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); } done = true; break; case GolangStatus::StopAndBuffer: - state_ = FilterState::WaitingAllData; + setFilterState(FilterState::WaitingAllData); break; case GolangStatus::StopAndBufferWatermark: - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); break; default: @@ -85,17 +85,17 @@ bool ProcessorState::handleHeaderGolangStatus(const GolangStatus status) { break; } - ENVOY_LOG(debug, "golang filter after handle header status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter after handle header status, state: {}, status: {}", stateStr(), + int(status)); return done; }; bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { - ENVOY_LOG(debug, "golang filter handle data status, state: {}, phase: {}, status: {}", stateStr(), - phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter handle data status, state: {}, status: {}", stateStr(), + int(status)); - ASSERT(state_ == FilterState::ProcessingData); + ASSERT(filterState() == FilterState::ProcessingData); bool done = false; @@ -112,9 +112,9 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { case GolangStatus::Continue: if (do_end_stream_) { - state_ = FilterState::Done; + setFilterState(FilterState::Done); } else { - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); } done = true; break; @@ -124,7 +124,7 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { ENVOY_LOG(error, "want more data while stream is end"); // TODO: terminate the stream? } - state_ = FilterState::WaitingAllData; + setFilterState(FilterState::WaitingAllData); break; case GolangStatus::StopAndBufferWatermark: @@ -132,7 +132,7 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { ENVOY_LOG(error, "want more data while stream is end"); // TODO: terminate the stream? } - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); break; case GolangStatus::StopNoBuffer: @@ -141,7 +141,7 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { // TODO: terminate the stream? } doDataList.clearLatest(); - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); break; default: @@ -151,13 +151,13 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { } // see trailers and no buffered data - if (seen_trailers_ && isBufferDataEmpty()) { - ENVOY_LOG(error, "see trailers and buffer is empty"); - state_ = FilterState::WaitingTrailer; + if (trailers != nullptr && isBufferDataEmpty()) { + ENVOY_LOG(debug, "see trailers and buffer is empty"); + setFilterState(FilterState::WaitingTrailer); } - ENVOY_LOG(debug, "golang filter after handle data status, state: {}, phase: {}, status: {}", - int(state_), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter after handle data status, state: {}, status: {}", stateStr(), + int(status)); return done; }; @@ -165,10 +165,10 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { // should set trailers_ to nullptr when return true. // means we should not read/write trailers then, since trailers will pass to next fitler. bool ProcessorState::handleTrailerGolangStatus(const GolangStatus status) { - ENVOY_LOG(debug, "golang filter handle trailer status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter handle trailer status, state: {}, status: {}", stateStr(), + int(status)); - ASSERT(state_ == FilterState::ProcessingTrailer); + ASSERT(filterState() == FilterState::ProcessingTrailer); auto done = false; @@ -182,7 +182,7 @@ bool ProcessorState::handleTrailerGolangStatus(const GolangStatus status) { break; case GolangStatus::Continue: - state_ = FilterState::Done; + setFilterState(FilterState::Done); done = true; break; @@ -192,8 +192,8 @@ bool ProcessorState::handleTrailerGolangStatus(const GolangStatus status) { break; } - ENVOY_LOG(debug, "golang filter after handle trailer status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter after handle trailer status, state: {}, status: {}", stateStr(), + int(status)); return done; }; @@ -204,12 +204,12 @@ bool ProcessorState::handleGolangStatus(GolangStatus status) { ASSERT(isProcessingInGo(), "unexpected state"); ENVOY_LOG(debug, - "before handle golang status, status: {}, state: {}, phase: {}, " - "do_end_stream_: {}", - int(status), stateStr(), phaseStr(), do_end_stream_); + "before handle golang status, status: {}, state: {}, " + "do_end_stream_: {}, seen trailers: {}", + int(status), stateStr(), do_end_stream_, trailers != nullptr); bool done = false; - switch (state_) { + switch (filterState()) { case FilterState::ProcessingHeader: done = handleHeaderGolangStatus(status); break; @@ -227,9 +227,9 @@ bool ProcessorState::handleGolangStatus(GolangStatus status) { } ENVOY_LOG(debug, - "after handle golang status, status: {}, state: {}, phase: {}, " - "do_end_stream_: {}", - int(status), stateStr(), phaseStr(), do_end_stream_); + "after handle golang status, status: {}, state: {}, " + "do_end_stream_: {}, done: {}, seen trailers: {}", + int(status), stateStr(), do_end_stream_, done, trailers != nullptr); return done; } @@ -244,8 +244,8 @@ void ProcessorState::drainBufferData() { } } -std::string ProcessorState::stateStr() { - switch (state_) { +std::string state2Str(FilterState state) { + switch (state) { case FilterState::WaitingHeader: return "WaitingHeader"; case FilterState::ProcessingHeader: @@ -263,51 +263,14 @@ std::string ProcessorState::stateStr() { case FilterState::Done: return "Done"; default: - return "unknown"; + return "unknown(" + std::to_string(static_cast(state)) + ")"; } } -Phase ProcessorState::state2Phase() { - Phase phase; - switch (state_) { - case FilterState::WaitingHeader: - case FilterState::ProcessingHeader: - phase = Phase::DecodeHeader; - break; - case FilterState::WaitingData: - case FilterState::WaitingAllData: - case FilterState::ProcessingData: - phase = Phase::DecodeData; - break; - case FilterState::WaitingTrailer: - case FilterState::ProcessingTrailer: - phase = Phase::DecodeTrailer; - break; - // decode Done state means encode header phase, encode done state means done phase - case FilterState::Done: - phase = Phase::EncodeHeader; - break; - } - return phase; -}; - -std::string ProcessorState::phaseStr() { - switch (phase()) { - case Phase::DecodeHeader: - return "DecodeHeader"; - case Phase::DecodeData: - return "DecodeData"; - case Phase::DecodeTrailer: - return "DecodeTrailer"; - case Phase::EncodeHeader: - return "EncodeHeader"; - case Phase::EncodeData: - return "EncodeData"; - case Phase::EncodeTrailer: - return "EncodeTrailer"; - default: - return "unknown"; - } +std::string ProcessorState::stateStr() { + std::string prefix = is_encoding == 1 ? "encoder" : "decoder"; + auto state_str = state2Str(filterState()); + return prefix + ":" + state_str; } void DecodingProcessorState::addBufferData(Buffer::Instance& data) { @@ -321,7 +284,7 @@ void DecodingProcessorState::addBufferData(Buffer::Instance& data) { } }, [this]() -> void { - if (state_ == FilterState::WaitingAllData) { + if (filterState() == FilterState::WaitingAllData) { // On the request path exceeding buffer limits will result in a 413. ENVOY_LOG(debug, "golang filter decode data buffer is full, reply with 413"); decoder_callbacks_->sendLocalReply( @@ -353,13 +316,15 @@ void EncodingProcessorState::addBufferData(Buffer::Instance& data) { } }, [this]() -> void { - if (state_ == FilterState::WaitingAllData) { - // On the request path exceeding buffer limits will result in a 413. - ENVOY_LOG(debug, "golang filter encode data buffer is full, reply with 413"); + if (filterState() == FilterState::WaitingAllData) { + ENVOY_LOG(debug, "golang filter encode data buffer is full, reply with 500"); + + // In this case, sendLocalReply will either send a response directly to the encoder, or + // reset the stream. encoder_callbacks_->sendLocalReply( - Http::Code::PayloadTooLarge, - Http::CodeUtility::toString(Http::Code::PayloadTooLarge), nullptr, absl::nullopt, - StreamInfo::ResponseCodeDetails::get().RequestPayloadTooLarge); + Http::Code::InternalServerError, + Http::CodeUtility::toString(Http::Code::InternalServerError), nullptr, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().ResponsePayloadTooLarge); return; } if (!watermark_requested_) { diff --git a/contrib/golang/filters/http/source/processor_state.h b/contrib/golang/filters/http/source/processor_state.h index 23d0c202e8c0c..3f3b541bc200e 100644 --- a/contrib/golang/filters/http/source/processor_state.h +++ b/contrib/golang/filters/http/source/processor_state.h @@ -13,6 +13,7 @@ #include "source/common/http/utility.h" #include "absl/status/status.h" +#include "contrib/golang/common/dso/dso.h" namespace Envoy { namespace Extensions { @@ -63,19 +64,6 @@ enum class FilterState { Done, }; -/* - * request phase - */ -enum class Phase { - DecodeHeader = 1, - DecodeData, - DecodeTrailer, - EncodeHeader, - EncodeData, - EncodeTrailer, - Done, -}; - /** * An enum specific for Golang status. */ @@ -90,26 +78,29 @@ enum class GolangStatus { StopNoBuffer, }; -class ProcessorState : public Logger::Loggable, NonCopyable { +class ProcessorState : public processState, public Logger::Loggable, NonCopyable { public: - explicit ProcessorState(Filter& filter) : filter_(filter) {} + explicit ProcessorState(Filter& filter, httpRequest* r) : filter_(filter) { + req = r; + setFilterState(FilterState::WaitingHeader); + } virtual ~ProcessorState() = default; - FilterState state() const { return state_; } + FilterState filterState() const { return static_cast(state); } + void setFilterState(FilterState st) { state = static_cast(st); } std::string stateStr(); - virtual Phase phase() PURE; - std::string phaseStr(); + virtual Http::StreamFilterCallbacks* getFilterCallbacks() const PURE; bool isProcessingInGo() { - return state_ == FilterState::ProcessingHeader || state_ == FilterState::ProcessingData || - state_ == FilterState::ProcessingTrailer; + return filterState() == FilterState::ProcessingHeader || + filterState() == FilterState::ProcessingData || + filterState() == FilterState::ProcessingTrailer || req->is_golang_processing_log; } - bool isProcessingHeader() { return state_ == FilterState::ProcessingHeader; } - Http::StreamFilterCallbacks* getFilterCallbacks() { return filter_callbacks_; }; + bool isProcessingHeader() { return filterState() == FilterState::ProcessingHeader; } - bool isThreadSafe() { return filter_callbacks_->dispatcher().isThreadSafe(); }; - Event::Dispatcher& getDispatcher() { return filter_callbacks_->dispatcher(); } + bool isThreadSafe() { return getFilterCallbacks()->dispatcher().isThreadSafe(); }; + Event::Dispatcher& getDispatcher() { return getFilterCallbacks()->dispatcher(); } /* data buffer */ // add data to state buffer @@ -119,7 +110,6 @@ class ProcessorState : public Logger::Loggable, NonCopyable { bool isBufferDataEmpty() { return data_buffer_ == nullptr || data_buffer_->length() == 0; }; void drainBufferData(); - void setSeenTrailers() { seen_trailers_ = true; } bool isProcessingEndStream() { return do_end_stream_; } virtual void continueProcessing() PURE; @@ -131,26 +121,30 @@ class ProcessorState : public Logger::Loggable, NonCopyable { Buffer::OwnedImpl data_to_write; doDataList.moveOut(data_to_write); + ENVOY_LOG(debug, "golang filter injecting data to filter chain, end_stream: {}", + do_end_stream_); injectDataToFilterChain(data_to_write, do_end_stream_); } void processHeader(bool end_stream) { - ASSERT(state_ == FilterState::WaitingHeader); - state_ = FilterState::ProcessingHeader; + ASSERT(filterState() == FilterState::WaitingHeader); + setFilterState(FilterState::ProcessingHeader); do_end_stream_ = end_stream; } void processData(bool end_stream) { - ASSERT(state_ == FilterState::WaitingData || - (state_ == FilterState::WaitingAllData && (end_stream || seen_trailers_))); - state_ = FilterState::ProcessingData; + ASSERT(filterState() == FilterState::WaitingData || + (filterState() == FilterState::WaitingAllData && (end_stream || trailers != nullptr))); + setFilterState(FilterState::ProcessingData); + do_end_stream_ = end_stream; } void processTrailer() { - ASSERT(state_ == FilterState::WaitingTrailer || state_ == FilterState::WaitingData || - state_ == FilterState::WaitingAllData); - state_ = FilterState::ProcessingTrailer; + ASSERT(filterState() == FilterState::WaitingTrailer || + filterState() == FilterState::WaitingData || + filterState() == FilterState::WaitingAllData); + setFilterState(FilterState::ProcessingTrailer); do_end_stream_ = true; } @@ -163,43 +157,44 @@ class ProcessorState : public Logger::Loggable, NonCopyable { std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details) PURE; - const StreamInfo::StreamInfo& streamInfo() const { return filter_callbacks_->streamInfo(); } - StreamInfo::StreamInfo& streamInfo() { return filter_callbacks_->streamInfo(); } + virtual void addData(Buffer::Instance& data, bool is_streaming) PURE; + + const StreamInfo::StreamInfo& streamInfo() const { return getFilterCallbacks()->streamInfo(); } + StreamInfo::StreamInfo& streamInfo() { return getFilterCallbacks()->streamInfo(); } void setEndStream(bool end_stream) { end_stream_ = end_stream; } bool getEndStream() { return end_stream_; } // seen trailers also means stream is end - bool isStreamEnd() { return end_stream_ || seen_trailers_; } + bool isStreamEnd() { return end_stream_ || trailers != nullptr; } + + Http::RequestOrResponseHeaderMap* headers{nullptr}; + Http::HeaderMap* trailers{nullptr}; BufferList doDataList; protected: - Phase state2Phase(); Filter& filter_; - Http::StreamFilterCallbacks* filter_callbacks_{nullptr}; bool watermark_requested_{false}; Buffer::InstancePtr data_buffer_{nullptr}; - FilterState state_{FilterState::WaitingHeader}; bool end_stream_{false}; bool do_end_stream_{false}; - bool seen_trailers_{false}; }; class DecodingProcessorState : public ProcessorState { public: - explicit DecodingProcessorState(Filter& filter) : ProcessorState(filter) {} + explicit DecodingProcessorState(Filter& filter, httpRequest* r) : ProcessorState(filter, r) { + is_encoding = 0; + } void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) { decoder_callbacks_ = &callbacks; - filter_callbacks_ = &callbacks; } + Http::StreamFilterCallbacks* getFilterCallbacks() const override { return decoder_callbacks_; } void injectDataToFilterChain(Buffer::Instance& data, bool end_stream) override { decoder_callbacks_->injectDecodedDataToFilterChain(data, end_stream); } - Phase phase() override { return state2Phase(); }; - void addBufferData(Buffer::Instance& data) override; void continueProcessing() override { @@ -209,33 +204,38 @@ class DecodingProcessorState : public ProcessorState { void sendLocalReply(Http::Code response_code, absl::string_view body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details) override { - // it's safe to reset state_, since it is read/write in safe thread. + // it's safe to reset filterState(), since it is read/write in safe thread. ENVOY_LOG(debug, "golang filter phase grow to EncodeHeader and state grow to WaitHeader before " "sendLocalReply"); - state_ = FilterState::WaitingHeader; + setFilterState(FilterState::WaitingHeader); decoder_callbacks_->sendLocalReply(response_code, body_text, modify_headers, grpc_status, details); }; + void addData(Buffer::Instance& data, bool is_streaming) override { + ENVOY_LOG(debug, "golang filter addData when decoding, is_streaming: {}", is_streaming); + decoder_callbacks_->addDecodedData(data, is_streaming); + } + private: Http::StreamDecoderFilterCallbacks* decoder_callbacks_{nullptr}; }; class EncodingProcessorState : public ProcessorState { public: - explicit EncodingProcessorState(Filter& filter) : ProcessorState(filter) {} + explicit EncodingProcessorState(Filter& filter, httpRequest* r) : ProcessorState(filter, r) { + is_encoding = 1; + } void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) { encoder_callbacks_ = &callbacks; - filter_callbacks_ = &callbacks; } + Http::StreamFilterCallbacks* getFilterCallbacks() const override { return encoder_callbacks_; } void injectDataToFilterChain(Buffer::Instance& data, bool end_stream) override { encoder_callbacks_->injectEncodedDataToFilterChain(data, end_stream); } - Phase phase() override { return static_cast(static_cast(state2Phase()) + 3); }; - void addBufferData(Buffer::Instance& data) override; void continueProcessing() override { @@ -249,6 +249,11 @@ class EncodingProcessorState : public ProcessorState { details); }; + void addData(Buffer::Instance& data, bool is_streaming) override { + ENVOY_LOG(debug, "golang filter addData when encoding, is_streaming: {}", is_streaming); + encoder_callbacks_->addEncodedData(data, is_streaming); + } + private: Http::StreamEncoderFilterCallbacks* encoder_callbacks_{nullptr}; }; diff --git a/contrib/golang/filters/http/test/BUILD b/contrib/golang/filters/http/test/BUILD index 06a0ef413329f..e7ab7e4b1842d 100644 --- a/contrib/golang/filters/http/test/BUILD +++ b/contrib/golang/filters/http/test/BUILD @@ -14,9 +14,8 @@ envoy_cc_test( name = "config_test", srcs = ["config_test.cc"], data = [ - "//contrib/golang/filters/http/test/test_data/passthrough:filter.so", + "//contrib/golang/filters/http/test/test_data:plugins.so", ], - env = {"GODEBUG": "cgocheck=0"}, deps = [ "//contrib/golang/filters/http/source:config", "//test/mocks/server:factory_context_mocks", @@ -28,10 +27,8 @@ envoy_cc_test( name = "golang_filter_test", srcs = ["golang_filter_test.cc"], data = [ - "//contrib/golang/filters/http/test/test_data/passthrough:filter.so", - "//contrib/golang/filters/http/test/test_data/routeconfig:filter.so", + "//contrib/golang/filters/http/test/test_data:plugins.so", ], - env = {"GODEBUG": "cgocheck=0"}, deps = [ "//contrib/golang/filters/http/source:golang_filter_lib", "//source/common/stream_info:stream_info_lib", @@ -52,12 +49,8 @@ envoy_cc_test( name = "golang_integration_test", srcs = ["golang_integration_test.cc"], data = [ - "//contrib/golang/filters/http/test/test_data/basic:filter.so", - "//contrib/golang/filters/http/test/test_data/echo:filter.so", - "//contrib/golang/filters/http/test/test_data/passthrough:filter.so", - "//contrib/golang/filters/http/test/test_data/routeconfig:filter.so", + "//contrib/golang/filters/http/test/test_data:plugins.so", ], - env = {"GODEBUG": "cgocheck=0"}, deps = [ "//contrib/golang/filters/http/source:config", "//source/exe:main_common_lib", @@ -95,3 +88,26 @@ envoy_cc_fuzz_test( "@envoy_api//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "websocket_integration_test", + size = "large", + srcs = ["websocket_integration_test.cc"], + data = [ + "//contrib/golang/filters/http/test/test_data:plugins.so", + ], + tags = [ + "cpu:3", + ], + deps = [ + "//contrib/golang/filters/http/source:config", + "//source/common/http:header_map_lib", + "//source/extensions/access_loggers/file:config", + "//source/extensions/filters/http/buffer:config", + "//test/integration:http_protocol_integration_lib", + "//test/integration:websocket_integration_test_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + ], +) diff --git a/contrib/golang/filters/http/test/config_test.cc b/contrib/golang/filters/http/test/config_test.cc index d85792ee35b1c..0ced3ae2e9ca8 100644 --- a/contrib/golang/filters/http/test/config_test.cc +++ b/contrib/golang/filters/http/test/config_test.cc @@ -13,6 +13,7 @@ #include "gtest/gtest.h" using testing::_; +using ::testing::Invoke; namespace Envoy { namespace Extensions { @@ -20,11 +21,13 @@ namespace HttpFilters { namespace Golang { namespace { -std::string genSoPath(std::string name) { +std::string genSoPath() { return TestEnvironment::substitute( - "{{ test_rundir }}/contrib/golang/filters/http/test/test_data/" + name + "/filter.so"); + "{{ test_rundir }}/contrib/golang/filters/http/test/test_data/plugins.so"); } +void cleanup() { Dso::DsoManager::cleanUpForTest(); } + TEST(GolangFilterConfigTest, InvalidateEmptyConfig) { NiceMock context; EXPECT_THROW_WITH_REGEX( @@ -49,19 +52,24 @@ TEST(GolangFilterConfigTest, GolangFilterWithValidConfig) { )EOF"; const std::string PASSTHROUGH{"passthrough"}; - auto yaml_string = absl::StrFormat(yaml_fmt, PASSTHROUGH, genSoPath(PASSTHROUGH)); + auto yaml_string = absl::StrFormat(yaml_fmt, PASSTHROUGH, genSoPath()); envoy::extensions::filters::http::golang::v3alpha::Config proto_config; TestUtility::loadFromYaml(yaml_string, proto_config); NiceMock context; GolangFilterConfig factory; Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, "stats", context); Http::MockFilterChainFactoryCallbacks filter_callback; - EXPECT_CALL(filter_callback, addStreamFilter(_)); + NiceMock dispatcher{"worker_0"}; + ON_CALL(filter_callback, dispatcher()).WillByDefault(ReturnRef(dispatcher)); + EXPECT_CALL(filter_callback, addStreamFilter(_)) + .WillOnce(Invoke([](Http::StreamDecoderFilterSharedPtr filter) { filter->onDestroy(); })); EXPECT_CALL(filter_callback, addAccessLogHandler(_)); auto plugin_config = proto_config.plugin_config(); std::string str; EXPECT_TRUE(plugin_config.SerializeToString(&str)); cb(filter_callback); + + cleanup(); } TEST(GolangFilterConfigTest, GolangFilterWithNilPluginConfig) { @@ -72,19 +80,24 @@ TEST(GolangFilterConfigTest, GolangFilterWithNilPluginConfig) { )EOF"; const std::string PASSTHROUGH{"passthrough"}; - auto yaml_string = absl::StrFormat(yaml_fmt, PASSTHROUGH, genSoPath(PASSTHROUGH)); + auto yaml_string = absl::StrFormat(yaml_fmt, PASSTHROUGH, genSoPath()); envoy::extensions::filters::http::golang::v3alpha::Config proto_config; TestUtility::loadFromYaml(yaml_string, proto_config); NiceMock context; GolangFilterConfig factory; Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, "stats", context); Http::MockFilterChainFactoryCallbacks filter_callback; - EXPECT_CALL(filter_callback, addStreamFilter(_)); + NiceMock dispatcher{"worker_0"}; + ON_CALL(filter_callback, dispatcher()).WillByDefault(ReturnRef(dispatcher)); + EXPECT_CALL(filter_callback, addStreamFilter(_)) + .WillOnce(Invoke([](Http::StreamDecoderFilterSharedPtr filter) { filter->onDestroy(); })); EXPECT_CALL(filter_callback, addAccessLogHandler(_)); auto plugin_config = proto_config.plugin_config(); std::string str; EXPECT_TRUE(plugin_config.SerializeToString(&str)); cb(filter_callback); + + cleanup(); } } // namespace diff --git a/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc b/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc index 202ddf3ba06f0..09237f0b1b6c0 100644 --- a/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc @@ -49,15 +49,21 @@ DEFINE_PROTO_FUZZER(const envoy::extensions::filters::http::golang::GolangFilter auto dso_lib = std::make_shared(); // hard code the return config_id to 1 since the default 0 is invalid. - ON_CALL(*dso_lib.get(), envoyGoFilterNewHttpPluginConfig(_, _, _, _)).WillByDefault(Return(1)); + ON_CALL(*dso_lib.get(), envoyGoFilterNewHttpPluginConfig(_)).WillByDefault(Return(1)); ON_CALL(*dso_lib.get(), envoyGoFilterOnHttpHeader(_, _, _, _)) .WillByDefault(Return(static_cast(GolangStatus::Continue))); ON_CALL(*dso_lib.get(), envoyGoFilterOnHttpData(_, _, _, _)) .WillByDefault(Return(static_cast(GolangStatus::Continue))); + ON_CALL(*dso_lib.get(), envoyGoFilterOnHttpLog(_, _, _, _, _, _, _, _, _, _, _, _)) + .WillByDefault( + Invoke([&](httpRequest*, int, processState*, processState*, GoUint64, GoUint64, GoUint64, + GoUint64, GoUint64, GoUint64, GoUint64, GoUint64) -> void {})); + ON_CALL(*dso_lib.get(), envoyGoFilterOnHttpStreamComplete(_)) + .WillByDefault(Invoke([&](httpRequest*) -> void {})); ON_CALL(*dso_lib.get(), envoyGoFilterOnHttpDestroy(_, _)) .WillByDefault(Invoke([&](httpRequest* p0, int) -> void { // delete the filter->req_, make LeakSanitizer happy. - auto req = reinterpret_cast(p0); + auto req = reinterpret_cast(p0); delete req; })); @@ -77,7 +83,7 @@ DEFINE_PROTO_FUZZER(const envoy::extensions::filters::http::golang::GolangFilter // Prepare filter. NiceMock context; FilterConfigSharedPtr config = std::make_shared(proto_config, dso_lib, "", context); - std::unique_ptr filter = std::make_unique(config, dso_lib); + std::unique_ptr filter = std::make_unique(config, dso_lib, 0); filter->setDecoderFilterCallbacks(mocks.decoder_callbacks_); filter->setEncoderFilterCallbacks(mocks.encoder_callbacks_); diff --git a/contrib/golang/filters/http/test/golang_filter_test.cc b/contrib/golang/filters/http/test/golang_filter_test.cc index e2701b971c6c4..53b0725f6ae84 100644 --- a/contrib/golang/filters/http/test/golang_filter_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_test.cc @@ -76,6 +76,7 @@ class GolangHttpFilterTest : public testing::Test { if (filter_ != nullptr) { filter_->onDestroy(); } + Dso::DsoManager::cleanUpForTest(); } void setup(const std::string& lib_id, const std::string& lib_path, @@ -103,9 +104,9 @@ class GolangHttpFilterTest : public testing::Test { setupFilter(plugin_name); } - std::string genSoPath(std::string name) { + std::string genSoPath() { return TestEnvironment::substitute( - "{{ test_rundir }}/contrib/golang/filters/http/test/test_data/" + name + "/filter.so"); + "{{ test_rundir }}/contrib/golang/filters/http/test/test_data/plugins.so"); } void setupDso(std::string id, std::string path, std::string plugin_name) { @@ -120,6 +121,7 @@ class GolangHttpFilterTest : public testing::Test { config_ = std::make_shared( proto_config, Dso::DsoManager::getDsoByPluginName(plugin_name), "", context_); + config_->newGoPluginConfig(); // Setup per route config for Golang filter. per_route_config_ = std::make_shared(per_route_proto_config, server_factory_context_); @@ -130,7 +132,7 @@ class GolangHttpFilterTest : public testing::Test { test_time.setSystemTime(std::chrono::microseconds(1583879145572237)); filter_ = std::make_unique( - config_, Dso::DsoManager::getDsoByPluginName(plugin_name)); + config_, Dso::DsoManager::getDsoByPluginName(plugin_name), 0); filter_->setDecoderFilterCallbacks(decoder_callbacks_); filter_->setEncoderFilterCallbacks(encoder_callbacks_); } @@ -164,7 +166,7 @@ class GolangHttpFilterTest : public testing::Test { // request that is headers only. TEST_F(GolangHttpFilterTest, ScriptHeadersOnlyRequestHeadersOnly) { InSequence s; - setup(PASSTHROUGH, genSoPath(PASSTHROUGH), PASSTHROUGH); + setup(PASSTHROUGH, genSoPath(), PASSTHROUGH); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -174,15 +176,18 @@ TEST_F(GolangHttpFilterTest, ScriptHeadersOnlyRequestHeadersOnly) { // setHeader at wrong stage TEST_F(GolangHttpFilterTest, SetHeaderAtWrongStage) { InSequence s; - setup(PASSTHROUGH, genSoPath(PASSTHROUGH), PASSTHROUGH); + setup(PASSTHROUGH, genSoPath(), PASSTHROUGH); + auto req = new HttpRequestInternal(*filter_); - EXPECT_EQ(CAPINotInGo, filter_->setHeader("foo", "bar", HeaderSet)); + EXPECT_EQ(CAPINotInGo, filter_->setHeader(req->decodingState(), "foo", "bar", HeaderSet)); + + delete req; } // invalid config for routeconfig filter TEST_F(GolangHttpFilterTest, InvalidConfigForRouteConfigFilter) { InSequence s; - EXPECT_THROW_WITH_REGEX(setup(ROUTECONFIG, genSoPath(ROUTECONFIG), ROUTECONFIG), EnvoyException, + EXPECT_THROW_WITH_REGEX(setup(ROUTECONFIG, genSoPath(), ROUTECONFIG), EnvoyException, "golang filter failed to parse plugin config"); } diff --git a/contrib/golang/filters/http/test/golang_integration_test.cc b/contrib/golang/filters/http/test/golang_integration_test.cc index 3459626c00821..077915203b874 100644 --- a/contrib/golang/filters/http/test/golang_integration_test.cc +++ b/contrib/golang/filters/http/test/golang_integration_test.cc @@ -11,6 +11,8 @@ namespace Envoy { +using testing::HasSubstr; + // helper function absl::string_view getHeader(const Http::HeaderMap& headers, absl::string_view key) { auto values = headers.get(Http::LowerCaseString(key)); @@ -88,9 +90,9 @@ class GolangIntegrationTest : public testing::TestWithParammutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_match() + ->set_prefix("/property"); + + // setting route name for testing + hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0)->set_name( + "test-route-name"); + hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster("cluster_0"); + }); + + config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); + config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); + } + + void initializeAddDataConfig() { + const auto yaml_fmt = R"EOF( +name: golang +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config + library_id: %s + library_path: %s + plugin_name: %s + plugin_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct +)EOF"; + + auto so_id = ADDDATA; + auto yaml_string = absl::StrFormat(yaml_fmt, so_id, genSoPath(), so_id); + config_helper_.prependFilter(yaml_string); + config_helper_.skipPortUsageValidation(); + + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_match() + ->set_prefix("/test"); + + // setting route name for testing + hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0)->set_name( + "test-route-name"); + hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster("cluster_0"); + }); + + config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); + config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); + + initialize(); + } + void testBasic(std::string path) { initializeBasicFilter(BASIC, "test.com"); @@ -267,8 +351,7 @@ name: golang EXPECT_EQ("foo", getHeader(upstream_request_->headers(), "test-x-set-header-0")); // check header exists which removed in golang side: x-test-header-1 - EXPECT_EQ(true, - upstream_request_->headers().get(Http::LowerCaseString("x-test-header-1")).empty()); + EXPECT_TRUE(upstream_request_->headers().get(Http::LowerCaseString("x-test-header-1")).empty()); // check header value which set in golang: req-downstream-local-address EXPECT_TRUE( @@ -301,14 +384,15 @@ name: golang entries = upstream_request_->trailers()->get(Http::LowerCaseString("existed-trailer")); EXPECT_EQ(2, entries.size()); EXPECT_EQ("foo", entries[0]->value().getStringView()); - EXPECT_EQ("bar", entries[1]->value().getStringView()); + if (entries.size() == 2) { + EXPECT_EQ("bar", entries[1]->value().getStringView()); + } // check trailer value which set in golang: x-test-trailer-0 entries = upstream_request_->trailers()->get(Http::LowerCaseString("x-test-trailer-0")); EXPECT_EQ("bar", entries[0]->value().getStringView()); - EXPECT_EQ( - true, + EXPECT_TRUE( upstream_request_->trailers()->get(Http::LowerCaseString("x-test-trailer-1")).empty()); // check trailer value which add in golang: x-test-trailer-2 @@ -337,7 +421,7 @@ name: golang EXPECT_EQ("foo", getHeader(response->headers(), "test-x-set-header-0")); // check resp header exists which removed in golang side: x-test-header-1 - EXPECT_EQ(true, response->headers().get(Http::LowerCaseString("x-test-header-1")).empty()); + EXPECT_TRUE(response->headers().get(Http::LowerCaseString("x-test-header-1")).empty()); // check header value which is appended in golang: existed-header entries = response->headers().get(Http::LowerCaseString("existed-header")); @@ -361,11 +445,10 @@ name: golang EXPECT_EQ("HTTP/1.1", getHeader(response->headers(), "rsp-protocol")); // check filter chain name in encode phase, exists. - EXPECT_EQ(false, - response->headers().get(Http::LowerCaseString("rsp-filter-chain-name")).empty()); + EXPECT_FALSE(response->headers().get(Http::LowerCaseString("rsp-filter-chain-name")).empty()); // check response code in encode phase, not exists. - EXPECT_EQ(true, response->headers().get(Http::LowerCaseString("rsp-response-code")).empty()); + EXPECT_FALSE(response->headers().get(Http::LowerCaseString("rsp-response-code")).empty()); // check response code details in encode phase EXPECT_EQ("via_upstream", getHeader(response->headers(), "rsp-response-code-details")); @@ -388,7 +471,7 @@ name: golang EXPECT_EQ("200", getHeader(response->headers(), "rsp-status")); // verify protocol - EXPECT_EQ(true, response->headers().get(Http::LowerCaseString("test-protocol")).empty()); + EXPECT_TRUE(response->headers().get(Http::LowerCaseString("test-protocol")).empty()); // verify scheme EXPECT_EQ("http", getHeader(response->headers(), "test-scheme")); @@ -411,6 +494,36 @@ name: golang cleanup(); } + void testMetric(std::string path) { + initializeBasicFilter(METRIC); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", path}, {":scheme", "http"}, {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(); + + EXPECT_EQ("2", getHeader(upstream_request_->headers(), "go-metric-counter-test-header-key")); + + EXPECT_EQ("3", getHeader(upstream_request_->headers(), "go-metric-gauge-test-header-key")); + + EXPECT_EQ("3", + getHeader(upstream_request_->headers(), "go-metric-counter-record-test-header-key")); + + EXPECT_EQ("1", + getHeader(upstream_request_->headers(), "go-metric-gauge-record-test-header-key")); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, true); + + ASSERT_TRUE(response->waitForEndStream()); + + cleanup(); + } + void testRouteConfig(std::string domain, std::string path, bool header_0_existing, std::string set_header) { initializeRouteConfig(ROUTECONFIG); @@ -439,6 +552,35 @@ name: golang cleanup(); } + void testRouteCache(std::string path, bool clear) { + initializeBasicFilter(BASIC); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", path}, {":scheme", "http"}, {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + // no route found after clearing + if (!clear) { + waitForNextUpstreamRequest(); + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, true); + } + + ASSERT_TRUE(response->waitForEndStream()); + + // check resp status + if (clear) { + EXPECT_EQ("404", response->headers().getStatusValue()); + } else { + EXPECT_EQ("200", response->headers().getStatusValue()); + } + + cleanup(); + } + void testSendLocalReply(std::string path, std::string phase) { initializeBasicFilter(BASIC); @@ -484,6 +626,15 @@ name: golang // verify content-type EXPECT_EQ("text/html", getHeader(response->headers(), "content-type")); + // verify two values + auto values = response->headers().get(Http::LowerCaseString("x-two-values")); + if (values.size() == 2) { + EXPECT_EQ("foo", values[0]->value().getStringView()); + EXPECT_EQ("bar", values[1]->value().getStringView()); + } else { + EXPECT_EQ(values.size(), 2); + } + cleanup(); } @@ -558,14 +709,9 @@ name: golang } void cleanup() { - codec_client_->close(); + cleanupUpstreamAndDownstream(); - if (fake_upstream_connection_ != nullptr) { - AssertionResult result = fake_upstream_connection_->close(); - RELEASE_ASSERT(result, result.message()); - result = fake_upstream_connection_->waitForDisconnect(); - RELEASE_ASSERT(result, result.message()); - } + Dso::DsoManager::cleanUpForTest(); } void testDynamicMetadata(std::string path) { @@ -595,10 +741,76 @@ name: golang cleanup(); } + void testActionWithoutData(std::string query) { + initializeBasicFilter(ACTION, "test.com"); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/test?" + query}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + if (query.find("encodeHeadersRet") != std::string::npos) { + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, true); + } + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("500", response->headers().getStatusValue()); + + EXPECT_EQ(1, test_server_->counter("http.config_test.golang.panic_error")->value()); + + cleanup(); + } + + void testBufferApi(std::string query) { + initializeBasicFilter(BUFFER, "test.com"); + + auto path = std::string("/test?") + query; + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, + {":path", path}, + {":scheme", "http"}, + {":authority", "test.com"}, + }; + + auto encoder_decoder = codec_client_->startRequest(request_headers); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + std::string data = ""; + for (int i = 0; i < 10; i++) { + data += "12345"; + } + codec_client_->sendData(request_encoder, data, true); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, false); + Buffer::OwnedImpl response_data("goodbye"); + upstream_request_->encodeData(response_data, true); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("200", response->headers().getStatusValue()); + cleanup(); + } + const std::string ECHO{"echo"}; const std::string BASIC{"basic"}; const std::string PASSTHROUGH{"passthrough"}; + const std::string BUFFER{"buffer"}; const std::string ROUTECONFIG{"routeconfig"}; + const std::string PROPERTY{"property"}; + const std::string ACCESSLOG{"access_log"}; + const std::string METRIC{"metric"}; + const std::string ACTION{"action"}; + const std::string ADDDATA{"add_data"}; + const std::string BUFFERINJECTDATA{"bufferinjectdata"}; }; INSTANTIATE_TEST_SUITE_P(IpVersions, GolangIntegrationTest, @@ -606,7 +818,7 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, GolangIntegrationTest, TestUtility::ipTestParamsToString); TEST_P(GolangIntegrationTest, Echo) { - initializeConfig(ECHO, genSoPath(ECHO), ECHO); + initializeConfig(ECHO, genSoPath(), ECHO); initialize(); registerTestServerPorts({"http"}); @@ -632,7 +844,7 @@ TEST_P(GolangIntegrationTest, Echo) { } TEST_P(GolangIntegrationTest, Passthrough) { - initializeConfig(PASSTHROUGH, genSoPath(PASSTHROUGH), PASSTHROUGH); + initializeConfig(PASSTHROUGH, genSoPath(), PASSTHROUGH); initialize(); registerTestServerPorts({"http"}); @@ -668,6 +880,187 @@ TEST_P(GolangIntegrationTest, Passthrough) { cleanup(); } +TEST_P(GolangIntegrationTest, PluginNotFound) { + initializeConfig(ECHO, genSoPath(), PASSTHROUGH); + initialize(); + registerTestServerPorts({"http"}); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", "/"}, {":scheme", "http"}, {":authority", "test.com"}}; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_EQ("200", response->headers().getStatusValue()); + cleanup(); +} + +TEST_P(GolangIntegrationTest, BufferDrain) { testBufferApi("Drain"); } + +TEST_P(GolangIntegrationTest, BufferReset) { testBufferApi("Reset"); } + +TEST_P(GolangIntegrationTest, BufferResetAfterDrain) { testBufferApi("ResetAfterDrain"); } + +TEST_P(GolangIntegrationTest, BufferLen) { testBufferApi("Len"); } + +TEST_P(GolangIntegrationTest, Property) { + initializePropertyConfig(PROPERTY, genSoPath(), PROPERTY); + initialize(); + registerTestServerPorts({"http"}); + + auto path = "/property?a=1"; + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", path}, {":scheme", "http"}, {":authority", "test.com"}, + {"User-Agent", "ua"}, {"Referer", "r"}, {"X-Request-Id", "xri"}, + }; + + auto encoder_decoder = codec_client_->startRequest(request_headers); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_client_->sendData(request_encoder, "helloworld", true); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, false); + Buffer::OwnedImpl response_data("goodbye"); + upstream_request_->encodeData(response_data, true); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("200", response->headers().getStatusValue()); + cleanup(); +} + +TEST_P(GolangIntegrationTest, AccessLog) { + useAccessLog("%DYNAMIC_METADATA(golang:access_log_var)%"); + initializeBasicFilter(ACCESSLOG, "test.com"); + + auto path = "/test"; + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", path}, {":scheme", "http"}, + {":authority", "test.com"}, {"Referer", "r"}, + }; + + auto encoder_decoder = codec_client_->startRequest(request_headers); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_client_->sendData(request_encoder, "helloworld", false); + + Http::TestRequestTrailerMapImpl request_trailers{ + {"x-trailer", "foo"}, + }; + codec_client_->sendTrailers(request_encoder, request_trailers); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "206"}, + }; + upstream_request_->encodeHeaders(response_headers, false); + Buffer::OwnedImpl response_data1("good"); + upstream_request_->encodeData(response_data1, false); + Buffer::OwnedImpl response_data2("bye"); + upstream_request_->encodeData(response_data2, false); + + Http::TestResponseTrailerMapImpl response_trailers{{"x-trailer", "bar"}}; + upstream_request_->encodeTrailers(response_trailers); + + ASSERT_TRUE(response->waitForEndStream()); + codec_client_->close(); + + std::string log = waitForAccessLog(access_log_name_); + EXPECT_THAT(log, HasSubstr("access_log_var written by Golang filter")); + + // use the second request to get the logged data + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("206", getHeader(upstream_request_->headers(), "respCode")); + EXPECT_EQ("7", getHeader(upstream_request_->headers(), "respSize")); + EXPECT_EQ("true", getHeader(upstream_request_->headers(), "canRunAsyncly")); + EXPECT_EQ("foo", getHeader(upstream_request_->headers(), "x-req-trailer")); + EXPECT_EQ("bar", getHeader(upstream_request_->headers(), "x-resp-trailer")); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AccessLogDownstreamStart) { + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + hcm.mutable_access_log_options()->set_flush_access_log_on_new_request(true); + }); + initializeBasicFilter(ACCESSLOG, "test.com"); + + auto path = "/test"; + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", path}, {":scheme", "http"}, + {":authority", "test.com"}, {"Referer", "r"}, + }; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + EXPECT_TRUE(response->complete()); + codec_client_->close(); + + // use the second request to get the logged data + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + Http::TestRequestHeaderMapImpl request_headers2{ + {":method", "POST"}, {":path", path}, {":scheme", "http"}, + {":authority", "test.com"}, {"Referer", "r2"}, + }; + + response = sendRequestAndWaitForResponse(request_headers2, 0, default_response_headers_, 0); + + EXPECT_TRUE(response->complete()); + EXPECT_EQ("r;r2", getHeader(upstream_request_->headers(), "referers")); + EXPECT_EQ("true", getHeader(upstream_request_->headers(), "canRunAsynclyForDownstreamStart")); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AccessLogDownstreamPeriodic) { + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + hcm.mutable_access_log_options()->mutable_access_log_flush_interval()->set_nanos( + 100000000); // 0.1 seconds + }); + initializeBasicFilter(ACCESSLOG, "test.com"); + + auto path = "/test?periodic=1"; + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", path}, {":scheme", "http"}, + {":authority", "test.com"}, {"Referer", "r"}, + }; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + EXPECT_TRUE(response->complete()); + codec_client_->close(); + + // use the second request to get the logged data + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + EXPECT_TRUE(response->complete()); + EXPECT_EQ("r", getHeader(upstream_request_->headers(), "referers")); + EXPECT_EQ("true", getHeader(upstream_request_->headers(), "canRunAsynclyForDownstreamPeriodic")); + + cleanup(); +} + +// Mertic API testing +TEST_P(GolangIntegrationTest, Metric) { testMetric("/test"); } + +// Metric API testing in async mode. +TEST_P(GolangIntegrationTest, AsyncMetric) { testMetric("/test?async=1"); } + // Basic API testing, i.e. add/remove/set Headers & data rewrite. TEST_P(GolangIntegrationTest, Basic) { testBasic("/test"); } @@ -772,6 +1165,344 @@ name: envoy.filters.http.lua cleanup(); } +TEST_P(GolangIntegrationTest, AddDataInDecodeHeaders) { + initializeAddDataConfig(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "HEAD"}, + {":path", "/test?calledInDecodeHeaders=foo"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + // no body + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + + waitForNextUpstreamRequest(); + + EXPECT_EQ("POST", getHeader(upstream_request_->headers(), ":method")); + // body added + auto body = "foo"; + EXPECT_EQ(body, upstream_request_->body().toString()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AddDataRejectedWhenProcessingData) { + initializeAddDataConfig(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?calledInDecodeData=bar"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + codec_client_->sendData(request_encoder, "addData", true); + + waitForNextUpstreamRequest(); + + auto body = "addData called in DecodeData is not allowed"; + EXPECT_EQ(body, upstream_request_->body().toString()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AddDataInDecodeTrailers) { + initializeAddDataConfig(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?calledInDecodeTrailers=bar"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + codec_client_->sendData(request_encoder, "foo", false); + Http::TestRequestTrailerMapImpl request_trailers{{"x-trailer", "bar"}}; + codec_client_->sendTrailers(request_encoder, request_trailers); + + waitForNextUpstreamRequest(); + + // bar added in trailers + auto body = "foobar"; + EXPECT_EQ(body, upstream_request_->body().toString()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AddDataBufferAllData) { + initializeAddDataConfig(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, + {":path", "/test?calledInDecodeTrailers=bar&bufferAllData=true"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + codec_client_->sendData(request_encoder, "foo", false); + Http::TestRequestTrailerMapImpl request_trailers{{"x-trailer", "bar"}}; + codec_client_->sendTrailers(request_encoder, request_trailers); + + waitForNextUpstreamRequest(); + + // bar added in trailers + auto body = "foobar"; + EXPECT_EQ(body, upstream_request_->body().toString()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AddDataInEncodeHeaders) { + initializeAddDataConfig(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?calledInEncodeHeaders=foo"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "200"}, + }; + // no body + upstream_request_->encodeHeaders(response_headers, true); + + ASSERT_TRUE(response->waitForEndStream()); + + // body added + auto body = "foo"; + EXPECT_EQ(body, response->body()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AddDataInEncodeTrailers) { + initializeAddDataConfig(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?calledInEncodeTrailers=bar"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "200"}, + }; + upstream_request_->encodeHeaders(response_headers, false); + Buffer::OwnedImpl response_data("foo"); + upstream_request_->encodeData(response_data, false); + Http::TestResponseTrailerMapImpl response_trailers{{"x-trailer", "bar"}}; + upstream_request_->encodeTrailers(response_trailers); + + ASSERT_TRUE(response->waitForEndStream()); + + // bar added in trailers + auto body = "foobar"; + EXPECT_EQ(body, response->body()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, AddDataBufferAllDataAndAsync) { + initializeAddDataConfig(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, + {":path", "/test?calledInEncodeTrailers=bar&bufferAllData=true"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "200"}, + }; + upstream_request_->encodeHeaders(response_headers, false); + Buffer::OwnedImpl response_data("foo"); + upstream_request_->encodeData(response_data, false); + Http::TestResponseTrailerMapImpl response_trailers{{"x-trailer", "bar"}}; + upstream_request_->encodeTrailers(response_trailers); + + ASSERT_TRUE(response->waitForEndStream()); + + // bar added in trailers + auto body = "foobar"; + EXPECT_EQ(body, response->body()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, BufferInjectData_InBufferedDownstreamRequest) { + initializeBasicFilter(BUFFERINJECTDATA, "test.com"); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?bufferingly_decode"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, false); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + codec_client_->sendData(request_encoder, "To ", false); + codec_client_->sendData(request_encoder, "be, ", true); + + waitForNextUpstreamRequest(); + + auto body = "To be, or not to be, that is the question"; + EXPECT_EQ(body, upstream_request_->body().toString()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, BufferInjectData_InNonBufferedDownstreamRequest) { + initializeBasicFilter(BUFFERINJECTDATA, "test.com"); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?nonbufferingly_decode"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, false); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + codec_client_->sendData(request_encoder, "To be, ", false); + timeSystem().advanceTimeAndRun(std::chrono::milliseconds(10), *dispatcher_, + Event::Dispatcher::RunType::NonBlock); + codec_client_->sendData(request_encoder, "that is ", true); + + waitForNextUpstreamRequest(); + + auto body = "To be, or not to be, that is the question"; + EXPECT_EQ(body, upstream_request_->body().toString()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, BufferInjectData_InBufferedUpstreamResponse) { + initializeBasicFilter(BUFFERINJECTDATA, "test.com"); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?bufferingly_encode"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "200"}, + }; + upstream_request_->encodeHeaders(response_headers, false); + Buffer::OwnedImpl response_data("To "); + upstream_request_->encodeData(response_data, false); + Buffer::OwnedImpl response_data2("be, "); + upstream_request_->encodeData(response_data2, true); + + ASSERT_TRUE(response->waitForEndStream()); + + auto body = "To be, or not to be, that is the question"; + EXPECT_EQ(body, response->body()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, BufferInjectData_InNonBufferedUpstreamResponse) { + initializeBasicFilter(BUFFERINJECTDATA, "test.com"); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test?nonbufferingly_encode"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "200"}, + }; + upstream_request_->encodeHeaders(response_headers, false); + Buffer::OwnedImpl response_data("To be, "); + upstream_request_->encodeData(response_data, false); + timeSystem().advanceTimeAndRun(std::chrono::milliseconds(10), *dispatcher_, + Event::Dispatcher::RunType::NonBlock); + Buffer::OwnedImpl response_data2("that is "); + upstream_request_->encodeData(response_data2, true); + + ASSERT_TRUE(response->waitForEndStream()); + + auto body = "To be, or not to be, that is the question"; + EXPECT_EQ(body, response->body()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, BufferInjectData_WithoutProcessingData) { + initializeBasicFilter(BUFFERINJECTDATA, "test.com"); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, + {":path", "/test?inject_data_when_processing_header"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_EQ("400", response->headers().getStatusValue()); + + cleanup(); +} + +TEST_P(GolangIntegrationTest, BufferInjectData_ProcessingDataSynchronously) { + initializeBasicFilter(BUFFERINJECTDATA, "test.com"); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, + {":path", "/test?inject_data_when_processing_data_synchronously"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, false); + Http::RequestEncoder& request_encoder = encoder_decoder.first; + codec_client_->sendData(request_encoder, "blahblah", true); + auto response = std::move(encoder_decoder.second); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_EQ("400", response->headers().getStatusValue()); + + cleanup(); +} + // Buffer exceed limit in decode header phase. TEST_P(GolangIntegrationTest, BufferExceedLimit_DecodeHeader) { testBufferExceedLimit("/test?databuffer=decode-header"); @@ -798,6 +1529,16 @@ TEST_P(GolangIntegrationTest, RouteConfig_Route) { testRouteConfig("test.com", "/route-config-test", false, "baz"); } +// Set new path without clear route cache, will get 200 response status +TEST_P(GolangIntegrationTest, RouteCache_noClear) { + testRouteCache("/test?newPath=/not-found-path", false); +} + +// Set new path with clear route cache, will get 404 response status +TEST_P(GolangIntegrationTest, RouteCache_Clear) { + testRouteCache("/test?newPath=/not-found-path&clearRoute=1", true); +} + // Out of range in decode header phase TEST_P(GolangIntegrationTest, PanicRecover_DecodeHeader) { testPanicRecover("/test?panic=decode-header", "decode-header"); @@ -838,4 +1579,108 @@ TEST_P(GolangIntegrationTest, DynamicMetadata_Async_Sleep) { testDynamicMetadata("/test?dymeta=1&async=1&sleep=1"); } +TEST_P(GolangIntegrationTest, DecodeHeadersWithoutData_StopAndBuffer) { + testActionWithoutData("decodeHeadersRet=StopAndBuffer"); +} + +TEST_P(GolangIntegrationTest, DecodeHeadersWithoutData_StopAndBufferWatermark) { + testActionWithoutData("decodeHeadersRet=StopAndBufferWatermark"); +} + +TEST_P(GolangIntegrationTest, DecodeHeadersWithoutData_StopAndBuffer_Async) { + testActionWithoutData("decodeHeadersRet=StopAndBuffer&aysnc=1"); +} + +TEST_P(GolangIntegrationTest, DecodeHeadersWithoutData_StopAndBufferWatermark_Async) { + testActionWithoutData("decodeHeadersRet=StopAndBufferWatermark&aysnc=1"); +} + +TEST_P(GolangIntegrationTest, EncodeHeadersWithoutData_StopAndBuffer) { + testActionWithoutData("encodeHeadersRet=StopAndBuffer"); +} + +TEST_P(GolangIntegrationTest, EncodeHeadersWithoutData_StopAndBufferWatermark) { + testActionWithoutData("encodeHeadersRet=StopAndBufferWatermark"); +} + +TEST_P(GolangIntegrationTest, EncodeHeadersWithoutData_StopAndBuffer_Async) { + testActionWithoutData("encodeHeadersRet=StopAndBuffer&aysnc=1"); +} + +TEST_P(GolangIntegrationTest, EncodeHeadersWithoutData_StopAndBufferWatermark_Async) { + testActionWithoutData("encodeHeadersRet=StopAndBufferWatermark&aysnc=1"); +} + +TEST_P(GolangIntegrationTest, RefreshRouteCache) { + const std::string& so_id = BASIC; + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + const std::string key = "golang"; + const auto yaml_fmt = + R"EOF( + "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.ConfigsPerRoute + plugins_config: + %s: + config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + type_url: map + value: + )EOF"; + auto yaml = absl::StrFormat(yaml_fmt, so_id); + ProtobufWkt::Any value; + TestUtility::loadFromYaml(yaml, value); + + auto* route_first_matched = + hcm.mutable_route_config()->mutable_virtual_hosts(0)->add_routes(); + route_first_matched->mutable_match()->set_prefix("/disney/api"); + route_first_matched->mutable_typed_per_filter_config()->insert( + Protobuf::MapPair(key, value)); + auto* resp_header = route_first_matched->add_response_headers_to_add(); + auto* header = resp_header->mutable_header(); + header->set_key("add-header-from"); + header->set_value("first_matched"); + route_first_matched->mutable_route()->set_cluster("cluster_0"); + + auto* route_second_matched = + hcm.mutable_route_config()->mutable_virtual_hosts(0)->add_routes(); + route_second_matched->mutable_match()->set_prefix("/user/api"); + resp_header = route_second_matched->add_response_headers_to_add(); + header = resp_header->mutable_header(); + header->set_key("add-header-from"); + header->set_value("second_matched"); + route_second_matched->mutable_route()->set_cluster("cluster_0"); + + auto* route_should_not_matched = + hcm.mutable_route_config()->mutable_virtual_hosts(0)->add_routes(); + route_should_not_matched->mutable_match()->set_prefix("/api"); + resp_header = route_should_not_matched->add_response_headers_to_add(); + header = resp_header->mutable_header(); + header->set_key("add-header-from"); + header->set_value("should_not_matched"); + route_should_not_matched->mutable_route()->set_cluster("cluster_0"); + }); + + initializeBasicFilter(so_id, "test.com"); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/disney/api/xx?refreshRoute=1"}, + {":scheme", "http"}, + {":authority", "test.com"}}; + + auto encoder_decoder = codec_client_->startRequest(request_headers, true); + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + upstream_request_->encodeHeaders(response_headers, true); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("second_matched", getHeader(response->headers(), "add-header-from")); + + cleanup(); +} + } // namespace Envoy diff --git a/contrib/golang/filters/http/test/test_data/BUILD b/contrib/golang/filters/http/test/test_data/BUILD new file mode 100644 index 0000000000000..7c0c418d0c284 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/BUILD @@ -0,0 +1,33 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +licenses(["notice"]) # Apache 2 + +go_binary( + name = "plugins.so", + srcs = [ + "plugins.go", + ], + out = "plugins.so", + cgo = True, + importpath = "github.com/envoyproxy/envoy/contrib/golang/filters/http/test/test_data", + linkmode = "c-shared", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "//contrib/golang/filters/http/test/test_data/access_log", + "//contrib/golang/filters/http/test/test_data/action", + "//contrib/golang/filters/http/test/test_data/add_data", + "//contrib/golang/filters/http/test/test_data/basic", + "//contrib/golang/filters/http/test/test_data/buffer", + "//contrib/golang/filters/http/test/test_data/bufferinjectdata", + "//contrib/golang/filters/http/test/test_data/echo", + "//contrib/golang/filters/http/test/test_data/metric", + "//contrib/golang/filters/http/test/test_data/passthrough", + "//contrib/golang/filters/http/test/test_data/property", + "//contrib/golang/filters/http/test/test_data/routeconfig", + "//contrib/golang/filters/http/test/test_data/websocket", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/access_log/BUILD b/contrib/golang/filters/http/test/test_data/access_log/BUILD new file mode 100644 index 0000000000000..f8cec79007a87 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/access_log/BUILD @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "access_log", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/access_log", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@com_github_cncf_xds_go//xds/type/v3:type", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/access_log/config.go b/contrib/golang/filters/http/test/test_data/access_log/config.go new file mode 100644 index 0000000000000..e73902790162f --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/access_log/config.go @@ -0,0 +1,40 @@ +package access_log + +import ( + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "access_log" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) +} + +type config struct { +} + +type parser struct { +} + +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + conf := &config{} + return conf, nil +} + +func (p *parser) Merge(parent interface{}, child interface{}) interface{} { + return child +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + conf, ok := c.(*config) + if !ok { + panic("unexpected config type") + } + return &filter{ + callbacks: callbacks, + config: conf, + } +} diff --git a/contrib/golang/filters/http/test/test_data/access_log/filter.go b/contrib/golang/filters/http/test/test_data/access_log/filter.go new file mode 100644 index 0000000000000..18f6b01572bd9 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/access_log/filter.go @@ -0,0 +1,173 @@ +package access_log + +import ( + "strconv" + "strings" + "sync" + "time" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +var ( + counter = 0 + wg = &sync.WaitGroup{} + + respCode string + respSize string + canRunAsyncly bool + + canRunAsynclyForDownstreamStart bool + + canRunAsynclyForDownstreamPeriodic bool + + referers = []string{} + + xReqTrailer string + xRespTrailer string +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + config *config +} + +func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + if counter == 0 { + query, _ := f.callbacks.GetProperty("request.query") + if query == "periodic=1" { + go func() { + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() + + // trigger AccessLogDownstreamPeriodic + time.Sleep(110 * time.Millisecond) + f.callbacks.DecoderFilterCallbacks().Continue(api.Continue) + }() + return api.Running + } + } + + if counter > 0 { + wg.Wait() + header.Set("respCode", respCode) + header.Set("respSize", respSize) + header.Set("canRunAsyncly", strconv.FormatBool(canRunAsyncly)) + header.Set("canRunAsynclyForDownstreamStart", strconv.FormatBool(canRunAsynclyForDownstreamStart)) + header.Set("canRunAsynclyForDownstreamPeriodic", strconv.FormatBool(canRunAsynclyForDownstreamPeriodic)) + + header.Set("referers", strings.Join(referers, ";")) + + // reset for the next test + referers = []string{} + // the counter will be 0 when this request is ended + counter = -1 + + header.Set("x-req-trailer", xReqTrailer) + header.Set("x-resp-trailer", xRespTrailer) + } + + return api.Continue +} + +func (f *filter) OnLogDownstreamStart(reqHeader api.RequestHeaderMap) { + referer, err := f.callbacks.GetProperty("request.referer") + if err != nil { + api.LogErrorf("err: %s", err) + return + } + + refererFromHdr, _ := reqHeader.Get("referer") + if referer != refererFromHdr { + api.LogErrorf("referer from property: %s, referer from header: %s", referer, refererFromHdr) + return + } + + referers = append(referers, referer) + + wg.Add(1) + go func() { + time.Sleep(1 * time.Millisecond) + canRunAsynclyForDownstreamStart = true + wg.Done() + }() +} + +func (f *filter) OnLogDownstreamPeriodic(reqHeader api.RequestHeaderMap, reqTrailer api.RequestTrailerMap, respHeader api.ResponseHeaderMap, respTrailer api.ResponseTrailerMap) { + referer, err := f.callbacks.GetProperty("request.referer") + if err != nil { + api.LogErrorf("err: %s", err) + return + } + + refererFromHdr, _ := reqHeader.Get("referer") + if referer != refererFromHdr { + api.LogErrorf("referer from property: %s, referer from header: %s", referer, refererFromHdr) + return + } + + referers = append(referers, referer) + + wg.Add(1) + go func() { + time.Sleep(1 * time.Millisecond) + canRunAsynclyForDownstreamPeriodic = true + wg.Done() + }() +} + +func (f *filter) OnStreamComplete() { + f.callbacks.StreamInfo().DynamicMetadata().Set("golang", "access_log_var", "access_log_var written by Golang filter") +} + +func (f *filter) OnLog(reqHeader api.RequestHeaderMap, reqTrailer api.RequestTrailerMap, respHeader api.ResponseHeaderMap, respTrailer api.ResponseTrailerMap) { + referer, err := f.callbacks.GetProperty("request.referer") + if err != nil { + api.LogErrorf("err: %s", err) + return + } + + refererFromHdr, _ := reqHeader.Get("referer") + if referer != refererFromHdr { + api.LogErrorf("referer from property: %s, referer from header: %s", referer, refererFromHdr) + return + } + + if reqTrailer != nil { + xReqTrailer, _ = reqTrailer.Get("x-trailer") + } + + code, ok := f.callbacks.StreamInfo().ResponseCode() + if !ok { + return + } + respCode = strconv.Itoa(int(code)) + api.LogCritical(respCode) + + status, _ := respHeader.Get(":status") + if status != respCode { + api.LogErrorf("status from StreamInfo: %s, status from header: %s", respCode, status) + return + } + + if respTrailer != nil { + xRespTrailer, _ = respTrailer.Get("x-trailer") + } + + size, err := f.callbacks.GetProperty("response.size") + if err != nil { + api.LogErrorf("err: %s", err) + return + } + respSize = size + + wg.Add(1) + go func() { + time.Sleep(1 * time.Millisecond) + canRunAsyncly = true + wg.Done() + }() + + counter++ +} diff --git a/contrib/golang/filters/http/test/test_data/action/BUILD b/contrib/golang/filters/http/test/test_data/action/BUILD new file mode 100644 index 0000000000000..9f514ee18b7cb --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/action/BUILD @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "action", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/action", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@com_github_cncf_xds_go//xds/type/v3:type", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/action/config.go b/contrib/golang/filters/http/test/test_data/action/config.go new file mode 100644 index 0000000000000..5804943012928 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/action/config.go @@ -0,0 +1,18 @@ +package action + +import ( + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "action" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, http.NullParser) +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + return &filter{ + callbacks: callbacks, + } +} diff --git a/contrib/golang/filters/http/test/test_data/action/filter.go b/contrib/golang/filters/http/test/test_data/action/filter.go new file mode 100644 index 0000000000000..a28e3fab04700 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/action/filter.go @@ -0,0 +1,72 @@ +package action + +import ( + "net/url" + "strings" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + query_params url.Values +} + +func parseQuery(path string) url.Values { + if idx := strings.Index(path, "?"); idx >= 0 { + query := path[idx+1:] + values, _ := url.ParseQuery(query) + return values + } + return make(url.Values) +} + +func getStatus(status string) api.StatusType { + switch status { + case "StopAndBuffer": + return api.StopAndBuffer + case "StopAndBufferWatermark": + return api.StopAndBufferWatermark + } + return api.Continue +} + +func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + f.query_params = parseQuery(header.Path()) + + decodeHeadersRet := f.query_params.Get("decodeHeadersRet") + async := f.query_params.Get("async") + if decodeHeadersRet != "" { + if async != "" { + go func() { + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() + f.callbacks.DecoderFilterCallbacks().Continue(getStatus(decodeHeadersRet)) + }() + return api.Running + } + + return getStatus(decodeHeadersRet) + } + + return api.Continue +} + +func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { + encodeHeadersRet := f.query_params.Get("encodeHeadersRet") + async := f.query_params.Get("async") + if encodeHeadersRet != "" { + if async != "" { + go func() { + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() + f.callbacks.EncoderFilterCallbacks().Continue(getStatus(encodeHeadersRet)) + }() + return api.Running + } + + return getStatus(encodeHeadersRet) + } + + return api.Continue +} diff --git a/contrib/golang/filters/http/test/test_data/add_data/BUILD b/contrib/golang/filters/http/test/test_data/add_data/BUILD new file mode 100644 index 0000000000000..b91299616a422 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/add_data/BUILD @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "add_data", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/add_data", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@com_github_cncf_xds_go//xds/type/v3:type", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/add_data/config.go b/contrib/golang/filters/http/test/test_data/add_data/config.go new file mode 100644 index 0000000000000..d257e56989f22 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/add_data/config.go @@ -0,0 +1,39 @@ +package add_data + +import ( + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "add_data" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) +} + +type config struct { +} + +type parser struct { +} + +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + return &config{}, nil +} + +func (p *parser) Merge(parent interface{}, child interface{}) interface{} { + return child +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + conf, ok := c.(*config) + if !ok { + panic("unexpected config type") + } + return &filter{ + callbacks: callbacks, + config: conf, + } +} diff --git a/contrib/golang/filters/http/test/test_data/add_data/filter.go b/contrib/golang/filters/http/test/test_data/add_data/filter.go new file mode 100644 index 0000000000000..fdf7ae8f83fc6 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/add_data/filter.go @@ -0,0 +1,92 @@ +package add_data + +import ( + "net/url" + "strconv" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + config *config + params url.Values +} + +func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api.StatusType { + path := headers.Path() + u, _ := url.Parse(path) + f.params = u.Query() + + if f.params.Has("calledInDecodeHeaders") { + headers.Set(":method", "POST") + headers.Set("content-length", strconv.Itoa(len(f.params.Get("calledInDecodeHeaders")))) + f.callbacks.DecoderFilterCallbacks().AddData([]byte(f.params.Get("calledInDecodeHeaders")), true) + } + + return api.Continue +} + +func (f *filter) callNotAllowed(data string, buffer api.BufferInstance) { + defer func() { + if p := recover(); p != nil { + buffer.Append([]byte(" called in DecodeData is not allowed")) + } + }() + f.callbacks.DecoderFilterCallbacks().AddData([]byte(data), true) +} + +func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + data := f.params.Get("calledInDecodeData") + if len(data) > 0 && endStream { + f.callNotAllowed(data, buffer) + } + + if f.params.Has("bufferAllData") { + return api.StopAndBuffer + } + return api.Continue +} + +func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType { + if f.params.Has("calledInDecodeTrailers") { + streaming := !f.params.Has("bufferAllData") + f.callbacks.DecoderFilterCallbacks().AddData([]byte(f.params.Get("calledInDecodeTrailers")), streaming) + } + return api.Continue +} + +func (f *filter) EncodeHeaders(headers api.ResponseHeaderMap, endStream bool) api.StatusType { + if f.params.Has("calledInEncodeHeaders") { + // Test both sync and async paths + go func() { + headers.Set("content-length", strconv.Itoa(len(f.params.Get("calledInEncodeHeaders")))) + f.callbacks.EncoderFilterCallbacks().AddData([]byte(f.params.Get("calledInEncodeHeaders")), true) + f.callbacks.EncoderFilterCallbacks().Continue(api.Continue) + }() + return api.Running + } + + return api.Continue +} + +func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + if f.params.Has("bufferAllData") { + return api.StopAndBuffer + } + return api.Continue +} + +func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType { + if f.params.Has("calledInEncodeTrailers") { + go func() { + streaming := !f.params.Has("bufferAllData") + f.callbacks.EncoderFilterCallbacks().AddData([]byte(f.params.Get("calledInEncodeTrailers")), streaming) + f.callbacks.EncoderFilterCallbacks().Continue(api.Continue) + }() + return api.Running + } + return api.Continue +} diff --git a/contrib/golang/filters/http/test/test_data/basic/BUILD b/contrib/golang/filters/http/test/test_data/basic/BUILD index f12124da24234..88ba80afa8d0e 100644 --- a/contrib/golang/filters/http/test/test_data/basic/BUILD +++ b/contrib/golang/filters/http/test/test_data/basic/BUILD @@ -1,20 +1,20 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary") +load("@io_bazel_rules_go//go:def.bzl", "go_library") licenses(["notice"]) # Apache 2 -go_binary( - name = "filter.so", +go_library( + name = "basic", srcs = [ "config.go", "filter.go", ], - out = "filter.so", cgo = True, - importpath = "github.com/envoyproxy/envoy/contrib/golang/filters/http/test/test_data/basic", - linkmode = "c-shared", + importpath = "example.com/test-data/basic", visibility = ["//visibility:public"], deps = [ "//contrib/golang/common/go/api", "//contrib/golang/filters/http/source/go/pkg/http", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", ], ) diff --git a/contrib/golang/filters/http/test/test_data/basic/config.go b/contrib/golang/filters/http/test/test_data/basic/config.go index c55011d5054dc..60b4f62d2e23f 100644 --- a/contrib/golang/filters/http/test/test_data/basic/config.go +++ b/contrib/golang/filters/http/test/test_data/basic/config.go @@ -1,4 +1,4 @@ -package main +package basic import ( "github.com/envoyproxy/envoy/contrib/golang/common/go/api" @@ -8,16 +8,14 @@ import ( const Name = "basic" func init() { - http.RegisterHttpFilterConfigFactoryAndParser(Name, ConfigFactory, nil) -} + api.LogCritical("init") + api.LogCritical(api.GetLogLevel().String()) -func ConfigFactory(interface{}) api.StreamFilterFactory { - return func(callbacks api.FilterCallbackHandler) api.StreamFilter { - return &filter{ - callbacks: callbacks, - } - } + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, http.NullParser) } -func main() { +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + return &filter{ + callbacks: callbacks, + } } diff --git a/contrib/golang/filters/http/test/test_data/basic/filter.go b/contrib/golang/filters/http/test/test_data/basic/filter.go index ad5c8c839c344..66a5d73321289 100644 --- a/contrib/golang/filters/http/test/test_data/basic/filter.go +++ b/contrib/golang/filters/http/test/test_data/basic/filter.go @@ -1,8 +1,9 @@ -package main +package basic import ( "fmt" "net/url" + "reflect" "strconv" "strings" "time" @@ -16,11 +17,11 @@ type filter struct { callbacks api.FilterCallbackHandler req_body_length uint64 query_params url.Values - protocol string scheme string method string path string host string + all_headers map[string][]string // for bad api call testing header api.RequestHeaderMap @@ -33,6 +34,10 @@ type filter struct { databuffer string // return api.Stop panic string // hit panic in which phase badapi bool // bad api call + newPath string // set new path + clearRoute bool // clear route cache + + refreshRoute bool // refresh route cache } func parseQuery(path string) url.Values { @@ -55,7 +60,6 @@ func (f *filter) initRequest(header api.RequestHeaderMap) { f.req_body_length = 0 - f.protocol = header.Protocol() f.scheme = header.Scheme() f.method = header.Method() f.path = header.Path() @@ -78,21 +82,26 @@ func (f *filter) initRequest(header api.RequestHeaderMap) { f.localreplay = f.query_params.Get("localreply") f.panic = f.query_params.Get("panic") f.badapi = f.query_params.Get("badapi") != "" + f.newPath = f.query_params.Get("newPath") + f.clearRoute = f.query_params.Get("clearRoute") != "" + f.refreshRoute = f.query_params.Get("refreshRoute") != "" } -func (f *filter) fail(msg string, a ...any) api.StatusType { +func (f *filter) fail(callbacks api.FilterProcessCallbacks, msg string, a ...any) api.StatusType { body := fmt.Sprintf(msg, a...) - f.callbacks.SendLocalReply(500, body, nil, 0, "") + f.callbacks.Log(api.Error, fmt.Sprintf("test failed: %s", body)) + callbacks.SendLocalReply(500, body, nil, 0, "") return api.LocalReply } -func (f *filter) sendLocalReply(phase string) api.StatusType { - headers := map[string]string{ - "Content-type": "text/html", - "test-phase": phase, +func (f *filter) sendLocalReply(callbacks api.FilterProcessCallbacks, phase string) api.StatusType { + headers := map[string][]string{ + "Content-type": {"text/html"}, + "test-phase": {phase}, + "x-two-values": {"foo", "bar"}, } body := fmt.Sprintf("forbidden from go in %s\r\n", phase) - f.callbacks.SendLocalReply(403, body, headers, 0, "") + callbacks.SendLocalReply(403, body, headers, 0, "") return api.LocalReply } @@ -106,6 +115,24 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. f.callbacks.Log(api.Error, "log test") f.callbacks.Log(api.Critical, "log test") + api.LogTrace("log test") + api.LogDebug("log test") + api.LogInfo("log test") + api.LogWarn("log test") + api.LogError("log test") + api.LogCritical("log test") + + api.LogTracef("log test %v", endStream) + api.LogDebugf("log test %v", endStream) + api.LogInfof("log test %v", endStream) + api.LogWarnf("log test %v", endStream) + api.LogErrorf("log test %v", endStream) + api.LogCriticalf("log test %v", endStream) + + if f.callbacks.LogLevel() != api.GetLogLevel() { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "log level mismatch") + } + if f.sleep { time.Sleep(time.Millisecond * 100) // sleep 100 ms } @@ -115,21 +142,21 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. md := f.callbacks.StreamInfo().DynamicMetadata() empty_metadata := md.Get("filter.go") if len(empty_metadata) != 0 { - return f.fail("Metadata should be empty") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata should be empty") } md.Set("filter.go", "foo", "bar") metadata := md.Get("filter.go") if len(metadata) == 0 { - return f.fail("Metadata should not be empty") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata should not be empty") } k, ok := metadata["foo"] if !ok { - return f.fail("Metadata foo should be found") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata foo should be found") } if fmt.Sprint(k) != "bar" { - return f.fail("Metadata foo has unexpected value %v", k) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata foo has unexpected value %v", k) } } @@ -140,12 +167,12 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. header.Add("go-state-test-header-key", val) if strings.Contains(f.localreplay, "decode-header") { - return f.sendLocalReply("decode-header") + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), "decode-header") } header.Range(func(key, value string) bool { if key == ":path" && value != f.path { - f.fail("path not match in Range") + f.fail(f.callbacks.DecoderFilterCallbacks(), "path not match in Range") return false } return true @@ -153,20 +180,69 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. header.RangeWithCopy(func(key, value string) bool { if key == ":path" && value != f.path { - f.fail("path not match in RangeWithCopy") + f.fail(f.callbacks.DecoderFilterCallbacks(), "path not match in RangeWithCopy") return false } return true }) + test_header_key := "test-header-copy" + + old_value := "old-value" + + header.Set(test_header_key, old_value) + + f.all_headers = make(map[string][]string) + + header.RangeWithCopy(func(key, value string) bool { + f.all_headers[key] = append(f.all_headers[key], value) + return true + }) + + header_map := header.GetAllHeaders() + + if !reflect.DeepEqual(f.all_headers, header_map) { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "GetAllHeaders returned incorrect data, expected:\n%v\n got:\n%v", f.all_headers, header_map) + } + + header.Set(test_header_key, "new-value") + + if !reflect.DeepEqual(header_map[test_header_key], []string{old_value}) { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "GetAllHeaders output changed - expected '%v', got '%v'", []string{old_value}, header_map[test_header_key]) + } + origin, found := header.Get("x-test-header-0") hdrs := header.Values("x-test-header-0") if found { if origin != hdrs[0] { - return f.fail("Values return incorrect data %v", hdrs) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values return incorrect data %v", hdrs) } } else if hdrs != nil { - return f.fail("Values return unexpected data %v", hdrs) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values return unexpected data %v", hdrs) + } + + if found { + upperCase, _ := header.Get("X-Test-Header-0") + if upperCase != origin { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Get should be case-insensitive") + } + upperCaseHdrs := header.Values("X-Test-Header-0") + if hdrs[0] != upperCaseHdrs[0] { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values should be case-insensitive") + } + } + + header.Add("UpperCase", "header") + if hdr, _ := header.Get("uppercase"); hdr != "header" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Add should be case-insensitive") + } + header.Set("UpperCase", "header") + if hdr, _ := header.Get("uppercase"); hdr != "header" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Set should be case-insensitive") + } + header.Del("UpperCase") + if hdr, _ := header.Get("uppercase"); hdr != "" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Del should be case-insensitive") } header.Add("existed-header", "bar") @@ -185,6 +261,19 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. if f.panic == "decode-header" { badcode() } + + if f.newPath != "" { + header.SetPath(f.newPath) + } + if f.clearRoute { + f.callbacks.ClearRouteCache() + } + + if f.refreshRoute { + header.SetPath("/user/api/") // path used to match the new route + f.callbacks.RefreshRouteCache() + header.SetPath("/api/") // path used by the upstream + } return api.Continue } @@ -194,11 +283,16 @@ func (f *filter) decodeData(buffer api.BufferInstance, endStream bool) api.Statu time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "decode-data") { - return f.sendLocalReply("decode-data") + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), "decode-data") } f.req_body_length += uint64(buffer.Len()) if buffer.Len() != 0 { data := buffer.String() + if string(buffer.Bytes()) != data { + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), fmt.Sprintf("data in bytes: %s vs data in string: %s", + string(buffer.Bytes()), data)) + } + buffer.SetString(strings.ToUpper(data)) buffer.AppendString("_append") buffer.PrependString("prepend_") @@ -222,17 +316,40 @@ func (f *filter) decodeTrailers(trailers api.RequestTrailerMap) api.StatusType { time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "decode-trailer") { - return f.sendLocalReply("decode-trailer") + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), "decode-trailer") } trailers.Add("existed-trailer", "bar") trailers.Set("x-test-trailer-0", "bar") trailers.Del("x-test-trailer-1") - if trailers.GetRaw("existed-trailer") == "foo" { + existed, _ := trailers.Get("existed-trailer") + if existed == "foo" { trailers.Add("x-test-trailer-2", "bar") } + upperCase, _ := trailers.Get("X-Test-Trailer-0") + if upperCase != "bar" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Get should be case-insensitive") + } + upperCaseHdrs := trailers.Values("X-Test-Trailer-0") + if upperCaseHdrs[0] != "bar" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values should be case-insensitive") + } + + trailers.Add("UpperCase", "trailers") + if hdr, _ := trailers.Get("uppercase"); hdr != "trailers" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Add should be case-insensitive") + } + trailers.Set("UpperCase", "trailers") + if hdr, _ := trailers.Get("uppercase"); hdr != "trailers" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Set should be case-insensitive") + } + trailers.Del("UpperCase") + if hdr, _ := trailers.Get("uppercase"); hdr != "" { + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Del should be case-insensitive") + } + if f.panic == "decode-trailer" { badcode() } @@ -244,7 +361,7 @@ func (f *filter) encodeHeaders(header api.ResponseHeaderMap, endStream bool) api time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "encode-header") { - return f.sendLocalReply("encode-header") + return f.sendLocalReply(f.callbacks.EncoderFilterCallbacks(), "encode-header") } if protocol, ok := f.callbacks.StreamInfo().Protocol(); ok { @@ -267,10 +384,10 @@ func (f *filter) encodeHeaders(header api.ResponseHeaderMap, endStream bool) api hdrs := header.Values("x-test-header-0") if found { if origin != hdrs[0] { - return f.fail("Values return incorrect data %v", hdrs) + return f.fail(f.callbacks.EncoderFilterCallbacks(), "Values return incorrect data %v", hdrs) } } else if hdrs != nil { - return f.fail("Values return unexpected data %v", hdrs) + return f.fail(f.callbacks.EncoderFilterCallbacks(), "Values return unexpected data %v", hdrs) } if status, ok := header.Status(); ok { @@ -310,7 +427,7 @@ func (f *filter) encodeData(buffer api.BufferInstance, endStream bool) api.Statu time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "encode-data") { - return f.sendLocalReply("encode-data") + return f.sendLocalReply(f.callbacks.EncoderFilterCallbacks(), "encode-data") } data := buffer.String() buffer.SetString(strings.ToUpper(data)) @@ -326,7 +443,7 @@ func (f *filter) encodeTrailers(trailers api.ResponseTrailerMap) api.StatusType time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "encode-trailer") { - return f.sendLocalReply("encode-trailer") + return f.sendLocalReply(f.callbacks.EncoderFilterCallbacks(), "encode-trailer") } if f.panic == "encode-trailer" { @@ -339,11 +456,11 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. f.initRequest(header) if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() status := f.decodeHeaders(header, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.DecoderFilterCallbacks().Continue(status) } }() return api.Running @@ -356,11 +473,11 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() status := f.decodeData(buffer, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.DecoderFilterCallbacks().Continue(status) } }() return api.Running @@ -373,11 +490,11 @@ func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.Statu func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() status := f.decodeTrailers(trailers) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.DecoderFilterCallbacks().Continue(status) } }() return api.Running @@ -390,11 +507,11 @@ func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType { func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() status := f.encodeHeaders(header, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.EncoderFilterCallbacks().Continue(status) } }() return api.Running @@ -407,11 +524,11 @@ func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() status := f.encodeData(buffer, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.EncoderFilterCallbacks().Continue(status) } }() return api.Running @@ -424,11 +541,11 @@ func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.Statu func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() status := f.encodeTrailers(trailers) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.EncoderFilterCallbacks().Continue(status) } }() return api.Running @@ -438,5 +555,9 @@ func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType } } +func (f *filter) OnLog(reqHeader api.RequestHeaderMap, reqTrailer api.RequestTrailerMap, respHeader api.ResponseHeaderMap, respTrailer api.ResponseTrailerMap) { + api.LogError("call log in OnLog") +} + func (f *filter) OnDestroy(reason api.DestroyReason) { } diff --git a/contrib/golang/filters/http/test/test_data/basic/go.mod b/contrib/golang/filters/http/test/test_data/basic/go.mod deleted file mode 100644 index 2a652f058099a..0000000000000 --- a/contrib/golang/filters/http/test/test_data/basic/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module example.com/basic - -go 1.18 - -require github.com/envoyproxy/envoy v1.24.0 - -require google.golang.org/protobuf v1.33.0 // indirect - -replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/buffer/BUILD b/contrib/golang/filters/http/test/test_data/buffer/BUILD new file mode 100644 index 0000000000000..8c1540b70c5a0 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/buffer/BUILD @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "buffer", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/buffer", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@com_github_cncf_xds_go//xds/type/v3:type", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/buffer/config.go b/contrib/golang/filters/http/test/test_data/buffer/config.go new file mode 100644 index 0000000000000..a047a98a45f66 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/buffer/config.go @@ -0,0 +1,40 @@ +package buffer + +import ( + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "buffer" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) +} + +type config struct { +} + +type parser struct { +} + +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + conf := &config{} + return conf, nil +} + +func (p *parser) Merge(parent interface{}, child interface{}) interface{} { + return child +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + conf, ok := c.(*config) + if !ok { + panic("unexpected config type") + } + return &filter{ + callbacks: callbacks, + config: conf, + } +} diff --git a/contrib/golang/filters/http/test/test_data/buffer/filter.go b/contrib/golang/filters/http/test/test_data/buffer/filter.go new file mode 100644 index 0000000000000..62b0233d0b7f6 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/buffer/filter.go @@ -0,0 +1,138 @@ +package buffer + +import ( + "fmt" + "reflect" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + path string + config *config + + failed bool +} + +func testReset(b api.BufferInstance) { + b.Reset() + + bs := b.Bytes() + if len(bs) > 0 { + panic(fmt.Sprintf("unexpected data: %s", string(bs))) + } +} + +func testDrain(b api.BufferInstance) { + b.Drain(40) + bs := b.Bytes() + if string(bs) != "1234512345" { + panic(fmt.Sprintf("unexpected data: %s", string(bs))) + } + + b.Drain(5) + bs = b.Bytes() + if string(bs) != "12345" { + panic(fmt.Sprintf("unexpected data: %s", string(bs))) + } + + b.Drain(10) + bs = b.Bytes() + if string(bs) != "" { + panic(fmt.Sprintf("unexpected data: %s", string(bs))) + } + + // drain when all data are drained + b.Drain(10) + bs = b.Bytes() + if string(bs) != "" { + panic(fmt.Sprintf("unexpected data: %s", string(bs))) + } + + // bad offset + for _, n := range []int{-1, 0} { + b.Drain(n) + } +} + +func testResetAfterDrain(b api.BufferInstance) { + b.Drain(40) + b.Reset() + bs := b.Bytes() + if string(bs) != "" { + panic(fmt.Sprintf("unexpected data: %s", string(bs))) + } +} + +func panicIfNotEqual(a, b any) { + if !reflect.DeepEqual(a, b) { + panic(fmt.Sprintf("expected %v, got %v", a, b)) + } +} + +func panicIfLenMismatch(b api.BufferInstance, size int) { + panicIfNotEqual(size, b.Len()) + panicIfNotEqual(len(b.Bytes()), b.Len()) +} + +func testLen(b api.BufferInstance) { + b.Set([]byte("12")) + panicIfLenMismatch(b, 2) + b.SetString("123") + panicIfLenMismatch(b, 3) + + b.Write([]byte("45")) + panicIfLenMismatch(b, 5) + b.WriteString("67") + panicIfLenMismatch(b, 7) + b.WriteByte('8') + panicIfLenMismatch(b, 8) + b.WriteUint16(90) + panicIfLenMismatch(b, 10) + b.WriteUint32(12) + panicIfLenMismatch(b, 12) + b.WriteUint64(12) + panicIfLenMismatch(b, 14) + + b.Drain(2) + panicIfLenMismatch(b, 12) + b.Write([]byte("45")) + panicIfLenMismatch(b, 14) + + b.Reset() + panicIfLenMismatch(b, 0) + + b.Append([]byte("12")) + panicIfLenMismatch(b, 2) + b.Prepend([]byte("0")) + panicIfLenMismatch(b, 3) + b.AppendString("345") + panicIfLenMismatch(b, 6) + b.PrependString("00") + panicIfLenMismatch(b, 8) +} + +func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + if endStream { + return api.Continue + } + // run once + + query, _ := f.callbacks.GetProperty("request.query") + switch query { + case "Reset": + testReset(buffer) + case "ResetAfterDrain": + testResetAfterDrain(buffer) + case "Drain": + testDrain(buffer) + case "Len": + testLen(buffer) + default: + panic(fmt.Sprintf("unknown case %s", query)) + } + return api.Continue +} diff --git a/contrib/golang/filters/http/test/test_data/bufferinjectdata/BUILD b/contrib/golang/filters/http/test/test_data/bufferinjectdata/BUILD new file mode 100644 index 0000000000000..181eff4e54b90 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/bufferinjectdata/BUILD @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "bufferinjectdata", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/bufferinjectdata", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@com_github_cncf_xds_go//xds/type/v3:type", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/bufferinjectdata/config.go b/contrib/golang/filters/http/test/test_data/bufferinjectdata/config.go new file mode 100644 index 0000000000000..12476751744de --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/bufferinjectdata/config.go @@ -0,0 +1,39 @@ +package bufferinjectdata + +import ( + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "bufferinjectdata" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) +} + +type config struct { +} + +type parser struct { +} + +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + return &config{}, nil +} + +func (p *parser) Merge(parent interface{}, child interface{}) interface{} { + return child +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + conf, ok := c.(*config) + if !ok { + panic("unexpected config type") + } + return &filter{ + callbacks: callbacks, + config: conf, + } +} diff --git a/contrib/golang/filters/http/test/test_data/bufferinjectdata/filter.go b/contrib/golang/filters/http/test/test_data/bufferinjectdata/filter.go new file mode 100644 index 0000000000000..7163cf297d3a7 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/bufferinjectdata/filter.go @@ -0,0 +1,152 @@ +package bufferinjectdata + +import ( + "net/url" + "runtime/debug" + "sync" + "time" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + params url.Values + config *config + + count int +} + +func (f *filter) disallowInjectData() { + defer func() { + if p := recover(); p != nil { + api.LogErrorf("panic: %v\n%s", p, debug.Stack()) + f.callbacks.DecoderFilterCallbacks().SendLocalReply(400, "Not allowed", nil, 0, "") + } + }() + f.callbacks.DecoderFilterCallbacks().InjectData([]byte("just try")) +} + +func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api.StatusType { + path := headers.Path() + u, _ := url.Parse(path) + f.params = u.Query() + + if f.params.Has("inject_data_when_processing_header") { + f.disallowInjectData() + return api.LocalReply + } + + if f.params.Has("bufferingly_decode") { + return api.StopAndBuffer + } + return api.Continue +} + +func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + if f.params.Has("inject_data_when_processing_data_synchronously") { + f.disallowInjectData() + return api.LocalReply + } + + // buffer.InjectData must be called in async mode + go func() { + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() + + status := f.decodeData(buffer, endStream) + if status != api.LocalReply { + f.callbacks.DecoderFilterCallbacks().Continue(status) + } + }() + return api.Running +} + +func (f *filter) decodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + cb := f.callbacks.DecoderFilterCallbacks() + if f.params.Has("nonbufferingly_decode") { + return f.processDataNonbufferingly(cb, buffer, endStream) + } else if f.params.Has("bufferingly_decode") { + return f.processDataBufferingly(cb, buffer, endStream) + } + return api.Continue +} + +func (f *filter) EncodeHeaders(headers api.ResponseHeaderMap, endStream bool) api.StatusType { + if f.params.Has("bufferingly_encode") { + return api.StopAndBuffer + } + return api.Continue +} + +func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + // buffer.InjectData must be called in async mode + go func() { + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() + + status := f.encodeData(buffer, endStream) + if status != api.LocalReply { + f.callbacks.EncoderFilterCallbacks().Continue(status) + } + }() + return api.Running +} + +func (f *filter) encodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + cb := f.callbacks.EncoderFilterCallbacks() + if f.params.Has("nonbufferingly_encode") { + return f.processDataNonbufferingly(cb, buffer, endStream) + } + if f.params.Has("bufferingly_encode") { + return f.processDataBufferingly(cb, buffer, endStream) + } + return api.Continue +} + +func (f *filter) processDataNonbufferingly(cb api.FilterProcessCallbacks, buffer api.BufferInstance, endStream bool) api.StatusType { + f.flushInNonbufferedResponse(cb, buffer) + f.count++ + return api.Continue +} + +func injectData(cb api.FilterProcessCallbacks, data string, wait bool) { + cb.InjectData([]byte(data)) +} + +func (f *filter) flushInNonbufferedResponse(cb api.FilterProcessCallbacks, buffer api.BufferInstance) { + // The remote sends: "To be, " and then "that is " + api.LogInfof("The remote sends %s", buffer.String()) + cb.InjectData(buffer.Bytes()) + buffer.Reset() + if f.count == 0 { + injectData(cb, "or not to be, ", false) + } else if f.count == 1 { + injectData(cb, "the question", false) + } +} + +func (f *filter) processDataBufferingly(cb api.FilterProcessCallbacks, buffer api.BufferInstance, endStream bool) api.StatusType { + if !endStream { + return api.StopAndBuffer + } + + var wg sync.WaitGroup + wg.Add(1) + go func(buffer api.BufferInstance) { + defer wg.Done() + flushInBufferedResponse(cb, buffer) + }(buffer) + wg.Wait() + return api.Continue +} + +func flushInBufferedResponse(cb api.FilterProcessCallbacks, buffer api.BufferInstance) { + // The remote sends: "To be, " + api.LogInfof("The remote sends %s", buffer.String()) + cb.InjectData(buffer.Bytes()) + buffer.Reset() + injectData(cb, "or not to be, ", false) + time.Sleep(10 * time.Millisecond) + injectData(cb, "that is the question", false) +} diff --git a/contrib/golang/filters/http/test/test_data/dummy/go.mod b/contrib/golang/filters/http/test/test_data/dummy/go.mod index 9e0b2bb864103..f6fc21cf81322 100644 --- a/contrib/golang/filters/http/test/test_data/dummy/go.mod +++ b/contrib/golang/filters/http/test/test_data/dummy/go.mod @@ -1,9 +1,9 @@ module example.com/dummy -go 1.18 +go 1.20 require github.com/envoyproxy/envoy v1.24.0 -require google.golang.org/protobuf v1.33.0 // indirect +require google.golang.org/protobuf v1.36.1 // indirect replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/dummy/plugin.go b/contrib/golang/filters/http/test/test_data/dummy/plugin.go index 2fbb9bac79519..29e3978337749 100644 --- a/contrib/golang/filters/http/test/test_data/dummy/plugin.go +++ b/contrib/golang/filters/http/test/test_data/dummy/plugin.go @@ -7,7 +7,7 @@ import ( ) func init() { - http.RegisterHttpFilterConfigFactoryAndParser("", http.PassThroughFactory, nil) + http.RegisterHttpFilterFactoryAndConfigParser("", http.PassThroughFactory, http.NullParser) } func main() { diff --git a/contrib/golang/filters/http/test/test_data/echo/BUILD b/contrib/golang/filters/http/test/test_data/echo/BUILD index 1764f4ea0d171..37ab2f0a8fa72 100644 --- a/contrib/golang/filters/http/test/test_data/echo/BUILD +++ b/contrib/golang/filters/http/test/test_data/echo/BUILD @@ -1,17 +1,15 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary") +load("@io_bazel_rules_go//go:def.bzl", "go_library") licenses(["notice"]) # Apache 2 -go_binary( - name = "filter.so", +go_library( + name = "echo", srcs = [ "config.go", "filter.go", ], - out = "filter.so", cgo = True, - importpath = "github.com/envoyproxy/envoy/contrib/golang/filters/http/test/test_data/echo", - linkmode = "c-shared", + importpath = "example.com/test-data/echo", visibility = ["//visibility:public"], deps = [ "//contrib/golang/common/go/api", diff --git a/contrib/golang/filters/http/test/test_data/echo/config.go b/contrib/golang/filters/http/test/test_data/echo/config.go index 18c3b65bc2bde..90c719b538328 100644 --- a/contrib/golang/filters/http/test/test_data/echo/config.go +++ b/contrib/golang/filters/http/test/test_data/echo/config.go @@ -1,4 +1,4 @@ -package main +package echo import ( xds "github.com/cncf/xds/go/xds/type/v3" @@ -11,7 +11,7 @@ import ( const Name = "echo" func init() { - http.RegisterHttpFilterConfigFactoryAndParser(Name, ConfigFactory, &parser{}) + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) } type config struct { @@ -22,7 +22,7 @@ type config struct { type parser struct { } -func (p *parser) Parse(any *anypb.Any) (interface{}, error) { +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { configStruct := &xds.TypedStruct{} if err := any.UnmarshalTo(configStruct); err != nil { return nil, err @@ -43,17 +43,13 @@ func (p *parser) Merge(parent interface{}, child interface{}) interface{} { panic("TODO") } -func ConfigFactory(c interface{}) api.StreamFilterFactory { +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { conf, ok := c.(*config) if !ok { panic("unexpected config type") } - return func(callbacks api.FilterCallbackHandler) api.StreamFilter { - return &filter{ - callbacks: callbacks, - config: conf, - } + return &filter{ + callbacks: callbacks, + config: conf, } } - -func main() {} diff --git a/contrib/golang/filters/http/test/test_data/echo/filter.go b/contrib/golang/filters/http/test/test_data/echo/filter.go index 0b53066a3cff4..36cf4ed9738e9 100644 --- a/contrib/golang/filters/http/test/test_data/echo/filter.go +++ b/contrib/golang/filters/http/test/test_data/echo/filter.go @@ -1,4 +1,4 @@ -package main +package echo import ( "fmt" @@ -19,7 +19,7 @@ func (f *filter) sendLocalReply() api.StatusType { echoBody := f.config.echoBody { body := fmt.Sprintf("%s, path: %s\r\n", echoBody, f.path) - f.callbacks.SendLocalReply(403, body, nil, 0, "") + f.callbacks.DecoderFilterCallbacks().SendLocalReply(403, body, nil, 0, "") } // Force GC to free the body string. // For the case that C++ shouldn't touch the memory of the body string, diff --git a/contrib/golang/filters/http/test/test_data/echo/go.mod b/contrib/golang/filters/http/test/test_data/echo/go.mod deleted file mode 100644 index 2bc12bff34ef9..0000000000000 --- a/contrib/golang/filters/http/test/test_data/echo/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module example.com/echo - -go 1.18 - -require ( - github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 - github.com/envoyproxy/envoy v1.24.0 -) - -require github.com/google/go-cmp v0.5.9 // indirect - -require ( - github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect - google.golang.org/protobuf v1.33.0 -) - -replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/go.mod b/contrib/golang/filters/http/test/test_data/go.mod new file mode 100644 index 0000000000000..7fc0f07ced60f --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/go.mod @@ -0,0 +1,19 @@ +module example.com/test-data + +go 1.22 + +require github.com/envoyproxy/envoy v1.24.0 + +require ( + github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 + google.golang.org/protobuf v1.36.1 +) + +require ( + cel.dev/expr v0.15.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect +) + +replace github.com/envoyproxy/envoy => ../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/metric/BUILD b/contrib/golang/filters/http/test/test_data/metric/BUILD new file mode 100644 index 0000000000000..c334bf9e0577b --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/metric/BUILD @@ -0,0 +1,20 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "metric", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/metric", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/metric/config.go b/contrib/golang/filters/http/test/test_data/metric/config.go new file mode 100644 index 0000000000000..ced3f54478abb --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/metric/config.go @@ -0,0 +1,49 @@ +package metric + +import ( + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "metric" + +func init() { + api.LogCritical("init") + api.LogCritical(api.GetLogLevel().String()) + + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) +} + +type config struct { + counter api.CounterMetric + gauge api.GaugeMetric +} + +type parser struct { +} + +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + conf := &config{} + if callbacks != nil { + conf.counter = callbacks.DefineCounterMetric("test-counter") + conf.gauge = callbacks.DefineGaugeMetric("test-gauge") + } + return conf, nil +} + +func (p *parser) Merge(parent interface{}, child interface{}) interface{} { + panic("TODO") +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + conf, ok := c.(*config) + if !ok { + panic("unexpected config type") + } + return &filter{ + callbacks: callbacks, + config: conf, + } +} diff --git a/contrib/golang/filters/http/test/test_data/metric/filter.go b/contrib/golang/filters/http/test/test_data/metric/filter.go new file mode 100644 index 0000000000000..50cdd273321a1 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/metric/filter.go @@ -0,0 +1,76 @@ +package metric + +import ( + "net/url" + "strconv" + "strings" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + config *config + query_params url.Values + path string + + // test mode, from query parameters + async bool +} + +func parseQuery(path string) url.Values { + if idx := strings.Index(path, "?"); idx >= 0 { + query := path[idx+1:] + values, _ := url.ParseQuery(query) + return values + } + return make(url.Values) +} + +func (f *filter) initRequest(header api.RequestHeaderMap) { + f.path = header.Path() + f.query_params = parseQuery(f.path) + if f.query_params.Get("async") != "" { + f.async = true + } +} + +func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + f.config.counter.Increment(2) + value := f.config.counter.Get() + header.Add("go-metric-counter-test-header-key", strconv.FormatUint(value, 10)) + + f.config.counter.Record(1) + value = f.config.counter.Get() + header.Add("go-metric-counter-record-test-header-key", strconv.FormatUint(value, 10)) + + f.config.gauge.Increment(3) + value = f.config.gauge.Get() + header.Add("go-metric-gauge-test-header-key", strconv.FormatUint(value, 10)) + + f.config.gauge.Record(1) + value = f.config.gauge.Get() + header.Add("go-metric-gauge-record-test-header-key", strconv.FormatUint(value, 10)) + + return api.Continue +} + +func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + f.initRequest(header) + if f.async { + go func() { + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() + + status := f.decodeHeaders(header, endStream) + if status != api.LocalReply { + f.callbacks.DecoderFilterCallbacks().Continue(status) + } + }() + return api.Running + } else { + status := f.decodeHeaders(header, endStream) + return status + } +} diff --git a/contrib/golang/filters/http/test/test_data/passthrough/BUILD b/contrib/golang/filters/http/test/test_data/passthrough/BUILD index a9dd87316c58f..0126623ea5748 100644 --- a/contrib/golang/filters/http/test/test_data/passthrough/BUILD +++ b/contrib/golang/filters/http/test/test_data/passthrough/BUILD @@ -1,16 +1,14 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary") +load("@io_bazel_rules_go//go:def.bzl", "go_library") licenses(["notice"]) # Apache 2 -go_binary( - name = "filter.so", +go_library( + name = "passthrough", srcs = [ "filter.go", ], - out = "filter.so", cgo = True, - importpath = "github.com/envoyproxy/envoy/contrib/golang/filters/http/test/test_data/passthrough", - linkmode = "c-shared", + importpath = "example.com/test-data/passthrough", visibility = ["//visibility:public"], deps = [ "//contrib/golang/common/go/api", diff --git a/contrib/golang/filters/http/test/test_data/passthrough/filter.go b/contrib/golang/filters/http/test/test_data/passthrough/filter.go index 17b40b0e459e5..4669d047f08b4 100644 --- a/contrib/golang/filters/http/test/test_data/passthrough/filter.go +++ b/contrib/golang/filters/http/test/test_data/passthrough/filter.go @@ -1,12 +1,9 @@ -package main +package passthrough import ( "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" ) func init() { - http.RegisterHttpFilterConfigFactoryAndParser("passthrough", http.PassThroughFactory, nil) -} - -func main() { + http.RegisterHttpFilterFactoryAndConfigParser("passthrough", http.PassThroughFactory, http.NullParser) } diff --git a/contrib/golang/filters/http/test/test_data/passthrough/go.mod b/contrib/golang/filters/http/test/test_data/passthrough/go.mod deleted file mode 100644 index ac9f86a83f761..0000000000000 --- a/contrib/golang/filters/http/test/test_data/passthrough/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module example.com/passthrough - -go 1.18 - -require github.com/envoyproxy/envoy v1.24.0 - -require google.golang.org/protobuf v1.33.0 // indirect - -replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/passthrough/go.sum b/contrib/golang/filters/http/test/test_data/passthrough/go.sum deleted file mode 100644 index 00f5993c956c4..0000000000000 --- a/contrib/golang/filters/http/test/test_data/passthrough/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/contrib/golang/filters/http/test/test_data/plugins.go b/contrib/golang/filters/http/test/test_data/plugins.go new file mode 100644 index 0000000000000..4afedeea77b77 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/plugins.go @@ -0,0 +1,18 @@ +package main + +import ( + _ "example.com/test-data/access_log" + _ "example.com/test-data/action" + _ "example.com/test-data/add_data" + _ "example.com/test-data/basic" + _ "example.com/test-data/buffer" + _ "example.com/test-data/bufferinjectdata" + _ "example.com/test-data/echo" + _ "example.com/test-data/metric" + _ "example.com/test-data/passthrough" + _ "example.com/test-data/property" + _ "example.com/test-data/routeconfig" + _ "example.com/test-data/websocket" +) + +func main() {} diff --git a/contrib/golang/filters/http/test/test_data/property/BUILD b/contrib/golang/filters/http/test/test_data/property/BUILD new file mode 100644 index 0000000000000..29ae5208c2ffd --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/property/BUILD @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "property", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/property", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@com_github_cncf_xds_go//xds/type/v3:type", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/property/config.go b/contrib/golang/filters/http/test/test_data/property/config.go new file mode 100644 index 0000000000000..80d9119f228bf --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/property/config.go @@ -0,0 +1,40 @@ +package property + +import ( + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "property" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) +} + +type config struct { +} + +type parser struct { +} + +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { + conf := &config{} + return conf, nil +} + +func (p *parser) Merge(parent interface{}, child interface{}) interface{} { + return child +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + conf, ok := c.(*config) + if !ok { + panic("unexpected config type") + } + return &filter{ + callbacks: callbacks, + config: conf, + } +} diff --git a/contrib/golang/filters/http/test/test_data/property/filter.go b/contrib/golang/filters/http/test/test_data/property/filter.go new file mode 100644 index 0000000000000..efbd314122a6d --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/property/filter.go @@ -0,0 +1,137 @@ +package property + +import ( + "strconv" + "time" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler + path string + config *config + + failed bool +} + +func (f *filter) assertProperty(name, exp string) { + act, err := f.callbacks.GetProperty(name) + if err != nil { + act = err.Error() + } + if exp != act { + f.callbacks.Log(api.Critical, name+" expect "+exp+" got "+act) + f.failed = true + } +} + +func (f *filter) panicIfFailed() { + if f.failed { + panic("Check the critical log for the failed cases") + } +} + +func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + ts, _ := f.callbacks.GetProperty("request.time") + ymd := ts[:len("2023-07-31T00:00:00")] + startTime, _ := time.Parse("2006-01-02T15:04:05", ymd) + if time.Now().UTC().Sub(startTime) > 1*time.Minute { + f.callbacks.Log(api.Critical, "got request.time "+ts) + f.failed = true + } + + f.assertProperty("request.protocol", "HTTP/1.1") + f.assertProperty("request.path", "/property?a=1") + f.assertProperty("request.url_path", "/property") + f.assertProperty("request.query", "a=1") + f.assertProperty("request.host", "test.com") + f.assertProperty("request.scheme", "http") + f.assertProperty("request.method", "POST") + f.assertProperty("request.referer", "r") + f.assertProperty("request.useragent", "ua") + f.assertProperty("request.id", "xri") + + f.assertProperty("request.duration", api.ErrValueNotFound.Error()) // available only when the request is finished + + f.assertProperty("source.address", f.callbacks.StreamInfo().DownstreamRemoteAddress()) + f.assertProperty("destination.address", f.callbacks.StreamInfo().DownstreamLocalAddress()) + f.assertProperty("connection.mtls", "false") + // route name can be determinated in the decode phase + f.assertProperty("xds.route_name", "test-route-name") + + // non-existed attribute + f.assertProperty("request.user_agent", api.ErrValueNotFound.Error()) + + // access response attribute in the decode phase + f.assertProperty("response.total_size", "0") + + // bad case + // strange input + for _, attr := range []string{ + ".", + ".total_size", + } { + f.assertProperty(attr, api.ErrValueNotFound.Error()) + } + // unsupported value type + for _, attr := range []string{ + // unknown type + "", + // map type + "request", + "request.", + } { + f.assertProperty(attr, api.ErrSerializationFailure.Error()) + } + + // error handling + _, err := f.callbacks.GetProperty(".not_found") + if err != api.ErrValueNotFound { + f.callbacks.Log(api.Critical, "unexpected error "+err.Error()) + f.failed = true + } + return api.Continue +} + +func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { + f.assertProperty("xds.route_name", "test-route-name") + f.assertProperty("xds.cluster_name", "cluster_0") + f.assertProperty("xds.cluster_metadata", "") + + code, _ := f.callbacks.StreamInfo().ResponseCode() + exp := "" + if code != 0 { + exp = strconv.Itoa(int(code)) + } + f.assertProperty("response.code", exp) + f.assertProperty("response.code_details", "via_upstream") + + f.assertProperty("request.size", "10") // "helloworld" + size, _ := f.callbacks.GetProperty("request.total_size") + intSize, _ := strconv.Atoi(size) + if intSize <= 10 { + f.callbacks.Log(api.Critical, "got request.total_size "+size) + f.failed = true + } + f.assertProperty("request.referer", "r") + + return api.Continue +} + +func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + f.assertProperty("response.code", "200") + + // panic if any condition is not met + f.panicIfFailed() + return api.Continue +} + +func (f *filter) OnLog(reqHeader api.RequestHeaderMap, reqTrailer api.RequestTrailerMap, respHeader api.ResponseHeaderMap, respTrailer api.ResponseTrailerMap) { + f.assertProperty("response.size", "7") // "goodbye" + + // panic if any condition is not met + f.panicIfFailed() +} diff --git a/contrib/golang/filters/http/test/test_data/routeconfig/BUILD b/contrib/golang/filters/http/test/test_data/routeconfig/BUILD index a477ad3ddab3c..f3e6a69188812 100644 --- a/contrib/golang/filters/http/test/test_data/routeconfig/BUILD +++ b/contrib/golang/filters/http/test/test_data/routeconfig/BUILD @@ -1,17 +1,15 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary") +load("@io_bazel_rules_go//go:def.bzl", "go_library") licenses(["notice"]) # Apache 2 -go_binary( - name = "filter.so", +go_library( + name = "routeconfig", srcs = [ "config.go", "filter.go", ], - out = "filter.so", cgo = True, - importpath = "github.com/envoyproxy/envoy/contrib/golang/filters/http/test/test_data/routeconfig", - linkmode = "c-shared", + importpath = "example.com/test-data/routeconfig", visibility = ["//visibility:public"], deps = [ "//contrib/golang/common/go/api", diff --git a/contrib/golang/filters/http/test/test_data/routeconfig/config.go b/contrib/golang/filters/http/test/test_data/routeconfig/config.go index 2c8779ceb8ad5..bd84db78f8a23 100644 --- a/contrib/golang/filters/http/test/test_data/routeconfig/config.go +++ b/contrib/golang/filters/http/test/test_data/routeconfig/config.go @@ -1,4 +1,4 @@ -package main +package routeconfig import ( "errors" @@ -13,19 +13,17 @@ import ( const Name = "routeconfig" func init() { - http.RegisterHttpFilterConfigFactoryAndParser(Name, configFactory, &parser{}) + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) } -func configFactory(c interface{}) api.StreamFilterFactory { +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { conf, ok := c.(*config) if !ok { panic("unexpected config type") } - return func(callbacks api.FilterCallbackHandler) api.StreamFilter { - return &filter{ - config: conf, - callbacks: callbacks, - } + return &filter{ + config: conf, + callbacks: callbacks, } } @@ -37,7 +35,7 @@ type config struct { type parser struct { } -func (p *parser) Parse(any *anypb.Any) (interface{}, error) { +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { configStruct := &xds.TypedStruct{} if err := any.UnmarshalTo(configStruct); err != nil { return nil, err @@ -71,6 +69,3 @@ func (p *parser) Merge(parent interface{}, child interface{}) interface{} { } return &newConfig } - -func main() { -} diff --git a/contrib/golang/filters/http/test/test_data/routeconfig/filter.go b/contrib/golang/filters/http/test/test_data/routeconfig/filter.go index d365d683ce129..85e693ee9b9b9 100644 --- a/contrib/golang/filters/http/test/test_data/routeconfig/filter.go +++ b/contrib/golang/filters/http/test/test_data/routeconfig/filter.go @@ -1,4 +1,4 @@ -package main +package routeconfig import ( "github.com/envoyproxy/envoy/contrib/golang/common/go/api" diff --git a/contrib/golang/filters/http/test/test_data/routeconfig/go.mod b/contrib/golang/filters/http/test/test_data/routeconfig/go.mod deleted file mode 100644 index 5522e19383203..0000000000000 --- a/contrib/golang/filters/http/test/test_data/routeconfig/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module example.com/routeconfig - -go 1.18 - -require ( - github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 - github.com/envoyproxy/envoy v1.24.0 -) - -require github.com/google/go-cmp v0.5.9 // indirect - -require ( - github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect - google.golang.org/protobuf v1.33.0 -) - -replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/test_data/websocket/BUILD b/contrib/golang/filters/http/test/test_data/websocket/BUILD new file mode 100644 index 0000000000000..4abd0f3c5abe3 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/websocket/BUILD @@ -0,0 +1,20 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +licenses(["notice"]) # Apache 2 + +go_library( + name = "websocket", + srcs = [ + "config.go", + "filter.go", + ], + cgo = True, + importpath = "example.com/test-data/websocket", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/websocket/config.go b/contrib/golang/filters/http/test/test_data/websocket/config.go new file mode 100644 index 0000000000000..2d377e1421f10 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/websocket/config.go @@ -0,0 +1,18 @@ +package websocket + +import ( + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "websocket" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, http.NullParser) +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + return &filter{ + callbacks: callbacks, + } +} diff --git a/contrib/golang/filters/http/test/test_data/websocket/filter.go b/contrib/golang/filters/http/test/test_data/websocket/filter.go new file mode 100644 index 0000000000000..468a946df39c6 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/websocket/filter.go @@ -0,0 +1,39 @@ +package websocket + +import ( + "fmt" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler +} + +func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + header.Set("test-websocket-req-key", "foo") + return api.Continue +} + +func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + f.callbacks.Log(api.Error, fmt.Sprintf("body: %s, end_stream: %v", buffer.String(), endStream)) + if !endStream && buffer.Len() != 0 { + buffer.PrependString("Hello_") + } + return api.Continue +} + +func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { + header.Set("test-websocket-rsp-key", "bar") + return api.Continue +} + +func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + f.callbacks.Log(api.Error, fmt.Sprintf("body: %s, end_stream: %v", buffer.String(), endStream)) + if !endStream && buffer.Len() != 0 { + buffer.PrependString("Bye_") + } + return api.Continue +} diff --git a/contrib/golang/filters/http/test/websocket_integration_test.cc b/contrib/golang/filters/http/test/websocket_integration_test.cc new file mode 100644 index 0000000000000..dcab4eb798846 --- /dev/null +++ b/contrib/golang/filters/http/test/websocket_integration_test.cc @@ -0,0 +1,121 @@ +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" + +#include "source/common/http/header_map_impl.h" +#include "source/common/protobuf/utility.h" + +#include "test/integration/utility.h" +#include "test/integration/websocket_integration_test.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/printers.h" +#include "test/test_common/utility.h" + +#include "absl/strings/str_cat.h" +#include "contrib/golang/filters/http/source/golang_filter.h" +#include "gtest/gtest.h" + +namespace Envoy { + +class GolangWebsocketIntegrationTest : public WebsocketIntegrationTest { +public: + void cleanup() { Dso::DsoManager::cleanUpForTest(); } +}; + +INSTANTIATE_TEST_SUITE_P(Protocols, GolangWebsocketIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +ConfigHelper::HttpModifierFunction setRouteUsingWebsocket() { + return [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { hcm.add_upgrade_configs()->set_upgrade_type("websocket"); }; +} + +void WebsocketIntegrationTest::initialize() { HttpProtocolIntegrationTest::initialize(); } + +std::string genSoPath() { + return TestEnvironment::substitute( + "{{ test_rundir }}/contrib/golang/filters/http/test/test_data/plugins.so"); +} + +std::string filterConfig(const std::string& name) { + const auto yaml_fmt = R"EOF( +name: golang +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config + library_id: %s + library_path: %s + plugin_name: %s + plugin_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + value: + echo_body: "echo from go" + match_path: "/echo" +)EOF"; + + return absl::StrFormat(yaml_fmt, name, genSoPath(), name); +} + +TEST_P(GolangWebsocketIntegrationTest, WebsocketGolangFilterChain) { + if (downstreamProtocol() != Http::CodecType::HTTP1 || + upstreamProtocol() != Http::CodecType::HTTP1) { + return; + } + + config_helper_.addConfigModifier(setRouteUsingWebsocket()); + config_helper_.prependFilter(filterConfig("websocket")); + config_helper_.skipPortUsageValidation(); + + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("http")); + + // Send upgrade request without CL and TE headers + ASSERT_TRUE(tcp_client->write( + "GET / HTTP/1.1\r\nHost: host\r\nconnection: upgrade\r\nupgrade: websocket\r\n\r\n", false, + false)); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT(fake_upstream_connection != nullptr); + std::string received_data; + ASSERT_TRUE(fake_upstream_connection->waitForData( + FakeRawConnection::waitForInexactMatch("\r\n\r\n"), &received_data)); + // Make sure Envoy did not add TE or CL headers + ASSERT_FALSE(absl::StrContains(received_data, "content-length")); + ASSERT_FALSE(absl::StrContains(received_data, "transfer-encoding")); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(received_data, "test-websocket-req-key: foo")); + ASSERT_TRUE(fake_upstream_connection->write( + "HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\n\r\n", + false)); + + tcp_client->waitForData("\r\n\r\n", false); + // Make sure Envoy did not add TE or CL on the response path + ASSERT_FALSE(absl::StrContains(tcp_client->data(), "content-length")); + ASSERT_FALSE(absl::StrContains(tcp_client->data(), "transfer-encoding")); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(tcp_client->data(), "test-websocket-rsp-key: bar")); + + fake_upstream_connection->clearData(); + // Send data and make sure Envoy did not add chunk framing + ASSERT_TRUE(tcp_client->write("foo bar\r\n", false, false)); + ASSERT_TRUE(fake_upstream_connection->waitForData(FakeRawConnection::waitForInexactMatch("\r\n"), + &received_data)); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(received_data, "Hello_foo bar")); + + tcp_client->clearData(); + // Send response data and make sure Envoy did not add chunk framing on the response path + ASSERT_TRUE(fake_upstream_connection->write("bar foo\r\n", false)); + tcp_client->waitForData("bar foo\r\n", false); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(tcp_client->data(), "Bye_bar foo")); + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + + cleanup(); +} + +} // namespace Envoy diff --git a/contrib/golang/filters/network/source/BUILD b/contrib/golang/filters/network/source/BUILD index e4a1151e8a9cd..81b9faa2c4ea5 100644 --- a/contrib/golang/filters/network/source/BUILD +++ b/contrib/golang/filters/network/source/BUILD @@ -19,6 +19,7 @@ envoy_cc_library( ":cgo", ":upstream", "//contrib/golang/common/dso:dso_lib", + "//source/common/router:string_accessor_lib", "@envoy_api//contrib/envoy/extensions/filters/network/golang/v3alpha:pkg_cc_proto", ], ) @@ -43,10 +44,11 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", "//source/common/common:thread_lib", - "//source/common/http:header_map_lib", - "//source/common/http:headers_lib", "//source/common/memory:utils_lib", "//source/common/network:connection_lib", + "//source/common/network:filter_state_dst_address_lib", + "//source/common/network:utility_lib", + "//source/common/stream_info:stream_info_lib", "//source/common/tcp:conn_pool_lib", "//source/common/upstream:load_balancer_lib", "//source/extensions/filters/network/common:factory_base_lib", @@ -81,6 +83,7 @@ envoy_cc_contrib_extension( ], deps = [ "//contrib/golang/common/dso:dso_lib", + "//contrib/golang/common/log:log_lib", "//envoy/buffer:buffer_interface", "//envoy/event:dispatcher_interface", "//envoy/network:connection_interface", @@ -93,8 +96,6 @@ envoy_cc_contrib_extension( "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", - "//source/common/http:header_map_lib", - "//source/common/http:headers_lib", "//source/common/memory:utils_lib", "//source/common/network:connection_lib", "//source/common/upstream:load_balancer_lib", diff --git a/contrib/golang/filters/network/source/cgo.cc b/contrib/golang/filters/network/source/cgo.cc index ea9cef396cce4..8b3c5e11a8a70 100644 --- a/contrib/golang/filters/network/source/cgo.cc +++ b/contrib/golang/filters/network/source/cgo.cc @@ -116,10 +116,11 @@ CAPIStatus envoyGoFilterDownstreamInfo(void* f, int info_type, void* ret) { // Upstream // -void* envoyGoFilterUpstreamConnect(void* library_id, void* addr) { +void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, uint64_t conn_id) { std::string id = copyGoString(library_id); auto dynamic_lib = Dso::DsoManager::getDsoByID(id); - UpstreamConnPtr conn_ptr = std::make_shared(copyGoString(addr), dynamic_lib); + UpstreamConnPtr conn_ptr = + std::make_shared(copyGoString(addr), dynamic_lib, conn_id); // the upstream connect wrapper will be deleted by envoyGoFilterUpstreamFinalize UpstreamConnWrapper* wrapper = new UpstreamConnWrapper(conn_ptr); conn_ptr->setWrapper(wrapper); @@ -129,6 +130,15 @@ void* envoyGoFilterUpstreamConnect(void* library_id, void* addr) { return static_cast(wrapper); } +CAPIStatus envoyGoFilterUpstreamConnEnableHalfClose(void* u, int enable_half_close) { + auto* wrapper = reinterpret_cast(u); + UpstreamConnPtr& conn_ptr = wrapper->conn_ptr_; + + conn_ptr->enableHalfClose(static_cast(enable_half_close)); + + return CAPIOK; +} + CAPIStatus envoyGoFilterUpstreamWrite(void* u, void* buffer_ptr, int buffer_len, int end_stream) { auto* wrapper = reinterpret_cast(u); UpstreamConnPtr& conn_ptr = wrapper->conn_ptr_; diff --git a/contrib/golang/filters/network/source/go/pkg/network/capi.go b/contrib/golang/filters/network/source/go/pkg/network/capi.go index 97685a8a794ee..39b05ff7c3020 100644 --- a/contrib/golang/filters/network/source/go/pkg/network/capi.go +++ b/contrib/golang/filters/network/source/go/pkg/network/capi.go @@ -20,7 +20,7 @@ package network /* // ref https://github.com/golang/go/issues/25832 -#cgo CFLAGS: -I../api +#cgo CFLAGS: -I../../../../../../common/go/api -I../api #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup @@ -91,8 +91,12 @@ func (c *cgoApiImpl) SetFilterState(f unsafe.Pointer, key string, value string, // TODO: handle res } -func (c *cgoApiImpl) UpstreamConnect(libraryID string, addr string) unsafe.Pointer { - return unsafe.Pointer(C.envoyGoFilterUpstreamConnect(unsafe.Pointer(&libraryID), unsafe.Pointer(&addr))) +func (c *cgoApiImpl) UpstreamConnect(libraryID string, addr string, connID uint64) unsafe.Pointer { + return unsafe.Pointer(C.envoyGoFilterUpstreamConnect(unsafe.Pointer(&libraryID), unsafe.Pointer(&addr), C.uint64_t(connID))) +} + +func (c *cgoApiImpl) UpstreamConnEnableHalfClose(f unsafe.Pointer, enableHalfClose int) { + C.envoyGoFilterUpstreamConnEnableHalfClose(f, C.int(enableHalfClose)) } func (c *cgoApiImpl) UpstreamWrite(f unsafe.Pointer, bufferPtr unsafe.Pointer, bufferLen int, endStream int) { diff --git a/contrib/golang/filters/network/source/go/pkg/network/filter.go b/contrib/golang/filters/network/source/go/pkg/network/filter.go index 6a113fd5c6625..140f3a27eb951 100644 --- a/contrib/golang/filters/network/source/go/pkg/network/filter.go +++ b/contrib/golang/filters/network/source/go/pkg/network/filter.go @@ -25,15 +25,16 @@ import ( ) type connectionCallback struct { - wrapper unsafe.Pointer - writeFunc func(envoyFilter unsafe.Pointer, buffers unsafe.Pointer, buffersNum int, endStream int) - closeFunc func(envoyFilter unsafe.Pointer, closeType int) - infoFunc func(envoyFilter unsafe.Pointer, infoType int) string - streamInfo api.StreamInfo - state *filterState - sema sync.WaitGroup - waitingOnEnvoy int32 - mutex sync.Mutex + wrapper unsafe.Pointer + writeFunc func(envoyFilter unsafe.Pointer, buffers unsafe.Pointer, buffersNum int, endStream int) + closeFunc func(envoyFilter unsafe.Pointer, closeType int) + infoFunc func(envoyFilter unsafe.Pointer, infoType int) string + connEnableHalfCloseFunc func(envoyFilter unsafe.Pointer, enableHalfClose int) + streamInfo api.StreamInfo + state *filterState + sema sync.WaitGroup + waitingOnEnvoy int32 + mutex sync.Mutex } var _ api.ConnectionCallback = (*connectionCallback)(nil) @@ -55,6 +56,17 @@ func (n *connectionCallback) StreamInfo() api.StreamInfo { return n } +func (n *connectionCallback) EnableHalfClose(enabled bool) { + var enableHalfCloseInt int + if enabled { + enableHalfCloseInt = 1 + } + if n.connEnableHalfCloseFunc == nil { + panic("EnableHalfClose is not supported for downstream connection yet") + } + n.connEnableHalfCloseFunc(n.wrapper, enableHalfCloseInt) +} + func (n *connectionCallback) GetRouteName() string { panic("implement me") } @@ -113,6 +125,10 @@ func (n *connectionCallback) VirtualClusterName() (string, bool) { panic("implement me") } +func (n *connectionCallback) WorkerID() uint32 { + panic("implement me") +} + type filterState struct { wrapper unsafe.Pointer setFunc func(envoyFilter unsafe.Pointer, key string, value string, stateType api.StateType, lifeSpan api.LifeSpan, streamSharing api.StreamSharing) diff --git a/contrib/golang/filters/network/source/go/pkg/network/shim.go b/contrib/golang/filters/network/source/go/pkg/network/shim.go index 0e5e993d74e8b..8309566192a23 100644 --- a/contrib/golang/filters/network/source/go/pkg/network/shim.go +++ b/contrib/golang/filters/network/source/go/pkg/network/shim.go @@ -58,16 +58,30 @@ var ( configIDGenerator uint64 configCache = &sync.Map{} // uint64 -> *anypb.Any + upstreamConnIDGenerator uint64 + libraryID string ) +// wrap the UpstreamFilter to ensure that the runtime.finalizer can be triggered +// regardless of whether there is a circular reference in the UpstreamFilter. +type upstreamConnWrapper struct { + api.UpstreamFilter + finalizer *int +} + func CreateUpstreamConn(addr string, filter api.UpstreamFilter) { - h := uint64(uintptr(cgoAPI.UpstreamConnect(libraryID, addr))) - // TODO: handle error - _ = UpstreamFilters.StoreFilter(h, filter) + conn := &upstreamConnWrapper{ + UpstreamFilter: filter, + finalizer: new(int), + } + connID := atomic.AddUint64(&upstreamConnIDGenerator, 1) + _ = UpstreamFilters.StoreFilterByConnID(connID, conn) + + h := cgoAPI.UpstreamConnect(libraryID, addr, connID) // NP: make sure filter will be deleted. - runtime.SetFinalizer(filter, func(f api.UpstreamFilter) { + runtime.SetFinalizer(conn.finalizer, func(_ *int) { cgoAPI.UpstreamFinalize(unsafe.Pointer(uintptr(h)), api.NormalFinalize) }) } @@ -178,27 +192,31 @@ func envoyGoFilterOnSemaDec(wrapper unsafe.Pointer) { } //export envoyGoFilterOnUpstreamConnectionReady -func envoyGoFilterOnUpstreamConnectionReady(wrapper unsafe.Pointer) { +func envoyGoFilterOnUpstreamConnectionReady(wrapper unsafe.Pointer, connID uint64) { cb := &connectionCallback{ - wrapper: wrapper, - writeFunc: cgoAPI.UpstreamWrite, - closeFunc: cgoAPI.UpstreamClose, - infoFunc: cgoAPI.UpstreamInfo, + wrapper: wrapper, + writeFunc: cgoAPI.UpstreamWrite, + closeFunc: cgoAPI.UpstreamClose, + infoFunc: cgoAPI.UpstreamInfo, + connEnableHalfCloseFunc: cgoAPI.UpstreamConnEnableHalfClose, } - filter := UpstreamFilters.GetFilter(uint64(uintptr(wrapper))) + // switch filter from idMap to wrapperMap + filter := UpstreamFilters.GetFilterByConnID(connID) + UpstreamFilters.DeleteFilterByConnID(connID) + UpstreamFilters.StoreFilterByWrapper(uint64(uintptr(wrapper)), filter) filter.OnPoolReady(cb) } //export envoyGoFilterOnUpstreamConnectionFailure -func envoyGoFilterOnUpstreamConnectionFailure(wrapper unsafe.Pointer, reason int) { - filter := UpstreamFilters.GetFilter(uint64(uintptr(wrapper))) +func envoyGoFilterOnUpstreamConnectionFailure(wrapper unsafe.Pointer, reason int, connID uint64) { + filter := UpstreamFilters.GetFilterByConnID(connID) + UpstreamFilters.DeleteFilterByConnID(connID) filter.OnPoolFailure(api.PoolFailureReason(reason), "") - UpstreamFilters.DeleteFilter(uint64(uintptr(wrapper))) } //export envoyGoFilterOnUpstreamData func envoyGoFilterOnUpstreamData(wrapper unsafe.Pointer, dataSize uint64, dataPtr uint64, sliceNum int, endOfStream int) { - filter := UpstreamFilters.GetFilter(uint64(uintptr(wrapper))) + filter := UpstreamFilters.GetFilterByWrapper(uint64(uintptr(wrapper))) var buf []byte @@ -216,11 +234,11 @@ func envoyGoFilterOnUpstreamData(wrapper unsafe.Pointer, dataSize uint64, dataPt //export envoyGoFilterOnUpstreamEvent func envoyGoFilterOnUpstreamEvent(wrapper unsafe.Pointer, event int) { - filter := UpstreamFilters.GetFilter(uint64(uintptr(wrapper))) + filter := UpstreamFilters.GetFilterByWrapper(uint64(uintptr(wrapper))) e := api.ConnectionEvent(event) filter.OnEvent(e) if e == api.LocalClose || e == api.RemoteClose { - UpstreamFilters.DeleteFilter(uint64(uintptr(wrapper))) + UpstreamFilters.DeleteFilterByWrapper(uint64(uintptr(wrapper))) } } @@ -267,30 +285,53 @@ func (f *DownstreamFilterMap) Clear() { } type UpstreamFilterMap struct { - m sync.Map // uint64 -> UpstreamFilter + idMap sync.Map // upstreamConnID(uint) -> UpstreamFilter + wrapperMap sync.Map // wrapper(uint64) -> UpstreamFilter } -func (f *UpstreamFilterMap) StoreFilter(key uint64, filter api.UpstreamFilter) error { - if _, loaded := f.m.LoadOrStore(key, filter); loaded { +func (f *UpstreamFilterMap) StoreFilterByConnID(key uint64, filter api.UpstreamFilter) error { + if _, loaded := f.idMap.LoadOrStore(key, filter); loaded { return ErrDupRequestKey } return nil } -func (f *UpstreamFilterMap) GetFilter(key uint64) api.UpstreamFilter { - if v, ok := f.m.Load(key); ok { +func (f *UpstreamFilterMap) StoreFilterByWrapper(key uint64, filter api.UpstreamFilter) error { + if _, loaded := f.wrapperMap.LoadOrStore(key, filter); loaded { + return ErrDupRequestKey + } + return nil +} + +func (f *UpstreamFilterMap) GetFilterByConnID(key uint64) api.UpstreamFilter { + if v, ok := f.idMap.Load(key); ok { return v.(api.UpstreamFilter) } return nil } -func (f *UpstreamFilterMap) DeleteFilter(key uint64) { - f.m.Delete(key) +func (f *UpstreamFilterMap) GetFilterByWrapper(key uint64) api.UpstreamFilter { + if v, ok := f.wrapperMap.Load(key); ok { + return v.(api.UpstreamFilter) + } + return nil +} + +func (f *UpstreamFilterMap) DeleteFilterByConnID(key uint64) { + f.idMap.Delete(key) +} + +func (f *UpstreamFilterMap) DeleteFilterByWrapper(key uint64) { + f.wrapperMap.Delete(key) } func (f *UpstreamFilterMap) Clear() { - f.m.Range(func(key, _ interface{}) bool { - f.m.Delete(key) + f.idMap.Range(func(key, _ interface{}) bool { + f.idMap.Delete(key) + return true + }) + f.wrapperMap.Range(func(key, _ interface{}) bool { + f.wrapperMap.Delete(key) return true }) } diff --git a/contrib/golang/filters/network/source/golang.cc b/contrib/golang/filters/network/source/golang.cc index 16f38a8488b38..43c09309fd2e5 100644 --- a/contrib/golang/filters/network/source/golang.cc +++ b/contrib/golang/filters/network/source/golang.cc @@ -3,8 +3,10 @@ #include #include "envoy/network/connection.h" +#include "envoy/router/string_accessor.h" #include "source/common/common/assert.h" +#include "source/common/router/string_accessor_impl.h" namespace Envoy { namespace Extensions { @@ -32,7 +34,7 @@ void Filter::close(Network::ConnectionCloseType close_type) { } ENVOY_CONN_LOG(debug, "close addr: {}, type: {}", read_callbacks_->connection(), addr_, static_cast(close_type)); - read_callbacks_->connection().close(close_type); + read_callbacks_->connection().close(close_type, "go_downstream_close"); } void Filter::write(Buffer::Instance& buf, bool end_stream) { @@ -107,9 +109,6 @@ Network::FilterStatus Filter::onWrite(Buffer::Instance& data, bool end_stream) { auto ret = dynamic_lib_->envoyGoFilterOnDownstreamWrite( wrapper_, data.length(), reinterpret_cast(slices), slice_num, end_stream); - // TODO: do not drain buffer by default - data.drain(data.length()); - delete[] slices; return Network::FilterStatus(ret); @@ -126,13 +125,13 @@ CAPIStatus Filter::setFilterState(absl::string_view key, absl::string_view value if (dispatcher_->isThreadSafe()) { read_callbacks_->connection().streamInfo().filterState()->setData( - key, std::make_shared(value), + key, std::make_shared(value), static_cast(state_type), static_cast(life_span), static_cast(stream_sharing)); } else { auto key_str = std::string(key); - auto filter_state = std::make_shared(value); + auto filter_state = std::make_shared(value); auto weak_ptr = weak_from_this(); dispatcher_->post( [this, weak_ptr, key_str, filter_state, state_type, life_span, stream_sharing] { @@ -163,9 +162,9 @@ CAPIStatus Filter::getFilterState(absl::string_view key, GoString* value_str) { auto go_filter_state = read_callbacks_->connection() .streamInfo() .filterState() - ->getDataReadOnly(key); + ->getDataReadOnly(key); if (go_filter_state) { - wrapper_->str_value_ = go_filter_state->value(); + wrapper_->str_value_ = go_filter_state->asString(); value_str->p = wrapper_->str_value_.data(); value_str->n = wrapper_->str_value_.length(); } @@ -177,9 +176,9 @@ CAPIStatus Filter::getFilterState(absl::string_view key, GoString* value_str) { auto go_filter_state = read_callbacks_->connection() .streamInfo() .filterState() - ->getDataReadOnly(key_str); + ->getDataReadOnly(key_str); if (go_filter_state) { - wrapper_->str_value_ = go_filter_state->value(); + wrapper_->str_value_ = go_filter_state->asString(); value_str->p = wrapper_->str_value_.data(); value_str->n = wrapper_->str_value_.length(); } diff --git a/contrib/golang/filters/network/source/golang.h b/contrib/golang/filters/network/source/golang.h index 2e7cb9f071ccd..8101fd7566e31 100644 --- a/contrib/golang/filters/network/source/golang.h +++ b/contrib/golang/filters/network/source/golang.h @@ -2,7 +2,6 @@ #include "envoy/buffer/buffer.h" #include "envoy/event/dispatcher.h" -#include "envoy/http/header_map.h" #include "envoy/network/connection.h" #include "envoy/network/filter.h" #include "envoy/ssl/connection.h" @@ -11,8 +10,6 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/common/logger.h" -#include "source/common/http/header_map_impl.h" -#include "source/common/http/headers.h" #include "source/common/network/connection_impl.h" #include "source/common/upstream/load_balancer_impl.h" #include "source/extensions/filters/network/common/factory_base.h" @@ -27,7 +24,7 @@ namespace NetworkFilters { namespace Golang { /** - * Configuration for the HTTP golang extension filter. + * Configuration for the Golang network filter. */ class FilterConfig { public: @@ -118,15 +115,6 @@ struct FilterWrapper { std::string str_value_; }; -class GoStringFilterState : public StreamInfo::FilterState::Object { -public: - GoStringFilterState(absl::string_view value) : value_(value) {} - const std::string& value() const { return value_; } - -private: - const std::string value_; -}; - } // namespace Golang } // namespace NetworkFilters } // namespace Extensions diff --git a/contrib/golang/filters/network/source/upstream.cc b/contrib/golang/filters/network/source/upstream.cc index 500c287c579ef..e83f13ee14a33 100644 --- a/contrib/golang/filters/network/source/upstream.cc +++ b/contrib/golang/filters/network/source/upstream.cc @@ -6,6 +6,8 @@ #include "envoy/tcp/conn_pool.h" #include "source/common/common/assert.h" +#include "source/common/network/filter_state_dst_address.h" +#include "source/common/network/utility.h" namespace Envoy { namespace Extensions { @@ -44,8 +46,8 @@ void UpstreamConn::initThreadLocalStorage(Server::Configuration::FactoryContext& } UpstreamConn::UpstreamConn(std::string addr, Dso::NetworkFilterDsoPtr dynamic_lib, - Event::Dispatcher* dispatcher) - : dynamic_lib_(dynamic_lib), dispatcher_(dispatcher), addr_(addr) { + unsigned long long int goConnID, Event::Dispatcher* dispatcher) + : dynamic_lib_(dynamic_lib), goConnID_(goConnID), dispatcher_(dispatcher), addr_(addr) { if (dispatcher_ == nullptr) { DispatcherStore& store = dispatcherStore(); Thread::LockGuard guard(store.lock_); @@ -53,8 +55,14 @@ UpstreamConn::UpstreamConn(std::string addr, Dso::NetworkFilterDsoPtr dynamic_li ASSERT(!store.dispatchers_.empty()); dispatcher_ = &store.dispatchers_[store.dispatcher_idx_++ % store.dispatchers_.size()].get(); } - header_map_ = Http::createHeaderMap( - {{Http::Headers::get().EnvoyOriginalDstHost, addr}}); + stream_info_ = std::make_unique( + dispatcher_->timeSource(), nullptr, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info_->filterState()->setData( + Network::DestinationAddress::key(), + std::make_shared( + Network::Utility::parseInternetAddressAndPort(addr, false)), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain, + StreamInfo::StreamSharingMayImpactPooling::None); } void UpstreamConn::connect() { @@ -87,6 +95,17 @@ void UpstreamConn::connect() { } } +void UpstreamConn::enableHalfClose(bool enabled) { + if (closed_) { + ENVOY_LOG(warn, "connection has closed, addr: {}", addr_); + return; + } + ASSERT(conn_ != nullptr); + conn_->connection().enableHalfClose(enabled); + ENVOY_CONN_LOG(debug, "set enableHalfClose to addr: {}, enabled: {}, actualEnabled: {}", + conn_->connection(), addr_, enabled, conn_->connection().isHalfCloseEnabled()); +} + void UpstreamConn::write(Buffer::Instance& buf, bool end_stream) { if (closed_) { ENVOY_LOG(warn, "connection has closed, addr: {}", addr_); @@ -106,7 +125,7 @@ void UpstreamConn::close(Network::ConnectionCloseType close_type) { ENVOY_CONN_LOG(debug, "close addr: {}, type: {}", conn_->connection(), addr_, static_cast(close_type)); ASSERT(conn_ != nullptr); - conn_->connection().close(close_type); + conn_->connection().close(close_type, "go_upstream_close"); } void UpstreamConn::onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn, @@ -121,7 +140,7 @@ void UpstreamConn::onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn, conn_->addUpstreamCallbacks(*this); remote_addr_ = conn_->connection().connectionInfoProvider().directRemoteAddress()->asString(); - dynamic_lib_->envoyGoFilterOnUpstreamConnectionReady(wrapper_); + dynamic_lib_->envoyGoFilterOnUpstreamConnectionReady(wrapper_, goConnID_); } void UpstreamConn::onPoolFailure(Tcp::ConnectionPool::PoolFailureReason reason, @@ -133,7 +152,8 @@ void UpstreamConn::onPoolFailure(Tcp::ConnectionPool::PoolFailureReason reason, handler_ = nullptr; } - dynamic_lib_->envoyGoFilterOnUpstreamConnectionFailure(wrapper_, static_cast(reason)); + dynamic_lib_->envoyGoFilterOnUpstreamConnectionFailure(wrapper_, static_cast(reason), + goConnID_); } void UpstreamConn::onEvent(Network::ConnectionEvent event) { diff --git a/contrib/golang/filters/network/source/upstream.h b/contrib/golang/filters/network/source/upstream.h index 8573f83b99066..f3aaa05cfb125 100644 --- a/contrib/golang/filters/network/source/upstream.h +++ b/contrib/golang/filters/network/source/upstream.h @@ -5,7 +5,6 @@ #include "envoy/buffer/buffer.h" #include "envoy/event/dispatcher.h" -#include "envoy/http/header_map.h" #include "envoy/network/connection.h" #include "envoy/network/filter.h" #include "envoy/tcp/conn_pool.h" @@ -14,10 +13,9 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/common/logger.h" #include "source/common/common/thread.h" -#include "source/common/http/header_map_impl.h" -#include "source/common/http/headers.h" #include "source/common/memory/utils.h" #include "source/common/network/connection_impl.h" +#include "source/common/stream_info/stream_info_impl.h" #include "source/common/upstream/load_balancer_impl.h" #include "source/extensions/filters/network/common/factory_base.h" @@ -38,7 +36,7 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, Logger::Loggable { public: UpstreamConn(std::string addr, Dso::NetworkFilterDsoPtr dynamic_lib, - Event::Dispatcher* dispatcher = nullptr); + unsigned long long int goConnID, Event::Dispatcher* dispatcher = nullptr); ~UpstreamConn() override { if (handler_) { handler_->cancel(Tcp::ConnectionPool::CancelPolicy::Default); @@ -63,9 +61,10 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, void onEvent(Network::ConnectionEvent event) override; // Upstream::LoadBalancerContextBase - const Http::RequestHeaderMap* downstreamHeaders() const override { return header_map_.get(); }; + const StreamInfo::StreamInfo* requestStreamInfo() const override { return stream_info_.get(); } void connect(); + void enableHalfClose(bool enabled); void write(Buffer::Instance& buf, bool end_stream); void close(Network::ConnectionCloseType close_type); @@ -97,9 +96,10 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, } Dso::NetworkFilterDsoPtr dynamic_lib_{nullptr}; + unsigned long long int goConnID_{0}; UpstreamConnWrapper* wrapper_{nullptr}; Event::Dispatcher* dispatcher_{nullptr}; - std::unique_ptr header_map_{nullptr}; + std::unique_ptr stream_info_{nullptr}; Tcp::ConnectionPool::ConnectionDataPtr conn_{nullptr}; Upstream::HostDescriptionConstSharedPtr host_{nullptr}; Tcp::ConnectionPool::Cancellable* handler_{nullptr}; diff --git a/contrib/golang/filters/network/test/BUILD b/contrib/golang/filters/network/test/BUILD index 16d212c1bfdb5..359cf1b3bbb1c 100644 --- a/contrib/golang/filters/network/test/BUILD +++ b/contrib/golang/filters/network/test/BUILD @@ -45,6 +45,7 @@ envoy_cc_test( deps = [ "//contrib/golang/common/dso/test:dso_mocks", "//contrib/golang/filters/network/source:upstream", + "//source/common/network:filter_state_dst_address_lib", "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", diff --git a/contrib/golang/filters/network/test/filter_test.cc b/contrib/golang/filters/network/test/filter_test.cc index e3583f8f8f26f..cde51bac8b6f4 100644 --- a/contrib/golang/filters/network/test/filter_test.cc +++ b/contrib/golang/filters/network/test/filter_test.cc @@ -105,7 +105,7 @@ TEST_F(FilterTest, WriteAndClose) { EXPECT_CALL(filter_callbacks_.connection_, write(_, false)); filter_->write(someData, false); - EXPECT_CALL(filter_callbacks_.connection_, close(_)); + EXPECT_CALL(filter_callbacks_.connection_, close(_, "go_downstream_close")); EXPECT_CALL(*dso_.get(), envoyGoFilterOnDownstreamEvent(_, _)); filter_->close(Network::ConnectionCloseType::NoFlush); diff --git a/contrib/golang/filters/network/test/test_data/filter.go b/contrib/golang/filters/network/test/test_data/filter.go index 76eb8e885ee74..3728ca20e0bae 100644 --- a/contrib/golang/filters/network/test/test_data/filter.go +++ b/contrib/golang/filters/network/test/test_data/filter.go @@ -23,22 +23,8 @@ func (f *SimpleFilterFactory) CreateFilter(cb api.ConnectionCallback) api.Downst return &SimpleFilter{} } -type SimpleFilter struct{} - -func (f *SimpleFilter) OnNewConnection() api.FilterStatus { - panic("implement me") -} - -func (f *SimpleFilter) OnData(buffer []byte, endOfStream bool) api.FilterStatus { - panic("implement me") -} - -func (f *SimpleFilter) OnEvent(event api.ConnectionEvent) { - panic("implement me") -} - -func (f *SimpleFilter) OnWrite(buffer []byte, endOfStream bool) api.FilterStatus { - panic("implement me") +type SimpleFilter struct { + api.EmptyDownstreamFilter } func main() { diff --git a/contrib/golang/filters/network/test/upstream_test.cc b/contrib/golang/filters/network/test/upstream_test.cc index ecb5583c672c8..31a8413f11273 100644 --- a/contrib/golang/filters/network/test/upstream_test.cc +++ b/contrib/golang/filters/network/test/upstream_test.cc @@ -2,6 +2,8 @@ #include "envoy/registry/registry.h" +#include "source/common/network/filter_state_dst_address.h" + #include "test/mocks/server/factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" @@ -40,7 +42,7 @@ class UpstreamConnTest : public testing::Test { ON_CALL(context_.api_, threadFactory()).WillByDefault(ReturnRef(thread_factory_)); UpstreamConn::initThreadLocalStorage(context_, slot_allocator_); dso_ = std::make_shared(); - upConn_ = std::make_shared(addr_, dso_, &dispatcher_); + upConn_ = std::make_shared(addr_, dso_, 0, &dispatcher_); } ThreadLocal::MockInstance slot_allocator_; @@ -57,6 +59,11 @@ class UpstreamConnTest : public testing::Test { TEST_F(UpstreamConnTest, ConnectUpstream) { initialize(); + const auto* dst_addr = + upConn_->requestStreamInfo()->filterState().getDataReadOnly( + Network::DestinationAddress::key()); + EXPECT_EQ(dst_addr->address()->asString(), addr_); + EXPECT_CALL(context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_, newConnection(_)) .WillOnce( Invoke([&](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { @@ -65,7 +72,7 @@ TEST_F(UpstreamConnTest, ConnectUpstream) { upstream_connection_); return nullptr; })); - EXPECT_CALL(*dso_.get(), envoyGoFilterOnUpstreamConnectionReady(_)); + EXPECT_CALL(*dso_.get(), envoyGoFilterOnUpstreamConnectionReady(_, _)); upConn_->connect(); EXPECT_CALL(context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_, newConnection(_)) @@ -78,7 +85,7 @@ TEST_F(UpstreamConnTest, ConnectUpstream) { })); EXPECT_CALL(*dso_.get(), envoyGoFilterOnUpstreamConnectionFailure( - _, GoInt(ConnectionPool::PoolFailureReason::RemoteConnectionFailure))); + _, GoInt(ConnectionPool::PoolFailureReason::RemoteConnectionFailure), _)); upConn_->connect(); } @@ -96,16 +103,19 @@ TEST_F(UpstreamConnTest, InvokeDsoOnEventOrData) { TEST_F(UpstreamConnTest, WriteAndClose) { initialize(); - EXPECT_CALL(*dso_.get(), envoyGoFilterOnUpstreamConnectionReady(_)); + EXPECT_CALL(*dso_.get(), envoyGoFilterOnUpstreamConnectionReady(_, _)); auto data = std::make_unique>(); EXPECT_CALL(*data, connection()).WillRepeatedly(ReturnRef(upstream_connection_)); upConn_->onPoolReady(std::move(data), nullptr); + EXPECT_CALL(upstream_connection_, enableHalfClose(true)); + upConn_->enableHalfClose(true); + Buffer::OwnedImpl someData("123"); EXPECT_CALL(upstream_connection_, write(_, false)); upConn_->write(someData, false); - EXPECT_CALL(upstream_connection_, close(_)); + EXPECT_CALL(upstream_connection_, close(_, "go_upstream_close")); EXPECT_CALL(*dso_.get(), envoyGoFilterOnUpstreamEvent(_, _)); upConn_->close(Network::ConnectionCloseType::NoFlush); upConn_->onEvent(Network::ConnectionEvent::RemoteClose); diff --git a/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go b/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go index 52847c22aca3b..c2bb447322364 100644 --- a/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go +++ b/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier/capi_impl.go @@ -20,7 +20,6 @@ package cluster_specifier /* // ref https://github.com/golang/go/issues/25832 -#cgo CFLAGS: -I../api #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup diff --git a/docs/root/configuration/http/http_filters/golang_filter.rst b/docs/root/configuration/http/http_filters/golang_filter.rst index 8721ce9e838df..4c690f86f8465 100644 --- a/docs/root/configuration/http/http_filters/golang_filter.rst +++ b/docs/root/configuration/http/http_filters/golang_filter.rst @@ -12,13 +12,6 @@ See the `Envoy's Golang extension proposal documentation `_ for more details on the filter's implementation. -.. warning:: - The Envoy Golang filter is designed to be run with the ``GODEBUG=cgocheck=0`` environment variable set. - - This disables the cgo pointer check. - - Failure to set this environment variable will cause Envoy to crash! - Developing a Go plugin ---------------------- diff --git a/docs/root/start/sandboxes/golang-http.rst b/docs/root/start/sandboxes/golang-http.rst index 89f483a07914d..002c8e6cb66aa 100644 --- a/docs/root/start/sandboxes/golang-http.rst +++ b/docs/root/start/sandboxes/golang-http.rst @@ -40,15 +40,6 @@ Step 2: Start all of our containers Start all the containers. -.. warning:: - The Envoy Golang filter is designed to be run with the ``GODEBUG=cgocheck=0`` environment variable set. - - This disables the cgo pointer check. - - Failure to set this environment variable will cause Envoy to crash! - - Here, we have set this environment variable in :repo:`Dockerfile ` - .. code-block:: console $ docker compose pull diff --git a/examples/golang-http/simple/config.go b/examples/golang-http/simple/config.go index be7257a6f2a63..2c635d9f02bb3 100644 --- a/examples/golang-http/simple/config.go +++ b/examples/golang-http/simple/config.go @@ -14,7 +14,7 @@ import ( const Name = "simple" func init() { - http.RegisterHttpFilterConfigFactoryAndParser(Name, ConfigFactory, &parser{}) + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{}) } type config struct { @@ -25,7 +25,9 @@ type config struct { type parser struct { } -func (p *parser) Parse(any *anypb.Any) (interface{}, error) { +// Parse the filter configuration. We can call the ConfigCallbackHandler to control the filter's +// behavior +func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) { configStruct := &xds.TypedStruct{} if err := any.UnmarshalTo(configStruct); err != nil { return nil, err @@ -45,6 +47,7 @@ func (p *parser) Parse(any *anypb.Any) (interface{}, error) { return conf, nil } +// Merge configuration from the inherited parent configuration func (p *parser) Merge(parent interface{}, child interface{}) interface{} { parentConfig := parent.(*config) childConfig := child.(*config) @@ -57,17 +60,14 @@ func (p *parser) Merge(parent interface{}, child interface{}) interface{} { return &newConfig } -func ConfigFactory(c interface{}) api.StreamFilterFactory { +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { conf, ok := c.(*config) if !ok { panic("unexpected config type") } - - return func(callbacks api.FilterCallbackHandler) api.StreamFilter { - return &filter{ - callbacks: callbacks, - config: conf, - } + return &filter{ + callbacks: callbacks, + config: conf, } } diff --git a/examples/golang-http/simple/filter.go b/examples/golang-http/simple/filter.go index 0a08ff62f0ef2..52afefaac091c 100644 --- a/examples/golang-http/simple/filter.go +++ b/examples/golang-http/simple/filter.go @@ -9,6 +9,8 @@ import ( var UpdateUpstreamBody = "upstream response body updated by the simple plugin" +// The callbacks in the filter, like `DecodeHeaders`, can be implemented on demand. +// Because api.PassThroughStreamFilter provides a default implementation. type filter struct { api.PassThroughStreamFilter @@ -19,59 +21,119 @@ type filter struct { func (f *filter) sendLocalReplyInternal() api.StatusType { body := fmt.Sprintf("%s, path: %s\r\n", f.config.echoBody, f.path) - f.callbacks.SendLocalReply(200, body, nil, 0, "") + f.callbacks.DecoderFilterCallbacks().SendLocalReply(200, body, nil, 0, "") + // Remember to return LocalReply when the request is replied locally return api.LocalReply } // Callbacks which are called in request path +// The endStream is true if the request doesn't have body func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { f.path, _ = header.Get(":path") + api.LogDebugf("get path %s", f.path) + if f.path == "/localreply_by_config" { return f.sendLocalReplyInternal() } return api.Continue -} + /* + // If the code is time-consuming, to avoid blocking the Envoy, + // we need to run the code in a background goroutine + // and suspend & resume the filter + go func() { + defer f.callbacks.RecoverPanic() + // do time-consuming jobs -/* -The callbacks can be implemented on demand + // resume the filter + f.callbacks.Continue(status) + }() + + // suspend the filter + return api.Running + */ +} +// DecodeData might be called multiple times during handling the request body. +// The endStream is true when handling the last piece of the body. func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + // support suspending & resuming the filter in a background goroutine return api.Continue } func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType { + // support suspending & resuming the filter in a background goroutine return api.Continue } -*/ +// Callbacks which are called in response path +// The endStream is true if the response doesn't have body func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { if f.path == "/update_upstream_response" { header.Set("Content-Length", strconv.Itoa(len(UpdateUpstreamBody))) } header.Set("Rsp-Header-From-Go", "bar-test") + // support suspending & resuming the filter in a background goroutine return api.Continue } -// Callbacks which are called in response path +// EncodeData might be called multiple times during handling the response body. +// The endStream is true when handling the last piece of the body. func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { if f.path == "/update_upstream_response" { if endStream { buffer.SetString(UpdateUpstreamBody) } else { - // TODO implement buffer->Drain, buffer.SetString means buffer->Drain(buffer.Len()) - buffer.SetString("") + buffer.Reset() } } + // support suspending & resuming the filter in a background goroutine return api.Continue } -/* -The callbacks can be implemented on demand - func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType { return api.Continue } +// OnLog is called when the HTTP stream is ended on HTTP Connection Manager filter. +func (f *filter) OnLog() { + code, _ := f.callbacks.StreamInfo().ResponseCode() + respCode := strconv.Itoa(int(code)) + api.LogDebug(respCode) + + /* + // It's possible to kick off a goroutine here. + // But it's unsafe to access the f.callbacks because the FilterCallbackHandler + // may be already released when the goroutine is scheduled. + go func() { + defer func() { + if p := recover(); p != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + fmt.Printf("http: panic serving: %v\n%s", p, buf) + } + }() + + // do time-consuming jobs + }() + */ +} + +// OnLogDownstreamStart is called when HTTP Connection Manager filter receives a new HTTP request +// (required the corresponding access log type is enabled) +func (f *filter) OnLogDownstreamStart() { + // also support kicking off a goroutine here, like OnLog. +} + +// OnLogDownstreamPeriodic is called on any HTTP Connection Manager periodic log record +// (required the corresponding access log type is enabled) +func (f *filter) OnLogDownstreamPeriodic() { + // also support kicking off a goroutine here, like OnLog. +} + func (f *filter) OnDestroy(reason api.DestroyReason) { + // One should not access f.callbacks here because the FilterCallbackHandler + // is released. But we can still access other Go fields in the filter f. + + // goroutine can be used everywhere. } -*/ diff --git a/examples/golang-http/simple/go.mod b/examples/golang-http/simple/go.mod index 2b66642c61ac9..20653ca69530f 100644 --- a/examples/golang-http/simple/go.mod +++ b/examples/golang-http/simple/go.mod @@ -1,7 +1,7 @@ module github.com/envoyproxy/envoy/examples/golang-http/simple // the version should >= 1.18 -go 1.18 +go 1.20 // NOTICE: these lines could be generated automatically by "go mod tidy" require ( diff --git a/examples/golang-network/envoy.yaml b/examples/golang-network/envoy.yaml index 8c70ec813d32e..4a96a9091f315 100644 --- a/examples/golang-network/envoy.yaml +++ b/examples/golang-network/envoy.yaml @@ -23,5 +23,3 @@ static_resources: - name: plainText type: ORIGINAL_DST lb_policy: CLUSTER_PROVIDED - original_dst_lb_config: - use_http_header: true diff --git a/examples/golang-network/simple/filter.go b/examples/golang-network/simple/filter.go index e81338c0d0480..615d3a0e6aaed 100644 --- a/examples/golang-network/simple/filter.go +++ b/examples/golang-network/simple/filter.go @@ -49,6 +49,8 @@ func (f *filterFactory) CreateFilter(cb api.ConnectionCallback) api.DownstreamFi } type downFilter struct { + api.EmptyDownstreamFilter + cb api.ConnectionCallback upAddr string upFilter *upFilter @@ -85,6 +87,8 @@ func (f *downFilter) OnWrite(buffer []byte, endOfStream bool) api.FilterStatus { } type upFilter struct { + api.EmptyUpstreamFilter + cb api.ConnectionCallback downFilter *downFilter ch chan []byte @@ -92,6 +96,7 @@ type upFilter struct { func (f *upFilter) OnPoolReady(cb api.ConnectionCallback) { f.cb = cb + f.cb.EnableHalfClose(false) localAddr, _ := f.cb.StreamInfo().UpstreamLocalAddress() remoteAddr, _ := f.cb.StreamInfo().UpstreamRemoteAddress() fmt.Printf("OnPoolReady, local: %v, remote: %v\n", localAddr, remoteAddr) diff --git a/go.mod b/go.mod index 5a9ad90398549..f52c724d3fefd 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/envoyproxy/envoy -go 1.18 +go 1.22 -require google.golang.org/protobuf v1.33.0 +require google.golang.org/protobuf v1.36.5 + +require github.com/google/go-cmp v0.5.9 // indirect \ No newline at end of file From 317f5e8fb578ff44bc5a16fdde3ecf3c13900d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Wed, 26 Mar 2025 11:37:56 +0800 Subject: [PATCH 250/274] Update wasm cpp host (#6) --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index defb0d288cd15..95f7acc34539d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "7850d1721fe3dd2ccfb86a06116f76c23b1f1bf8", - sha256 = "740690fc1d749849f6e24b5bc48a07dabc0565a7d03b6cd13425dba693956c57", + version = "70cab1ab3700960d5f72907769a78cf541293f26", + sha256 = "f49ca5a2c1d8f9504b636088d63b237350a2aadf781112be6ad13b9b9f6e4960", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From 3d93dd6644fa70407907fcd77ec851bd1ca0b127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 27 Mar 2025 15:13:05 +0800 Subject: [PATCH 251/274] golang: add callback method for http plugin config destruction (#38597) (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: François JACQUES Co-authored-by: François JACQUES --- changelogs/current.yaml | 25 +++++++++ contrib/golang/common/go/api/filter.go | 8 +++ .../filters/http/source/go/pkg/http/config.go | 11 +++- contrib/golang/filters/http/test/BUILD | 1 + .../golang/filters/http/test/config_test.cc | 30 ++++++++++ .../golang/filters/http/test/test_data/BUILD | 1 + .../http/test/test_data/destroyconfig/BUILD | 35 ++++++++++++ .../http/test/test_data/destroyconfig/api.h | 1 + .../test/test_data/destroyconfig/config.go | 55 +++++++++++++++++++ .../test_data/destroyconfig/destroyconfig.h | 25 +++++++++ .../filters/http/test/test_data/plugins.go | 1 + 11 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 contrib/golang/filters/http/test/test_data/destroyconfig/BUILD create mode 120000 contrib/golang/filters/http/test/test_data/destroyconfig/api.h create mode 100644 contrib/golang/filters/http/test/test_data/destroyconfig/config.go create mode 100644 contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 81912d9d3b197..26d799cbd618f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -5,3 +5,28 @@ bug_fixes: change: | Fixed a bug where additional :ref:`cookie attributes ` are not sent properly to clients. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: +- area: access_log + change: | + added %RESPONSE_FLAGS_LONG% substitution string, that will output a pascal case string representing the response flags. + The output response flags will correspond with %RESPONSE_FLAGS%, only with a long textual string representation. +- area: extension_discovery_service + change: | + added ECDS support for :ref:` downstream network filters`. +- area: access_log + change: | + Added support for logging upstream connection establishment duration in the + :ref:`%COMMON_DURATION% ` access log + formatter operator. The following time points were added: ``%US_CX_BEG%``, + ``%US_CX_END%``, ``%US_HS_END%``. +- area: golang + change: | + added http golang filter config destroy callback. When a config gets deleted from envoy, the go plugin calls the + Destroy function on the config instance. config should implement the new + github.com/envoyproxy/envoy/contrib/golang/common/go/api.Config interface, implementing the Destroy function. + +deprecated: diff --git a/contrib/golang/common/go/api/filter.go b/contrib/golang/common/go/api/filter.go index 569d48b7ac603..482fe39a7f6f5 100644 --- a/contrib/golang/common/go/api/filter.go +++ b/contrib/golang/common/go/api/filter.go @@ -105,14 +105,22 @@ func (*PassThroughStreamFilter) OnDestroy(DestroyReason) { func (*PassThroughStreamFilter) OnStreamComplete() { } +type Config interface { + // Called when the current config is deleted due to an update or removal of plugin. + // You can use this method is you store some resources in the config to be released later. + Destroy() +} + type StreamFilterConfigParser interface { // Parse the proto message to any Go value, and return error to reject the config. // This is called when Envoy receives the config from the control plane. // Also, you can define Metrics through the callbacks, and the callbacks will be nil when parsing the route config. + // You can return a config implementing the Config interface if you need fine control over its lifecycle. Parse(any *anypb.Any, callbacks ConfigCallbackHandler) (interface{}, error) // Merge the two configs(filter level config or route level config) into one. // May merge multi-level configurations, i.e. filter level, virtualhost level, router level and weighted cluster level, // into a single one recursively, by invoking this method multiple times. + // You can return a config implementing the Config interface if you need fine control over its lifecycle. Merge(parentConfig interface{}, childConfig interface{}) interface{} } diff --git a/contrib/golang/filters/http/source/go/pkg/http/config.go b/contrib/golang/filters/http/source/go/pkg/http/config.go index 96df664579615..e1ae3f00e4b38 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/config.go +++ b/contrib/golang/filters/http/source/go/pkg/http/config.go @@ -105,17 +105,24 @@ func envoyGoFilterDestroyHttpPluginConfig(id uint64, needDelay int) { // 2. while B envoy worker thread may update the merged_config_id_ in getMergedConfigId, that will delete the id. // so, we delay deleting the id in the Go side. time.AfterFunc(delayDeleteTime, func() { - configCache.Delete(id) + destroyConfig(id) }) } else { // there is no race for non-merged config. - configCache.Delete(id) + destroyConfig(id) } if asanTestEnabled { forceGCFinalizer() } } +func destroyConfig(id uint64) { + c, _ := configCache.LoadAndDelete(id) + if conf, ok := c.(api.Config); ok { + conf.Destroy() + } +} + //export envoyGoFilterMergeHttpPluginConfig func envoyGoFilterMergeHttpPluginConfig(namePtr, nameLen, parentId, childId uint64) uint64 { name := utils.BytesToString(namePtr, nameLen) diff --git a/contrib/golang/filters/http/test/BUILD b/contrib/golang/filters/http/test/BUILD index e7ab7e4b1842d..f73d7f7ebfc04 100644 --- a/contrib/golang/filters/http/test/BUILD +++ b/contrib/golang/filters/http/test/BUILD @@ -18,6 +18,7 @@ envoy_cc_test( ], deps = [ "//contrib/golang/filters/http/source:config", + "//contrib/golang/filters/http/test/test_data/destroyconfig:destroyconfig_test_lib", "//test/mocks/server:factory_context_mocks", "//test/test_common:utility_lib", ], diff --git a/contrib/golang/filters/http/test/config_test.cc b/contrib/golang/filters/http/test/config_test.cc index 0ced3ae2e9ca8..3bccb82fcc4ad 100644 --- a/contrib/golang/filters/http/test/config_test.cc +++ b/contrib/golang/filters/http/test/config_test.cc @@ -9,6 +9,7 @@ #include "absl/strings/str_format.h" #include "contrib/golang/filters/http/source/config.h" #include "contrib/golang/filters/http/source/golang_filter.h" +#include "contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -100,6 +101,35 @@ TEST(GolangFilterConfigTest, GolangFilterWithNilPluginConfig) { cleanup(); } +TEST(GolangFilterConfigTest, GolangFilterDestroyConfig) { + const auto yaml_fmt = R"EOF( + library_id: %s + library_path: %s + plugin_name: %s + )EOF"; + + const std::string DESTROYCONFIG{"destroyconfig"}; + auto yaml_string = absl::StrFormat(yaml_fmt, DESTROYCONFIG, genSoPath(), DESTROYCONFIG); + envoy::extensions::filters::http::golang::v3alpha::Config proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + + auto dso_lib = Dso::DsoManager::load( + proto_config.library_id(), proto_config.library_path(), proto_config.plugin_name()); + auto config_ = new httpDestroyableConfig(); + config_->plugin_name_ptr = reinterpret_cast(DESTROYCONFIG.data()); + config_->plugin_name_len = DESTROYCONFIG.length(); + config_->config_ptr = 0; + config_->config_len = 0; + config_->is_route_config = 0; + config_->concurrency = 0; + config_->destroyed = 0; + auto config_id_ = dso_lib->envoyGoFilterNewHttpPluginConfig(config_); + dso_lib->envoyGoFilterDestroyHttpPluginConfig(config_id_, 0); + EXPECT_TRUE(config_->destroyed); + delete config_; + cleanup(); +} + } // namespace } // namespace Golang } // namespace HttpFilters diff --git a/contrib/golang/filters/http/test/test_data/BUILD b/contrib/golang/filters/http/test/test_data/BUILD index 7c0c418d0c284..c011d87cf3610 100644 --- a/contrib/golang/filters/http/test/test_data/BUILD +++ b/contrib/golang/filters/http/test/test_data/BUILD @@ -21,6 +21,7 @@ go_binary( "//contrib/golang/filters/http/test/test_data/basic", "//contrib/golang/filters/http/test/test_data/buffer", "//contrib/golang/filters/http/test/test_data/bufferinjectdata", + "//contrib/golang/filters/http/test/test_data/destroyconfig", "//contrib/golang/filters/http/test/test_data/echo", "//contrib/golang/filters/http/test/test_data/metric", "//contrib/golang/filters/http/test/test_data/passthrough", diff --git a/contrib/golang/filters/http/test/test_data/destroyconfig/BUILD b/contrib/golang/filters/http/test/test_data/destroyconfig/BUILD new file mode 100644 index 0000000000000..06d0358cc3fda --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/destroyconfig/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test_library( + name = "destroyconfig_test_lib", + hdrs = [ + "api.h", + "destroyconfig.h", + ], +) + +go_library( + name = "destroyconfig", + srcs = [ + "api.h", + "config.go", + "destroyconfig.h", + ], + cgo = True, + importpath = "example.com/test-data/destroyconfig", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@org_golang_google_protobuf//types/known/anypb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/destroyconfig/api.h b/contrib/golang/filters/http/test/test_data/destroyconfig/api.h new file mode 120000 index 0000000000000..7b35c995072f7 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/destroyconfig/api.h @@ -0,0 +1 @@ +../../../../../common/go/api/api.h \ No newline at end of file diff --git a/contrib/golang/filters/http/test/test_data/destroyconfig/config.go b/contrib/golang/filters/http/test/test_data/destroyconfig/config.go new file mode 100644 index 0000000000000..2d81b97a7aeeb --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/destroyconfig/config.go @@ -0,0 +1,55 @@ +package destroyconfig + +/* +#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all +#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup + +#include "destroyconfig.h" + +*/ +import "C" +import ( + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" + "google.golang.org/protobuf/types/known/anypb" + "unsafe" +) + +const Name = "destroyconfig" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, http.PassThroughFactory, &parser{}) +} + +var cfgPointer unsafe.Pointer + +type config struct { + cb api.ConfigCallbackHandler +} + +func (c *config) Destroy() { + // call cApi.HttpDefineMetric to store the config pointer + c.cb.DefineCounterMetric("") + C.envoyGoConfigDestroy(cfgPointer) +} + +type capi struct { + api.HttpCAPI +} + +func (c *capi) HttpConfigFinalize(_ unsafe.Pointer) {} + +func (c *capi) HttpDefineMetric(cfg unsafe.Pointer, _ api.MetricType, _ string) uint32 { + cfgPointer = cfg + return 0 +} + +type parser struct { + api.StreamFilterConfigParser +} + +func (p *parser) Parse(_ *anypb.Any, cb api.ConfigCallbackHandler) (interface{}, error) { + http.SetHttpCAPI(&capi{}) + conf := &config{cb} + return conf, nil +} diff --git a/contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h b/contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h new file mode 100644 index 0000000000000..b0473e7e0a46e --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h @@ -0,0 +1,25 @@ +#pragma once +// NOLINT(namespace-envoy) +#pragma GCC diagnostic ignored "-Wold-style-cast" +#include "api.h" + +#ifdef __cplusplus +struct httpDestroyableConfig : httpConfig { + int destroyed; +}; +extern "C" { +#else +typedef struct { + httpConfig c; + int destroyed; +} httpDestroyableConfig; +#endif + +void envoyGoConfigDestroy(void* c) { + httpDestroyableConfig* dc = (httpDestroyableConfig*)(c); + dc->destroyed = 1; +}; + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/contrib/golang/filters/http/test/test_data/plugins.go b/contrib/golang/filters/http/test/test_data/plugins.go index 4afedeea77b77..9c1336e7e2edf 100644 --- a/contrib/golang/filters/http/test/test_data/plugins.go +++ b/contrib/golang/filters/http/test/test_data/plugins.go @@ -7,6 +7,7 @@ import ( _ "example.com/test-data/basic" _ "example.com/test-data/buffer" _ "example.com/test-data/bufferinjectdata" + _ "example.com/test-data/destroyconfig" _ "example.com/test-data/echo" _ "example.com/test-data/metric" _ "example.com/test-data/passthrough" From db291447e7f95766ae17825fbbd909d3cb332ec9 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 1 Apr 2025 14:46:52 +0800 Subject: [PATCH 252/274] update proxy-wasm-cpp-host --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 95f7acc34539d..7dd6a8499c048 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "70cab1ab3700960d5f72907769a78cf541293f26", - sha256 = "f49ca5a2c1d8f9504b636088d63b237350a2aadf781112be6ad13b9b9f6e4960", + version = "3f379709371133385d759ff15664bc0360935d28", + sha256 = "12bf34e5f5ce899888226bb9c10cbbeebc14b65ed5e47c47c62a03568ee7e4b0", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From a2c5a079600cfbd88c896d526f44da8a6ff1769d Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 1 Apr 2025 22:48:33 +0800 Subject: [PATCH 253/274] fix poll_oneof in wasm host --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7dd6a8499c048..92ea228b100d9 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "3f379709371133385d759ff15664bc0360935d28", - sha256 = "12bf34e5f5ce899888226bb9c10cbbeebc14b65ed5e47c47c62a03568ee7e4b0", + version = "cde187acecafa55830a7dfe6d126459e109dee5a", + sha256 = "9b9c0011ec1743bc39ac5659557473f4602866ee4eb31f1b77d1f76c0ea1f418", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From e114a74dd8ad8608e9230c0228924fa63e7b1c43 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 14 Apr 2025 14:30:38 +0800 Subject: [PATCH 254/274] fix go runtime gc --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 92ea228b100d9..56c1b5228a5d7 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "cde187acecafa55830a7dfe6d126459e109dee5a", - sha256 = "9b9c0011ec1743bc39ac5659557473f4602866ee4eb31f1b77d1f76c0ea1f418", + version = "b31ae6ffc244309787103e370aabc37691c00b0b", + sha256 = "eefab07a792cfa9110d99be1289ee1bc46fa911c419e4553987fb58b061f9a51", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From cd0c7a9a57bc29f2e847724ea057f573d271d9c2 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 14 Apr 2025 20:46:29 +0800 Subject: [PATCH 255/274] Remove restricted callback limitations during malloc --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 56c1b5228a5d7..c6068a42e2c57 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "b31ae6ffc244309787103e370aabc37691c00b0b", - sha256 = "eefab07a792cfa9110d99be1289ee1bc46fa911c419e4553987fb58b061f9a51", + version = "2e960999e6d24cb9037d7cf4e7bab5755d8ae2fb.tar.gz", + sha256 = "a2f681576b0f12c735cc4fe97fab62d97fdbb3924c26153cd22149b42b7c1f3d", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From 80f354c9441cd32a833aa19dfc61ae715b052ab7 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 14 Apr 2025 20:50:39 +0800 Subject: [PATCH 256/274] fix version typo --- bazel/repository_locations.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index c6068a42e2c57..efef5e8add834 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,7 +1362,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "2e960999e6d24cb9037d7cf4e7bab5755d8ae2fb.tar.gz", + version = "2e960999e6d24cb9037d7cf4e7bab5755d8ae2fb", sha256 = "a2f681576b0f12c735cc4fe97fab62d97fdbb3924c26153cd22149b42b7c1f3d", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], From 17cf01d9f6448d329f776df13ac18ea001618748 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 28 Apr 2025 11:04:12 +0800 Subject: [PATCH 257/274] fix golang-filter crash --- .bazelrc | 7 ++++++- contrib/golang/filters/http/source/go/pkg/http/shim.go | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.bazelrc b/.bazelrc index 138dc5e00a511..055f7c48197b9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -532,4 +532,9 @@ try-import %workspace%/clang.bazelrc try-import %workspace%/user.bazelrc try-import %workspace%/local_tsan.bazelrc -build --define tcmalloc=gperftools \ No newline at end of file + +# Prevent crashes caused by the new version of tcmalloc using the percpu feature +build --define tcmalloc=gperftools + +# Avoid affecting the signal handling in golang-filter +build --define signal_trace=disabled diff --git a/contrib/golang/filters/http/source/go/pkg/http/shim.go b/contrib/golang/filters/http/source/go/pkg/http/shim.go index 8b2ffc1a88d70..97529b644a229 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/shim.go +++ b/contrib/golang/filters/http/source/go/pkg/http/shim.go @@ -275,6 +275,7 @@ func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64, encodingState := getOrCreateState(encodingStateWrapper) req := getRequest(r) if req == nil { + // When creating DownstreamStart access log, the request is not initialized yet req = createRequest(r) } @@ -352,7 +353,10 @@ func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64, func envoyGoFilterOnHttpStreamComplete(r *C.httpRequest) { req := getRequest(r) defer req.recoverPanic() - + if req == nil { + // When the client aborts, the request may be not initialized yet + req = createRequest(r) + } f := req.httpFilter f.OnStreamComplete() } @@ -362,6 +366,10 @@ func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) { req := getRequest(r) // do nothing even when req.panic is true, since filter is already destroying. defer req.recoverPanic() + if req == nil { + // When the client aborts, the request may be not initialized yet + req = createRequest(r) + } req.resumeWaitCallback() From 70e1ac0831e474e485474c5fd165514e059a4879 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 28 Apr 2025 17:34:22 +0800 Subject: [PATCH 258/274] update proxy wasm cpp host commit --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index efef5e8add834..270b7fd8cd6eb 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "2e960999e6d24cb9037d7cf4e7bab5755d8ae2fb", - sha256 = "a2f681576b0f12c735cc4fe97fab62d97fdbb3924c26153cd22149b42b7c1f3d", + version = "71f6a9e19e99e0121d6bd62f2e0e6c8b158f69d7", + sha256 = "58b497216f6a0e72414300c34bfd5d2b97c3aa110957b5feee038c0e6c0d5b85", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From cf96e878817e9f4ebbd4598b49d4c5c4f35d7178 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Wed, 30 Apr 2025 22:35:42 +0800 Subject: [PATCH 259/274] fix golang filter --- bazel/foreign_cc/BUILD | 2 ++ .../golang/filters/http/source/go/pkg/http/shim.go | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index c8b5838ee522f..2d202b26bba44 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -616,6 +616,8 @@ envoy_cmake( "WAMR_BUILD_DUMP_CALL_STACK": "1", # linux perf. only for jit and aot "WAMR_BUILD_LINUX_PERF": "1", + # avoid conflicts between os_thread_signal_init and the signal stack in the golang filter. + "WASM_DISABLE_HW_BOUND_CHECK": "1", }, lib_source = "@com_github_wamr//:all", out_static_libs = ["libvmlib.a"], diff --git a/contrib/golang/filters/http/source/go/pkg/http/shim.go b/contrib/golang/filters/http/source/go/pkg/http/shim.go index 97529b644a229..4d97a09255067 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/shim.go +++ b/contrib/golang/filters/http/source/go/pkg/http/shim.go @@ -276,7 +276,7 @@ func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64, req := getRequest(r) if req == nil { // When creating DownstreamStart access log, the request is not initialized yet - req = createRequest(r) + return } defer req.recoverPanic() @@ -352,11 +352,11 @@ func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64, //export envoyGoFilterOnHttpStreamComplete func envoyGoFilterOnHttpStreamComplete(r *C.httpRequest) { req := getRequest(r) - defer req.recoverPanic() if req == nil { // When the client aborts, the request may be not initialized yet - req = createRequest(r) + return } + defer req.recoverPanic() f := req.httpFilter f.OnStreamComplete() } @@ -364,12 +364,12 @@ func envoyGoFilterOnHttpStreamComplete(r *C.httpRequest) { //export envoyGoFilterOnHttpDestroy func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) { req := getRequest(r) - // do nothing even when req.panic is true, since filter is already destroying. - defer req.recoverPanic() if req == nil { // When the client aborts, the request may be not initialized yet - req = createRequest(r) + return } + // do nothing even when req.panic is true, since filter is already destroying. + defer req.recoverPanic() req.resumeWaitCallback() From 2c556780b65cb13dd1962226ce576bd2d844bb41 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Wed, 30 Apr 2025 23:13:13 +0800 Subject: [PATCH 260/274] fix typo --- bazel/foreign_cc/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index 2d202b26bba44..b87c01db5fc98 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -617,7 +617,7 @@ envoy_cmake( # linux perf. only for jit and aot "WAMR_BUILD_LINUX_PERF": "1", # avoid conflicts between os_thread_signal_init and the signal stack in the golang filter. - "WASM_DISABLE_HW_BOUND_CHECK": "1", + "WAMR_DISABLE_HW_BOUND_CHECK": "1", }, lib_source = "@com_github_wamr//:all", out_static_libs = ["libvmlib.a"], From b37a2988a1ff776d20ea3f21030e1f7a457d9a3a Mon Sep 17 00:00:00 2001 From: rinfx Date: Thu, 5 Jun 2025 17:04:07 +0800 Subject: [PATCH 261/274] [feature] extend wasm abi, includes: injectEncodedDataToFilterChain for handle stream response body, getUpstreamHosts & setUpstreamOverrideHost for self-defined load balance policy (#8) --- api/envoy/api/v2/core/health_check.proto | 2 + api/envoy/config/core/v3/health_check.proto | 2 + bazel/repository_locations.bzl | 4 +- envoy/upstream/upstream.h | 11 ++++ source/common/upstream/upstream_impl.h | 31 +++++++++++ source/extensions/common/wasm/context.cc | 52 +++++++++++++++++++ source/extensions/common/wasm/context.h | 7 +++ .../common/health_checker_base_impl.cc | 3 ++ .../common/health_checker_base_impl.h | 3 ++ .../http/health_checker_impl.cc | 12 +++++ .../upstream/health_checker_impl_test.cc | 43 +++++++++++++++ 11 files changed, 168 insertions(+), 2 deletions(-) diff --git a/api/envoy/api/v2/core/health_check.proto b/api/envoy/api/v2/core/health_check.proto index 347ac9c96b909..0b50677829cf8 100644 --- a/api/envoy/api/v2/core/health_check.proto +++ b/api/envoy/api/v2/core/health_check.proto @@ -306,4 +306,6 @@ message HealthCheck { // This allows overriding the cluster TLS settings, just for health check connections. TlsOptions tls_options = 21; + + bool store_metrics = 127; } diff --git a/api/envoy/config/core/v3/health_check.proto b/api/envoy/config/core/v3/health_check.proto index 2ec258d8ac095..2b6bce6ba61b2 100644 --- a/api/envoy/config/core/v3/health_check.proto +++ b/api/envoy/config/core/v3/health_check.proto @@ -426,4 +426,6 @@ message HealthCheck { // the cluster's :ref:`transport socket ` // will be used for health check socket configuration. google.protobuf.Struct transport_socket_match_criteria = 23; + + bool store_metrics = 127; } diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 270b7fd8cd6eb..b33154c676b6a 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "71f6a9e19e99e0121d6bd62f2e0e6c8b158f69d7", - sha256 = "58b497216f6a0e72414300c34bfd5d2b97c3aa110957b5feee038c0e6c0d5b85", + version = "27df3052072dd97527945c4167ef912020a0a92e", + sha256 = "523ef02a84a69b03bdcde76053f3f412d73a20150f4bb11d6c11a127ed7df7dc", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h index a57a66cb8b4e3..1f72795e336bd 100644 --- a/envoy/upstream/upstream.h +++ b/envoy/upstream/upstream.h @@ -282,6 +282,17 @@ class Host : virtual public HostDescription { * Set true to disable active health check for the host. */ virtual void setDisableActiveHealthCheck(bool disable_active_health_check) PURE; + +#if defined(HIGRESS) + /** + * @return endpoint metrics string. + */ + virtual std::string getEndpointMetrics() const PURE; + /** + * set endpoint metrics string. + */ + virtual void setEndpointMetrics(absl::string_view endpoint_metrics) PURE; +#endif }; using HostConstSharedPtr = std::shared_ptr; diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 3bcdf86ef941a..5a09d17766147 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -455,6 +456,29 @@ class HostImpl : public HostDescriptionImpl, return std::make_unique(shared_from_this()); } +#if defined(HIGRESS) + std::string getEndpointMetrics() const override { + if (endpoint_metrics_ptr_ != nullptr) { + auto* ptr = endpoint_metrics_ptr_.load(std::memory_order_acquire); + return *ptr; + } else { + return ""; + } + } + + void setEndpointMetrics(absl::string_view endpoint_metrics) override { + if (set_backup_) { + endpoint_metrics_backup_ = endpoint_metrics; + endpoint_metrics_ptr_.store(&endpoint_metrics_backup_, std::memory_order_release); + set_backup_ = false; + } else { + endpoint_metrics_ = endpoint_metrics; + endpoint_metrics_ptr_.store(&endpoint_metrics_, std::memory_order_release); + set_backup_ = true; + } + } +#endif + protected: static CreateConnectionData createConnection(Event::Dispatcher& dispatcher, const ClusterInfo& cluster, @@ -492,6 +516,13 @@ class HostImpl : public HostDescriptionImpl, const std::weak_ptr parent_; }; mutable std::atomic handle_count_{}; + +#if defined(HIGRESS) + std::string endpoint_metrics_; + std::string endpoint_metrics_backup_; + std::atomic endpoint_metrics_ptr_; + bool set_backup_ = false; +#endif }; class HostsPerLocalityImpl : public HostsPerLocality { diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 6cb577789d534..96f863cb24d85 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include "envoy/common/exception.h" #include "envoy/extensions/wasm/v3/wasm.pb.validate.h" @@ -45,6 +46,7 @@ #include "openssl/bytestring.h" #include "openssl/hmac.h" #include "openssl/sha.h" +#include "include/nlohmann/json.hpp" using proxy_wasm::MetricType; using proxy_wasm::Word; @@ -1930,6 +1932,56 @@ WasmResult Context::sendLocalResponse(uint32_t response_code, std::string_view b return WasmResult::Ok; } +#if defined(HIGRESS) +WasmResult Context::injectEncodedDataToFilterChain(std::string_view body_text, bool end_stream) { + if (encoder_callbacks_) { + auto buffer = ::Envoy::Buffer::OwnedImpl(body_text); + encoder_callbacks_->injectEncodedDataToFilterChain(buffer, end_stream); + } + return WasmResult::Ok; +} +std::string convertHealthStatusToString(Upstream::Host::Health status) { + if (status == Upstream::Host::Health::Unhealthy) { + return "Unhealthy"; + } else if (status == Upstream::Host::Health::Degraded) { + return "Degraded"; + } else { + return "Healthy"; + } +} +WasmResult Context::getUpstreamHosts(StringPairs * result) { + if (decoder_callbacks_) { + auto upstream_cluster = decoder_callbacks_->clusterInfo(); + if (!upstream_cluster) { + return WasmResult::Ok; + } + for (auto& p : this->clusterManager().getThreadLocalCluster(upstream_cluster->name())->prioritySet().hostSetsPerPriority()) { + for (auto& h : p->hosts()) { + std::map info_map; + if (!h->getEndpointMetrics().empty()) { + info_map["metrics"] = h->getEndpointMetrics(); + } + info_map["health_status"] = convertHealthStatusToString(h->coarseHealth()); + try { + nlohmann::json j = info_map; + result->push_back(std::make_pair(h->address()->asString(), j.dump(0))); + } catch (const std::exception& e) { + ENVOY_LOG(error, "getUpstreamHosts json dump failed: {}", e.what()); + result->push_back(std::make_pair(h->address()->asString(), "{\"error\": \"Failed to get host info\"}")); + } + } + } + } + return WasmResult::Ok; +} +WasmResult Context::setUpstreamOverrideHost(std::string_view address) { + if (decoder_callbacks_) { + decoder_callbacks_->setUpstreamOverrideHost(address); + } + return WasmResult::Ok; +} +#endif + Http::FilterHeadersStatus Context::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { onCreate(); request_headers_ = &headers; diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 6006e7291f360..3744773965875 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -30,6 +30,7 @@ using proxy_wasm::CloseType; using proxy_wasm::ContextBase; using proxy_wasm::Pairs; using proxy_wasm::PairsWithStringValues; +using proxy_wasm::StringPairs; using proxy_wasm::PluginBase; using proxy_wasm::PluginHandleBase; using proxy_wasm::SharedQueueDequeueToken; @@ -217,6 +218,12 @@ class Context : public proxy_wasm::ContextBase, WasmResult sendLocalResponse(uint32_t response_code, std::string_view body_text, Pairs additional_headers, uint32_t grpc_status, std::string_view details) override; +#if defined(HIGRESS) + WasmResult injectEncodedDataToFilterChain(std::string_view body_text, bool end_stream) override; + WasmResult getUpstreamHosts(StringPairs * result) override; + WasmResult setUpstreamOverrideHost(std::string_view address) override; +#endif + void clearRouteCache() override { #if defined(HIGRESS) if (decoder_callbacks_ && !disable_clear_route_cache_) { diff --git a/source/extensions/health_checkers/common/health_checker_base_impl.cc b/source/extensions/health_checkers/common/health_checker_base_impl.cc index 963930829f00a..df7b3c63c7cb5 100644 --- a/source/extensions/health_checkers/common/health_checker_base_impl.cc +++ b/source/extensions/health_checkers/common/health_checker_base_impl.cc @@ -18,6 +18,9 @@ HealthCheckerImplBase::HealthCheckerImplBase(const Cluster& cluster, Random::RandomGenerator& random, HealthCheckEventLoggerPtr&& event_logger) : always_log_health_check_failures_(config.always_log_health_check_failures()), +#if defined(HIGRESS) + store_metrics_(config.store_metrics()), +#endif cluster_(cluster), dispatcher_(dispatcher), timeout_(PROTOBUF_GET_MS_REQUIRED(config, timeout)), unhealthy_threshold_(PROTOBUF_GET_WRAPPED_REQUIRED(config, unhealthy_threshold)), diff --git a/source/extensions/health_checkers/common/health_checker_base_impl.h b/source/extensions/health_checkers/common/health_checker_base_impl.h index 3eed7583c9e0f..b56112fc788c3 100644 --- a/source/extensions/health_checkers/common/health_checker_base_impl.h +++ b/source/extensions/health_checkers/common/health_checker_base_impl.h @@ -104,6 +104,9 @@ class HealthCheckerImplBase : public HealthChecker, virtual envoy::data::core::v3::HealthCheckerType healthCheckerType() const PURE; const bool always_log_health_check_failures_; +#if defined(HIGRESS) + const bool store_metrics_; +#endif const Cluster& cluster_; Event::Dispatcher& dispatcher_; const std::chrono::milliseconds timeout_; diff --git a/source/extensions/health_checkers/http/health_checker_impl.cc b/source/extensions/health_checkers/http/health_checker_impl.cc index 49b07984222e8..bc5164a02ddf5 100644 --- a/source/extensions/health_checkers/http/health_checker_impl.cc +++ b/source/extensions/health_checkers/http/health_checker_impl.cc @@ -213,7 +213,13 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::decodeHeaders( void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::decodeData(Buffer::Instance& data, bool end_stream) { +#if defined(HIGRESS) + if (parent_.store_metrics_) { + response_body_->move(data, data.length()); + } else if (parent_.response_buffer_size_ != 0) { +#else if (parent_.response_buffer_size_ != 0) { +#endif if (!parent_.receive_bytes_.empty() && response_body_->length() < parent_.response_buffer_size_) { response_body_->move(data, parent_.response_buffer_size_ - response_body_->length()); @@ -324,6 +330,12 @@ HttpHealthCheckerImpl::HttpActiveHealthCheckSession::healthCheckResult() { ENVOY_CONN_LOG(debug, "hc response_code={} health_flags={}", *client_, response_code, HostUtility::healthFlagsToString(*host_)); +#if defined(HIGRESS) + ENVOY_CONN_LOG(debug, "hc hostname={}, address={} response_body_length={}", + *client_, host_->hostname(), host_->address()->asString(), response_body_->length()); + host_->setEndpointMetrics(response_body_->toString()); +#endif + if (!parent_.receive_bytes_.empty()) { // If the expected response is set, check the first 1024 bytes of actual response if contains // the expected response. diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 9558e88d7e398..466b2134c6433 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -549,6 +549,25 @@ class HttpHealthCheckerImplTest : public Event::TestUsingSimulatedTime, addCompletionCallback(); } + +#if defined(HIGRESS) + void setupLLMServiceWithExpectedResponseHC() { + // Response: Base64 string of "Everything OK". + std::string yaml = R"EOF( + timeout: 1s + interval: 1s + interval_jitter: 1s + unhealthy_threshold: 2 + healthy_threshold: 2 + store_metrics: true + http_health_check: + path: /metrics + )EOF"; + allocHealthChecker(yaml); + addCompletionCallback(); + } +#endif + const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig makeHealthCheckConfig(const uint32_t port_value) { envoy::config::endpoint::v3::Endpoint::HealthCheckConfig config; @@ -6648,6 +6667,30 @@ TEST(HealthCheckProto, Validation) { } } +#if defined(HIGRESS) +TEST_F(HttpHealthCheckerImplTest, LLMServiceHealthCheckSuccess) { + setupLLMServiceWithExpectedResponseHC(); + EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; + cluster_->info_->trafficStats()->upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); + health_checker_->start(); + EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); + EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) + .WillOnce(Return(45000)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); + respondBody(0, "200", {"Test Everything OK"}); + EXPECT_EQ(Host::Health::Healthy, + cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->coarseHealth()); +} +#endif + + } // namespace } // namespace Upstream } // namespace Envoy From 9917bb96f5f2c901cbc0fd3f82485f897c049c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Mon, 16 Jun 2025 15:09:10 +0800 Subject: [PATCH 262/274] add wasm abi: injectEncodedDataToFilterChainOnHeader (#9) --- source/extensions/common/wasm/BUILD | 1 + source/extensions/common/wasm/context.cc | 23 +++++++++-- source/extensions/common/wasm/context.h | 1 + source/extensions/common/wasm/ext/BUILD | 18 ++++++++ .../common/wasm/ext/inject_encoded_data.proto | 8 ++++ source/extensions/common/wasm/foreign.cc | 41 +++++++++++++++++++ 6 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 source/extensions/common/wasm/ext/inject_encoded_data.proto diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index ba67df990189f..d29bee624c83b 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -91,6 +91,7 @@ envoy_cc_extension( "//source/common/network/dns_resolver:dns_factory_util_lib", "//source/common/tracing:http_tracer_lib", "//source/extensions/common/wasm/ext:declare_property_cc_proto", + "//source/extensions/common/wasm/ext:inject_encoded_data_cc_proto", "//source/extensions/common/wasm/ext:envoy_null_vm_wasm_api", "//source/extensions/filters/common/expr:context_lib", "@com_google_cel_cpp//eval/public:builtin_func_registrar", diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 96f863cb24d85..592e50d1038d7 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1940,6 +1940,19 @@ WasmResult Context::injectEncodedDataToFilterChain(std::string_view body_text, b } return WasmResult::Ok; } + +WasmResult Context::injectEncodedDataToFilterChainOnHeader(std::string_view body_text, + bool end_stream) { + if (encoder_callbacks_) { + std::string body_text_copy(body_text); + encoder_callbacks_->dispatcher().post([=]() { + auto buffer = ::Envoy::Buffer::OwnedImpl(body_text_copy); + encoder_callbacks_->injectEncodedDataToFilterChain(buffer, end_stream); + }); + } + return WasmResult::Ok; +} + std::string convertHealthStatusToString(Upstream::Host::Health status) { if (status == Upstream::Host::Health::Unhealthy) { return "Unhealthy"; @@ -1949,13 +1962,16 @@ std::string convertHealthStatusToString(Upstream::Host::Health status) { return "Healthy"; } } -WasmResult Context::getUpstreamHosts(StringPairs * result) { +WasmResult Context::getUpstreamHosts(StringPairs* result) { if (decoder_callbacks_) { auto upstream_cluster = decoder_callbacks_->clusterInfo(); if (!upstream_cluster) { return WasmResult::Ok; } - for (auto& p : this->clusterManager().getThreadLocalCluster(upstream_cluster->name())->prioritySet().hostSetsPerPriority()) { + for (auto& p : this->clusterManager() + .getThreadLocalCluster(upstream_cluster->name()) + ->prioritySet() + .hostSetsPerPriority()) { for (auto& h : p->hosts()) { std::map info_map; if (!h->getEndpointMetrics().empty()) { @@ -1967,7 +1983,8 @@ WasmResult Context::getUpstreamHosts(StringPairs * result) { result->push_back(std::make_pair(h->address()->asString(), j.dump(0))); } catch (const std::exception& e) { ENVOY_LOG(error, "getUpstreamHosts json dump failed: {}", e.what()); - result->push_back(std::make_pair(h->address()->asString(), "{\"error\": \"Failed to get host info\"}")); + result->push_back( + std::make_pair(h->address()->asString(), "{\"error\": \"Failed to get host info\"}")); } } } diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 3744773965875..32e6cf30a54a8 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -220,6 +220,7 @@ class Context : public proxy_wasm::ContextBase, std::string_view details) override; #if defined(HIGRESS) WasmResult injectEncodedDataToFilterChain(std::string_view body_text, bool end_stream) override; + WasmResult injectEncodedDataToFilterChainOnHeader(std::string_view body_text, bool end_stream); WasmResult getUpstreamHosts(StringPairs * result) override; WasmResult setUpstreamOverrideHost(std::string_view address) override; #endif diff --git a/source/extensions/common/wasm/ext/BUILD b/source/extensions/common/wasm/ext/BUILD index 12903ef33fe58..5294f016a8acd 100644 --- a/source/extensions/common/wasm/ext/BUILD +++ b/source/extensions/common/wasm/ext/BUILD @@ -89,3 +89,21 @@ cc_proto_library( # "//external:protobuf_clib", ], ) + +# NB: this target is compiled both to native code and to Wasm. Hence the generic rule. +proto_library( + name = "inject_encoded_data_proto", + srcs = ["inject_encoded_data.proto"], + deps = [ + "@com_google_protobuf//:struct_proto", + ], +) + +# NB: this target is compiled both to native code and to Wasm. Hence the generic rule. +cc_proto_library( + name = "inject_encoded_data_cc_proto", + deps = [ + ":inject_encoded_data_proto", + # "//external:protobuf_clib", + ], +) \ No newline at end of file diff --git a/source/extensions/common/wasm/ext/inject_encoded_data.proto b/source/extensions/common/wasm/ext/inject_encoded_data.proto new file mode 100644 index 0000000000000..78ec04d35ae2f --- /dev/null +++ b/source/extensions/common/wasm/ext/inject_encoded_data.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package envoy.source.extensions.common.wasm; + +message InjectEncodedDataToFilterChainArguments { + string body = 1; + bool endstream = 2; +}; diff --git a/source/extensions/common/wasm/foreign.cc b/source/extensions/common/wasm/foreign.cc index 3f153461e96c9..0edbe03a33410 100644 --- a/source/extensions/common/wasm/foreign.cc +++ b/source/extensions/common/wasm/foreign.cc @@ -1,5 +1,6 @@ #include "source/common/common/logger.h" #include "source/extensions/common/wasm/ext/declare_property.pb.h" +#include "source/extensions/common/wasm/ext/inject_encoded_data.pb.h" #include "source/extensions/common/wasm/wasm.h" #if defined(WASM_USE_CEL_PARSER) @@ -271,6 +272,46 @@ RegisterForeignFunction registerDeclarePropertyForeignFunction("declare_property", createFromClass()); +#if defined(HIGRESS) +class InjectEncodedDataToFilterChainFactory: public Logger::Loggable { +public: + WasmForeignFunction create(std::shared_ptr self) const { + WasmForeignFunction f = [self](WasmBase&, std::string_view arguments, + const std::function&) -> WasmResult { + envoy::source::extensions::common::wasm::InjectEncodedDataToFilterChainArguments args; + if (args.ParseFromArray(arguments.data(), arguments.size())) { + auto context = static_cast(proxy_wasm::current_context_); + return context->injectEncodedDataToFilterChain(args.body(), args.endstream()); + } + return WasmResult::BadArgument; + }; + return f; + } +}; +RegisterForeignFunction + registerInjectEncodedDataToFilterChainFactory("inject_encoded_data_to_filter_chain", + createFromClass()); + +class InjectEncodedDataToFilterChainOnHeaderFactory: public Logger::Loggable { + public: + WasmForeignFunction create(std::shared_ptr self) const { + WasmForeignFunction f = [self](WasmBase&, std::string_view arguments, + const std::function&) -> WasmResult { + envoy::source::extensions::common::wasm::InjectEncodedDataToFilterChainArguments args; + if (args.ParseFromArray(arguments.data(), arguments.size())) { + auto context = static_cast(proxy_wasm::current_context_); + return context->injectEncodedDataToFilterChainOnHeader(args.body(), args.endstream()); + } + return WasmResult::BadArgument; + }; + return f; + } + }; + RegisterForeignFunction + registerInjectEncodedDataToFilterChainOnHeaderFactory("inject_encoded_data_to_filter_chain_on_header", + createFromClass()); +#endif + } // namespace Wasm } // namespace Common } // namespace Extensions From 583feb54cef9e6a5bec08b30ab1516f84ef58ca8 Mon Sep 17 00:00:00 2001 From: rinfx Date: Tue, 17 Jun 2025 17:13:38 +0800 Subject: [PATCH 263/274] bugfix (#10) --- source/common/upstream/upstream_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 5a09d17766147..1387413e829b0 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -520,7 +520,7 @@ class HostImpl : public HostDescriptionImpl, #if defined(HIGRESS) std::string endpoint_metrics_; std::string endpoint_metrics_backup_; - std::atomic endpoint_metrics_ptr_; + std::atomic endpoint_metrics_ptr_{nullptr}; bool set_backup_ = false; #endif }; From 916bc6259eb503ea30a82a85eb5e7b65d7c70811 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Mon, 19 May 2025 20:12:51 +0800 Subject: [PATCH 264/274] support direct local address cherry-pick: https://github.com/envoyproxy/envoy/commit/090e73d82462f46d32c112554206a5ed0861eba2 use directLocalAddress instead of localAddress in LocalPortValueExtractorImpl of srds Link: https://code.alibaba-inc.com/Ingress/envoy/codereview/21602142 * support direct local address * add metrics stats --- .../observability/access_log/usage.rst | 40 +++++++++++++++++ envoy/network/socket.h | 6 +++ source/common/http/conn_manager_config.h | 10 +++++ source/common/http/conn_manager_impl.cc | 16 ++++++- source/common/http/filter_manager.h | 3 ++ source/common/network/socket_impl.h | 8 +++- source/common/router/scoped_config_impl.cc | 2 +- .../extensions/filters/http/lua/wrappers.cc | 7 +++ source/extensions/filters/http/lua/wrappers.h | 10 ++++- .../formatter/substitution_formatter_test.cc | 44 ++++++++++++++++++- .../http/conn_manager_impl_test_base.cc | 9 ++++ test/common/router/header_formatter_test.cc | 3 ++ .../filters/http/lua/lua_integration_test.cc | 9 ++++ 13 files changed, 161 insertions(+), 6 deletions(-) diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index 5815ba665fc22..f9b5e72548b34 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -624,14 +624,54 @@ The following command operators are supported: If the original connection was redirected by iptables TPROXY, and the listener's transparent option was set to true, this represents the original destination address and port. + .. note:: + + This may not be the physical remote address of the peer if the address has been inferred from + :ref:`Proxy Protocol filter `. + +%DOWNSTREAM_DIRECT_LOCAL_ADDRESS% + Direct local address of the downstream connection. + + .. note:: + + This is always the physical local address even if the downstream remote address has been inferred from + :ref:`Proxy Protocol filter `. + %DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT% Local address of the downstream connection, without any port component. IP addresses are the only address type with a port component. + .. note:: + + This may not be the physical local address if the downstream local address has been inferred from + :ref:`Proxy Protocol filter `. + +%DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT% + Direct local address of the downstream connection, without any port component. + + .. note:: + + This is always the physical local address even if the downstream local address has been inferred from + :ref:`Proxy Protocol filter `. + %DOWNSTREAM_LOCAL_PORT% Local port of the downstream connection. IP addresses are the only address type with a port component. + .. note:: + + This may not be the physical port if the downstream local address has been inferred from + :ref:`Proxy Protocol filter `. + +%DOWNSTREAM_DIRECT_LOCAL_PORT% + Direct local port of the downstream connection. + IP addresses are the only address type with a port component. + + .. note:: + + This is always the listener port even if the downstream local address has been inferred from + :ref:`Proxy Protocol filter `. + .. _config_access_log_format_connection_id: %CONNECTION_ID% diff --git a/envoy/network/socket.h b/envoy/network/socket.h index 0f19735d03131..20afec6b18ab8 100644 --- a/envoy/network/socket.h +++ b/envoy/network/socket.h @@ -176,6 +176,12 @@ class ConnectionInfoProvider { */ virtual const Address::InstanceConstSharedPtr& localAddress() const PURE; + /** + * @return the direct local address of the socket. This is the listener address and it can not be + * modified by listener filters. + */ + virtual const Address::InstanceConstSharedPtr& directLocalAddress() const PURE; + /** * @return true if the local address has been restored to a value that is different from the * address the socket was initially accepted at. diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index f3652f7c64aa4..69e6c48a03c26 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -22,6 +22,12 @@ namespace Envoy { namespace Http { +#if defined(ALIMESH) +#define HIGRESS_EXT_HTTP_CONN_MAN_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(downstream_rq_retry_scope_found_total) \ + COUNTER(downstream_rq_retry_scope_not_found_total) +#endif + /** * All stats for the connection manager. @see stats_macros.h */ @@ -92,6 +98,10 @@ namespace Http { */ struct ConnectionManagerNamedStats { ALL_HTTP_CONN_MAN_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) +#if defined(ALIMESH) + HIGRESS_EXT_HTTP_CONN_MAN_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, + GENERATE_HISTOGRAM_STRUCT) +#endif }; struct ConnectionManagerStats { diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 4fa358b16db40..85cb0e1de8891 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -80,8 +80,16 @@ bool requestWasConnect(const RequestHeaderMapSharedPtr& headers, Protocol protoc ConnectionManagerStats ConnectionManagerImpl::generateStats(const std::string& prefix, Stats::Scope& scope) { return ConnectionManagerStats( +#if defined(ALIMESH) + {ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER_PREFIX(scope, prefix), POOL_GAUGE_PREFIX(scope, prefix), + POOL_HISTOGRAM_PREFIX(scope, prefix)) + HIGRESS_EXT_HTTP_CONN_MAN_STATS(POOL_COUNTER_PREFIX(scope, prefix), + POOL_GAUGE_PREFIX(scope, prefix), + POOL_HISTOGRAM_PREFIX(scope, prefix))}, +#else {ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER_PREFIX(scope, prefix), POOL_GAUGE_PREFIX(scope, prefix), POOL_HISTOGRAM_PREFIX(scope, prefix))}, +#endif prefix, scope); } @@ -1673,9 +1681,15 @@ void ConnectionManagerImpl::ActiveStream::refreshCachedRoute(const Router::Route } route = snapped_route_config_->route(cb, *request_headers_, filter_manager_.streamInfo(), stream_id_); + bool retry_found = route != nullptr; ENVOY_STREAM_LOG(debug, "after the route was not found, search again in other scopes and found:{}", - *this, route != nullptr); + *this, retry_found); + if (retry_found) { + connection_manager_.stats_.named_.downstream_rq_retry_scope_found_total_.inc(); + } else { + connection_manager_.stats_.named_.downstream_rq_retry_scope_not_found_total_.inc(); + } } } #endif diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 3f9cb804cc23e..04da25d575466 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -569,6 +569,9 @@ class OverridableRemoteConnectionInfoSetterStreamInfo : public StreamInfo::Strea const Network::Address::InstanceConstSharedPtr& localAddress() const override { return StreamInfoImpl::downstreamAddressProvider().localAddress(); } + const Network::Address::InstanceConstSharedPtr& directLocalAddress() const override { + return StreamInfoImpl::downstreamAddressProvider().directLocalAddress(); + } bool localAddressRestored() const override { return StreamInfoImpl::downstreamAddressProvider().localAddressRestored(); } diff --git a/source/common/network/socket_impl.h b/source/common/network/socket_impl.h index 09964abfc5171..ece1696e29c8a 100644 --- a/source/common/network/socket_impl.h +++ b/source/common/network/socket_impl.h @@ -13,8 +13,8 @@ class ConnectionInfoSetterImpl : public ConnectionInfoSetter { public: ConnectionInfoSetterImpl(const Address::InstanceConstSharedPtr& local_address, const Address::InstanceConstSharedPtr& remote_address) - : local_address_(local_address), remote_address_(remote_address), - direct_remote_address_(remote_address) {} + : local_address_(local_address), direct_local_address_(local_address), + remote_address_(remote_address), direct_remote_address_(remote_address) {} void setDirectRemoteAddressForTest(const Address::InstanceConstSharedPtr& direct_remote_address) { direct_remote_address_ = direct_remote_address; @@ -31,6 +31,9 @@ class ConnectionInfoSetterImpl : public ConnectionInfoSetter { // ConnectionInfoSetter const Address::InstanceConstSharedPtr& localAddress() const override { return local_address_; } + const Address::InstanceConstSharedPtr& directLocalAddress() const override { + return direct_local_address_; + } void setLocalAddress(const Address::InstanceConstSharedPtr& local_address) override { local_address_ = local_address; } @@ -76,6 +79,7 @@ class ConnectionInfoSetterImpl : public ConnectionInfoSetter { private: Address::InstanceConstSharedPtr local_address_; + Address::InstanceConstSharedPtr direct_local_address_; bool local_address_restored_{false}; Address::InstanceConstSharedPtr remote_address_; Address::InstanceConstSharedPtr direct_remote_address_; diff --git a/source/common/router/scoped_config_impl.cc b/source/common/router/scoped_config_impl.cc index 0ad3aabff5ef5..779aa1586608f 100644 --- a/source/common/router/scoped_config_impl.cc +++ b/source/common/router/scoped_config_impl.cc @@ -43,7 +43,7 @@ LocalPortValueExtractorImpl::LocalPortValueExtractorImpl( std::unique_ptr LocalPortValueExtractorImpl::computeFragment( const Http::HeaderMap&, const StreamInfo::StreamInfo* info, ReComputeCbPtr&) const { ASSERT(info != nullptr, "streamInfo is nullptr."); - auto port = info->downstreamAddressProvider().localAddress()->ip()->port(); + auto port = info->downstreamAddressProvider().directLocalAddress()->ip()->port(); return std::make_unique(std::to_string(long(port))); } diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index 78e33443f6b7c..0473e02dc9bbd 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -192,6 +192,13 @@ int StreamInfoWrapper::luaDownstreamLocalAddress(lua_State* state) { return 1; } +int StreamInfoWrapper::luaDownstreamDirectLocalAddress(lua_State* state) { + const std::string& local_address = + stream_info_.downstreamAddressProvider().directLocalAddress()->asString(); + lua_pushlstring(state, local_address.data(), local_address.size()); + return 1; +} + int StreamInfoWrapper::luaDownstreamDirectRemoteAddress(lua_State* state) { const std::string& direct_remote_address = stream_info_.downstreamAddressProvider().directRemoteAddress()->asString(); diff --git a/source/extensions/filters/http/lua/wrappers.h b/source/extensions/filters/http/lua/wrappers.h index f61ea91237a69..3937f47d24c62 100644 --- a/source/extensions/filters/http/lua/wrappers.h +++ b/source/extensions/filters/http/lua/wrappers.h @@ -207,6 +207,7 @@ class StreamInfoWrapper : public Filters::Common::Lua::BaseLuaObjectlocalAddress(); + stream_info.downstream_connection_info_provider_->setLocalAddress(address); + EXPECT_EQ("127.0.0.2:0", format.formatWithContext({}, stream_info)); + EXPECT_THAT(format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.2:0"))); + stream_info.downstream_connection_info_provider_->setLocalAddress(original_address); + } + { StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT"); EXPECT_EQ("127.0.0.2", @@ -891,8 +903,33 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { } { - StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_PORT"); + StreamInfoFormatter format("DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT"); + EXPECT_EQ("127.0.0.2", format.formatWithContext({}, stream_info)); + EXPECT_THAT(format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.2"))); + } + { + StreamInfoFormatter format("DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT"); + auto address = Network::Address::InstanceConstSharedPtr{ + new Network::Address::Ipv4Instance("127.1.2.3", 8900)}; + stream_info.downstream_connection_info_provider_->setLocalAddress(address); + EXPECT_EQ("127.0.0.2", format.formatWithContext({}, stream_info)); + EXPECT_THAT(format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.2"))); + } + + { + StreamInfoFormatter format("DOWNSTREAM_DIRECT_LOCAL_PORT"); + EXPECT_EQ("0", format.formatWithContext({}, stream_info)); + EXPECT_THAT(format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(0))); + } + + { + StreamInfoFormatter downstream_local_port_format("DOWNSTREAM_LOCAL_PORT"), + downstream_direct_downstream_local_port_format("DOWNSTREAM_DIRECT_LOCAL_PORT"); + + StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_PORT"); // Validate for IPv4 address auto address = Network::Address::InstanceConstSharedPtr{ new Network::Address::Ipv4Instance("127.1.2.3", 8443)}; @@ -942,6 +979,11 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_THAT(upstream_format.formatValue(request_headers, response_headers, response_trailers, stream_info, body, AccessLog::AccessLogType::NotSet), ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ("0", + downstream_direct_downstream_local_port_format.formatWithContext({}, stream_info)); + EXPECT_THAT( + downstream_direct_downstream_local_port_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::numberValue(0))); } { diff --git a/test/common/http/conn_manager_impl_test_base.cc b/test/common/http/conn_manager_impl_test_base.cc index 641ca6fae0821..47a99dbfcdc54 100644 --- a/test/common/http/conn_manager_impl_test_base.cc +++ b/test/common/http/conn_manager_impl_test_base.cc @@ -19,9 +19,18 @@ HttpConnectionManagerImplMixin::HttpConnectionManagerImplMixin() Filesystem::FilePathAndType{Filesystem::DestinationType::File, access_log_path_}, {}, Formatter::SubstitutionFormatUtils::defaultSubstitutionFormatter(), log_manager_)}}, codec_(new NiceMock()), +#if defined(ALIMESH) + stats_({ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), + POOL_GAUGE(*fake_stats_.rootScope()), + POOL_HISTOGRAM(*fake_stats_.rootScope())) + HIGRESS_EXT_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), + POOL_GAUGE(*fake_stats_.rootScope()), + POOL_HISTOGRAM(*fake_stats_.rootScope()))}, +#else stats_({ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), POOL_GAUGE(*fake_stats_.rootScope()), POOL_HISTOGRAM(*fake_stats_.rootScope()))}, +#endif "", *fake_stats_.rootScope()), listener_stats_({CONN_MAN_LISTENER_STATS(POOL_COUNTER(fake_listener_stats_))}), diff --git a/test/common/router/header_formatter_test.cc b/test/common/router/header_formatter_test.cc index 49198acd68d92..7e5899b0e6a06 100644 --- a/test/common/router/header_formatter_test.cc +++ b/test/common/router/header_formatter_test.cc @@ -71,6 +71,9 @@ TEST(HeaderParserTest, TestParse) { {"%DOWNSTREAM_LOCAL_ADDRESS%", {"127.0.0.2:0"}, {}}, {"%DOWNSTREAM_LOCAL_PORT%", {"0"}, {}}, {"%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%", {"127.0.0.2"}, {}}, + {"%DOWNSTREAM_DIRECT_LOCAL_ADDRESS%", {"127.0.0.2:0"}, {}}, + {"%DOWNSTREAM_DIRECT_LOCAL_PORT%", {"0"}, {}}, + {"%DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT%", {"127.0.0.2"}, {}}, {"%UPSTREAM_METADATA([\"ns\", \"key\"])%", {"value"}, {}}, {"[%UPSTREAM_METADATA([\"ns\", \"key\"])%", {"[value"}, {}}, {"%UPSTREAM_METADATA([\"ns\", \"key\"])%]", {"value]"}, {}}, diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index f60ac3d40118b..379dba86c1174 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -307,6 +307,8 @@ name: lua end request_handle:headers():add("request_protocol", request_handle:streamInfo():protocol()) request_handle:headers():add("request_dynamic_metadata_value", dynamic_metadata_value) + request_handle:headers():add("request_downstream_direct_local_address_value", + request_handle:streamInfo():downstreamDirectLocalAddress()) request_handle:headers():add("request_downstream_local_address_value", request_handle:streamInfo():downstreamLocalAddress()) request_handle:headers():add("request_downstream_directremote_address_value", @@ -406,6 +408,13 @@ name: lua ->value() .getStringView()); + EXPECT_TRUE(absl::StrContains( + upstream_request_->headers() + .get(Http::LowerCaseString("request_downstream_direct_local_address_value"))[0] + ->value() + .getStringView(), + GetParam() == Network::Address::IpVersion::v4 ? "127.0.0.1:" : "[::1]:")); + EXPECT_TRUE( absl::StrContains(upstream_request_->headers() .get(Http::LowerCaseString("request_downstream_local_address_value"))[0] From e2707255f18baa841e723e85f7b7ea13ea93c2e5 Mon Sep 17 00:00:00 2001 From: Jingze <52855280+Jing-ze@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:25:27 +0800 Subject: [PATCH 265/274] update proxy wasm cpp host version (#11) --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index b33154c676b6a..78cf6fab07c1c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "27df3052072dd97527945c4167ef912020a0a92e", - sha256 = "523ef02a84a69b03bdcde76053f3f412d73a20150f4bb11d6c11a127ed7df7dc", + version = "913c1b02cbbdba00682ac29787b0986d85890a54", + sha256 = "af094042c91663eabc2c01cd33903c154d318b1be516f8f2428a252ac74fc9ad", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From 1caa1184e8034dd2fc2ef92dd0871d7d252bb136 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Fri, 25 Jul 2025 20:46:27 +0800 Subject: [PATCH 266/274] remove non-existent stats --- source/common/http/conn_manager_impl.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 85cb0e1de8891..87ca935ceb8c8 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1685,11 +1685,6 @@ void ConnectionManagerImpl::ActiveStream::refreshCachedRoute(const Router::Route ENVOY_STREAM_LOG(debug, "after the route was not found, search again in other scopes and found:{}", *this, retry_found); - if (retry_found) { - connection_manager_.stats_.named_.downstream_rq_retry_scope_found_total_.inc(); - } else { - connection_manager_.stats_.named_.downstream_rq_retry_scope_not_found_total_.inc(); - } } } #endif From bd693d652e63416776a29fea6a897b1f81440ac0 Mon Sep 17 00:00:00 2001 From: Silver Duke Dragon Date: Mon, 25 Aug 2025 11:01:12 +0800 Subject: [PATCH 267/274] mcp sse stateful session in contrib (#12) Signed-off-by: jue-yin --- CODEOWNERS | 1 + api/BUILD | 2 + .../extensions/filters/http/mcp_proxy/BUILD | 0 .../mcp_sse_stateful_session/v3alpha/BUILD | 12 + .../v3alpha/mcp_sse_stateful_session.proto | 46 ++ .../envelope/v3alpha/BUILD | 9 + .../envelope/v3alpha/envelope.proto | 75 +++ api/versioning/BUILD | 2 + contrib/contrib_build_config.bzl | 9 +- contrib/extensions_metadata.yaml | 15 + .../filters/http/source/BUILD | 44 ++ .../filters/http/source/config.cc | 36 ++ .../filters/http/source/config.h | 36 ++ .../http/source/mcp_sse_stateful_session.cc | 113 ++++ .../http/source/mcp_sse_stateful_session.h | 88 +++ .../filters/http/test/BUILD | 52 ++ .../filters/http/test/config_test.cc | 104 ++++ .../filters/http/test/mocks/BUILD | 18 + .../test/mocks/mcp_sse_stateful_session.cc | 21 + .../test/mocks/mcp_sse_stateful_session.h | 46 ++ .../test/stateful_session_integration_test.cc | 509 ++++++++++++++++++ .../http/test/stateful_session_test.cc | 268 +++++++++ .../http/source/BUILD | 40 ++ .../http/source/config.cc | 30 ++ .../http/source/config.h | 33 ++ .../http/source/envelope.cc | 198 +++++++ .../http/source/envelope.h | 77 +++ .../http/test/config_test.cc | 36 ++ .../http/test/envelope_test.cc | 382 +++++++++++++ envoy/http/BUILD | 11 + envoy/http/filter.h | 4 +- envoy/http/mcp_sse_stateful_session.h | 97 ++++ envoy/upstream/load_balancer.h | 2 +- source/common/http/async_client_impl.h | 4 +- source/common/http/filter_manager.cc | 11 +- source/common/http/filter_manager.h | 4 +- source/common/upstream/host_utility.cc | 2 +- source/extensions/common/wasm/context.cc | 2 +- .../http/stateful_session/stateful_session.cc | 2 +- test/common/http/filter_manager_test.cc | 5 +- .../stateful_session/stateful_session_test.cc | 15 +- test/mocks/http/mocks.cc | 2 +- test/mocks/http/mocks.h | 4 +- test/mocks/upstream/host.h | 5 + tools/extensions/extensions_schema.yaml | 1 + 45 files changed, 2450 insertions(+), 23 deletions(-) create mode 100644 api/contrib/envoy/extensions/filters/http/mcp_proxy/BUILD create mode 100644 api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/BUILD create mode 100644 api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/mcp_sse_stateful_session.proto create mode 100644 api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/BUILD create mode 100644 api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/envelope.proto create mode 100644 contrib/mcp_sse_stateful_session/filters/http/source/BUILD create mode 100644 contrib/mcp_sse_stateful_session/filters/http/source/config.cc create mode 100644 contrib/mcp_sse_stateful_session/filters/http/source/config.h create mode 100644 contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.cc create mode 100644 contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.h create mode 100644 contrib/mcp_sse_stateful_session/filters/http/test/BUILD create mode 100644 contrib/mcp_sse_stateful_session/filters/http/test/config_test.cc create mode 100644 contrib/mcp_sse_stateful_session/filters/http/test/mocks/BUILD create mode 100644 contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.cc create mode 100644 contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.h create mode 100644 contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_integration_test.cc create mode 100644 contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_test.cc create mode 100644 contrib/mcp_sse_stateful_session/http/source/BUILD create mode 100644 contrib/mcp_sse_stateful_session/http/source/config.cc create mode 100644 contrib/mcp_sse_stateful_session/http/source/config.h create mode 100644 contrib/mcp_sse_stateful_session/http/source/envelope.cc create mode 100644 contrib/mcp_sse_stateful_session/http/source/envelope.h create mode 100644 contrib/mcp_sse_stateful_session/http/test/config_test.cc create mode 100644 contrib/mcp_sse_stateful_session/http/test/envelope_test.cc create mode 100644 envoy/http/mcp_sse_stateful_session.h diff --git a/CODEOWNERS b/CODEOWNERS index d3ac2b86305fa..a8927fdf68680 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -365,3 +365,4 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /contrib/network/connection_balance/dlb @mattklein123 @daixiang0 /contrib/qat/ @giantcroc @soulxu /contrib/generic_proxy/ @wbpcode @soulxu @zhaohuabing @rojkov @htuch +/contrib/mcp_sse_stateful_session/ @jue-yin @UNOWNED diff --git a/api/BUILD b/api/BUILD index 16b2487284916..6671b20fcc817 100644 --- a/api/BUILD +++ b/api/BUILD @@ -76,6 +76,7 @@ proto_library( "//contrib/envoy/extensions/filters/http/http_dubbo_transcoder/v3:pkg", "//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/language/v3alpha:pkg", + "//contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/squash/v3:pkg", "//contrib/envoy/extensions/filters/http/sxg/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/llm_inference/v3:pkg", @@ -94,6 +95,7 @@ proto_library( "//contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/sip_proxy/tra/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg", + "//contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha:pkg", "//contrib/envoy/extensions/matching/input_matchers/hyperscan/v3alpha:pkg", "//contrib/envoy/extensions/network/connection_balance/dlb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha:pkg", diff --git a/api/contrib/envoy/extensions/filters/http/mcp_proxy/BUILD b/api/contrib/envoy/extensions/filters/http/mcp_proxy/BUILD new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/BUILD b/api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/BUILD new file mode 100644 index 0000000000000..1c1a6f6b44235 --- /dev/null +++ b/api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/config/core/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/mcp_sse_stateful_session.proto b/api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/mcp_sse_stateful_session.proto new file mode 100644 index 0000000000000..cdb4d16dbbbe0 --- /dev/null +++ b/api/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/mcp_sse_stateful_session.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha; + +import "envoy/config/core/v3/extension.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha"; +option java_outer_classname = "McpSseStatefulSessionProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Model Context Protocol(MCP) server-side events(SSE) Stateful session filter] +// MCP SSE Stateful session :ref:`configuration overview `. +// [#extension: envoy.filters.http.mcp_sse_stateful_session] + +// +message McpSseStatefulSession { + // Specifies the implementation of session state. This session state is used to store and retrieve the address of the + // upstream host assigned to the session. + // + // [#extension-category: envoy.http.mcp_sse_stateful_session] + config.core.v3.TypedExtensionConfig session_state = 1; + + // Determines whether the HTTP request must be strictly routed to the requested destination. When set to ``true``, + // if the requested destination is unavailable, Envoy will return a 503 status code. The default value is ``false``, + // which allows Envoy to fall back to its load balancing mechanism. In this case, if the requested destination is not + // found, the request will be routed according to the load balancing algorithm. + bool strict = 2; +} + +message McpSseStatefulSessionPerRoute { + oneof override { + option (validate.required) = true; + + // Disable the stateful session filter for this particular vhost or route. If disabled is + // specified in multiple per-filter-configs, the most specific one will be used. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Per-route stateful session configuration that can be served by RDS or static route table. + McpSseStatefulSession mcp_sse_stateful_session = 2; + } +} diff --git a/api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/BUILD b/api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/envelope.proto b/api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/envelope.proto new file mode 100644 index 0000000000000..67a85d00debaf --- /dev/null +++ b/api/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/envelope.proto @@ -0,0 +1,75 @@ +syntax = "proto3"; + +package envoy.extensions.http.mcp_sse_stateful_session.envelope.v3alpha; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.http.mcp_sse_stateful_session.envelope.v3alpha"; +option java_outer_classname = "EnvelopeProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Model Context Protocol(MCP) server-side events(SSE) stateful session extension] + +// The extension implements MCP 241105 spec for SSE-based session tracking. +// It enables Envoy to handle session context in SSE event streams, allowing session ID +// and upstream host to be encoded/decoded as required by the protocol. +// +// When processing the response from the upstream, Envoy will check if the SSE data stream contains +// the session context. If the SSE data stream contains the session context, Envoy will join it and +// the upstream host as new session context using a separator. +// +// When processing the request from the downstream, Envoy will check if the url query params contain +// the session context. If the request contains the session context, Envoy will strip the +// upstream host from the session context. +// [#extension: envoy.http.mcp_sse_stateful_session.envelope] +message EnvelopeSessionState { + // The query parameter name used to track the session state in SSE data streams. + // If the query parameter specified by this field is present in the SSE data stream, + // the upstream host address will be encoded in following format: + // + // .. code-block:: none + // + // sessionId={original_value}.{encoded_host} + // + // Where {encoded_host} is the Base64Url encoded host address. + // + // When processing the request from downstream, this extension will: + // 1. Split the value at the last dot + // 2. Decode the host address for upstream routing + // 3. Keep only the original session ID in the request + // + // For example: + // + // .. code-block:: none + // + // GET /path?sessionId=original_session_id.{encoded_host} + // # after processing: + // GET /path?sessionId=original_session_id + // + // Note: Uses Base64Url encoding for the host address and '.' as separator. + string param_name = 1 [(validate.rules).string = {min_len: 1}]; + + // The list of patterns to match the chunk end in the SSE data stream. + // Any of these patterns matched will be considered as the end of a chunk. + // recommended value is ["\r\n\r\n", "\n\n", "\r\r"] + // according to the HTML standard, the end of a server-sent-events' chunk can be + // - \r\n\r\n (double Carriage-Return Line-Feed) + // - \n\n (double Line-Feed) + // - \r\r (double Carriage-Return) + // https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream + // Customized patterns can be added to match the chunk end pattern. + repeated string chunk_end_patterns = 2 [(validate.rules).repeated = { + min_items: 1 + items {string {min_len: 1}} + }]; + + // The maximum size of the pending chunk. + // If the pending chunk size is greater than this value, this filter will be disabled. + // This is to prevent the filter from consuming too much memory when the SSE data stream is large. + // In normal cases, the sessionId should be the initialize message and be in a small chunk. + // The default value is 4KB. + int32 max_pending_chunk_size = 3; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 32939e2249848..80eef01359e7a 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -16,6 +16,7 @@ proto_library( "//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/language/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/llm_inference/v3:pkg", + "//contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/squash/v3:pkg", "//contrib/envoy/extensions/filters/http/sxg/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/client_ssl_auth/v3:pkg", @@ -33,6 +34,7 @@ proto_library( "//contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/sip_proxy/tra/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg", + "//contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha:pkg", "//contrib/envoy/extensions/matching/input_matchers/hyperscan/v3alpha:pkg", "//contrib/envoy/extensions/network/connection_balance/dlb/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha:pkg", diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 77219192e66c1..2629b59a207e9 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -7,7 +7,8 @@ CONTRIB_EXTENSIONS = { "envoy.filters.http.dynamo": "//contrib/dynamo/filters/http/source:config", "envoy.filters.http.http_dubbo_transcoder": "//contrib/http_dubbo_transcoder/filters/http/source:config", "envoy.filters.http.golang": "//contrib/golang/filters/http/source:config", - "envoy.filters.http.language": "//contrib/language/filters/http/source:config_lib", + "envoy.filters.http.language": "//contrib/language/filters/http/source:config_lib" + "envoy.filters.http.mcp_sse_stateful_session": "//contrib/mcp_sse_stateful_session/filters/http/source:config",, "envoy.filters.http.squash": "//contrib/squash/filters/http/source:config", "envoy.filters.http.sxg": "//contrib/sxg/filters/http/source:config", "envoy.filters.http.llm_inference": "//contrib/llm_inference/filters/http/source:config", @@ -92,4 +93,10 @@ CONTRIB_EXTENSIONS = { # "envoy.router.cluster_specifier_plugin.golang": "//contrib/golang/router/cluster_specifier/source:config", + + # + # mcp sse stateful session + # + + "envoy.http.mcp_sse_stateful_session.envelope": "//contrib/mcp_sse_stateful_session/http/source:config", } diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index 2628a9d8931d5..560c69abe5f7a 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -145,3 +145,18 @@ envoy.router.cluster_specifier_plugin.golang: - envoy.router.cluster_specifier_plugin security_posture: requires_trusted_downstream_and_upstream status: alpha +envoy.filters.http.mcp_sse_stateful_session: + categories: + - envoy.filters.http + security_posture: requires_trusted_downstream_and_upstream + status: alpha + type_urls: + - envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSession + - envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSessionPerRoute +envoy.http.mcp_sse_stateful_session.envelope: + categories: + - envoy.http.mcp_sse_stateful_session + security_posture: unknown + status: alpha + type_urls: + - envoy.extensions.http.mcp_sse_stateful_session.envelope.v3alpha.EnvelopeSessionState diff --git a/contrib/mcp_sse_stateful_session/filters/http/source/BUILD b/contrib/mcp_sse_stateful_session/filters/http/source/BUILD new file mode 100644 index 0000000000000..fb420388797c9 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/source/BUILD @@ -0,0 +1,44 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "mcp_sse_stateful_session_lib", + srcs = ["mcp_sse_stateful_session.cc"], + hdrs = ["mcp_sse_stateful_session.h"], + deps = [ + "//envoy/http:filter_interface", + "//envoy/http:mcp_sse_stateful_session_interface", + "//envoy/server:filter_config_interface", + "//envoy/upstream:load_balancer_interface", + "//source/common/config:utility_lib", + "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + "//source/common/protobuf:utility_lib", + "//source/common/upstream:load_balancer_lib", + "//source/extensions/filters/http:well_known_names", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha:pkg_cc_proto", + ], +) + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + "//contrib/mcp_sse_stateful_session/filters/http/source:mcp_sse_stateful_session_lib", + "//envoy/http:mcp_sse_stateful_session_interface", + "//envoy/registry", + "//source/common/protobuf:utility_lib", + "//source/extensions/filters/http/common:factory_base_lib", + "@envoy_api//contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha:pkg_cc_proto", + ], +) diff --git a/contrib/mcp_sse_stateful_session/filters/http/source/config.cc b/contrib/mcp_sse_stateful_session/filters/http/source/config.cc new file mode 100644 index 0000000000000..1569a8535ed43 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/source/config.cc @@ -0,0 +1,36 @@ +#include "contrib/mcp_sse_stateful_session/filters/http/source/config.h" + +#include + +#include "envoy/registry/registry.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace McpSseStatefulSession { + +Envoy::Http::FilterFactoryCb McpSseStatefulSessionFactoryConfig::createFilterFactoryFromProtoTyped( + const ProtoConfig& proto_config, const std::string&, + Server::Configuration::FactoryContext& context) { + + auto filter_config(std::make_shared(proto_config, context)); + return [filter_config](Envoy::Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter( + Envoy::Http::StreamFilterSharedPtr{new McpSseStatefulSession(filter_config)}); + }; +} + +Router::RouteSpecificFilterConfigConstSharedPtr +McpSseStatefulSessionFactoryConfig::createRouteSpecificFilterConfigTyped( + const PerRouteProtoConfig& proto_config, Server::Configuration::ServerFactoryContext& context, + ProtobufMessage::ValidationVisitor&) { + return std::make_shared(proto_config, context); +} + +REGISTER_FACTORY(McpSseStatefulSessionFactoryConfig, + Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace McpSseStatefulSession +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/source/config.h b/contrib/mcp_sse_stateful_session/filters/http/source/config.h new file mode 100644 index 0000000000000..8777a10799cf2 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/source/config.h @@ -0,0 +1,36 @@ +#pragma once + +#include "source/extensions/filters/http/common/factory_base.h" + +#include "contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/mcp_sse_stateful_session.pb.h" +#include "contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace McpSseStatefulSession { + +/** + * Config registration for the stateful session filter. @see NamedHttpFilterConfigFactory. + */ +class McpSseStatefulSessionFactoryConfig + : public Common::FactoryBase { +public: + McpSseStatefulSessionFactoryConfig() + : FactoryBase("envoy.filters.http.mcp_sse_stateful_session") {} + +private: + Envoy::Http::FilterFactoryCb + createFilterFactoryFromProtoTyped(const ProtoConfig& proto_config, + const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) override; + Router::RouteSpecificFilterConfigConstSharedPtr + createRouteSpecificFilterConfigTyped(const PerRouteProtoConfig& proto_config, + Server::Configuration::ServerFactoryContext& context, + ProtobufMessage::ValidationVisitor& visitor) override; +}; + +} // namespace McpSseStatefulSession +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.cc b/contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.cc new file mode 100644 index 0000000000000..0371d934678bd --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.cc @@ -0,0 +1,113 @@ +#include "contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.h" + +#include +#include + +#include "source/common/config/utility.h" +#include "source/common/http/utility.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace McpSseStatefulSession { + +namespace { + +class EmptySessionStateFactory : public Envoy::Http::McpSseSessionStateFactory { +public: + Envoy::Http::McpSseSessionStatePtr create(Envoy::Http::RequestHeaderMap&) const override { + return nullptr; + } +}; + +} // namespace + +McpSseStatefulSessionConfig::McpSseStatefulSessionConfig( + const ProtoConfig& config, Server::Configuration::CommonFactoryContext& context) + : strict_(config.strict()) { + if (!config.has_session_state()) { + factory_ = std::make_shared(); + return; + } + + auto& factory = Envoy::Config::Utility::getAndCheckFactoryByName< + Envoy::Http::McpSseSessionStateFactoryConfig>(config.session_state().name()); + + auto typed_config = Envoy::Config::Utility::translateAnyToFactoryConfig( + config.session_state().typed_config(), context.messageValidationVisitor(), factory); + + factory_ = factory.createSessionStateFactory(*typed_config, context); +} + +PerRouteMcpSseStatefulSession::PerRouteMcpSseStatefulSession( + const PerRouteProtoConfig& config, Server::Configuration::CommonFactoryContext& context) { + if (config.override_case() == PerRouteProtoConfig::kDisabled) { + disabled_ = true; + return; + } + config_ = + std::make_shared(config.mcp_sse_stateful_session(), context); +} + +Envoy::Http::FilterHeadersStatus +McpSseStatefulSession::decodeHeaders(Envoy::Http::RequestHeaderMap& headers, bool) { + const auto route_config = + Envoy::Http::Utility::resolveMostSpecificPerFilterConfig( + decoder_callbacks_); + + if (route_config != nullptr && route_config->disabled()) { + return Envoy::Http::FilterHeadersStatus::Continue; + } + + const McpSseStatefulSessionConfig& effective_config = + (route_config != nullptr) ? *route_config->statefulSessionConfig() : *config_; + + session_state_ = effective_config.createSessionState(headers); + if (session_state_ == nullptr) { + return Envoy::Http::FilterHeadersStatus::Continue; + } + + if (auto upstream_address = session_state_->upstreamAddress(); upstream_address.has_value()) { + decoder_callbacks_->setUpstreamOverrideHost( + std::make_pair(upstream_address.value(), effective_config.isStrict())); + } + return Envoy::Http::FilterHeadersStatus::Continue; +} + +Envoy::Http::FilterHeadersStatus +McpSseStatefulSession::encodeHeaders(Envoy::Http::ResponseHeaderMap& headers, bool) { + if (session_state_ == nullptr) { + return Envoy::Http::FilterHeadersStatus::Continue; + } + + if (auto upstream_info = encoder_callbacks_->streamInfo().upstreamInfo(); + upstream_info != nullptr) { + auto host = upstream_info->upstreamHost(); + if (host != nullptr) { + session_state_->onUpdateHeader(host->address()->asStringView(), headers); + } + } + + return Envoy::Http::FilterHeadersStatus::Continue; +} + +Envoy::Http::FilterDataStatus McpSseStatefulSession::encodeData(Buffer::Instance& data, + bool end_stream) { + if (session_state_ == nullptr) { + return Envoy::Http::FilterDataStatus::Continue; + } + + if (auto upstream_info = encoder_callbacks_->streamInfo().upstreamInfo(); + upstream_info != nullptr) { + auto host = upstream_info->upstreamHost(); + if (host != nullptr) { + return session_state_->onUpdateData(host->address()->asStringView(), data, end_stream); + } + } + return Envoy::Http::FilterDataStatus::Continue; +} + +} // namespace McpSseStatefulSession +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.h b/contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.h new file mode 100644 index 0000000000000..d87f66673bb90 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "envoy/http/mcp_sse_stateful_session.h" +#include "envoy/upstream/load_balancer.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/logger.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +#include "absl/strings/string_view.h" +#include "contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/mcp_sse_stateful_session.pb.h" +#include "contrib/envoy/extensions/filters/http/mcp_sse_stateful_session/v3alpha/mcp_sse_stateful_session.pb.validate.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace McpSseStatefulSession { + +using ProtoConfig = + envoy::extensions::filters::http::mcp_sse_stateful_session::v3alpha::McpSseStatefulSession; +using PerRouteProtoConfig = envoy::extensions::filters::http::mcp_sse_stateful_session::v3alpha:: + McpSseStatefulSessionPerRoute; + +class McpSseStatefulSessionConfig { +public: + McpSseStatefulSessionConfig(const ProtoConfig& config, + Server::Configuration::CommonFactoryContext& context); + + Envoy::Http::McpSseSessionStatePtr + createSessionState(Envoy::Http::RequestHeaderMap& headers) const { + ASSERT(factory_ != nullptr); + return factory_->create(headers); + } + + bool isStrict() const { return strict_; } + +private: + Envoy::Http::McpSseSessionStateFactorySharedPtr factory_; + bool strict_{false}; +}; +using McpSseStatefulSessionConfigSharedPtr = std::shared_ptr; + +class PerRouteMcpSseStatefulSession : public Router::RouteSpecificFilterConfig { +public: + PerRouteMcpSseStatefulSession(const PerRouteProtoConfig& config, + Server::Configuration::CommonFactoryContext& context); + + bool disabled() const { return disabled_; } + McpSseStatefulSessionConfig* statefulSessionConfig() const { return config_.get(); } + +private: + bool disabled_{}; + McpSseStatefulSessionConfigSharedPtr config_; +}; +using PerRouteMcpSseStatefulSessionConfigSharedPtr = std::shared_ptr; + +class McpSseStatefulSession : public Envoy::Http::PassThroughFilter, + public Logger::Loggable { +public: + McpSseStatefulSession(McpSseStatefulSessionConfigSharedPtr config) : config_(std::move(config)) {} + + // Http::StreamDecoderFilter + Envoy::Http::FilterHeadersStatus decodeHeaders(Envoy::Http::RequestHeaderMap& headers, + bool) override; + + // Http::StreamEncoderFilter + Envoy::Http::FilterHeadersStatus encodeHeaders(Envoy::Http::ResponseHeaderMap& headers, + bool) override; + + Envoy::Http::FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) override; + + Envoy::Http::McpSseSessionStatePtr& sessionStateForTest() { return session_state_; } + +private: + Envoy::Http::McpSseSessionStatePtr session_state_; + McpSseStatefulSessionConfigSharedPtr config_; +}; + +} // namespace McpSseStatefulSession +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/test/BUILD b/contrib/mcp_sse_stateful_session/filters/http/test/BUILD new file mode 100644 index 0000000000000..8d7480a9c64b1 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/test/BUILD @@ -0,0 +1,52 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "stateful_session_test", + srcs = ["stateful_session_test.cc"], + deps = [ + "//contrib/mcp_sse_stateful_session/filters/http/source:config", + "//contrib/mcp_sse_stateful_session/filters/http/test/mocks:mcp_sse_stateful_session_mock", + "//test/mocks/api:api_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "stateful_session_integration_test", + size = "large", + srcs = [ + "stateful_session_integration_test.cc", + ], + deps = [ + "//contrib/mcp_sse_stateful_session/filters/http/source:config", + "//contrib/mcp_sse_stateful_session/http/source:config", + "//source/common/protobuf", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + "//contrib/mcp_sse_stateful_session/filters/http/source:config", + "//contrib/mcp_sse_stateful_session/filters/http/test/mocks:mcp_sse_stateful_session_mock", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:instance_mocks", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/contrib/mcp_sse_stateful_session/filters/http/test/config_test.cc b/contrib/mcp_sse_stateful_session/filters/http/test/config_test.cc new file mode 100644 index 0000000000000..c35c3eb670013 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/test/config_test.cc @@ -0,0 +1,104 @@ +#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/instance.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "contrib/mcp_sse_stateful_session/filters/http/source/config.h" +#include "contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.h" +#include "gtest/gtest.h" + +using testing::NiceMock; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace McpSseStatefulSession { +namespace { + +constexpr absl::string_view ConfigYaml = R"EOF( +session_state: + name: "envoy.http.mcp_sse_stateful_session.mock" + typed_config: {} +)EOF"; + +constexpr absl::string_view DisableYaml = R"EOF( +disabled: true +)EOF"; + +constexpr absl::string_view RouteConfigYaml = R"EOF( +mcp_sse_stateful_session: + session_state: + name: "envoy.http.mcp_sse_stateful_session.mock" + typed_config: {} +)EOF"; + +constexpr absl::string_view NotExistYaml = R"EOF( +mcp_sse_stateful_session: + session_state: + name: "envoy.http.mcp_sse_stateful_session.not_exist" + typed_config: {} +)EOF"; + +constexpr absl::string_view EmptyStatefulSessionRouteYaml = R"EOF( +mcp_sse_stateful_session: {} +)EOF"; + +TEST(StatefulSessionFactoryConfigTest, SimpleConfigTest) { + testing::NiceMock config_factory; + Registry::InjectFactory registration( + config_factory); + + ProtoConfig proto_config; + PerRouteProtoConfig proto_route_config; + PerRouteProtoConfig disabled_config; + PerRouteProtoConfig not_exist_config; + ProtoConfig empty_proto_config; + PerRouteProtoConfig empty_proto_route_config; + + TestUtility::loadFromYamlAndValidate(std::string(ConfigYaml), proto_config); + TestUtility::loadFromYamlAndValidate(std::string(RouteConfigYaml), proto_route_config); + TestUtility::loadFromYamlAndValidate(std::string(DisableYaml), disabled_config); + TestUtility::loadFromYamlAndValidate(std::string(NotExistYaml), not_exist_config); + TestUtility::loadFromYamlAndValidate(std::string(EmptyStatefulSessionRouteYaml), + empty_proto_route_config); + + testing::NiceMock context; + testing::NiceMock server_context; + McpSseStatefulSessionFactoryConfig factory; + + Envoy::Http::FilterFactoryCb cb = + factory.createFilterFactoryFromProto(proto_config, "stats", context).value(); + Envoy::Http::MockFilterChainFactoryCallbacks filter_callbacks; + EXPECT_CALL(filter_callbacks, addStreamFilter(_)); + cb(filter_callbacks); + + EXPECT_TRUE(factory + .createRouteSpecificFilterConfig(proto_route_config, server_context, + context.messageValidationVisitor()) + .ok()); + EXPECT_TRUE(factory + .createRouteSpecificFilterConfig(disabled_config, server_context, + context.messageValidationVisitor()) + .ok()); + EXPECT_THROW_WITH_MESSAGE(factory + .createRouteSpecificFilterConfig(not_exist_config, server_context, + context.messageValidationVisitor()) + .value(), + EnvoyException, + "Didn't find a registered implementation for name: " + "'envoy.http.mcp_sse_stateful_session.not_exist'"); + + EXPECT_NO_THROW(factory.createFilterFactoryFromProto(empty_proto_config, "stats", context) + .status() + .IgnoreError()); + EXPECT_TRUE(factory + .createRouteSpecificFilterConfig(empty_proto_route_config, server_context, + context.messageValidationVisitor()) + .ok()); +} + +} // namespace +} // namespace McpSseStatefulSession +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/test/mocks/BUILD b/contrib/mcp_sse_stateful_session/filters/http/test/mocks/BUILD new file mode 100644 index 0000000000000..5b3f114a86958 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/test/mocks/BUILD @@ -0,0 +1,18 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_mock", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_mock( + name = "mcp_sse_stateful_session_mock", + srcs = ["mcp_sse_stateful_session.cc"], + hdrs = ["mcp_sse_stateful_session.h"], + deps = [ + "//envoy/http:mcp_sse_stateful_session_interface", + ], +) \ No newline at end of file diff --git a/contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.cc b/contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.cc new file mode 100644 index 0000000000000..be951f472b7bd --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.cc @@ -0,0 +1,21 @@ +#include "contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.h" + +using testing::_; +using testing::Return; + +namespace Envoy { +namespace Http { + +MockSessionStateFactory::MockSessionStateFactory() { + ON_CALL(*this, create(_)) + .WillByDefault( + Return(testing::ByMove(std::make_unique>()))); +} + +MockSessionStateFactoryConfig::MockSessionStateFactoryConfig() { + ON_CALL(*this, createSessionStateFactory(_, _)) + .WillByDefault(Return(std::make_shared>())); +} + +} // namespace Http +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.h b/contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.h new file mode 100644 index 0000000000000..7fb2551533d26 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.h @@ -0,0 +1,46 @@ +#pragma once + +#include "envoy/http/mcp_sse_stateful_session.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Http { + +class MockSessionState : public Envoy::Http::McpSseSessionState { +public: + MOCK_METHOD(absl::optional, upstreamAddress, (), (const)); + MOCK_METHOD(void, onUpdateHeader, + (absl::string_view host_address, Envoy::Http::ResponseHeaderMap& headers)); + MOCK_METHOD(Envoy::Http::FilterDataStatus, onUpdateData, + (absl::string_view host_address, Buffer::Instance& data, bool end_stream)); + MOCK_METHOD(bool, sessionIdFound, (), (const)); + MOCK_METHOD(void, resetSessionIdFound, ()); +}; + +class MockSessionStateFactory : public Envoy::Http::McpSseSessionStateFactory { +public: + MockSessionStateFactory(); + + MOCK_METHOD(Envoy::Http::McpSseSessionStatePtr, create, (Envoy::Http::RequestHeaderMap & headers), + (const)); + MOCK_METHOD(bool, isStrict, (), (const)); +}; + +class MockSessionStateFactoryConfig : public Envoy::Http::McpSseSessionStateFactoryConfig { +public: + MockSessionStateFactoryConfig(); + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + MOCK_METHOD(Envoy::Http::McpSseSessionStateFactorySharedPtr, createSessionStateFactory, + (const Protobuf::Message&, Server::Configuration::CommonFactoryContext&)); + + std::string name() const override { return "envoy.http.mcp_sse_stateful_session.mock"; } +}; + +} // namespace Http +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_integration_test.cc b/contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_integration_test.cc new file mode 100644 index 0000000000000..b23e2dfd7193f --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_integration_test.cc @@ -0,0 +1,509 @@ +#include +#include + +#include "envoy/config/endpoint/v3/endpoint_components.pb.h" +#include "envoy/http/mcp_sse_stateful_session.h" + +#include "source/common/common/base64.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/protobuf.h" + +#include "test/integration/http_integration.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace McpSseStatefulSession { +namespace { + +class StatefulSessionIntegrationTest : public Envoy::HttpIntegrationTest, public testing::Test { +public: + StatefulSessionIntegrationTest() + : HttpIntegrationTest(Envoy::Http::CodecType::HTTP1, Network::Address::IpVersion::v4) { + // Create 4 different upstream server for stateful session test. + setUpstreamCount(4); + + // Update endpoints of default cluster `cluster_0` to 4 different fake upstreams. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster_0 = bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0); + ASSERT(cluster_0->name() == "cluster_0"); + auto* endpoint = cluster_0->mutable_load_assignment()->mutable_endpoints()->Mutable(0); + + const std::string EndpointsYaml = R"EOF( + lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 0 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 0 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 0 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 0 + )EOF"; + + envoy::config::endpoint::v3::LocalityLbEndpoints new_lb_endpints; + TestUtility::loadFromYaml(EndpointsYaml, new_lb_endpints); + *endpoint = new_lb_endpints; + }); + } + + // Initialize route filter and per route config. + void initializeFilterAndRoute(const std::string& filter_yaml, + const std::string& per_route_config_yaml) { + config_helper_.prependFilter(filter_yaml); + + // Create virtual host with domain `stateful.session.com` and default route to `cluster_0` + auto virtual_host = config_helper_.createVirtualHost("stateful.session.com"); + + // Update per route config of default route. + if (!per_route_config_yaml.empty()) { + auto* route = virtual_host.mutable_routes(0); + Protobuf::Any per_route_config; + TestUtility::loadFromYaml(per_route_config_yaml, per_route_config); + + route->mutable_typed_per_filter_config()->insert( + {"envoy.filters.http.mcp_sse_stateful_session", per_route_config}); + } + config_helper_.addVirtualHost(virtual_host); + + initialize(); + } +}; + +static const std::string STATEFUL_SESSION_FILTER = + R"EOF( +name: envoy.filters.http.mcp_sse_stateful_session +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSession + session_state: + name: envoy.http.mcp_sse_stateful_session.envelope + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.mcp_sse_stateful_session.envelope.v3alpha.EnvelopeSessionState + param_name: sessionId + chunk_end_patterns: ["\r\n\r\n", "\n\n", "\r\r"] +)EOF"; + +static const std::string STATEFUL_SESSION_STRICT_MODE = + R"EOF( + strict: true +)EOF"; + +static const std::string DISABLE_STATEFUL_SESSION = + R"EOF( +"@type": type.googleapis.com/envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSessionPerRoute +disabled: true +)EOF"; + +static const std::string EMPTY_STATEFUL_SESSION = + R"EOF( +name: envoy.filters.http.mcp_sse_stateful_session +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSession + strict: false +)EOF"; + +static const std::string OVERRIDE_STATEFUL_SESSION = + R"EOF( +"@type": type.googleapis.com/envoy.extensions.filters.http.mcp_sse_stateful_session.v3alpha.McpSseStatefulSessionPerRoute +mcp_sse_stateful_session: + session_state: + name: envoy.http.mcp_sse_stateful_session.envelope + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.mcp_sse_stateful_session.envelope.v3alpha.EnvelopeSessionState + param_name: sessionId + chunk_end_patterns: ["\r\n\r\n", "\n\n", "\r\r"] + strict: true +)EOF"; + +// Tests upstream SSE response injection in Envelope + Strict mode. +// Verifies that session host address is correctly encoded and injected into SSE stream. +TEST_F(StatefulSessionIntegrationTest, McpSseStatefulSessionEnvelopeSseStrictMode) { + initializeFilterAndRoute(STATEFUL_SESSION_FILTER + STATEFUL_SESSION_STRICT_MODE, ""); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Construct SSE request + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{{":method", "GET"}, + {":path", "/sse"}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Wait for upstream request + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT(upstream_index.has_value()); + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(upstream_index.value(), endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + const std::string encoded_host = + Envoy::Base64Url::encode(address_string.data(), address_string.size()); + + // Set content type to text/event-stream (required for SSE) + default_response_headers_.addCopy(Envoy::Http::LowerCaseString("content-type"), + "text/event-stream"); + upstream_request_->encodeHeaders(default_response_headers_, false); // stream not closed yet + + // Build and send initial SSE event data + const std::string original_session_id = "abcdefg"; + const std::string sse_data = + fmt::format("data: https://example.com/test?sessionId={}\n\n", original_session_id); + upstream_request_->encodeData(sse_data, true); + ASSERT_TRUE(sse_response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(sse_response->complete()); + + // Build expected response with host address + const std::string expected_sse_data = + "data: https://example.com/test?sessionId=abcdefg." + encoded_host + "\n\n"; + + EXPECT_EQ(expected_sse_data, sse_response->body()); + + cleanupUpstreamAndDownstream(); +} + +// Test for downstream request with stateful session envelope SSE and strict mode. +// The request should be routed to the upstream server based on the encoded session ID in the SSE +// request. The test checks that the correct upstream server is selected based on the session ID +// and that the response is correctly formatted as an SSE event. +// It also checks that the strict mode works correctly by returning 503 for unknown server +// addresses. +TEST_F(StatefulSessionIntegrationTest, + DownstreamRequestWithMcpSseStatefulSessionEnvelopeAndStrictMode) { + initializeFilterAndRoute(STATEFUL_SESSION_FILTER + STATEFUL_SESSION_STRICT_MODE, ""); + // Upstream endpoint encoded in stateful session SSE points to the first server address. + // This should return the first server address. + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(1, endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + + // Encode upstream address using Base64Url + const std::string encoded_host = + Envoy::Base64Url::encode(address_string.data(), address_string.size()); + const std::string session_param = "abcdefg." + encoded_host; + + // Construct SSE request with encoded session parameter + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", fmt::format("/sse?sessionId={}", session_param)}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Wait for upstream request + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT_TRUE(upstream_index.has_value()); + + // Expect that the selected upstream is index 1 + EXPECT_EQ(upstream_index.value(), 1); + + // Send response headers and complete stream + default_response_headers_.addCopy(Envoy::Http::LowerCaseString("content-type"), + "text/event-stream"); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData("data: hello\n\n", true); + + ASSERT_TRUE(sse_response->waitForEndStream()); + + cleanupUpstreamAndDownstream(); + } + // Upstream endpoint encoded in stateful session SSE points to the second server address. + // This should return the second server address. + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(2, endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + + // Encode upstream address using Base64Url + const std::string encoded_host = + Envoy::Base64Url::encode(address_string.data(), address_string.size()); + const std::string session_param = "abcdefg." + encoded_host; + + // Construct SSE request with encoded session parameter + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", fmt::format("/sse?sessionId={}", session_param)}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Wait for upstream request + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT_TRUE(upstream_index.has_value()); + + // Expect that the selected upstream is index 2 + EXPECT_EQ(upstream_index.value(), 2); + + // Send response headers and complete stream + default_response_headers_.addCopy(Envoy::Http::LowerCaseString("content-type"), + "text/event-stream"); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData("data: hello\n\n", true); + + ASSERT_TRUE(sse_response->waitForEndStream()); + + cleanupUpstreamAndDownstream(); + } + // Upstream endpoint encoded in stateful session SSE points to unknown server address. + // This should return 503. + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + // This decodes to "127.0.0.9:50000" + const std::string host = "127.0.0.9:50000"; + const std::string encoded_host = Envoy::Base64Url::encode(host.data(), host.size()); + const std::string session_param = "abcdefg." + encoded_host; + + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", fmt::format("/sse?sessionId={}", session_param)}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Should return 503 because the host is unknown + ASSERT_TRUE(sse_response->waitForEndStream()); + EXPECT_EQ("503", sse_response->headers().getStatusValue()); + + cleanupUpstreamAndDownstream(); + } +} + +TEST_F(StatefulSessionIntegrationTest, StatefulSessionDisabledByRoute) { + initializeFilterAndRoute(STATEFUL_SESSION_FILTER, DISABLE_STATEFUL_SESSION); + + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Construct SSE request + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", "/sse"}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Wait for upstream request + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT(upstream_index.has_value()); + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(upstream_index.value(), endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + const std::string encoded_host = + Envoy::Base64Url::encode(address_string.data(), address_string.size()); + + // Set content type to text/event-stream (required for SSE) + default_response_headers_.addCopy(Envoy::Http::LowerCaseString("content-type"), + "text/event-stream"); + upstream_request_->encodeHeaders(default_response_headers_, false); // stream not closed yet + + // Build and send initial SSE event data + const std::string original_session_id = "abcdefg"; + const std::string sse_data = + fmt::format("data: https://example.com/test?sessionId={}\n\n", original_session_id); + upstream_request_->encodeData(sse_data, true); + ASSERT_TRUE(sse_response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(sse_response->complete()); + + // Build expected response with host address + const std::string expected_sse_data = "data: https://example.com/test?sessionId=abcdefg\n\n"; + + EXPECT_EQ(expected_sse_data, sse_response->body()); + + cleanupUpstreamAndDownstream(); + } +} + +// Empty stateful session should be overridden by per route config. +TEST_F(StatefulSessionIntegrationTest, StatefulSessionOverrideByRoute) { + initializeFilterAndRoute(EMPTY_STATEFUL_SESSION, OVERRIDE_STATEFUL_SESSION); + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Construct SSE request + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", "/sse"}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Wait for upstream request + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT(upstream_index.has_value()); + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(upstream_index.value(), endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + const std::string encoded_host = + Envoy::Base64Url::encode(address_string.data(), address_string.size()); + + // Set content type to text/event-stream (required for SSE) + default_response_headers_.addCopy(Envoy::Http::LowerCaseString("content-type"), + "text/event-stream"); + upstream_request_->encodeHeaders(default_response_headers_, false); // stream not closed yet + + // Build and send initial SSE event data + const std::string original_session_id = "abcdefg"; + const std::string sse_data = + fmt::format("data: https://example.com/test?sessionId={}\n\n", original_session_id); + upstream_request_->encodeData(sse_data, true); + ASSERT_TRUE(sse_response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(sse_response->complete()); + + // Build expected response with host address + const std::string expected_sse_data = + "data: https://example.com/test?sessionId=abcdefg." + encoded_host + "\n\n"; + + EXPECT_EQ(expected_sse_data, sse_response->body()); + + cleanupUpstreamAndDownstream(); + } + + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(1, endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + + // Encode upstream address using Base64Url + const std::string encoded_host = + Envoy::Base64Url::encode(address_string.data(), address_string.size()); + const std::string session_param = "abcdefg." + encoded_host; + + // Construct SSE request with encoded session parameter + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", fmt::format("/sse?sessionId={}", session_param)}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Wait for upstream request + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT_TRUE(upstream_index.has_value()); + + // Expect that the selected upstream is index 1 + EXPECT_EQ(upstream_index.value(), 1); + + // Send response headers and complete stream + default_response_headers_.addCopy(Envoy::Http::LowerCaseString("content-type"), + "text/event-stream"); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData("data: hello\n\n", true); + + ASSERT_TRUE(sse_response->waitForEndStream()); + + cleanupUpstreamAndDownstream(); + } + // Upstream endpoint encoded in stateful session SSE points to the second server address. + // This should return the second server address. + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(2, endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + + // Encode upstream address using Base64Url + const std::string encoded_host = + Envoy::Base64Url::encode(address_string.data(), address_string.size()); + const std::string session_param = "abcdefg." + encoded_host; + + // Construct SSE request with encoded session parameter + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", fmt::format("/sse?sessionId={}", session_param)}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Wait for upstream request + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT_TRUE(upstream_index.has_value()); + + // Expect that the selected upstream is index 1 + EXPECT_EQ(upstream_index.value(), 2); + + // Send response headers and complete stream + default_response_headers_.addCopy(Envoy::Http::LowerCaseString("content-type"), + "text/event-stream"); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData("data: hello\n\n", true); + + ASSERT_TRUE(sse_response->waitForEndStream()); + + cleanupUpstreamAndDownstream(); + } + // Upstream endpoint encoded in stateful session SSE points to unknown server address. + // This should return 503. + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + // This decodes to "127.0.0.9:50000" + const std::string host = "127.0.0.9:50000"; + const std::string encoded_host = Envoy::Base64Url::encode(host.data(), host.size()); + const std::string session_param = "abcdefg." + encoded_host; + + Envoy::Http::TestRequestHeaderMapImpl sse_request_headers{ + {":method", "GET"}, + {":path", fmt::format("/sse?sessionId={}", session_param)}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}}; + + auto sse_response = codec_client_->makeRequestWithBody(sse_request_headers, 0); + + // Should return 503 because the host is unknown + ASSERT_TRUE(sse_response->waitForEndStream()); + EXPECT_EQ("503", sse_response->headers().getStatusValue()); + + cleanupUpstreamAndDownstream(); + } +} + +} // namespace +} // namespace McpSseStatefulSession +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_test.cc b/contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_test.cc new file mode 100644 index 0000000000000..36880c34dd2bd --- /dev/null +++ b/contrib/mcp_sse_stateful_session/filters/http/test/stateful_session_test.cc @@ -0,0 +1,268 @@ +#include + +#include "source/server/generic_factory_context.h" + +#include "test/mocks/http/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "contrib/mcp_sse_stateful_session/filters/http/source/mcp_sse_stateful_session.h" +#include "contrib/mcp_sse_stateful_session/filters/http/test/mocks/mcp_sse_stateful_session.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace McpSseStatefulSession { +namespace { + +class StatefulSessionTest : public testing::Test { +public: + void initialize(absl::string_view config, absl::string_view route_config = "") { + Envoy::Http::MockSessionStateFactoryConfig config_factory; + Registry::InjectFactory registration( + config_factory); + + factory_ = std::make_shared>(); + EXPECT_CALL(config_factory, createSessionStateFactory(_, _)).WillOnce(Return(factory_)); + + ASSERT(!config.empty()); + ProtoConfig proto_config; + TestUtility::loadFromYaml(std::string(config), proto_config); + config_ = std::make_shared(proto_config, context_); + + filter_ = std::make_shared(config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + + if (!route_config.empty()) { + PerRouteProtoConfig proto_route_config; + TestUtility::loadFromYaml(std::string(route_config), proto_route_config); + + if (proto_route_config.has_mcp_sse_stateful_session()) { + route_factory_ = std::make_shared>(); + EXPECT_CALL(config_factory, createSessionStateFactory(_, _)) + .WillOnce(Return(route_factory_)); + } + + route_config_ = + std::make_shared(proto_route_config, context_); + + ON_CALL(*decoder_callbacks_.route_, mostSpecificPerFilterConfig(_)) + .WillByDefault(Return(route_config_.get())); + } + }; + + NiceMock context_; + + NiceMock decoder_callbacks_; + NiceMock encoder_callbacks_; + + std::shared_ptr> factory_; + std::shared_ptr> route_factory_; + + McpSseStatefulSessionConfigSharedPtr config_; + PerRouteMcpSseStatefulSessionConfigSharedPtr route_config_; + + std::shared_ptr filter_; +}; + +constexpr absl::string_view ConfigYaml = R"EOF( +session_state: + name: "envoy.http.mcp_sse_stateful_session.mock" + typed_config: {} +)EOF"; + +constexpr absl::string_view DisableYaml = R"EOF( +disabled: true +)EOF"; + +constexpr absl::string_view RouteConfigYaml = R"EOF( +mcp_sse_stateful_session: + session_state: + name: "envoy.http.mcp_sse_stateful_session.mock" + typed_config: {} +)EOF"; + +// Test the normal case that the stateful session is enabled. +TEST_F(StatefulSessionTest, NormalSessionStateTest) { + initialize(ConfigYaml); + Envoy::Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + Buffer::OwnedImpl data_buffer; + data_buffer.add("data: http://example.com?sessionId=abcdefg\n\n"); + + auto session_state = std::make_unique>(); + auto raw_session_state = session_state.get(); + + EXPECT_CALL(*factory_, create(_)).WillOnce(Return(testing::ByMove(std::move(session_state)))); + EXPECT_CALL(*raw_session_state, upstreamAddress()) + .WillOnce(Return(absl::make_optional("1.2.3.4"))); + EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) + .WillOnce(testing::Invoke([&](Upstream::LoadBalancerContext::OverrideHost host) { + EXPECT_EQ("1.2.3.4", host.first); + })); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers, true)); + + EXPECT_CALL(*raw_session_state, onUpdateHeader(_, _)); + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(response_headers, true)); + + EXPECT_CALL(*raw_session_state, onUpdateData(_, _, _)); + EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->encodeData(data_buffer, true)); +} + +// Test the case that the stateful session is disabled by the route config. +TEST_F(StatefulSessionTest, SessionStateDisabledByRoute) { + initialize(ConfigYaml, DisableYaml); + Envoy::Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + EXPECT_CALL(*factory_, create(_)).Times(0); + + EXPECT_EQ(nullptr, filter_->sessionStateForTest().get()); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(response_headers, true)); +} + +// Test the case that the stateful session is override by the route config. +TEST_F(StatefulSessionTest, SessionStateOverrideByRoute) { + initialize(ConfigYaml, RouteConfigYaml); + Envoy::Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + auto session_state = std::make_unique>(); + auto raw_session_state = session_state.get(); + + Buffer::OwnedImpl data_buffer; + data_buffer.add("data: http://example.com?sessionId=abcdefg\n\n"); + + EXPECT_CALL(*route_factory_, create(_)) + .WillOnce(Return(testing::ByMove(std::move(session_state)))); + EXPECT_CALL(*raw_session_state, upstreamAddress()) + .WillOnce(Return(absl::make_optional("1.2.3.4"))); + EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) + .WillOnce(testing::Invoke([&](Upstream::LoadBalancerContext::OverrideHost host) { + EXPECT_EQ("1.2.3.4", host.first); + })); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers, true)); + + EXPECT_CALL(*raw_session_state, onUpdateHeader(_, _)); + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(response_headers, true)); + + EXPECT_CALL(*raw_session_state, onUpdateData(_, _, _)); + EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->encodeData(data_buffer, true)); +} + +// Test the case that the session state has not valid upstream address. +TEST_F(StatefulSessionTest, SessionStateHasNoUpstreamAddress) { + initialize(ConfigYaml, RouteConfigYaml); + Envoy::Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + auto session_state = std::make_unique>(); + auto raw_session_state = session_state.get(); + + Buffer::OwnedImpl data_buffer; + data_buffer.add("data: http://example.com?sessionId=abcdefg\n\n"); + + EXPECT_CALL(*route_factory_, create(_)) + .WillOnce(Return(testing::ByMove(std::move(session_state)))); + EXPECT_CALL(*raw_session_state, upstreamAddress()).WillOnce(Return(absl::nullopt)); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers, true)); + + EXPECT_CALL(*raw_session_state, onUpdateHeader(_, _)); + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(response_headers, true)); + + EXPECT_CALL(*raw_session_state, onUpdateData(_, _, _)); + EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->encodeData(data_buffer, true)); +} + +// Test the case that no valid upstream host. +TEST_F(StatefulSessionTest, NoUpstreamHost) { + initialize(ConfigYaml); + Envoy::Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + auto session_state = std::make_unique>(); + auto raw_session_state = session_state.get(); + + Buffer::OwnedImpl data_buffer; + data_buffer.add("data: http://example.com?sessionId=abcdefg\n\n"); + + EXPECT_CALL(*factory_, create(_)).WillOnce(Return(testing::ByMove(std::move(session_state)))); + EXPECT_CALL(*raw_session_state, upstreamAddress()) + .WillOnce(Return(absl::make_optional("1.2.3.4"))); + EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) + .WillOnce(testing::Invoke([&](Upstream::LoadBalancerContext::OverrideHost host) { + EXPECT_EQ("1.2.3.4", host.first); + })); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers, true)); + + encoder_callbacks_.stream_info_.setUpstreamInfo(nullptr); + EXPECT_CALL(*raw_session_state, onUpdateHeader(_, _)).Times(0); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(response_headers, true)); + + EXPECT_CALL(*raw_session_state, onUpdateData(_, _, _)).Times(0); + EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->encodeData(data_buffer, true)); +} + +// Test the case that no valid session state. +TEST_F(StatefulSessionTest, NullSessionState) { + initialize(ConfigYaml); + Envoy::Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + Envoy::Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + EXPECT_CALL(*factory_, create(_)).WillOnce(Return(testing::ByMove(nullptr))); + EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)).Times(0); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers, true)); + + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(response_headers, true)); +} + +TEST(EmpytProtoConfigTest, EmpytProtoConfigTest) { + ProtoConfig empty_proto_config; + testing::NiceMock context; + + McpSseStatefulSessionConfig config(empty_proto_config, context); + + Envoy::Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + EXPECT_EQ(nullptr, config.createSessionState(request_headers)); +} + +} // namespace +} // namespace McpSseStatefulSession +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/http/source/BUILD b/contrib/mcp_sse_stateful_session/http/source/BUILD new file mode 100644 index 0000000000000..d99c7521c081e --- /dev/null +++ b/contrib/mcp_sse_stateful_session/http/source/BUILD @@ -0,0 +1,40 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "envelope_lib", + srcs = [ + "envelope.cc", + ], + hdrs = [ + "envelope.h", + ], + deps = [ + "//envoy/http:mcp_sse_stateful_session_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:base64_lib", + "//source/common/http:utility_lib", + "@envoy_api//contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha:pkg_cc_proto", + ], +) + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":envelope_lib", + "//envoy/http:mcp_sse_stateful_session_interface", + "//envoy/registry", + "//source/common/config:utility_lib", + "@envoy_api//contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha:pkg_cc_proto", + ], +) diff --git a/contrib/mcp_sse_stateful_session/http/source/config.cc b/contrib/mcp_sse_stateful_session/http/source/config.cc new file mode 100644 index 0000000000000..770d24156f016 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/http/source/config.cc @@ -0,0 +1,30 @@ +#include "contrib/mcp_sse_stateful_session/http/source/config.h" + +#include "source/common/protobuf/utility.h" + +#include "contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/envelope.pb.h" +#include "contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/envelope.pb.validate.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace McpSseSessionState { +namespace Envelope { + +Envoy::Http::McpSseSessionStateFactorySharedPtr +EnvelopeSessionStateFactoryConfig::createSessionStateFactory( + const Protobuf::Message& config, Server::Configuration::CommonFactoryContext& context) { + const auto& proto_config = + MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()); + return std::make_shared(proto_config); +} + +REGISTER_FACTORY(EnvelopeSessionStateFactoryConfig, Envoy::Http::McpSseSessionStateFactoryConfig); + +} // namespace Envelope +} // namespace McpSseSessionState +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/http/source/config.h b/contrib/mcp_sse_stateful_session/http/source/config.h new file mode 100644 index 0000000000000..f06a4e6a4612a --- /dev/null +++ b/contrib/mcp_sse_stateful_session/http/source/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/config/typed_config.h" +#include "envoy/http/mcp_sse_stateful_session.h" +#include "envoy/server/factory_context.h" + +#include "contrib/mcp_sse_stateful_session/http/source/envelope.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace McpSseSessionState { +namespace Envelope { + +class EnvelopeSessionStateFactoryConfig : public Envoy::Http::McpSseSessionStateFactoryConfig { +public: + Envoy::Http::McpSseSessionStateFactorySharedPtr + createSessionStateFactory(const Protobuf::Message& config, + Server::Configuration::CommonFactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return "envoy.http.mcp_sse_stateful_session.envelope"; } +}; + +} // namespace Envelope +} // namespace McpSseSessionState +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/http/source/envelope.cc b/contrib/mcp_sse_stateful_session/http/source/envelope.cc new file mode 100644 index 0000000000000..682b0240401b6 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/http/source/envelope.cc @@ -0,0 +1,198 @@ +#include "contrib/mcp_sse_stateful_session/http/source/envelope.h" + +#include "absl/container/inlined_vector.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace McpSseSessionState { +namespace Envelope { + +void EnvelopeSessionStateFactory::SessionStateImpl::onUpdateHeader( + absl::string_view host_address, Envoy::Http::ResponseHeaderMap& headers) { + // Store response headers for SSE detection + response_headers_ = &headers; + UNREFERENCED_PARAMETER(host_address); +} + +Envoy::Http::FilterDataStatus EnvelopeSessionStateFactory::SessionStateImpl::onUpdateData( + absl::string_view host_address, Buffer::Instance& data, bool end_stream) { + // Skip if not SSE response + if (!isSSEResponse()) { + return Envoy::Http::FilterDataStatus::Continue; + } + + // Skip if session ID is already found + if (session_id_found_) { + return Envoy::Http::FilterDataStatus::Continue; + } + + // Check the pending chunk size to prevent memory issues + // in case of wrong configuration on this filter + if (pending_chunk_.length() + data.length() > factory_.max_pending_chunk_size_) { + ENVOY_LOG(error, "Pending chunk size exceeds max pending chunk size: {}", + pending_chunk_.length() + data.length()); + pending_chunk_.move(data); + data.move(pending_chunk_); + session_id_found_ = true; // Skip the rest of the data + return Envoy::Http::FilterDataStatus::Continue; + } + + // Append new data to pending buffer + pending_chunk_.move(data); + + while (pending_chunk_.length() > 0) { + // Find next complete chunk by searching for chunk end patterns + ssize_t chunk_end_pos = -1; + size_t chunk_end_pattern_length = 0; + const std::string* found_pattern = nullptr; + + // Search for the first occurrence of any chunk end pattern + for (const auto& chunk_end_pattern : factory_.chunk_end_patterns_) { + ssize_t pos = + pending_chunk_.search(chunk_end_pattern.data(), chunk_end_pattern.length(), 0, 0); + if (pos >= 0 && (chunk_end_pos == -1 || pos < chunk_end_pos)) { + chunk_end_pos = pos; + chunk_end_pattern_length = chunk_end_pattern.length(); + found_pattern = &chunk_end_pattern; + } + } + + if (chunk_end_pos == -1) { + ENVOY_LOG(trace, "No complete chunk found, waiting for more data"); + break; + } + + // Process current complete chunk + Buffer::OwnedImpl chunk_buffer; + // Move chunk content (excluding the end pattern) to avoid copying + chunk_buffer.move(pending_chunk_, chunk_end_pos); + pending_chunk_.drain(chunk_end_pattern_length); + + // Search for the parameter name in the chunk + const std::string param_search = factory_.param_name_ + "="; + ssize_t param_pos = chunk_buffer.search(param_search.data(), param_search.length(), 0, 0); + + if (param_pos >= 0) { + // Found the parameter, extract its value + size_t value_start = param_pos + param_search.length(); + + // Search for the end of the parameter value (either '&' or end of string) + const char ampersand = '&'; + ssize_t value_end = chunk_buffer.search(&ersand, 1, value_start, 0); + + if (value_end == -1) { + // No '&' found, parameter value extends to end of chunk + value_end = chunk_buffer.length(); + } + + // Encode host address using Base64Url + const char* host_address_c = host_address.data(); + uint64_t host_address_length = static_cast(host_address.size()); + const std::string encoded_host = + Envoy::Base64Url::encode(host_address_c, host_address_length); + + // Build modified URL by moving buffers and adding encoded host + data.move(chunk_buffer, value_end); + // Add separator and encoded host + data.add(std::string(1, SEPARATOR)); + data.add(encoded_host); + // Move suffix (after parameter value) + data.move(chunk_buffer); + + session_id_found_ = true; + } else { + // Parameter not found, keep chunk unchanged + data.move(chunk_buffer); + } + + // Add chunk ending pattern + data.add(*found_pattern); + } + + if (end_stream) { + data.move(pending_chunk_); + } + + return Envoy::Http::FilterDataStatus::Continue; +} + +EnvelopeSessionStateFactory::EnvelopeSessionStateFactory(const EnvelopeSessionStateProto& config) + : param_name_(config.param_name()), + chunk_end_patterns_(config.chunk_end_patterns().begin(), config.chunk_end_patterns().end()), + max_pending_chunk_size_(config.max_pending_chunk_size() > 0 ? config.max_pending_chunk_size() + : 4096) { + ENVOY_LOG(debug, "max_pending_chunk_size: {}", max_pending_chunk_size_); +} + +absl::optional +EnvelopeSessionStateFactory::parseAddress(Envoy::Http::RequestHeaderMap& headers) const { + const auto* path = headers.Path(); + if (!path) { + return absl::nullopt; + } + + // Parse query parameters + const auto params = Envoy::Http::Utility::parseQueryString(path->value().getStringView()); + auto it = params.find(param_name_); + if (it == params.end() || it->second.empty()) { + return absl::nullopt; + } + const std::string& session_value = it->second; + ENVOY_LOG(debug, "Processing session value: {}", session_value); + + auto separator_pos = session_value.rfind(SEPARATOR); + if (separator_pos == std::string::npos) { + ENVOY_LOG(debug, "No separator found in session value: {}", session_value); + return absl::nullopt; + } + + std::string original_session_id = session_value.substr(0, separator_pos); + std::string host_address = Envoy::Base64Url::decode(session_value.substr(separator_pos + 1)); + + // Check if Base64Url decode was successful + if (host_address.empty()) { + ENVOY_LOG(debug, "Failed to decode host address from session value: {}", session_value); + return absl::nullopt; + } + + // Build new query + std::string new_query; + // Estimate size to avoid multiple reallocations + size_t estimated_size = param_name_.length() + 1 + original_session_id.length(); + for (const auto& param : params) { + if (param.first != param_name_) { + estimated_size += param.first.length() + 1 + param.second.length() + 1; // "&" + param_name + "=" + value + } + } + new_query.reserve(estimated_size); + + // First add the session ID parameter + absl::StrAppend(&new_query, param_name_, "=", original_session_id); + + // Then append all other parameters + for (const auto& param : params) { + if (param.first == param_name_) { + continue; // Skip the session ID as we already added it + } + absl::StrAppend(&new_query, "&", param.first, "=", param.second); + } + + // Build final path + const auto path_str = path->value().getStringView(); + auto query_start = path_str.find('?'); + std::string new_path; + new_path.reserve(query_start + 1 + new_query.length()); + absl::StrAppend(&new_path, path_str.substr(0, query_start + 1), new_query); + + headers.setPath(new_path); + ENVOY_LOG(debug, "Restored session ID: {}, host: {}", original_session_id, host_address); + + return host_address; +} + +} // namespace Envelope +} // namespace McpSseSessionState +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/http/source/envelope.h b/contrib/mcp_sse_stateful_session/http/source/envelope.h new file mode 100644 index 0000000000000..fe417341ef5b6 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/http/source/envelope.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include "envoy/http/filter.h" +#include "envoy/http/mcp_sse_stateful_session.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/base64.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" + +#include "contrib/envoy/extensions/http/mcp_sse_stateful_session/envelope/v3alpha/envelope.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace McpSseSessionState { +namespace Envelope { + +using EnvelopeSessionStateProto = + envoy::extensions::http::mcp_sse_stateful_session::envelope::v3alpha::EnvelopeSessionState; + +class EnvelopeSessionStateFactory : public Envoy::Http::McpSseSessionStateFactory, + public Logger::Loggable { + friend class SessionStateImpl; + +public: + class SessionStateImpl : public Envoy::Http::McpSseSessionState { + public: + SessionStateImpl(absl::optional address, + const EnvelopeSessionStateFactory& factory) + : upstream_address_(std::move(address)), factory_(factory) {} + + absl::optional upstreamAddress() const override { return upstream_address_; } + void onUpdateHeader(absl::string_view host_address, + Envoy::Http::ResponseHeaderMap& headers) override; + Envoy::Http::FilterDataStatus onUpdateData(absl::string_view host_address, + Buffer::Instance& data, bool end_stream) override; + bool sessionIdFound() const override { return session_id_found_; } + void resetSessionIdFound() override { session_id_found_ = false; } // only for testing + + private: + bool isSSEResponse() const { + return response_headers_ && response_headers_->ContentType() && + absl::StrContains( + absl::AsciiStrToLower(response_headers_->ContentType()->value().getStringView()), + absl::AsciiStrToLower( + Envoy::Http::Headers::get().ContentTypeValues.TextEventStream)); + } + absl::optional upstream_address_; + const EnvelopeSessionStateFactory& factory_; + Envoy::Http::ResponseHeaderMap* response_headers_{nullptr}; + Buffer::OwnedImpl pending_chunk_; + bool session_id_found_{false}; + }; + + EnvelopeSessionStateFactory(const EnvelopeSessionStateProto& config); + + Envoy::Http::McpSseSessionStatePtr create(Envoy::Http::RequestHeaderMap& headers) const override { + absl::optional address = parseAddress(headers); + return std::make_unique(address, *this); + } + +private: + absl::optional parseAddress(Envoy::Http::RequestHeaderMap& headers) const; + const std::string param_name_; + const std::vector chunk_end_patterns_; + const size_t max_pending_chunk_size_{4096}; + static constexpr char SEPARATOR = '.'; // separate session ID and host address +}; + +} // namespace Envelope +} // namespace McpSseSessionState +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/http/test/config_test.cc b/contrib/mcp_sse_stateful_session/http/test/config_test.cc new file mode 100644 index 0000000000000..e48f8dc958338 --- /dev/null +++ b/contrib/mcp_sse_stateful_session/http/test/config_test.cc @@ -0,0 +1,36 @@ +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "contrib/mcp_sse_stateful_session/http/source/config.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace McpSseSessionState { +namespace Envelope { +namespace { + +TEST(EnvelopeSessionStateFactoryConfigTest, BasicSse) { + auto* factory = + Registry::FactoryRegistry::getFactory( + "envoy.http.mcp_sse_stateful_session.envelope"); + ASSERT_NE(factory, nullptr); + + EnvelopeSessionStateProto proto_config; + const std::string yaml = R"EOF( + param_name: custom-endpoint-param-name + chunk_end_patterns: ["\r\n\r\n", "\n\n", "\r\r"] + )EOF"; + TestUtility::loadFromYaml(yaml, proto_config); + + NiceMock context; + EXPECT_NE(factory->createSessionStateFactory(proto_config, context), nullptr); +} + +} // namespace +} // namespace Envelope +} // namespace McpSseSessionState +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mcp_sse_stateful_session/http/test/envelope_test.cc b/contrib/mcp_sse_stateful_session/http/test/envelope_test.cc new file mode 100644 index 0000000000000..48872f70ce87c --- /dev/null +++ b/contrib/mcp_sse_stateful_session/http/test/envelope_test.cc @@ -0,0 +1,382 @@ +#include "test/test_common/utility.h" + +#include "contrib/mcp_sse_stateful_session/http/source/envelope.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace McpSseSessionState { +namespace Envelope { +namespace { + +constexpr absl::string_view CRLFCRLF = "\r\n\r\n"; +constexpr absl::string_view CRCR = "\r\r"; +constexpr absl::string_view LFLF = "\n\n"; +constexpr char SEPARATOR = '.'; // separate session ID and host address in sse mode + +TEST(EnvelopeSessionStateFactoryTest, EnvelopeSessionStateTestOnUpdateDataSse) { + EnvelopeSessionStateProto config; + config.set_param_name("sessionId"); + config.add_chunk_end_patterns("\r\n\r\n"); + config.add_chunk_end_patterns("\n\n"); + config.add_chunk_end_patterns("\r\r"); + EnvelopeSessionStateFactory factory(config); + Envoy::Http::TestRequestHeaderMapImpl request_headers; + auto session_state = factory.create(request_headers); + + const std::string host_address = "1.2.3.4:80"; + const std::string raw_session_id = "abcdefg"; + + // Base64Url encoded host address + const std::string encoded_host = + Envoy::Base64Url::encode(host_address.data(), host_address.size()); + const std::string session_value = raw_session_id + SEPARATOR + encoded_host; + + Buffer::OwnedImpl data_buffer; + + // Case 1: Incomplete chunk with valid Content-Type + { + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + // Add incomplete SSE data + data_buffer.add("data: http://example.com?sessionId=abcdefg"); + + // Call onUpdateData (this will move data to pending_chunk_) + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + EXPECT_EQ(data_buffer.length(), 0); // data_buffer should be drained + } + + // Case 2: Non-SSE response (Content-Type: text/plain) + { + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/plain"}}; + + session_state->onUpdateHeader(host_address, headers); + + data_buffer.add(absl::StrCat("data: http://example.com?sessionId=", raw_session_id, LFLF)); + + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + + const std::string result_data( + static_cast(data_buffer.linearize(data_buffer.length())), + data_buffer.length()); + + // Should NOT be modified + EXPECT_NE(result_data.find("sessionId=abcdefg"), std::string::npos); + EXPECT_EQ(result_data.find(encoded_host), std::string::npos); + data_buffer.drain(data_buffer.length()); + EXPECT_FALSE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + } + + // Case 3: Valid SSE response with LFLF \n\n + { + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + data_buffer.add(absl::StrCat("data: http://example.com?sessionId=", raw_session_id, LFLF)); + + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + + const std::string expected_url = + absl::StrCat("http://example.com?sessionId=", raw_session_id, ".", encoded_host); + + const std::string result_data( + static_cast(data_buffer.linearize(data_buffer.length())), + data_buffer.length()); + + EXPECT_NE(result_data.find(expected_url), std::string::npos); + data_buffer.drain(data_buffer.length()); + EXPECT_TRUE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + } + + // Case 4: Valid SSE response with CRCR \r\r + { + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + data_buffer.add(absl::StrCat("data: http://example.com?sessionId=", raw_session_id, CRCR)); + + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + + const std::string expected_url = + absl::StrCat("http://example.com?sessionId=", raw_session_id, ".", encoded_host); + + const std::string result_data( + static_cast(data_buffer.linearize(data_buffer.length())), + data_buffer.length()); + + EXPECT_NE(result_data.find(expected_url), std::string::npos); + data_buffer.drain(data_buffer.length()); + EXPECT_TRUE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + } + + // Case 5: Valid SSE response with CRLFCRLF \r\n\r\n + { + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + data_buffer.add(absl::StrCat("data: http://example.com?sessionId=", raw_session_id, CRLFCRLF)); + + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + + const std::string expected_url = + absl::StrCat("http://example.com?sessionId=", raw_session_id, ".", encoded_host); + + const std::string result_data( + static_cast(data_buffer.linearize(data_buffer.length())), + data_buffer.length()); + + EXPECT_NE(result_data.find(expected_url), std::string::npos); + data_buffer.drain(data_buffer.length()); + EXPECT_TRUE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + } + + // Case 6: sessionId contains SEPARATOR ('.') in the middle (e.g. "abc.def.ghi") + { + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + const std::string raw_session_id_with_separator = "abc.def.ghi"; + data_buffer.add( + absl::StrCat("data: http://example.com?sessionId=", raw_session_id_with_separator, LFLF)); + + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + + const std::string expected_url = absl::StrCat( + "http://example.com?sessionId=", raw_session_id_with_separator, ".", encoded_host); + + const std::string result_data( + static_cast(data_buffer.linearize(data_buffer.length())), + data_buffer.length()); + + EXPECT_NE(result_data.find(expected_url), std::string::npos); + data_buffer.drain(data_buffer.length()); + EXPECT_TRUE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + } + + // Case 7: after sessionId is found, no more data should be processed + { + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + data_buffer.add(absl::StrCat("data: http://example.com?sessionId=", raw_session_id, LFLF)); + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + EXPECT_TRUE(session_state->sessionIdFound()); + data_buffer.drain(data_buffer.length()); + + // Add more data + data_buffer.add(absl::StrCat("data: abcdefg")); // no LFLF at the end, incomplete chunk + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + + EXPECT_NE(data_buffer.length(), 0); + } +} +TEST(EnvelopeSessionStateFactoryTest, EnvelopeSessionStateTestSse) { + { + EnvelopeSessionStateProto config; + config.set_param_name("sessionId"); + config.add_chunk_end_patterns("\r\n\r\n"); + config.add_chunk_end_patterns("\n\n"); + config.add_chunk_end_patterns("\r\r"); + EnvelopeSessionStateFactory factory(config); + + // Case 1: Path not exist + Envoy::Http::TestRequestHeaderMapImpl request_headers1; + auto session_state1 = factory.create(request_headers1); + EXPECT_EQ(absl::nullopt, session_state1->upstreamAddress()); + + // Case 2: Query parameter not exist + Envoy::Http::TestRequestHeaderMapImpl request_headers2{{":path", "/path"}}; + auto session_state2 = factory.create(request_headers2); + EXPECT_EQ(absl::nullopt, session_state2->upstreamAddress()); + + // Case 3: Session value has no separator + const std::string raw_session_id = "abcdefg"; + Envoy::Http::TestRequestHeaderMapImpl request_headers3{ + {":path", "/path?sessionId=" + raw_session_id}}; + auto session_state3 = factory.create(request_headers3); + EXPECT_EQ(absl::nullopt, session_state3->upstreamAddress()); + EXPECT_EQ(request_headers3.getPathValue(), "/path?sessionId=abcdefg"); + + // Case 4: Session value with valid separator and encoded host + const std::string host = "1.2.3.4:80"; + const std::string encoded_host = Envoy::Base64Url::encode(host.data(), host.size()); + const std::string session_value = raw_session_id + SEPARATOR + encoded_host; + + Envoy::Http::TestRequestHeaderMapImpl request_headers4{ + {":path", "/path?sessionId=" + session_value}}; + auto session_state4 = factory.create(request_headers4); + ASSERT_TRUE(session_state4->upstreamAddress().has_value()); + EXPECT_EQ(session_state4->upstreamAddress().value(), "1.2.3.4:80"); + EXPECT_EQ(request_headers4.getPathValue(), "/path?sessionId=abcdefg"); + + // Case 5: With additional query parameters + Envoy::Http::TestRequestHeaderMapImpl request_headers5{ + {":path", "/path?sessionId=" + session_value + "&otherParam=highklm"}}; + auto session_state5 = factory.create(request_headers5); + ASSERT_TRUE(session_state5->upstreamAddress().has_value()); + EXPECT_EQ(session_state5->upstreamAddress().value(), "1.2.3.4:80"); + EXPECT_EQ(request_headers5.getPathValue(), "/path?sessionId=abcdefg&otherParam=highklm"); + } +} + +TEST(EnvelopeSessionStateFactoryTest, EnvelopeSessionStateTestMaxPendingChunkSize) { + EnvelopeSessionStateProto config; + config.set_param_name("sessionId"); + config.add_chunk_end_patterns("\r\n\r\n"); + config.add_chunk_end_patterns("\n\n"); + config.add_chunk_end_patterns("\r\r"); + EnvelopeSessionStateFactory factory(config); + Envoy::Http::TestRequestHeaderMapImpl request_headers; + auto session_state = factory.create(request_headers); + + const std::string host_address = "1.2.3.4:80"; + + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + // Base64Url encoded host address + const std::string encoded_host = + Envoy::Base64Url::encode(host_address.data(), host_address.size()); + + Buffer::OwnedImpl data_buffer; + + // Generate data larger than 4KB and add it to data_buffer + std::string large_data(5 * 1024, 'x'); + data_buffer.add(large_data); + + // Call onUpdateData (this will move data to pending_chunk_) + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, false), + Envoy::Http::FilterDataStatus::Continue); + + // Check if the session ID is found + EXPECT_TRUE(session_state->sessionIdFound()); + + data_buffer.drain(data_buffer.length()); +} + +TEST(EnvelopeSessionStateFactoryTest, EnvelopeSessionStateTestEndStream) { + EnvelopeSessionStateProto config; + config.set_param_name("sessionId"); + config.add_chunk_end_patterns("\r\n\r\n"); + config.add_chunk_end_patterns("\n\n"); + config.add_chunk_end_patterns("\r\r"); + EnvelopeSessionStateFactory factory(config); + Envoy::Http::TestRequestHeaderMapImpl request_headers; + auto session_state = factory.create(request_headers); + + const std::string host_address = "1.2.3.4:80"; + + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + // Base64Url encoded host address + const std::string encoded_host = + Envoy::Base64Url::encode(host_address.data(), host_address.size()); + + Buffer::OwnedImpl data_buffer; + + // data contain no end of line at the end, incomplete chunk + data_buffer.add("data: abcdefg"); + + // Call onUpdateData + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, true), + Envoy::Http::FilterDataStatus::Continue); + + // data_buffer should not be drained, because it's end of stream + EXPECT_NE(data_buffer.length(), 0); + + data_buffer.drain(data_buffer.length()); +} + +TEST(EnvelopeSessionStateFactoryTest, EnvelopeSessionStateTestCustomizedChunkEndPatterns) { + EnvelopeSessionStateProto config; + config.set_param_name("sessionId"); + config.add_chunk_end_patterns("chunk_end_pattern1"); + config.add_chunk_end_patterns("chunk_end_pattern2"); + EnvelopeSessionStateFactory factory(config); + Envoy::Http::TestRequestHeaderMapImpl request_headers; + auto session_state = factory.create(request_headers); + + const std::string host_address = "1.2.3.4:80"; + + Envoy::Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "text/event-stream"}}; + + session_state->onUpdateHeader(host_address, headers); + + Buffer::OwnedImpl data_buffer; + + // Case 1: data contain chunk_end_pattern1 + data_buffer.add("data: http://example.com?sessionId=abcdefg.chunk_end_pattern1"); + + // Call onUpdateData + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, true), + Envoy::Http::FilterDataStatus::Continue); + + // sessionId should be found + EXPECT_TRUE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + data_buffer.drain(data_buffer.length()); + + // Case 2: data contain chunk_end_pattern2 + data_buffer.add("data: http://example.com?sessionId=abcdefg.chunk_end_pattern2"); + + // Call onUpdateData + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, true), + Envoy::Http::FilterDataStatus::Continue); + + // sessionId should be found + EXPECT_TRUE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + data_buffer.drain(data_buffer.length()); + + // Case 3: data contain both chunk_end_pattern1 and chunk_end_pattern2 + data_buffer.add("data: http://example.com?sessionId=abcdefg\n\n"); + + // Call onUpdateData + EXPECT_EQ(session_state->onUpdateData(host_address, data_buffer, true), + Envoy::Http::FilterDataStatus::Continue); + + // sessionId should not be found, cause \n\n nolonger a valid chunk end pattern + EXPECT_FALSE(session_state->sessionIdFound()); + session_state->resetSessionIdFound(); + data_buffer.drain(data_buffer.length()); +} + +} // namespace +} // namespace Envelope +} // namespace McpSseSessionState +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/envoy/http/BUILD b/envoy/http/BUILD index ea9ef7595241c..f4d27a1b010d0 100644 --- a/envoy/http/BUILD +++ b/envoy/http/BUILD @@ -264,3 +264,14 @@ envoy_cc_library( "//source/common/singleton:const_singleton", ], ) + +envoy_cc_library( + name = "mcp_sse_stateful_session_interface", + hdrs = ["mcp_sse_stateful_session.h"], + deps = [ + "//envoy/config:typed_config_interface", + "//envoy/server:factory_context_interface", + "//envoy/upstream:upstream_interface", + "//source/common/buffer:buffer_lib", + ], +) diff --git a/envoy/http/filter.h b/envoy/http/filter.h index ff0c7f4d5ffd8..c97382d8c5090 100644 --- a/envoy/http/filter.h +++ b/envoy/http/filter.h @@ -761,13 +761,13 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, * host list of the routed cluster, the host should be selected first. * @param host The override host address. */ - virtual void setUpstreamOverrideHost(absl::string_view host) PURE; + virtual void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost host) PURE; /** * @return absl::optional optional override host for the upstream * load balancing. */ - virtual absl::optional upstreamOverrideHost() const PURE; + virtual absl::optional upstreamOverrideHost() const PURE; #if defined(HIGRESS) virtual bool needBuffering() const { return false; } diff --git a/envoy/http/mcp_sse_stateful_session.h b/envoy/http/mcp_sse_stateful_session.h new file mode 100644 index 0000000000000..84fc038e573fc --- /dev/null +++ b/envoy/http/mcp_sse_stateful_session.h @@ -0,0 +1,97 @@ +#pragma once + +#include + +#include "envoy/buffer/buffer.h" +#include "envoy/http/filter.h" +#include "envoy/http/header_map.h" +#include "envoy/server/factory_context.h" + +namespace Envoy { +namespace Http { + +/** + * Independent interface for session state that supports data processing. + * This is completely independent of the main Envoy stateful session interface. + */ +class McpSseSessionState { +public: + virtual ~McpSseSessionState() = default; + + /** + * Get address of upstream host that the current session stuck on. + * + * @return absl::optional optional upstream address. If there is no available + * session or no available address, absl::nullopt will be returned. + */ + virtual absl::optional upstreamAddress() const PURE; + + /** + * Called when response headers are available. + * + * @param host_address the upstream host that was selected. + * @param headers the response headers. + */ + virtual void onUpdateHeader(absl::string_view host_address, + Envoy::Http::ResponseHeaderMap& headers) PURE; + + /** + * Called when response data is available for processing. + * + * @param host_address the upstream host that was selected. + * @param data the response data buffer. + * @param end_stream whether this is the end of the stream. + * @return FilterDataStatus indicating how to proceed with the data. + */ + virtual Envoy::Http::FilterDataStatus onUpdateData(absl::string_view host_address, + Buffer::Instance& data, bool end_stream) PURE; + + virtual bool sessionIdFound() const PURE; + virtual void resetSessionIdFound() PURE; // only for testing +}; + +using McpSseSessionStatePtr = std::unique_ptr; + +/** + * Independent interface for creating session state from request headers. + */ +class McpSseSessionStateFactory { +public: + virtual ~McpSseSessionStateFactory() = default; + + /** + * Create session state from request headers. + * + * @param headers request headers. + */ + virtual McpSseSessionStatePtr create(Envoy::Http::RequestHeaderMap& headers) const PURE; +}; + +using McpSseSessionStateFactorySharedPtr = std::shared_ptr; + +/* + * Extension configuration for session state factory. + */ +class McpSseSessionStateFactoryConfig : public Envoy::Config::TypedFactory { +public: + ~McpSseSessionStateFactoryConfig() override = default; + + /** + * Creates a particular session state factory implementation. + * + * @param config supplies the configuration for the session state factory extension. + * @param context supplies the factory context. Please don't store the reference to + * the context as it is only valid during the call. + * @return SessionStateFactorySharedPtr the session state factory. + */ + virtual McpSseSessionStateFactorySharedPtr + createSessionStateFactory(const Protobuf::Message& config, + Server::Configuration::CommonFactoryContext& context) PURE; + + std::string category() const override { return "envoy.http.mcp_sse_stateful_session"; } +}; + +using McpSseSessionStateFactoryConfigPtr = std::unique_ptr; + +} // namespace Http +} // namespace Envoy diff --git a/envoy/upstream/load_balancer.h b/envoy/upstream/load_balancer.h index 0cf04779bb13c..b5c1194090995 100644 --- a/envoy/upstream/load_balancer.h +++ b/envoy/upstream/load_balancer.h @@ -96,7 +96,7 @@ class LoadBalancerContext { */ virtual Network::TransportSocketOptionsConstSharedPtr upstreamTransportSocketOptions() const PURE; - using OverrideHost = absl::string_view; + using OverrideHost = std::pair; /** * Returns the host the load balancer should select directly. If the expected host exists and * the host can be selected directly, the load balancer can bypass the load balancing algorithm diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index b201dca91cd26..b289f3aae4af0 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -494,8 +494,8 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, OptRef downstreamCallbacks() override { return {}; } OptRef upstreamCallbacks() override { return {}; } void resetIdleTimer() override {} - void setUpstreamOverrideHost(absl::string_view) override {} - absl::optional upstreamOverrideHost() const override { return {}; } + void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost) override {} + absl::optional upstreamOverrideHost() const override { return {}; } absl::string_view filterConfigName() const override { return ""; } // ScopeTrackedObject diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 621bf948e2467..155f31f107a9b 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -1756,12 +1756,15 @@ Buffer::BufferMemoryAccountSharedPtr ActiveStreamDecoderFilter::account() const return parent_.account(); } -void ActiveStreamDecoderFilter::setUpstreamOverrideHost(absl::string_view host) { - parent_.upstream_override_host_.emplace(std::move(host)); +void ActiveStreamDecoderFilter::setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost host) { + parent_.upstream_override_host_.emplace(std::move(host.first)); } -absl::optional ActiveStreamDecoderFilter::upstreamOverrideHost() const { - return parent_.upstream_override_host_; +absl::optional ActiveStreamDecoderFilter::upstreamOverrideHost() const { + if (parent_.upstream_override_host_.has_value()) { + return std::make_pair(parent_.upstream_override_host_.value(), false); + } + return absl::nullopt; } } // namespace Http diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 04da25d575466..9ce5785cde775 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -246,8 +246,8 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, Network::Socket::OptionsSharedPtr getUpstreamSocketOptions() const override; Buffer::BufferMemoryAccountSharedPtr account() const override; - void setUpstreamOverrideHost(absl::string_view host) override; - absl::optional upstreamOverrideHost() const override; + void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost host) override; + absl::optional upstreamOverrideHost() const override; #if defined(HIGRESS) bool needBuffering() const override { return need_buffering_; } void setNeedBuffering(bool need) override { need_buffering_ = need; } diff --git a/source/common/upstream/host_utility.cc b/source/common/upstream/host_utility.cc index 5efe759dfb36e..d7a2ba5c94b41 100644 --- a/source/common/upstream/host_utility.cc +++ b/source/common/upstream/host_utility.cc @@ -169,7 +169,7 @@ HostConstSharedPtr HostUtility::selectOverrideHost(const HostMap* host_map, Host return nullptr; } - auto host_iter = host_map->find(override_host.value()); + auto host_iter = host_map->find(override_host.value().first); // The override host cannot be found in the host map. if (host_iter == host_map->end()) { diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 592e50d1038d7..756be7b6b5467 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1993,7 +1993,7 @@ WasmResult Context::getUpstreamHosts(StringPairs* result) { } WasmResult Context::setUpstreamOverrideHost(std::string_view address) { if (decoder_callbacks_) { - decoder_callbacks_->setUpstreamOverrideHost(address); + decoder_callbacks_->setUpstreamOverrideHost(std::make_pair(address, false)); } return WasmResult::Ok; } diff --git a/source/extensions/filters/http/stateful_session/stateful_session.cc b/source/extensions/filters/http/stateful_session/stateful_session.cc index f39921080e715..c2873ee1e9b22 100644 --- a/source/extensions/filters/http/stateful_session/stateful_session.cc +++ b/source/extensions/filters/http/stateful_session/stateful_session.cc @@ -66,7 +66,7 @@ Http::FilterHeadersStatus StatefulSession::decodeHeaders(Http::RequestHeaderMap& } if (auto upstream_address = session_state_->upstreamAddress(); upstream_address.has_value()) { - decoder_callbacks_->setUpstreamOverrideHost(upstream_address.value()); + decoder_callbacks_->setUpstreamOverrideHost(std::make_pair(upstream_address.value(), false)); } return Http::FilterHeadersStatus::Continue; } diff --git a/test/common/http/filter_manager_test.cc b/test/common/http/filter_manager_test.cc index 42a5602a90063..20318ea0330df 100644 --- a/test/common/http/filter_manager_test.cc +++ b/test/common/http/filter_manager_test.cc @@ -335,10 +335,11 @@ TEST_F(FilterManagerTest, SetAndGetUpstreamOverrideHost) { })); filter_manager_->createFilterChain(); - decoder_filter->callbacks_->setUpstreamOverrideHost("1.2.3.4"); + decoder_filter->callbacks_->setUpstreamOverrideHost(std::make_pair("1.2.3.4", false)); auto override_host = decoder_filter->callbacks_->upstreamOverrideHost(); - EXPECT_EQ(override_host.value(), "1.2.3.4"); + EXPECT_EQ(override_host.value().first, "1.2.3.4"); + EXPECT_EQ(override_host.value().second, false); filter_manager_->destroyFilters(); }; diff --git a/test/extensions/filters/http/stateful_session/stateful_session_test.cc b/test/extensions/filters/http/stateful_session/stateful_session_test.cc index 82a5fc1452395..a5a2c1a405d21 100644 --- a/test/extensions/filters/http/stateful_session/stateful_session_test.cc +++ b/test/extensions/filters/http/stateful_session/stateful_session_test.cc @@ -101,7 +101,10 @@ TEST_F(StatefulSessionTest, NormalSessionStateTest) { EXPECT_CALL(*raw_session_state, upstreamAddress()) .WillOnce(Return(absl::make_optional("1.2.3.4"))); EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) - .WillOnce(testing::Invoke([&](absl::string_view host) { EXPECT_EQ("1.2.3.4", host); })); + .WillOnce(testing::Invoke([&](Upstream::LoadBalancerContext::OverrideHost host) { + EXPECT_EQ("1.2.3.4", host.first); + EXPECT_EQ(false, host.second); + })); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -139,7 +142,10 @@ TEST_F(StatefulSessionTest, SessionStateOverrideByRoute) { EXPECT_CALL(*raw_session_state, upstreamAddress()) .WillOnce(Return(absl::make_optional("1.2.3.4"))); EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) - .WillOnce(testing::Invoke([&](absl::string_view host) { EXPECT_EQ("1.2.3.4", host); })); + .WillOnce(testing::Invoke([&](Upstream::LoadBalancerContext::OverrideHost host) { + EXPECT_EQ("1.2.3.4", host.first); + EXPECT_EQ(false, host.second); + })); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -181,7 +187,10 @@ TEST_F(StatefulSessionTest, NoUpstreamHost) { EXPECT_CALL(*raw_session_state, upstreamAddress()) .WillOnce(Return(absl::make_optional("1.2.3.4"))); EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) - .WillOnce(testing::Invoke([&](absl::string_view host) { EXPECT_EQ("1.2.3.4", host); })); + .WillOnce(testing::Invoke([&](Upstream::LoadBalancerContext::OverrideHost host) { + EXPECT_EQ("1.2.3.4", host.first); + EXPECT_EQ(false, host.second); + })); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); diff --git a/test/mocks/http/mocks.cc b/test/mocks/http/mocks.cc index 3c4337d590375..bdfe59b263be5 100644 --- a/test/mocks/http/mocks.cc +++ b/test/mocks/http/mocks.cc @@ -112,7 +112,7 @@ MockStreamDecoderFilterCallbacks::MockStreamDecoderFilterCallbacks() { })); ON_CALL(*this, routeConfig()) .WillByDefault(Return(absl::optional())); - ON_CALL(*this, upstreamOverrideHost()).WillByDefault(Return(absl::optional())); + ON_CALL(*this, upstreamOverrideHost()).WillByDefault(Return(absl::optional())); ON_CALL(*this, mostSpecificPerFilterConfig()) .WillByDefault(Invoke([this]() -> const Router::RouteSpecificFilterConfig* { diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 41743445b1337..532f7691b5b58 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -322,8 +322,8 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, const absl::optional grpc_status, absl::string_view details)); MOCK_METHOD(Buffer::BufferMemoryAccountSharedPtr, account, (), (const)); - MOCK_METHOD(void, setUpstreamOverrideHost, (absl::string_view host)); - MOCK_METHOD(absl::optional, upstreamOverrideHost, (), (const)); + MOCK_METHOD(void, setUpstreamOverrideHost, (Upstream::LoadBalancerContext::OverrideHost host)); + MOCK_METHOD(absl::optional, upstreamOverrideHost, (), (const)); Buffer::InstancePtr buffer_; std::list callbacks_{}; diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index 25989561f6e9b..784d9dcffe152 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -225,6 +225,11 @@ class MockHost : public Host { MOCK_METHOD(bool, warmed, (), (const)); MOCK_METHOD(absl::optional, lastHcPassTime, (), (const)); +#if defined(HIGRESS) + MOCK_METHOD(std::string, getEndpointMetrics, (), (const)); + MOCK_METHOD(void, setEndpointMetrics, (absl::string_view endpoint_metrics)); +#endif + testing::NiceMock cluster_; Network::UpstreamTransportSocketFactoryPtr socket_factory_; testing::NiceMock outlier_detector_; diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml index 6262a972cd70e..27de95731bc19 100644 --- a/tools/extensions/extensions_schema.yaml +++ b/tools/extensions/extensions_schema.yaml @@ -132,6 +132,7 @@ categories: - envoy.http.early_header_mutation - envoy.http.custom_response - envoy.router.cluster_specifier_plugin +- envoy.http.mcp_sse_stateful_session status_values: - name: stable From 7f18940fbc390f7ca1be76d86dd8615c4c5009ce Mon Sep 17 00:00:00 2001 From: johnlanni Date: Mon, 25 Aug 2025 16:20:44 +0800 Subject: [PATCH 268/274] fix typo --- contrib/contrib_build_config.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 2629b59a207e9..3df8b2de96ef8 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -7,8 +7,8 @@ CONTRIB_EXTENSIONS = { "envoy.filters.http.dynamo": "//contrib/dynamo/filters/http/source:config", "envoy.filters.http.http_dubbo_transcoder": "//contrib/http_dubbo_transcoder/filters/http/source:config", "envoy.filters.http.golang": "//contrib/golang/filters/http/source:config", - "envoy.filters.http.language": "//contrib/language/filters/http/source:config_lib" - "envoy.filters.http.mcp_sse_stateful_session": "//contrib/mcp_sse_stateful_session/filters/http/source:config",, + "envoy.filters.http.language": "//contrib/language/filters/http/source:config_lib", + "envoy.filters.http.mcp_sse_stateful_session": "//contrib/mcp_sse_stateful_session/filters/http/source:config", "envoy.filters.http.squash": "//contrib/squash/filters/http/source:config", "envoy.filters.http.sxg": "//contrib/sxg/filters/http/source:config", "envoy.filters.http.llm_inference": "//contrib/llm_inference/filters/http/source:config", From f16899c564ae5943492e7f9d6fabda2bfc0ec374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 11 Nov 2025 18:50:34 +0800 Subject: [PATCH 269/274] Misc optimize (#14) Co-authored-by: jueyin.hsl --- bazel/protobuf_hash_cache.patch | 462 ++++++++++ bazel/repositories.bzl | 3 +- envoy/config/subscription.h | 1 + source/common/http/conn_manager_config.h | 4 +- source/common/http/conn_manager_impl.cc | 9 +- source/common/http/filter_manager.cc | 20 + source/common/protobuf/utility.h | 13 + .../rds/route_config_update_receiver_impl.h | 5 +- source/extensions/common/wasm/context.cc | 41 +- source/extensions/common/wasm/stats_handler.h | 1 + source/extensions/common/wasm/wasm.cc | 20 +- source/extensions/common/wasm/wasm.h | 2 +- .../grpc/grpc_subscription_impl.cc | 15 + .../filters/http/wasm/wasm_filter.h | 13 +- .../filters/network/wasm/wasm_filter.h | 2 +- .../redirect_policy/redirect_policy.cc | 11 +- .../filter_chain_manager_impl.cc | 6 + .../filter_chain_manager_impl.h | 7 + .../listener_manager/listener_impl.cc | 7 +- .../listener_manager/listener_manager_impl.cc | 5 +- .../http/conn_manager_impl_fuzz_test.cc | 13 +- .../http/conn_manager_impl_test_base.cc | 2 +- test/common/protobuf/utility_test.cc | 829 ++++++++++++++++++ .../stream_info/stream_info_impl_test.cc | 7 + .../filters/http/wasm/test_data/test_cpp.cc | 20 + .../filters/http/wasm/wasm_filter_test.cc | 80 ++ test/test_common/wasm_base.h | 12 +- 27 files changed, 1585 insertions(+), 25 deletions(-) create mode 100644 bazel/protobuf_hash_cache.patch diff --git a/bazel/protobuf_hash_cache.patch b/bazel/protobuf_hash_cache.patch new file mode 100644 index 0000000000000..13cef2cbfa644 --- /dev/null +++ b/bazel/protobuf_hash_cache.patch @@ -0,0 +1,462 @@ +diff --git a/src/google/protobuf/BUILD.bazel b/src/google/protobuf/BUILD.bazel +index 77ed2309f..825189ca5 100644 +--- a/src/google/protobuf/BUILD.bazel ++++ b/src/google/protobuf/BUILD.bazel +@@ -504,6 +504,7 @@ cc_library( + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@utf8_range//:utf8_validity", ++ "@com_github_cyan4973_xxhash//:xxhash", + ], + ) + +diff --git a/src/google/protobuf/message.cc b/src/google/protobuf/message.cc +index fc474dd7c..4db68a09d 100644 +--- a/src/google/protobuf/message.cc ++++ b/src/google/protobuf/message.cc +@@ -34,6 +34,7 @@ + + #include "google/protobuf/message.h" + ++#include + #include + #include + +@@ -60,7 +61,8 @@ + #include "google/protobuf/unknown_field_set.h" + #include "google/protobuf/wire_format.h" + #include "google/protobuf/wire_format_lite.h" +- ++#include "google/protobuf/dynamic_message.h" ++#include "xxhash.h" + + // Must be included last. + #include "google/protobuf/port_def.inc" +@@ -74,6 +76,93 @@ namespace internal { + // defined in generated_message_reflection.cc + void RegisterFileLevelMetadata(const DescriptorTable* descriptor_table); + ++// Helper function to extract type name from Any type_url ++std::string ExtractTypeNameFromUrl(const std::string& type_url) { ++ size_t last_slash = type_url.find_last_of('/'); ++ if (last_slash != std::string::npos && last_slash + 1 < type_url.length()) { ++ return type_url.substr(last_slash + 1); ++ } ++ return type_url; // Fallback to full URL if parsing fails ++} ++ ++// Helper function to check if map value is message type ++bool IsMapValueMessageTyped(const FieldDescriptor* map_field) { ++ return map_field->message_type()->field(1)->cpp_type() == ++ FieldDescriptor::CPPTYPE_MESSAGE; ++} ++ ++// Helper function to hash a single field value ++uint64_t HashFieldValue(const Reflection* reflection, const Message& message, ++ const FieldDescriptor* field, int index = -1) { ++ switch (field->cpp_type()) { ++ case FieldDescriptor::CPPTYPE_MESSAGE: ++ if (index >= 0) { ++ const Message& sub_message = reflection->GetRepeatedMessage(message, field, index); ++ return sub_message.GetCachedHashValue(); ++ } else if (reflection->HasField(message, field)) { ++ const Message& sub_message = reflection->GetMessage(message, field); ++ return sub_message.GetCachedHashValue(); ++ } ++ return 0; ++ case FieldDescriptor::CPPTYPE_INT32:{ ++ int32_t val = index >= 0 ? reflection->GetRepeatedInt32(message, field, index) ++ : reflection->GetInt32(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_INT64:{ ++ int64_t val = index >= 0 ? reflection->GetRepeatedInt64(message, field, index) ++ : reflection->GetInt64(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_UINT32:{ ++ uint32_t val = index >= 0 ? reflection->GetRepeatedUInt32(message, field, index) ++ : reflection->GetUInt32(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_UINT64:{ ++ uint64_t val = index >= 0 ? reflection->GetRepeatedUInt64(message, field, index) ++ : reflection->GetUInt64(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_DOUBLE:{ ++ double val = index >= 0 ? reflection->GetRepeatedDouble(message, field, index) ++ : reflection->GetDouble(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_FLOAT:{ ++ float val = index >= 0 ? reflection->GetRepeatedFloat(message, field, index) ++ : reflection->GetFloat(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_BOOL:{ ++ bool val = index >= 0 ? reflection->GetRepeatedBool(message, field, index) ++ : reflection->GetBool(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_ENUM:{ ++ int32_t val = index >= 0 ? reflection->GetRepeatedEnumValue(message, field, index) ++ : reflection->GetEnumValue(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_STRING:{ ++ std::string val = index >= 0 ? reflection->GetRepeatedString(message, field, index) ++ : reflection->GetString(message, field); ++ return XXH64(val.data(), val.size(), 0); ++ } ++ default:{ ++ if(index >= 0){ ++ fprintf(stderr, "Message::HashFieldValue: Unexpected repeated field type: %d\n", field->cpp_type()); ++ const Message& sub_message = reflection->GetRepeatedMessage(message, field, index); ++ return sub_message.GetCachedHashValue(); ++ } else if (reflection->HasField(message, field)){ ++ fprintf(stderr, "Message::HashFieldValue: Unexpected field type: %d\n", field->cpp_type()); ++ const Message& sub_message = reflection->GetMessage(message, field); ++ return sub_message.GetCachedHashValue(); ++ } ++ return 0; ++ } ++ } ++} + } // namespace internal + + using internal::DownCast; +@@ -215,6 +304,296 @@ uint64_t Message::GetInvariantPerBuild(uint64_t salt) { + return salt; + } + ++// Hash computation methods implementation ++uint64_t Message::ComputeHashValue() const { ++ ++ const Reflection* reflection = GetReflection(); ++ const Descriptor* descriptor = GetDescriptor(); ++ ++ // Use a stable hash seed that's consistent across runs ++ // This ensures deterministic hashing regardless of memory layout ++ uint64_t hash = 0x9e3779b97f4a7c15; // xxhash seed ++ ++ // Hash the descriptor type ++ hash = XXH64(descriptor->full_name().data(), descriptor->full_name().size(), hash); ++ ++ // Special handling for google.protobuf.Any type ++ if (descriptor->full_name() == "google.protobuf.Any") { ++ // For Any types, we need to hash the unpacked content to ensure consistency ++ // This mimics TextFormat's approach of expanding Any messages ++ const Reflection* reflection = GetReflection(); ++ const FieldDescriptor* type_url_field = descriptor->FindFieldByNumber(1); ++ const FieldDescriptor* value_field = descriptor->FindFieldByNumber(2); ++ ++ if (type_url_field && value_field && ++ reflection->HasField(*this, type_url_field) && ++ reflection->HasField(*this, value_field)) { ++ ++ std::string type_url = reflection->GetString(*this, type_url_field); ++ std::string serialized_value = reflection->GetString(*this, value_field); ++ ++ // Hash the type URL ++ hash = XXH64(type_url.data(), type_url.size(), hash); ++ /* ++ // Try to parse and hash the unpacked message for consistency ++ // This ensures that Any messages with same content produce same hash ++ // regardless of serialization order in the value field ++ try { ++ // Create a temporary message from the serialized value ++ DynamicMessageFactory factory; ++ const Descriptor* value_descriptor = ++ factory.GetPrototype(descriptor)->GetDescriptor()->file()->pool() ++ ->FindMessageTypeByName(internal::ExtractTypeNameFromUrl(type_url)); ++ ++ if (value_descriptor) { ++ std::unique_ptr unpacked_message( ++ factory.GetPrototype(value_descriptor)->New()); ++ if (unpacked_message->ParseFromString(serialized_value)) { ++ // Hash the unpacked message content ++ uint64_t unpacked_message_hash = unpacked_message->GetCachedHashValue(); ++ hash = XXH64(&unpacked_message_hash, sizeof(unpacked_message_hash), hash); ++ } else { ++ fprintf(stderr, "Message::ComputeHashValue: Parsing failed for Any message: %s\n", serialized_value.c_str()); ++ // If parsing fails, hash the raw serialized value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ } ++ } else { ++ fprintf(stderr, "Message::ComputeHashValue: Type not found: %s\n", type_url.c_str()); ++ // If type not found, hash the raw serialized value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ } ++ } catch (e) { ++ fprintf(stderr, "Message::ComputeHashValue: Error parsing Any message: %s\n", e.what()); ++ // If any error occurs, fall back to hashing the raw value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ } ++ */ ++ ++ // Skip the any parsing and just hash the serialized value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ ++ // Skip normal field processing for Any types since we've handled them specially ++ return hash; ++ } ++ } ++ ++ // Iterate through all fields and hash their values recursively ++ std::vector fields; ++ reflection->ListFields(*this, &fields); ++ ++ // Sort fields by field number to ensure consistent order ++ // Use stable_sort for deterministic ordering across runs ++ std::stable_sort(fields.begin(), fields.end(), ++ [](const FieldDescriptor* a, const FieldDescriptor* b) { ++ if (a->number() != b->number()) { ++ return a->number() < b->number(); // Primary: field number ++ } ++ // Secondary: field name for stability when field numbers are equal ++ return a->name() < b->name(); ++ }); ++ ++ for (const FieldDescriptor* field : fields) { ++ // Hash field number and type ++ uint32_t field_number = field->number(); ++ uint32_t field_type = field->type(); ++ hash = XXH64(&field_number, sizeof(field_number), hash); ++ hash = XXH64(&field_type, sizeof(field_type), hash); ++ ++ if (field->is_repeated()) { ++ // Handle repeated fields using RepeatedFieldAccessor for consistent access ++ const internal::RepeatedFieldAccessor* accessor = reflection->RepeatedFieldAccessor(field); ++ void* repeated_field_data = reflection->RepeatedFieldData(const_cast(this), field, ++ field->cpp_type(), ++ field->message_type()); ++ int size = accessor->Size(repeated_field_data); ++ hash = XXH64(&size, sizeof(size), hash); ++ ++ if (field->is_map()) { ++ // For map fields, use MapField to access the underlying map data ++ // This provides better performance and guarantees consistent ordering ++ ++ // Get key and value field descriptors ++ const Descriptor* map_entry_desc = field->message_type(); ++ const FieldDescriptor* key_field = map_entry_desc->field(0); // key field ++ const FieldDescriptor* value_field = map_entry_desc->field(1); // value field ++ ++ // Check if map value is message type ++ bool is_value_message = internal::IsMapValueMessageTyped(field); ++ ++ std::vector> map_entries; ++ ++ // Use MapIterator to iterate through the map ++ for (MapIterator iter = reflection->MapBegin(const_cast(this), field); ++ iter != reflection->MapEnd(const_cast(this), field); ++ ++iter) { ++ ++ const MapKey& key = iter.GetKey(); ++ const MapValueRef& value = iter.GetValueRef(); ++ ++ uint64_t key_hash = 0; ++ uint64_t value_hash = 0; ++ ++ // Hash key based on its type ++ switch (key_field->cpp_type()) { ++ case FieldDescriptor::CPPTYPE_STRING: { ++ std::string key_str = key.GetStringValue(); ++ key_hash = XXH64(key_str.data(), key_str.size(), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT64: { ++ int64_t key_int = key.GetInt64Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT32: { ++ int32_t key_int = key.GetInt32Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT64: { ++ uint64_t key_int = key.GetUInt64Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT32: { ++ uint32_t key_int = key.GetUInt32Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_BOOL: { ++ bool key_bool = key.GetBoolValue(); ++ key_hash = XXH64(&key_bool, sizeof(key_bool), 0); ++ break; ++ } ++ default: ++ // Should not reach here for valid map key types ++ fprintf(stderr, "Message::ComputeHashValue: Unexpected map key type: %d\n", key_field->cpp_type()); ++ break; ++ } ++ ++ // Hash value based on its type ++ if (is_value_message) { ++ // For message values, use GetCachedHashValue ++ const Message& value_msg = value.GetMessageValue(); ++ value_hash = value_msg.GetCachedHashValue(); ++ } else { ++ // For primitive values, hash directly ++ switch (value_field->cpp_type()) { ++ case FieldDescriptor::CPPTYPE_STRING: { ++ std::string value_str = value.GetStringValue(); ++ value_hash = XXH64(value_str.data(), value_str.size(), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT64: { ++ int64_t value_int = value.GetInt64Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT32: { ++ int32_t value_int = value.GetInt32Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT64: { ++ uint64_t value_int = value.GetUInt64Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT32: { ++ uint32_t value_int = value.GetUInt32Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_DOUBLE: { ++ double value_double = value.GetDoubleValue(); ++ value_hash = XXH64(&value_double, sizeof(value_double), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_FLOAT: { ++ float value_float = value.GetFloatValue(); ++ value_hash = XXH64(&value_float, sizeof(value_float), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_BOOL: { ++ bool value_bool = value.GetBoolValue(); ++ value_hash = XXH64(&value_bool, sizeof(value_bool), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_ENUM: { ++ int32_t value_enum = value.GetEnumValue(); ++ value_hash = XXH64(&value_enum, sizeof(value_enum), 0); ++ break; ++ } ++ default: ++ // Should not reach here for valid map value types ++ fprintf(stderr, "Message::ComputeHashValue: Unexpected map value type: %d\n", value_field->cpp_type()); ++ break; ++ } ++ } ++ ++ map_entries.emplace_back(key_hash, value_hash); ++ } ++ ++ // Sort map entries by key hash for consistent ordering ++ // MapField provides consistent iteration order, but we still sort for extra safety ++ std::stable_sort(map_entries.begin(), map_entries.end(), ++ [](const auto& a, const auto& b) { ++ if (a.first != b.first) { ++ return a.first < b.first; // Primary: key hash ++ } ++ return a.second < b.second; // Secondary: value hash ++ }); ++ ++ // Hash sorted map entries ++ for (const auto& entry : map_entries) { ++ hash = XXH64(&entry.first, sizeof(entry.first), hash); ++ hash = XXH64(&entry.second, sizeof(entry.second), hash); ++ } ++ } else { ++ // Handle regular repeated fields (non-map) using RepeatedFieldAccessor ++ for (int i = 0; i < size; ++i) { ++ // Use a simplified approach: directly use HashFieldValue with index ++ uint64_t hash_value = internal::HashFieldValue(reflection, *this, field, i); ++ hash = XXH64(&hash_value, sizeof(hash_value), hash); ++ } ++ } ++ } else { ++ // Handle singular fields ++ uint64_t field_value = internal::HashFieldValue(reflection, *this, field); ++ hash = XXH64(&field_value, sizeof(field_value), hash); ++ } ++ } ++ ++ // Hash unknown fields if present ++ if (_internal_metadata_.have_unknown_fields()) { ++ const UnknownFieldSet& unknown_fields = reflection->GetUnknownFields(*this); ++ // Use field count and space used for unknown fields hash ++ uint32_t field_count = unknown_fields.field_count(); ++ uint64_t space_used = unknown_fields.SpaceUsedLong(); ++ hash = XXH64(&field_count, sizeof(field_count), hash); ++ hash = XXH64(&space_used, sizeof(space_used), hash); ++ } ++ ++ return hash; ++} ++ ++uint64_t Message::GetCachedHashValue() const { ++ if (!hash_cached_) { ++ cached_hash_value_ = ComputeHashValue(); ++ hash_cached_ = true; ++ } ++ return cached_hash_value_; ++} ++ ++bool Message::HasCachedHashValue() const { ++ return hash_cached_; ++} ++ ++void Message::SetCachedHashValue(uint64_t hash_value) const { ++ cached_hash_value_ = hash_value; ++ hash_cached_ = true; ++} ++ + namespace internal { + void* CreateSplitMessageGeneric(Arena* arena, const void* default_split, + size_t size, const void* message, +diff --git a/src/google/protobuf/message.h b/src/google/protobuf/message.h +index 6c5e24f9d..b9078785c 100644 +--- a/src/google/protobuf/message.h ++++ b/src/google/protobuf/message.h +@@ -362,6 +362,22 @@ class PROTOBUF_EXPORT Message : public MessageLite { + uint8_t* _InternalSerialize(uint8_t* target, + io::EpsCopyOutputStream* stream) const override; + ++ // Hash computation methods ---------------------------------------- ++ // Optimized hash computation with caching support ++ ++ // Compute hash value for this message using recursive hashing ++ // This avoids serialization and provides better performance ++ uint64_t ComputeHashValue() const; ++ ++ // Get cached hash value if available, otherwise compute and cache it ++ uint64_t GetCachedHashValue() const; ++ ++ // Set cached hash value ++ void SetCachedHashValue(uint64_t hash_value) const; ++ ++ // Check if hash value is cached ++ bool HasCachedHashValue() const; ++ + private: + // This is called only by the default implementation of ByteSize(), to + // update the cached size. If you override ByteSize(), you do not need +@@ -418,6 +434,9 @@ class PROTOBUF_EXPORT Message : public MessageLite { + size_t MaybeComputeUnknownFieldsSize(size_t total_size, + internal::CachedSize* cached_size) const; + ++ // Hash caching support ++ mutable uint64_t cached_hash_value_ = 0; ++ mutable bool hash_cached_ = false; + + protected: + static uint64_t GetInvariantPerBuild(uint64_t salt); diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 540c70fb16ab8..e1bb52062301d 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -902,7 +902,8 @@ def _com_google_protobuf(): external_http_archive( "com_google_protobuf", - patches = ["@envoy//bazel:protobuf.patch"], + patches = ["@envoy//bazel:protobuf.patch", + "@envoy//bazel:protobuf_hash_cache.patch"], patch_args = ["-p1"], ) diff --git a/envoy/config/subscription.h b/envoy/config/subscription.h index 0c4d61a924c8e..f29fff44ad88a 100644 --- a/envoy/config/subscription.h +++ b/envoy/config/subscription.h @@ -243,6 +243,7 @@ using SubscriptionPtr = std::unique_ptr; COUNTER(update_failure) \ COUNTER(update_rejected) \ COUNTER(update_success) \ + GAUGE(last_update_success, NeverImport) \ GAUGE(update_time, NeverImport) \ GAUGE(version, NeverImport) \ HISTOGRAM(update_duration, Milliseconds) \ diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 69e6c48a03c26..670e321867a11 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -22,7 +22,7 @@ namespace Envoy { namespace Http { -#if defined(ALIMESH) +#if defined(HIGRESS) #define HIGRESS_EXT_HTTP_CONN_MAN_STATS(COUNTER, GAUGE, HISTOGRAM) \ COUNTER(downstream_rq_retry_scope_found_total) \ COUNTER(downstream_rq_retry_scope_not_found_total) @@ -98,7 +98,7 @@ namespace Http { */ struct ConnectionManagerNamedStats { ALL_HTTP_CONN_MAN_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) -#if defined(ALIMESH) +#if defined(HIGRESS) HIGRESS_EXT_HTTP_CONN_MAN_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) #endif diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 87ca935ceb8c8..d08dd83510457 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -80,7 +80,7 @@ bool requestWasConnect(const RequestHeaderMapSharedPtr& headers, Protocol protoc ConnectionManagerStats ConnectionManagerImpl::generateStats(const std::string& prefix, Stats::Scope& scope) { return ConnectionManagerStats( -#if defined(ALIMESH) +#if defined(HIGRESS) {ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER_PREFIX(scope, prefix), POOL_GAUGE_PREFIX(scope, prefix), POOL_HISTOGRAM_PREFIX(scope, prefix)) HIGRESS_EXT_HTTP_CONN_MAN_STATS(POOL_COUNTER_PREFIX(scope, prefix), @@ -2228,6 +2228,8 @@ void ConnectionManagerImpl::ActiveStream::recreateStream( // Prevent the stream from being used through the commonContinue process of // ActiveStreamDecoderFilter or ActiveStreamEncoderFilter. filter_manager_.interruptContinue(); + const auto& original_remote_address = + filter_manager_.streamInfo().downstreamAddressProvider().remoteAddress(); #else const auto& buffered_request_data = filter_manager_.bufferedRequestData(); const bool proxy_body = buffered_request_data != nullptr && buffered_request_data->length() > 0; @@ -2246,6 +2248,11 @@ void ConnectionManagerImpl::ActiveStream::recreateStream( RequestDecoder& new_stream = connection_manager_.newStream(*response_encoder, true); +#if defined(HIGRESS) + auto& active_stream = static_cast(new_stream); + active_stream.filter_manager_.setDownstreamRemoteAddress(original_remote_address); +#endif + // Set the new RequestDecoder on the ResponseEncoder. Even though all of the decoder callbacks // have already been called at this point, the encoder still needs the new decoder for deferred // logging in some cases. diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 155f31f107a9b..85fe480b72872 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -420,6 +420,16 @@ void ActiveStreamDecoderFilter::injectDecodedDataToFilterChain(Buffer::Instance& headers_continued_ = true; doHeaders(false); } +#if defined(HIGRESS) + // Fix: When injecting data with end_stream=true, we must set remote_decode_complete_ flag + // to ensure subsequent filter chain iterations (e.g., via commonContinue) correctly recognize + // the stream is complete. Without this, if a downstream filter returns StopIteration and later + // resumes via continueDecoding()->commonContinue()->doData(), the complete() check would + // incorrectly return false, causing end_stream state inconsistency across the filter chain. + if (end_stream) { + parent_.state_.remote_decode_complete_ = true; + } +#endif parent_.decodeData(this, data, end_stream, FilterManager::FilterIterationStartState::CanStartFromCurrent); } @@ -1679,6 +1689,16 @@ void ActiveStreamEncoderFilter::injectEncodedDataToFilterChain(Buffer::Instance& headers_continued_ = true; doHeaders(false); } +#if defined(HIGRESS) + // Fix: When injecting data with end_stream=true, we must set local_complete_ flag to ensure + // subsequent filter chain iterations (e.g., via commonContinue) correctly recognize the stream + // is complete. Without this, if a downstream filter returns StopIteration and later resumes + // via continueEncoding()->commonContinue()->doData(), the complete() check would incorrectly + // return false, causing end_stream state inconsistency across the filter chain. + if (end_stream) { + parent_.state_.local_complete_ = true; + } +#endif parent_.encodeData(this, data, end_stream, FilterManager::FilterIterationStartState::CanStartFromCurrent); } diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 8012a84f5fc80..18402565f371d 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -580,6 +580,19 @@ class MessageUtil { static std::string sanitizeUtf8String(absl::string_view str); }; +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) +class HashCachedMessageUtil : public MessageUtil { +public: + bool operator()(const Protobuf::Message& message) const { return message.GetCachedHashValue(); } + + bool operator()(const Protobuf::Message& lhs, const Protobuf::Message& rhs) const { + return lhs.GetCachedHashValue() == rhs.GetCachedHashValue(); + } + + static std::size_t hash(const Protobuf::Message& message) { return message.GetCachedHashValue(); } +}; +#endif + class ValueUtil { public: static std::size_t hash(const ProtobufWkt::Value& value) { return MessageUtil::hash(value); } diff --git a/source/common/rds/route_config_update_receiver_impl.h b/source/common/rds/route_config_update_receiver_impl.h index 153dab4d491bf..d708ffc310602 100644 --- a/source/common/rds/route_config_update_receiver_impl.h +++ b/source/common/rds/route_config_update_receiver_impl.h @@ -13,8 +13,11 @@ class RouteConfigUpdateReceiverImpl : public RouteConfigUpdateReceiver { public: RouteConfigUpdateReceiverImpl(ConfigTraits& config_traits, ProtoTraits& proto_traits, Server::Configuration::ServerFactoryContext& factory_context); - +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) + uint64_t getHash(const Protobuf::Message& rc) const { return HashCachedMessageUtil::hash(rc); } +#else uint64_t getHash(const Protobuf::Message& rc) const { return MessageUtil::hash(rc); } +#endif bool checkHash(uint64_t new_hash) const { return (new_hash != last_config_hash_); } void updateHash(uint64_t hash) { last_config_hash_ = hash; } void updateConfig(std::unique_ptr&& route_config_proto); diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 756be7b6b5467..39c2831f4e61a 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -67,6 +67,7 @@ constexpr std::string_view ClearRouteCacheKey = "clear_route_cache"; constexpr std::string_view DisableClearRouteCache = "off"; constexpr std::string_view SetDecoderBufferLimit = "set_decoder_buffer_limit"; constexpr std::string_view SetEncoderBufferLimit = "set_encoder_buffer_limit"; +constexpr std::string_view WasmRebuildKey = "wasm_need_rebuild"; bool stringViewToUint32(std::string_view str, uint32_t& out_value) { try { @@ -455,10 +456,17 @@ WasmResult serializeValue(Filters::Common::Expr::CelValue value, std::string* re return WasmResult::SerializationFailure; } +#if defined(HIGRESS) +#define PROPERTY_TOKENS(_f) \ + _f(NODE) _f(LISTENER_DIRECTION) _f(LISTENER_METADATA) _f(CLUSTER_NAME) _f(CLUSTER_METADATA) \ + _f(ROUTE_NAME) _f(ROUTE_METADATA) _f(PLUGIN_NAME) _f(UPSTREAM_HOST_METADATA) \ + _f(PLUGIN_ROOT_ID) _f(PLUGIN_VM_ID) _f(PLUGIN_VM_MEMORY) _f(CONNECTION_ID) +#else #define PROPERTY_TOKENS(_f) \ _f(NODE) _f(LISTENER_DIRECTION) _f(LISTENER_METADATA) _f(CLUSTER_NAME) _f(CLUSTER_METADATA) \ _f(ROUTE_NAME) _f(ROUTE_METADATA) _f(PLUGIN_NAME) _f(UPSTREAM_HOST_METADATA) \ _f(PLUGIN_ROOT_ID) _f(PLUGIN_VM_ID) _f(CONNECTION_ID) +#endif static inline std::string downCase(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); @@ -613,6 +621,13 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co return CelValue::CreateStringView(toAbslStringView(root_id())); case PropertyToken::PLUGIN_VM_ID: return CelValue::CreateStringView(toAbslStringView(wasm()->vm_id())); +#if defined(HIGRESS) + case PropertyToken::PLUGIN_VM_MEMORY: + if (wasm() && wasm()->wasm_vm()) { + return CelValue::CreateUint64(wasm()->wasm_vm()->getMemorySize()); + } + break; +#endif } return {}; } @@ -720,12 +735,30 @@ Http::HeaderMap* Context::getMap(WasmHeaderMapType type) { } const Http::HeaderMap* Context::getConstMap(WasmHeaderMapType type) { +#if defined(HIGRESS) + const StreamInfo::StreamInfo* request_stream_info = getConstRequestStreamInfo(); +#endif switch (type) { case WasmHeaderMapType::RequestHeaders: if (access_log_phase_) { return access_log_request_headers_; } +#if defined(HIGRESS) + // Fallback mechanism for retrieving request headers: + // 1. First try the cached request_headers_ pointer (most common case) + // 2. If null, attempt to retrieve from StreamInfo (e.g., after internal redirects or + // when headers are stored in stream info but not directly cached) + // 3. Return nullptr if both sources are unavailable + if (request_headers_ != nullptr) { + return request_headers_; + } + if (request_stream_info == nullptr) { + return nullptr; + } + return request_stream_info->getRequestHeaders(); +#else return request_headers_; +#endif case WasmHeaderMapType::RequestTrailers: if (access_log_phase_) { return nullptr; @@ -1354,7 +1387,13 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { prototype.life_span_); } #if defined(HIGRESS) - if (path == ClearRouteCacheKey) { + if (path == WasmRebuildKey) { + if (wasm_) { + wasm_->setShouldRebuild(true); + ENVOY_LOG(debug, "Wasm rebuild flag set by plugin"); + } + return WasmResult::Ok; + } else if (path == ClearRouteCacheKey) { disable_clear_route_cache_ = value == DisableClearRouteCache; } else if (path == SetDecoderBufferLimit && decoder_callbacks_) { uint32_t buffer_limit; diff --git a/source/extensions/common/wasm/stats_handler.h b/source/extensions/common/wasm/stats_handler.h index 2f492cc9c77e6..8f3d7a8b1346f 100644 --- a/source/extensions/common/wasm/stats_handler.h +++ b/source/extensions/common/wasm/stats_handler.h @@ -36,6 +36,7 @@ struct CreateWasmStats { COUNTER(created) \ GAUGE(active, NeverImport) \ PLUGIN_COUNTER(recover_total) \ + PLUGIN_COUNTER(rebuild_total) \ PLUGIN_COUNTER(crash_total) \ PLUGIN_COUNTER(recover_error) \ PLUGIN_GAUGE(crash, NeverImport) diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index ab96bed40594a..524e111617757 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -85,7 +85,7 @@ WasmEvent failStateToWasmEvent(FailState state) { PANIC("corrupt enum"); } -const int MIN_RECOVER_INTERVAL_SECONDS = 5; +const int MIN_RECOVER_INTERVAL_SECONDS = 1; #endif } // namespace @@ -195,7 +195,7 @@ Wasm::~Wasm() { } #if defined(HIGRESS) -bool PluginHandleSharedPtrThreadLocal::recover() { +bool PluginHandleSharedPtrThreadLocal::rebuild(bool is_fail_recovery) { if (handle_ == nullptr || handle_->wasmHandle() == nullptr || handle_->wasmHandle()->wasm() == nullptr) { ENVOY_LOG(warn, "wasm has not been initialized"); @@ -204,16 +204,22 @@ bool PluginHandleSharedPtrThreadLocal::recover() { auto& dispatcher = handle_->wasmHandle()->wasm()->dispatcher(); auto now = dispatcher.timeSource().monotonicTime() + cache_time_offset_for_testing; if (now - last_recover_time_ < std::chrono::seconds(MIN_RECOVER_INTERVAL_SECONDS)) { - ENVOY_LOG(debug, "recover interval has not been reached"); + ENVOY_LOG(info, "rebuild interval has not been reached"); return false; } - // Even if recovery fails, it will be retried after the interval + // Even if rebuild fails, it will be retried after the interval last_recover_time_ = now; std::shared_ptr new_handle; - if (handle_->doRecover(new_handle)) { + if (handle_->rebuild(new_handle)) { handle_ = std::static_pointer_cast(new_handle); - handle_->wasmHandle()->wasm()->lifecycleStats().recover_total_.inc(); - ENVOY_LOG(info, "wasm vm recover from crash success"); + // Increment appropriate metrics based on rebuild type + if (is_fail_recovery) { + handle_->wasmHandle()->wasm()->lifecycleStats().recover_total_.inc(); + ENVOY_LOG(info, "wasm vm recover from crash success"); + } else { + handle_->wasmHandle()->wasm()->lifecycleStats().rebuild_total_.inc(); + ENVOY_LOG(info, "wasm vm rebuild success"); + } return true; } return false; diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index e6974a97c4d4f..cf5658e6d33f8 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -162,7 +162,7 @@ class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject, public Logger::Loggable { public: PluginHandleSharedPtrThreadLocal(PluginHandleSharedPtr handle) : handle_(handle){}; - bool recover(); + bool rebuild(bool is_fail_recovery = false); #else class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject { public: diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc b/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc index d32adfa13160e..fe5c3b5bf2657 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc @@ -76,6 +76,9 @@ void GrpcSubscriptionImpl::onConfigUpdate(const std::vector( dispatcher_.timeSource().monotonicTime() - start); stats_.update_success_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(true); +#endif stats_.update_attempt_.inc(); stats_.update_time_.set(DateUtil::nowToMilliseconds(dispatcher_.timeSource())); stats_.version_.set(HashUtil::xxHash64(version_info)); @@ -101,6 +104,9 @@ void GrpcSubscriptionImpl::onConfigUpdate( std::chrono::milliseconds update_duration = std::chrono::duration_cast( dispatcher_.timeSource().monotonicTime() - start); stats_.update_success_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(true); +#endif stats_.update_time_.set(DateUtil::nowToMilliseconds(dispatcher_.timeSource())); stats_.version_.set(HashUtil::xxHash64(system_version_info)); stats_.version_text_.set(system_version_info); @@ -112,10 +118,16 @@ void GrpcSubscriptionImpl::onConfigUpdateFailed(ConfigUpdateFailureReason reason switch (reason) { case Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure: stats_.update_failure_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(false); +#endif ENVOY_LOG(debug, "gRPC update for {} failed", type_url_); break; case Envoy::Config::ConfigUpdateFailureReason::FetchTimedout: stats_.init_fetch_timeout_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(false); +#endif disableInitFetchTimeoutTimer(); ENVOY_LOG(warn, "gRPC config: initial fetch timed out for {}", type_url_); callbacks_.onConfigUpdateFailed(reason, e); @@ -125,6 +137,9 @@ void GrpcSubscriptionImpl::onConfigUpdateFailed(ConfigUpdateFailureReason reason ASSERT(e != nullptr); disableInitFetchTimeoutTimer(); stats_.update_rejected_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(false); +#endif ENVOY_LOG(warn, "gRPC config for {} rejected: {}", type_url_, e->what()); callbacks_.onConfigUpdateFailed(reason, e); break; diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index 49ae1f1a84ae9..3552bc7d3300f 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -48,7 +48,7 @@ class FilterConfig : Logger::Loggable { failed = true; } else if (wasm->isFailed()) { ENVOY_LOG(info, "wasm vm is crashed, try to recover"); - if (opt_ref->recover()) { + if (opt_ref->rebuild(true)) { ENVOY_LOG(info, "wasm vm recover success"); wasm = opt_ref->handle()->wasmHandle()->wasm().get(); handle = opt_ref->handle(); @@ -56,6 +56,17 @@ class FilterConfig : Logger::Loggable { ENVOY_LOG(info, "wasm vm recover failed"); failed = true; } + } else if (wasm->shouldRebuild()) { + ENVOY_LOG(info, "wasm vm requested rebuild, try to rebuild"); + if (opt_ref->rebuild(false)) { + ENVOY_LOG(info, "wasm vm rebuild success"); + wasm = opt_ref->handle()->wasmHandle()->wasm().get(); + handle = opt_ref->handle(); + // Reset rebuild state + wasm->setShouldRebuild(false); + } else { + ENVOY_LOG(info, "wasm vm rebuild failed, still using the stale one"); + } } if (failed) { if (handle->plugin()->fail_open_) { diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index 9a6e21eb81268..d21e76faed09f 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -48,7 +48,7 @@ class FilterConfig : Logger::Loggable { failed = true; } else if (wasm->isFailed()) { ENVOY_LOG(info, "wasm vm is crashed, try to recover"); - if (opt_ref->recover()) { + if (opt_ref->rebuild(true)) { ENVOY_LOG(info, "wasm vm recover success"); wasm = opt_ref->handle()->wasmHandle()->wasm().get(); } else { diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc index b1a71a7692edf..d1066111a2d25 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc @@ -280,6 +280,7 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( downstream_headers->setPath(path_and_query); #if defined(HIGRESS) } + auto original_upstream_cluster = encoder_callbacks->streamInfo().upstreamClusterInfo(); #endif if (decoder_callbacks->downstreamCallbacks()) { decoder_callbacks->downstreamCallbacks()->clearRouteCache(); @@ -307,9 +308,15 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( // Cache the original response code. absl::optional<::Envoy::Http::Code> original_response_code; #if defined(HIGRESS) + if (original_upstream_cluster.has_value()) { + encoder_callbacks->streamInfo().setUpstreamClusterInfo(*original_upstream_cluster); + } + absl::optional current_code = + ::Envoy::Http::Utility::getResponseStatusOrNullopt(headers); + if (current_code.has_value()) { + encoder_callbacks->streamInfo().setResponseCode(static_cast(*current_code)); + } if (keep_original_response_code_) { - absl::optional current_code = - ::Envoy::Http::Utility::getResponseStatusOrNullopt(headers); if (current_code.has_value()) { original_response_code = static_cast<::Envoy::Http::Code>(*current_code); } diff --git a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc index 356072181bb04..593537aaf249c 100644 --- a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc @@ -211,9 +211,15 @@ void FilterChainManagerImpl::addFilterChains( FilterChainFactoryBuilder& filter_chain_factory_builder, FilterChainFactoryContextCreator& context_creator) { Cleanup cleanup([this]() { origin_ = absl::nullopt; }); +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) + absl::node_hash_map + filter_chains; +#else absl::node_hash_map filter_chains; +#endif uint32_t new_filter_chain_size = 0; FilterChainsByName filter_chains_by_name; diff --git a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h index 7addd4f710ff3..ad2d3756e3d60 100644 --- a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h +++ b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h @@ -151,8 +151,15 @@ class FilterChainManagerImpl : public Network::FilterChainManager, Logger::Loggable { public: using FcContextMap = +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) + absl::flat_hash_map; +#else absl::flat_hash_map; +#endif + FilterChainManagerImpl(const std::vector& addresses, Configuration::FactoryContext& factory_context, Init::Manager& init_manager) diff --git a/source/extensions/listener_managers/listener_manager/listener_impl.cc b/source/extensions/listener_managers/listener_manager/listener_impl.cc index 51b42225f556d..10184501e87c7 100644 --- a/source/extensions/listener_managers/listener_manager/listener_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_impl.cc @@ -1055,7 +1055,12 @@ void ListenerImpl::diffFilterChain(const ListenerImpl& another_listener, } // Filter chain manager maintains an optional default filter chain besides the filter chains // indexed by message. - if (auto eq = MessageUtil(); + if (auto +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) + eq = HashCachedMessageUtil(); +#else + eq = MessageUtil(); +#endif filter_chain_manager_->defaultFilterChainMessage().has_value() && (!another_listener.filter_chain_manager_->defaultFilterChainMessage().has_value() || !eq(*another_listener.filter_chain_manager_->defaultFilterChainMessage(), diff --git a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc index 53182c0978626..f07290466db47 100644 --- a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc @@ -481,8 +481,11 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal( name, envoy::config::core::v3::TrafficDirection_Name(config.traffic_direction())); return false; } - +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) + const uint64_t hash = HashCachedMessageUtil::hash(config); +#else const uint64_t hash = MessageUtil::hash(config); +#endif ENVOY_LOG(debug, "begin add/update listener: name={} hash={}", name, hash); auto existing_active_listener = getListenerByName(active_listeners_, name); diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index d7ae59ec6877d..5589662f3edf1 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -53,9 +53,16 @@ class FuzzConfig : public ConnectionManagerConfig { public: FuzzConfig(envoy::extensions::filters::network::http_connection_manager::v3:: HttpConnectionManager::ForwardClientCertDetails forward_client_cert) - : stats_({ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), - POOL_GAUGE(fake_stats_), - POOL_HISTOGRAM(*fake_stats_.rootScope()))}, + : stats_({ConnectionManagerNamedStats{ + ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), + POOL_GAUGE(fake_stats_), + POOL_HISTOGRAM(*fake_stats_.rootScope())) +#if defined(HIGRESS) + HIGRESS_EXT_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), + POOL_GAUGE(fake_stats_), + POOL_HISTOGRAM(*fake_stats_.rootScope())) +#endif + }}, "", *fake_stats_.rootScope()), tracing_stats_{CONN_MAN_TRACING_STATS(POOL_COUNTER(fake_stats_))}, listener_stats_{CONN_MAN_LISTENER_STATS(POOL_COUNTER(fake_stats_))}, diff --git a/test/common/http/conn_manager_impl_test_base.cc b/test/common/http/conn_manager_impl_test_base.cc index 47a99dbfcdc54..883c60643dc48 100644 --- a/test/common/http/conn_manager_impl_test_base.cc +++ b/test/common/http/conn_manager_impl_test_base.cc @@ -19,7 +19,7 @@ HttpConnectionManagerImplMixin::HttpConnectionManagerImplMixin() Filesystem::FilePathAndType{Filesystem::DestinationType::File, access_log_path_}, {}, Formatter::SubstitutionFormatUtils::defaultSubstitutionFormatter(), log_manager_)}}, codec_(new NiceMock()), -#if defined(ALIMESH) +#if defined(HIGRESS) stats_({ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), POOL_GAUGE(*fake_stats_.rootScope()), POOL_HISTOGRAM(*fake_stats_.rootScope())) diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 5d89275dc343d..a2f635f10fe6a 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -171,6 +171,831 @@ TEST_F(ProtobufUtilityTest, EvaluateFractionalPercent) { } // namespace ProtobufPercentHelper +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) +TEST_F(ProtobufUtilityTest, HashCache) { + ProtobufWkt::StringValue str1, str2, str3; + TestUtility::loadFromJson("\"hello world\"", str1); + TestUtility::loadFromJson("\"hello world\"", str2); + TestUtility::loadFromJson("\"hello world!\"", str3); + + ProtobufWkt::Struct struct1, struct2, struct3; + (*struct1.mutable_fields())["field"].mutable_string_value()->assign(str1.value()); + (*struct2.mutable_fields())["field"].mutable_string_value()->assign(str2.value()); + (*struct3.mutable_fields())["field"].mutable_string_value()->assign(str3.value()); + + EXPECT_EQ(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct2)); + EXPECT_NE(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct3)); + + EXPECT_TRUE(struct1.fields().at("field").HasCachedHashValue()); + EXPECT_TRUE(struct2.fields().at("field").HasCachedHashValue()); + EXPECT_TRUE(struct3.fields().at("field").HasCachedHashValue()); + + ProtobufWkt::ListValue list1, list2, list3; + auto* v1 = list1.add_values(); + v1->set_string_value("hello"); + auto* v2 = list1.add_values(); + v2->set_string_value("world"); + + auto* v3 = list2.add_values(); + v3->set_string_value("hello"); + auto* v4 = list2.add_values(); + v4->set_string_value("world"); + + auto* v5 = list3.add_values(); + v5->set_string_value("hello"); + auto* v6 = list3.add_values(); + v6->set_string_value("world!"); + + EXPECT_EQ(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list2)); + EXPECT_NE(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list3)); + + EXPECT_TRUE(v1->HasCachedHashValue()); + EXPECT_TRUE(v2->HasCachedHashValue()); + EXPECT_TRUE(v3->HasCachedHashValue()); + EXPECT_TRUE(v4->HasCachedHashValue()); + EXPECT_TRUE(v5->HasCachedHashValue()); + EXPECT_TRUE(v6->HasCachedHashValue()); + + // Test direct message nesting (not map) - using Value with struct_value + ProtobufWkt::Value nested_value1, nested_value2, nested_value3; + + // Create nested structure: Value -> Struct -> Value -> StringValue + auto* nested_struct1 = nested_value1.mutable_struct_value(); + (*nested_struct1->mutable_fields())["nested_field"].set_string_value("nested hello world"); + + auto* nested_struct2 = nested_value2.mutable_struct_value(); + (*nested_struct2->mutable_fields())["nested_field"].set_string_value("nested hello world"); + + auto* nested_struct3 = nested_value3.mutable_struct_value(); + (*nested_struct3->mutable_fields())["nested_field"].set_string_value("nested hello world!"); + + EXPECT_EQ(HashCachedMessageUtil::hash(nested_value1), HashCachedMessageUtil::hash(nested_value2)); + EXPECT_NE(HashCachedMessageUtil::hash(nested_value1), HashCachedMessageUtil::hash(nested_value3)); + + // Check that all nested messages have cached hash values + EXPECT_TRUE(nested_value1.HasCachedHashValue()); + EXPECT_TRUE(nested_value2.HasCachedHashValue()); + EXPECT_TRUE(nested_value3.HasCachedHashValue()); + + // Check nested struct messages + EXPECT_TRUE(nested_value1.struct_value().HasCachedHashValue()); + EXPECT_TRUE(nested_value2.struct_value().HasCachedHashValue()); + EXPECT_TRUE(nested_value3.struct_value().HasCachedHashValue()); + + // Check the nested Value objects inside struct + EXPECT_TRUE(nested_value1.struct_value().fields().at("nested_field").HasCachedHashValue()); + EXPECT_TRUE(nested_value2.struct_value().fields().at("nested_field").HasCachedHashValue()); + EXPECT_TRUE(nested_value3.struct_value().fields().at("nested_field").HasCachedHashValue()); + + // Test deeper nesting: Value -> Struct -> Value -> Struct -> Value -> StringValue + ProtobufWkt::Value deep_nested_value1, deep_nested_value2; + + auto* deep_struct1 = deep_nested_value1.mutable_struct_value(); + auto* deep_inner_struct1 = (*deep_struct1->mutable_fields())["deep_field"].mutable_struct_value(); + (*deep_inner_struct1->mutable_fields())["inner_field"].set_string_value("deep nested value"); + + auto* deep_struct2 = deep_nested_value2.mutable_struct_value(); + auto* deep_inner_struct2 = (*deep_struct2->mutable_fields())["deep_field"].mutable_struct_value(); + (*deep_inner_struct2->mutable_fields())["inner_field"].set_string_value("deep nested value"); + + EXPECT_EQ(HashCachedMessageUtil::hash(deep_nested_value1), + HashCachedMessageUtil::hash(deep_nested_value2)); + + // Check that all levels of nesting have cached hash values + EXPECT_TRUE(deep_nested_value1.HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value().HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value().HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value().fields().at("deep_field").HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value().fields().at("deep_field").HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value() + .fields() + .at("deep_field") + .struct_value() + .HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value() + .fields() + .at("deep_field") + .struct_value() + .HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value() + .fields() + .at("deep_field") + .struct_value() + .fields() + .at("inner_field") + .HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value() + .fields() + .at("deep_field") + .struct_value() + .fields() + .at("inner_field") + .HasCachedHashValue()); +} + +TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHash) { + // Test string hashing using JSON to Proto message conversion + ProtobufWkt::StringValue str1, str2, str3; + + // Convert JSON strings to Proto messages + TestUtility::loadFromJson("\"hello world\"", str1); + TestUtility::loadFromJson("\"hello world\"", str2); + TestUtility::loadFromJson("\"hello world!\"", str3); + + // Test that identical strings produce same hash + EXPECT_EQ(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str2)); + + // Test that different strings produce different hashes + EXPECT_NE(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str3)); + + // Test that the hash is cached + EXPECT_EQ(str1.HasCachedHashValue(), true); + + // Test that hash is not zero + EXPECT_NE(0, HashCachedMessageUtil::hash(str1)); + EXPECT_NE(0, HashCachedMessageUtil::hash(str2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(str3)); + + // Test hash consistency + uint64_t hash1 = HashCachedMessageUtil::hash(str1); + uint64_t hash2 = HashCachedMessageUtil::hash(str1); + EXPECT_EQ(hash1, hash2); // Same string should always produce same hash + + // Test with different string types + ProtobufWkt::BytesValue bytes1, bytes2; + // BytesValue expects base64 encoded strings + TestUtility::loadFromJson("\"aGVsbG8gd29ybGQ=\"", bytes1); // "hello world" in base64 + TestUtility::loadFromJson("\"aGVsbG8gd29ybGQ=\"", bytes2); // "hello world" in base64 + + // BytesValue should also produce consistent hashes + EXPECT_EQ(HashCachedMessageUtil::hash(bytes1), HashCachedMessageUtil::hash(bytes2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(bytes1)); + + // Test with different base64 strings + ProtobufWkt::BytesValue bytes3; + TestUtility::loadFromJson("\"aGVsbG8gd29ybGQh\"", bytes3); // "hello world!" in base64 + EXPECT_NE(HashCachedMessageUtil::hash(bytes1), HashCachedMessageUtil::hash(bytes3)); +} + +TEST_F(ProtobufUtilityTest, MessageUtilHashComprehensive) { + // Test 1: Basic primitive types + { + // StringValue + ProtobufWkt::StringValue str1, str2, str3; + TestUtility::loadFromJson("\"test string\"", str1); + TestUtility::loadFromJson("\"test string\"", str2); + TestUtility::loadFromJson("\"different string\"", str3); + + EXPECT_EQ(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str2)); + EXPECT_NE(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(str1)); + + // BytesValue + ProtobufWkt::BytesValue bytes1, bytes2; + TestUtility::loadFromJson("\"dGVzdCBieXRlcw==\"", bytes1); // "test bytes" in base64 + TestUtility::loadFromJson("\"dGVzdCBieXRlcw==\"", bytes2); // "test bytes" in base64 + + EXPECT_EQ(HashCachedMessageUtil::hash(bytes1), HashCachedMessageUtil::hash(bytes2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(bytes1)); + } + + // Test 2: Numeric types + { + // Int32Value + ProtobufWkt::Int32Value int1, int2, int3; + int1.set_value(42); + int2.set_value(42); + int3.set_value(100); + + EXPECT_EQ(HashCachedMessageUtil::hash(int1), HashCachedMessageUtil::hash(int2)); + EXPECT_NE(HashCachedMessageUtil::hash(int1), HashCachedMessageUtil::hash(int3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(int1)); + + // UInt64Value + ProtobufWkt::UInt64Value uint1, uint2, uint3; + uint1.set_value(123456789); + uint2.set_value(123456789); + uint3.set_value(987654321); + + EXPECT_EQ(HashCachedMessageUtil::hash(uint1), HashCachedMessageUtil::hash(uint2)); + EXPECT_NE(HashCachedMessageUtil::hash(uint1), HashCachedMessageUtil::hash(uint3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(uint1)); + + // DoubleValue + ProtobufWkt::DoubleValue double1, double2, double3; + double1.set_value(3.14159); + double2.set_value(3.14159); + double3.set_value(2.71828); + + EXPECT_EQ(HashCachedMessageUtil::hash(double1), HashCachedMessageUtil::hash(double2)); + EXPECT_NE(HashCachedMessageUtil::hash(double1), HashCachedMessageUtil::hash(double3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(double1)); + + // BoolValue + ProtobufWkt::BoolValue bool1, bool2, bool3; + bool1.set_value(true); + bool2.set_value(true); + bool3.set_value(false); + + EXPECT_EQ(HashCachedMessageUtil::hash(bool1), HashCachedMessageUtil::hash(bool2)); + EXPECT_NE(HashCachedMessageUtil::hash(bool1), HashCachedMessageUtil::hash(bool3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(bool1)); + } + + // Test 3: Complex types with nested messages + { + // Struct with nested fields + ProtobufWkt::Struct struct1, struct2, struct3; + + // Build struct1 + (*struct1.mutable_fields())["string_field"].set_string_value("hello"); + (*struct1.mutable_fields())["number_field"].set_number_value(42.5); + (*struct1.mutable_fields())["bool_field"].set_bool_value(true); + + // Build struct2 (identical to struct1) + (*struct2.mutable_fields())["string_field"].set_string_value("hello"); + (*struct2.mutable_fields())["number_field"].set_number_value(42.5); + (*struct2.mutable_fields())["bool_field"].set_bool_value(true); + + // Build struct3 (different) + (*struct3.mutable_fields())["string_field"].set_string_value("world"); + (*struct3.mutable_fields())["number_field"].set_number_value(42.5); + (*struct3.mutable_fields())["bool_field"].set_bool_value(true); + + EXPECT_EQ(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct2)); + EXPECT_NE(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(struct1)); + + // Test field order independence (should produce same hash) + ProtobufWkt::Struct struct4; + (*struct4.mutable_fields())["bool_field"].set_bool_value(true); + (*struct4.mutable_fields())["number_field"].set_number_value(42.5); + (*struct4.mutable_fields())["string_field"].set_string_value("hello"); + + EXPECT_EQ(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct4)); + } + + // Test 4: Repeated fields + { + // ListValue with repeated elements + ProtobufWkt::ListValue list1, list2, list3, list4; + + // Build list1: [1, 2, 3] + list1.add_values()->set_number_value(1); + list1.add_values()->set_number_value(2); + list1.add_values()->set_number_value(3); + + // Build list2: [1, 2, 3] (identical) + list2.add_values()->set_number_value(1); + list2.add_values()->set_number_value(2); + list2.add_values()->set_number_value(3); + + // Build list3: [1, 2, 4] (different) + list3.add_values()->set_number_value(1); + list3.add_values()->set_number_value(2); + list3.add_values()->set_number_value(4); + + // Build list4: [3, 2, 1] (different order) + list4.add_values()->set_number_value(3); + list4.add_values()->set_number_value(2); + list4.add_values()->set_number_value(1); + + EXPECT_EQ(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list2)); + EXPECT_NE(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list3)); + EXPECT_NE(HashCachedMessageUtil::hash(list1), + HashCachedMessageUtil::hash(list4)); // Order matters + EXPECT_NE(0, HashCachedMessageUtil::hash(list1)); + + // Test empty list + ProtobufWkt::ListValue empty_list; + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_list)); + EXPECT_NE(HashCachedMessageUtil::hash(empty_list), HashCachedMessageUtil::hash(list1)); + } + + // Test 5: Any type with packed messages + { + // Pack Struct into Any + ProtobufWkt::Struct original_struct; + (*original_struct.mutable_fields())["key1"].set_string_value("value2"); + (*original_struct.mutable_fields())["key2"].set_number_value(123); + + ProtobufWkt::Any any1, any2, any3; + any1.PackFrom(original_struct); + any2.PackFrom(original_struct); + + // Create different struct for any3 + ProtobufWkt::Struct different_struct; + (*different_struct.mutable_fields())["key1"].set_string_value("value1"); + (*different_struct.mutable_fields())["key2"].set_number_value(456); // Different value + any3.PackFrom(different_struct); + + EXPECT_EQ(HashCachedMessageUtil::hash(any1), HashCachedMessageUtil::hash(any2)); + EXPECT_NE(HashCachedMessageUtil::hash(any1), HashCachedMessageUtil::hash(any3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(any1)); + + // Test that Any hash is different from original struct hash + EXPECT_NE(HashCachedMessageUtil::hash(any1), HashCachedMessageUtil::hash(original_struct)); + } + + // Test 6: Timestamp and Duration + { + // Timestamp + ProtobufWkt::Timestamp ts1, ts2, ts3; + ts1.set_seconds(1234567890); + ts1.set_nanos(123456789); + ts2.set_seconds(1234567890); + ts2.set_nanos(123456789); + ts3.set_seconds(1234567890); + ts3.set_nanos(987654321); + + EXPECT_EQ(HashCachedMessageUtil::hash(ts1), HashCachedMessageUtil::hash(ts2)); + EXPECT_NE(HashCachedMessageUtil::hash(ts1), HashCachedMessageUtil::hash(ts3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(ts1)); + + // Duration + ProtobufWkt::Duration dur1, dur2, dur3; + dur1.set_seconds(3600); + dur1.set_nanos(500000000); + dur2.set_seconds(3600); + dur2.set_nanos(500000000); + dur3.set_seconds(7200); + dur3.set_nanos(500000000); + + EXPECT_EQ(HashCachedMessageUtil::hash(dur1), HashCachedMessageUtil::hash(dur2)); + EXPECT_NE(HashCachedMessageUtil::hash(dur1), HashCachedMessageUtil::hash(dur3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(dur1)); + } + + // Test 7: Empty vs non-empty messages + { + ProtobufWkt::StringValue empty_str, non_empty_str; + TestUtility::loadFromJson("\"\"", empty_str); + TestUtility::loadFromJson("\"non-empty\"", non_empty_str); + + EXPECT_NE(HashCachedMessageUtil::hash(empty_str), HashCachedMessageUtil::hash(non_empty_str)); + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_str)); + EXPECT_NE(0, HashCachedMessageUtil::hash(non_empty_str)); + + // Empty Struct + ProtobufWkt::Struct empty_struct; + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_struct)); + EXPECT_NE(HashCachedMessageUtil::hash(empty_struct), HashCachedMessageUtil::hash(empty_str)); + } + + // Test 8: Hash consistency across multiple calls + { + ProtobufWkt::StringValue test_str; + TestUtility::loadFromJson("\"consistency test\"", test_str); + + uint64_t hash1 = HashCachedMessageUtil::hash(test_str); + uint64_t hash2 = HashCachedMessageUtil::hash(test_str); + uint64_t hash3 = HashCachedMessageUtil::hash(test_str); + + EXPECT_EQ(hash1, hash2); + EXPECT_EQ(hash2, hash3); + EXPECT_EQ(hash1, hash3); + EXPECT_NE(0, hash1); + } + + // Test 9: Large messages + { + // Create a large struct with many fields + ProtobufWkt::Struct large_struct; + for (int i = 0; i < 100; ++i) { + std::string field_name = "field_" + std::to_string(i); + std::string field_value = "value_" + std::to_string(i); + (*large_struct.mutable_fields())[field_name].set_string_value(field_value); + } + + EXPECT_NE(0, HashCachedMessageUtil::hash(large_struct)); + + // Create identical large struct + ProtobufWkt::Struct large_struct2; + for (int i = 0; i < 100; ++i) { + std::string field_name = "field_" + std::to_string(i); + std::string field_value = "value_" + std::to_string(i); + (*large_struct2.mutable_fields())[field_name].set_string_value(field_value); + } + + EXPECT_EQ(HashCachedMessageUtil::hash(large_struct), + HashCachedMessageUtil::hash(large_struct2)); + } + + // Test 10: Edge cases + { + // Very long string + std::string long_string(10000, 'a'); + ProtobufWkt::StringValue long_str; + long_str.set_value(long_string); + + EXPECT_NE(0, HashCachedMessageUtil::hash(long_str)); + + // String with special characters + ProtobufWkt::StringValue special_str; + special_str.set_value("!@#$%^&*()_+-=[]{}|;':\",./<>?"); + + EXPECT_NE(0, HashCachedMessageUtil::hash(special_str)); + EXPECT_NE(HashCachedMessageUtil::hash(long_str), HashCachedMessageUtil::hash(special_str)); + + // Unicode string + ProtobufWkt::StringValue unicode_str; + unicode_str.set_value("Hello 世界 🌍"); + + EXPECT_NE(0, HashCachedMessageUtil::hash(unicode_str)); + EXPECT_NE(HashCachedMessageUtil::hash(unicode_str), HashCachedMessageUtil::hash(special_str)); + } +} + +TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHashComplex) { + // Test recursive hashing with deeply nested structures + + // Create a complex nested structure + ProtobufWkt::Struct root_struct; + + // Level 1: Basic fields + (*root_struct.mutable_fields())["name"].set_string_value("root"); + (*root_struct.mutable_fields())["id"].set_number_value(1); + + // Level 2: Nested struct + ProtobufWkt::Struct* nested1 = (*root_struct.mutable_fields())["nested"].mutable_struct_value(); + (*nested1->mutable_fields())["level"].set_string_value("level2"); + (*nested1->mutable_fields())["count"].set_number_value(2); + + // Level 3: Another nested struct + ProtobufWkt::Struct* nested2 = (*nested1->mutable_fields())["deeper"].mutable_struct_value(); + (*nested2->mutable_fields())["level"].set_string_value("level3"); + (*nested2->mutable_fields())["final"].set_bool_value(true); + + // Level 4: List in nested struct + ProtobufWkt::ListValue* list = (*nested2->mutable_fields())["items"].mutable_list_value(); + list->add_values()->set_string_value("item1"); + list->add_values()->set_string_value("item2"); + list->add_values()->set_number_value(42); + + // Create identical structure + ProtobufWkt::Struct root_struct2; + (*root_struct2.mutable_fields())["name"].set_string_value("root"); + (*root_struct2.mutable_fields())["id"].set_number_value(1); + + ProtobufWkt::Struct* nested1_2 = + (*root_struct2.mutable_fields())["nested"].mutable_struct_value(); + (*nested1_2->mutable_fields())["level"].set_string_value("level2"); + (*nested1_2->mutable_fields())["count"].set_number_value(2); + + ProtobufWkt::Struct* nested2_2 = (*nested1_2->mutable_fields())["deeper"].mutable_struct_value(); + (*nested2_2->mutable_fields())["level"].set_string_value("level3"); + (*nested2_2->mutable_fields())["final"].set_bool_value(true); + + ProtobufWkt::ListValue* list2 = (*nested2_2->mutable_fields())["items"].mutable_list_value(); + list2->add_values()->set_string_value("item1"); + list2->add_values()->set_string_value("item2"); + list2->add_values()->set_number_value(42); + + // Test that identical nested structures produce same hash + EXPECT_EQ(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(root_struct2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(root_struct)); + + // Test that modifying any level changes the hash + ProtobufWkt::Struct modified_struct = root_struct; + (*modified_struct.mutable_fields())["name"].set_string_value("modified"); + + EXPECT_NE(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(modified_struct)); + + // Test modifying nested level + ProtobufWkt::Struct modified_nested = root_struct; + ProtobufWkt::Struct* nested_mod = + (*modified_nested.mutable_fields())["nested"].mutable_struct_value(); + (*nested_mod->mutable_fields())["level"].set_string_value("modified_level2"); + + EXPECT_NE(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(modified_nested)); + + // Test modifying deepest level + ProtobufWkt::Struct modified_deep = root_struct; + ProtobufWkt::Struct* nested_deep = + (*modified_deep.mutable_fields())["nested"].mutable_struct_value(); + ProtobufWkt::Struct* deeper_deep = + (*nested_deep->mutable_fields())["deeper"].mutable_struct_value(); + (*deeper_deep->mutable_fields())["final"].set_bool_value(false); + + EXPECT_NE(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(modified_deep)); +} + +TEST_F(ProtobufUtilityTest, MessageUtilHashFieldTypes) { + // Test all field types supported by Protobuf + + // String fields + ProtobufWkt::StringValue str_msg; + str_msg.set_value("test string"); + EXPECT_NE(0, HashCachedMessageUtil::hash(str_msg)); + + // Integer fields + ProtobufWkt::Int32Value int32_msg; + int32_msg.set_value(-42); + EXPECT_NE(0, HashCachedMessageUtil::hash(int32_msg)); + + ProtobufWkt::UInt32Value uint32_msg; + uint32_msg.set_value(42); + EXPECT_NE(0, HashCachedMessageUtil::hash(uint32_msg)); + + ProtobufWkt::Int64Value int64_msg; + int64_msg.set_value(-1234567890123456789LL); + EXPECT_NE(0, HashCachedMessageUtil::hash(int64_msg)); + + ProtobufWkt::UInt64Value uint64_msg; + uint64_msg.set_value(1234567890123456789ULL); + EXPECT_NE(0, HashCachedMessageUtil::hash(uint64_msg)); + + // Floating point fields + ProtobufWkt::FloatValue float_msg; + float_msg.set_value(3.14159f); + EXPECT_NE(0, HashCachedMessageUtil::hash(float_msg)); + + ProtobufWkt::DoubleValue double_msg; + double_msg.set_value(2.718281828459045); + EXPECT_NE(0, HashCachedMessageUtil::hash(double_msg)); + + // Boolean fields + ProtobufWkt::BoolValue bool_msg; + bool_msg.set_value(true); + EXPECT_NE(0, HashCachedMessageUtil::hash(bool_msg)); + + // Enum fields (using well-known types) + // Note: NullValue is not a Message, so we can't hash it directly + // Instead test with a Struct containing null value + ProtobufWkt::Struct null_struct; + (*null_struct.mutable_fields())["null_field"].set_null_value(ProtobufWkt::NullValue::NULL_VALUE); + EXPECT_NE(0, HashCachedMessageUtil::hash(null_struct)); + + // Test that different types produce different hashes + std::vector hashes = { + HashCachedMessageUtil::hash(str_msg), HashCachedMessageUtil::hash(int32_msg), + HashCachedMessageUtil::hash(uint32_msg), HashCachedMessageUtil::hash(int64_msg), + HashCachedMessageUtil::hash(uint64_msg), HashCachedMessageUtil::hash(float_msg), + HashCachedMessageUtil::hash(double_msg), HashCachedMessageUtil::hash(bool_msg), + HashCachedMessageUtil::hash(null_struct)}; + + // All hashes should be different (very unlikely to have collisions) + for (size_t i = 0; i < hashes.size(); ++i) { + for (size_t j = i + 1; j < hashes.size(); ++j) { + EXPECT_NE(hashes[i], hashes[j]) << "Hash collision between types " << i << " and " << j; + } + } +} + +TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHashEdgeCases) { + // Test edge cases for recursive hashing + + // Test 1: Empty messages + ProtobufWkt::Struct empty_struct; + ProtobufWkt::StringValue empty_string; + empty_string.set_value(""); + + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_struct)); + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_string)); + EXPECT_NE(HashCachedMessageUtil::hash(empty_struct), HashCachedMessageUtil::hash(empty_string)); + + // Test 2: Messages with only default values + ProtobufWkt::Int32Value default_int; + ProtobufWkt::BoolValue default_bool; + ProtobufWkt::StringValue default_string; + + EXPECT_NE(0, HashCachedMessageUtil::hash(default_int)); + EXPECT_NE(0, HashCachedMessageUtil::hash(default_bool)); + EXPECT_NE(0, HashCachedMessageUtil::hash(default_string)); + + // Test 3: Messages with zero values + ProtobufWkt::Int32Value zero_int; + zero_int.set_value(0); + ProtobufWkt::UInt64Value zero_uint; + zero_uint.set_value(0); + ProtobufWkt::DoubleValue zero_double; + zero_double.set_value(0.0); + + EXPECT_NE(0, HashCachedMessageUtil::hash(zero_int)); + EXPECT_NE(0, HashCachedMessageUtil::hash(zero_uint)); + EXPECT_NE(0, HashCachedMessageUtil::hash(zero_double)); + + // Test 4: Messages with extreme values + ProtobufWkt::Int64Value max_int64; + max_int64.set_value(INT64_MAX); + ProtobufWkt::Int64Value min_int64; + min_int64.set_value(INT64_MIN); + ProtobufWkt::UInt64Value max_uint64; + max_uint64.set_value(UINT64_MAX); + + EXPECT_NE(0, HashCachedMessageUtil::hash(max_int64)); + EXPECT_NE(0, HashCachedMessageUtil::hash(min_int64)); + EXPECT_NE(0, HashCachedMessageUtil::hash(max_uint64)); + + // Test 5: Messages with special floating point values + ProtobufWkt::DoubleValue inf_double; + inf_double.set_value(std::numeric_limits::infinity()); + ProtobufWkt::DoubleValue neg_inf_double; + neg_inf_double.set_value(-std::numeric_limits::infinity()); + ProtobufWkt::DoubleValue nan_double; + nan_double.set_value(std::numeric_limits::quiet_NaN()); + + EXPECT_NE(0, HashCachedMessageUtil::hash(inf_double)); + EXPECT_NE(0, HashCachedMessageUtil::hash(neg_inf_double)); + EXPECT_NE(0, HashCachedMessageUtil::hash(nan_double)); + + // Test 6: Messages with very long strings + std::string very_long_string(100000, 'x'); + ProtobufWkt::StringValue long_str; + long_str.set_value(very_long_string); + + EXPECT_NE(0, HashCachedMessageUtil::hash(long_str)); + + // Test 7: Messages with binary data + std::string binary_data; + for (int i = 0; i < 256; ++i) { + binary_data.push_back(static_cast(i)); + } + ProtobufWkt::BytesValue binary_msg; + // Use Base64::encode with correct parameters + std::string encoded_data = Base64::encode(binary_data.data(), binary_data.length()); + TestUtility::loadFromJson("\"" + encoded_data + "\"", binary_msg); + + EXPECT_NE(0, HashCachedMessageUtil::hash(binary_msg)); + + // Test 8: Messages with mixed content types + ProtobufWkt::Struct mixed_struct; + (*mixed_struct.mutable_fields())["string"].set_string_value("mixed"); + (*mixed_struct.mutable_fields())["number"].set_number_value(42.5); + (*mixed_struct.mutable_fields())["boolean"].set_bool_value(true); + (*mixed_struct.mutable_fields())["null"].set_null_value(ProtobufWkt::NullValue::NULL_VALUE); + + EXPECT_NE(0, HashCachedMessageUtil::hash(mixed_struct)); + + // Test 9: Circular reference prevention (should not crash) + // This tests that the hash function can handle complex structures + ProtobufWkt::Struct complex_struct; + (*complex_struct.mutable_fields())["self"].mutable_struct_value(); + // Note: We don't create actual circular references as they would cause issues + + EXPECT_NE(0, HashCachedMessageUtil::hash(complex_struct)); +} + +TEST_F(ProtobufUtilityTest, MessageUtilHashCollisionDetection) { + // Test for potential hash collisions and hash quality + + // Test 1: Birthday paradox simulation + // Create many different messages and check for collisions + std::unordered_set hashes; + std::vector messages; + + // Generate 1000 different messages + for (int i = 0; i < 1000; ++i) { + ProtobufWkt::StringValue msg; + msg.set_value("unique_message_" + std::to_string(i) + "_" + std::to_string(i * 12345)); + messages.push_back(msg); + + uint64_t hash = HashCachedMessageUtil::hash(msg); + hashes.insert(hash); + } + + // Check collision rate (should be very low for good hash function) + double collision_rate = 1.0 - (static_cast(hashes.size()) / messages.size()); + EXPECT_LT(collision_rate, 0.001); // Expect less than 0.1% collision rate + + // Test 2: Similar input collision detection + // Test strings that differ by only one character + std::vector similar_strings = { + "hello world", "hello world!", "hello world!!", "hello world!!!", + "hello world!!!!", "hello world!!!!!", "hello world!!!!!!", "hello world!!!!!!!", + "hello world!!!!!!!!", "hello world!!!!!!!!!"}; + + std::unordered_set similar_hashes; + for (const auto& str : similar_strings) { + ProtobufWkt::StringValue msg; + msg.set_value(str); + similar_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // Similar strings should produce different hashes + EXPECT_EQ(similar_hashes.size(), similar_strings.size()); + + // Test 3: Numeric proximity collision detection + // Test numbers that are very close to each other + std::vector close_numbers = {1.0, 1.0000001, 1.0000002, 1.0000003, 1.0000004, + 1.0000005, 1.0000006, 1.0000007, 1.0000008, 1.0000009}; + + std::unordered_set numeric_hashes; + for (double num : close_numbers) { + ProtobufWkt::DoubleValue msg; + msg.set_value(num); + numeric_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // Close numbers should produce different hashes + EXPECT_EQ(numeric_hashes.size(), close_numbers.size()); + + // Test 4: Structure similarity collision detection + // Test structs with similar field names but different values + std::vector similar_structs; + + for (int i = 0; i < 10; ++i) { + ProtobufWkt::Struct msg; + (*msg.mutable_fields())["field_a"].set_string_value("value_" + std::to_string(i)); + (*msg.mutable_fields())["field_b"].set_number_value(i); + (*msg.mutable_fields())["field_c"].set_bool_value(i % 2 == 0); + similar_structs.push_back(msg); + } + + std::unordered_set struct_hashes; + for (const auto& msg : similar_structs) { + struct_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // Similar structures should produce different hashes + EXPECT_EQ(struct_hashes.size(), similar_structs.size()); + + // Test 5: Hash avalanche effect + // Small changes should produce significantly different hashes + + // Test single character changes + std::vector avalanche_tests = { + "base message for avalanche test", // Original + "base message for avalanche test!", // Add exclamation + "base message for avalanche test?", // Change to question + "base message for avalanche test.", // Change to period + "base message for avalanche testx", // Change last character + "xbase message for avalanche test", // Change first character + "base message for avalanche test ", // Add space at end + " base message for avalanche test", // Add space at beginning + "Base message for avalanche test", // Capitalize first letter + "base Message for avalanche test" // Capitalize middle word + }; + + std::unordered_set avalanche_hashes; + for (const auto& str : avalanche_tests) { + ProtobufWkt::StringValue msg; + msg.set_value(str); + avalanche_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // All avalanche tests should produce different hashes + EXPECT_EQ(avalanche_hashes.size(), avalanche_tests.size()); + + // Test 6: Hash distribution quality + // Check that hashes are well distributed across the hash space + std::vector all_hashes; + all_hashes.insert(all_hashes.end(), hashes.begin(), hashes.end()); + all_hashes.insert(all_hashes.end(), similar_hashes.begin(), similar_hashes.end()); + all_hashes.insert(all_hashes.end(), numeric_hashes.begin(), numeric_hashes.end()); + all_hashes.insert(all_hashes.end(), struct_hashes.begin(), struct_hashes.end()); + all_hashes.insert(all_hashes.end(), avalanche_hashes.begin(), avalanche_hashes.end()); + + // Calculate hash distribution statistics + if (all_hashes.size() > 1) { + uint64_t min_hash = *std::min_element(all_hashes.begin(), all_hashes.end()); + uint64_t max_hash = *std::max_element(all_hashes.begin(), all_hashes.end()); + uint64_t hash_range = max_hash - min_hash; + + // Hash range should be substantial (not all hashes clustered together) + EXPECT_GT(hash_range, UINT64_MAX / 100); // Should use at least 1% of hash space + } + + // Test 7: Deterministic hash behavior + // Same input should always produce same hash + ProtobufWkt::StringValue test_msg; + test_msg.set_value("deterministic test message"); + + uint64_t hash1 = HashCachedMessageUtil::hash(test_msg); + uint64_t hash2 = HashCachedMessageUtil::hash(test_msg); + uint64_t hash3 = HashCachedMessageUtil::hash(test_msg); + + EXPECT_EQ(hash1, hash2); + EXPECT_EQ(hash2, hash3); + EXPECT_EQ(hash1, hash3); + + // Test 8: Hash uniqueness across different types + // Different message types should produce different hashes + ProtobufWkt::StringValue str_msg; + str_msg.set_value("test"); + + ProtobufWkt::Int32Value int_msg; + int_msg.set_value(42); + + ProtobufWkt::BoolValue bool_msg; + bool_msg.set_value(true); + + uint64_t str_hash = HashCachedMessageUtil::hash(str_msg); + uint64_t int_hash = HashCachedMessageUtil::hash(int_msg); + uint64_t bool_hash = HashCachedMessageUtil::hash(bool_msg); + + // All should be different + EXPECT_NE(str_hash, int_hash); + EXPECT_NE(int_hash, bool_hash); + EXPECT_NE(str_hash, bool_hash); +} +#endif // HIGRESS + TEST_F(ProtobufUtilityTest, MessageUtilHash) { ProtobufWkt::Struct s; (*s.mutable_fields())["ab"].set_string_value("fgh"); @@ -185,8 +1010,12 @@ TEST_F(ProtobufUtilityTest, MessageUtilHash) { ProtobufWkt::Any a3 = a1; a3.set_value(Base64::decode("CgsKAmFiEgUaA2ZnaAoLCgNjZGUSBBoCaWo=")); +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) + // the message hash skip the any type parse, it cause unordered map in any to be different +#else EXPECT_EQ(MessageUtil::hash(a1), MessageUtil::hash(a2)); EXPECT_EQ(MessageUtil::hash(a2), MessageUtil::hash(a3)); +#endif EXPECT_NE(0, MessageUtil::hash(a1)); EXPECT_NE(MessageUtil::hash(s), MessageUtil::hash(a1)); } diff --git a/test/common/stream_info/stream_info_impl_test.cc b/test/common/stream_info/stream_info_impl_test.cc index 46459efd069ea..77918a2ad3393 100644 --- a/test/common/stream_info/stream_info_impl_test.cc +++ b/test/common/stream_info/stream_info_impl_test.cc @@ -39,6 +39,13 @@ class StreamInfoImplTest : public testing::Test { void assertStreamInfoSize(StreamInfoImpl stream_info) { ASSERT_TRUE(sizeof(stream_info) == 840 || sizeof(stream_info) == 856 || sizeof(stream_info) == 888 || sizeof(stream_info) == 776 || +#if defined(HIGRESS) + sizeof(stream_info) == 816 || sizeof(stream_info) == 768 || + + // add hash cache to protobuf message + // detail: bazel/protobuf_hash_cache.patch + sizeof(stream_info) == 784 || +#endif sizeof(stream_info) == 728 || sizeof(stream_info) == 744) << "If adding fields to StreamInfoImpl, please check to see if you " "need to add them to setFromForRecreateStream or setFrom! Current size " diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index 9ea317568fc00..16b2dd580ce24 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -286,10 +286,30 @@ FilterHeadersStatus TestContext::onRequestHeaders(uint32_t, bool) { logError("get route name failed"); } return FilterHeadersStatus::Continue; + } else if (test == "GetVMMemorySize") { + std::string value; + if (getValue({"plugin_vm_memory"}, &value)) { + // The value is stored as binary uint64_t, convert to string for logging + if (value.size() == sizeof(uint64_t)) { + uint64_t memory_size; + memcpy(&memory_size, value.data(), sizeof(uint64_t)); + logInfo("vm memory size is " + std::to_string(memory_size)); + } else { + logError("invalid memory size format"); + } + } else { + logError("get vm memory size failed"); + } + return FilterHeadersStatus::Continue; } else if (test == "CrashRecover") { if (!getRequestHeader("crash")->toString().empty()) { abort(); } + } else if (test == "RebuildTest") { + if (!getRequestHeader("rebuild")->toString().empty()) { + logInfo("Setting rebuild flag"); + setFilterState("wasm_rebuild", "true"); + } } else if (test == "DisableClearRouteCache") { setFilterState("clear_route_cache", "off"); logDebug(std::string("onRequestHeaders ") + std::to_string(id()) + std::string(" ") + test); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index f05b1716a8801..a985af3113d36 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -1850,6 +1850,21 @@ TEST_P(WasmHttpFilterTest, GetRouteName) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); filter().onDestroy(); } +TEST_P(WasmHttpFilterTest, GetVMMemorySize) { + auto runtime = std::get<0>(GetParam()); + if (runtime == "null") { + return; + } + if (std::get<1>(GetParam()) != "cpp") { + return; + } + setupTest("", "GetVMMemorySize"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::info, testing::StartsWith("vm memory size is "))); + Http::TestRequestHeaderMapImpl request_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + filter().onDestroy(); +} TEST_P(WasmHttpFilterTest, RecoverFromCrash) { auto runtime = std::get<0>(GetParam()); if (runtime == "null") { @@ -1949,6 +1964,71 @@ TEST_P(WasmHttpFilterTest, RecoverFromCrash) { filter().onDestroy(); } + +TEST_P(WasmHttpFilterTest, ProactiveRebuild) { + auto runtime = std::get<0>(GetParam()); + if (runtime == "null") { + return; + } + if (std::get<1>(GetParam()) != "cpp") { + return; + } + setupTest("", "RebuildTest"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + auto& rebuild_total = scope_->counterFromString("wasm.envoy.wasm.runtime." + runtime + + ".plugin.plugin_name.rebuild_total"); + auto& recover_total = scope_->counterFromString("wasm.envoy.wasm.runtime." + runtime + + ".plugin.plugin_name.recover_total"); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_EQ(0U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // First request: normal processing + Http::TestRequestHeaderMapImpl request_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(0U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Second request: set rebuild state by sending rebuild header + request_headers = Http::TestRequestHeaderMapImpl{{"rebuild", "true"}}; + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("Setting rebuild flag"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(0U, rebuild_total.value()); // No rebuild yet, just set the flag + EXPECT_EQ(0U, recover_total.value()); + + // Now trigger the actual rebuild using doRebuild() + doRebuild(); + EXPECT_EQ(1U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Verify new instance is working + request_headers = {}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(1U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Set rebuild state again + request_headers = Http::TestRequestHeaderMapImpl{{"rebuild", "true"}}; + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("Setting rebuild flag"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(1U, rebuild_total.value()); // Still 1, just set the flag again + EXPECT_EQ(0U, recover_total.value()); + + // Trigger second rebuild using doRebuild() + doRebuild(); + EXPECT_EQ(2U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Verify new instance is still working after second rebuild + request_headers = {}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(2U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + filter().onDestroy(); +} #endif // Test metadata access including CEL expressions. diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 1cf6d2d1b9482..7fb66a3dc143b 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -150,13 +150,23 @@ template class WasmHttpFilterTestBase : public W #if defined(HIGRESS) template void doRecover() { std::shared_ptr new_handle; - if (WasmTestBase::plugin_handle_->doRecover(new_handle)) { + if (WasmTestBase::plugin_handle_->rebuild(new_handle)) { WasmTestBase::plugin_handle_ = std::static_pointer_cast(new_handle); WasmTestBase::wasm_ = WasmTestBase::plugin_handle_->wasmHandle(); WasmTestBase::wasm_->wasm()->lifecycleStats().recover_total_.inc(); setupFilterBase(); } } + + template void doRebuild() { + std::shared_ptr new_handle; + if (WasmTestBase::plugin_handle_->rebuild(new_handle)) { + WasmTestBase::plugin_handle_ = std::static_pointer_cast(new_handle); + WasmTestBase::wasm_ = WasmTestBase::plugin_handle_->wasmHandle(); + WasmTestBase::wasm_->wasm()->lifecycleStats().rebuild_total_.inc(); + setupFilterBase(); + } + } #endif std::unique_ptr context_; From c6084ed7c5f48692191be1106d0362f99440aace Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 19:03:50 +0800 Subject: [PATCH 270/274] fix typo Change-Id: If54bbcd6a39c6ef13243bd2b20c3b2c848f95ae6 --- source/common/protobuf/utility.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 18402565f371d..7f43a1f3c6f96 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -583,7 +583,9 @@ class MessageUtil { #if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) class HashCachedMessageUtil : public MessageUtil { public: - bool operator()(const Protobuf::Message& message) const { return message.GetCachedHashValue(); } + std::size_t operator()(const Protobuf::Message& message) const { + return message.GetCachedHashValue(); + } bool operator()(const Protobuf::Message& lhs, const Protobuf::Message& rhs) const { return lhs.GetCachedHashValue() == rhs.GetCachedHashValue(); From 2f61478258bd773c2875097a848189559d2a688c Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 19:47:48 +0800 Subject: [PATCH 271/274] update proxy-wasm-cpp-host version Change-Id: Ie2f2feb07941843f202825061f84cb1bf546f098 --- bazel/repository_locations.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 78cf6fab07c1c..bfcbeb1eef4ae 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1362,8 +1362,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/higress-group/proxy-wasm-cpp-host", - version = "913c1b02cbbdba00682ac29787b0986d85890a54", - sha256 = "af094042c91663eabc2c01cd33903c154d318b1be516f8f2428a252ac74fc9ad", + version = "04ef279d83a39d507d882bb35e3199abcecfe5af", + sha256 = "2573ecab4f3c12c10a61f2e34a69a3c4d6f20525c9ae07bcaac72b0a9921df78", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], From f1e3f3bee6913ce290fbd6466e100fb69996567d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 13 Nov 2025 15:16:34 +0800 Subject: [PATCH 272/274] fix: resolve use-after-free in RawClientImpl config access (#15) --- source/common/redis/async_client_impl.cc | 2 +- .../filters/network/common/redis/raw_client.h | 2 +- .../network/common/redis/raw_client_impl.cc | 20 +++++++++---------- .../network/common/redis/raw_client_impl.h | 9 +++++---- .../network/common/redis/client_impl_test.cc | 8 ++++---- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/source/common/redis/async_client_impl.cc b/source/common/redis/async_client_impl.cc index 334fcc8443e56..39dad5a041ca2 100644 --- a/source/common/redis/async_client_impl.cc +++ b/source/common/redis/async_client_impl.cc @@ -275,7 +275,7 @@ AsyncClientImpl::threadLocalActiveClient(Upstream::HostConstSharedPtr host) { client = std::make_unique(*this); client->host_ = host; client->redis_client_ = - client_factory_.create(host, dispatcher_, *config_, redis_command_stats_, *(stats_scope_), + client_factory_.create(host, dispatcher_, config_, redis_command_stats_, *(stats_scope_), auth_username_, auth_password_, params_); client->redis_client_->addConnectionCallbacks(*client); } diff --git a/source/extensions/filters/network/common/redis/raw_client.h b/source/extensions/filters/network/common/redis/raw_client.h index ae1f0a10ba76b..acf3ab0b15f0a 100644 --- a/source/extensions/filters/network/common/redis/raw_client.h +++ b/source/extensions/filters/network/common/redis/raw_client.h @@ -74,7 +74,7 @@ class RawClientFactory { virtual ~RawClientFactory() = default; virtual RawClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, - const Config& config, + ConfigSharedPtr config, const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope, const std::string& auth_username, const std::string& auth_password, diff --git a/source/extensions/filters/network/common/redis/raw_client_impl.cc b/source/extensions/filters/network/common/redis/raw_client_impl.cc index 9ed11b352efba..7f7c730f5b41c 100644 --- a/source/extensions/filters/network/common/redis/raw_client_impl.cc +++ b/source/extensions/filters/network/common/redis/raw_client_impl.cc @@ -16,7 +16,7 @@ const std::string& RedisDBParamKey = "db"; RawClientPtr RawClientImpl::create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, - const Config& config, + ConfigSharedPtr config, const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope) { auto client = std::make_unique( @@ -31,7 +31,7 @@ RawClientPtr RawClientImpl::create(Upstream::HostConstSharedPtr host, Event::Dis RawClientImpl::RawClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, - const Config& config, + ConfigSharedPtr config, const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope) : host_(host), encoder_(std::move(encoder)), decoder_(decoder_factory.create(*this)), @@ -77,10 +77,10 @@ PoolRequest* RawClientImpl::makeRawRequest(std::string_view request, encoder_->encode(request, encoder_buffer_); // If buffer is full, flush. If the buffer was empty before the request, start the timer. - if (encoder_buffer_.length() >= config_.maxBufferSizeBeforeFlush()) { + if (encoder_buffer_.length() >= config_->maxBufferSizeBeforeFlush()) { flushBufferAndResetTimer(); } else if (empty_buffer) { - flush_timer_->enableTimer(std::chrono::milliseconds(config_.bufferFlushTimeoutInMs())); + flush_timer_->enableTimer(std::chrono::milliseconds(config_->bufferFlushTimeoutInMs())); } // Only boost the op timeout if: @@ -90,7 +90,7 @@ PoolRequest* RawClientImpl::makeRawRequest(std::string_view request, // - This is the first request on the pipeline. Otherwise the timeout would effectively start on // the last operation. if (connected_ && pending_requests_.size() == 1) { - connect_or_op_timer_->enableTimer(config_.opTimeout()); + connect_or_op_timer_->enableTimer(config_->opTimeout()); } return &pending_requests_.back(); @@ -125,7 +125,7 @@ void RawClientImpl::onData(Buffer::Instance& data) { } void RawClientImpl::putOutlierEvent(Upstream::Outlier::Result result) { - if (!config_.disableOutlierEvents()) { + if (!config_->disableOutlierEvents()) { host_->outlierDetector().putResult(result); } } @@ -157,7 +157,7 @@ void RawClientImpl::onEvent(Network::ConnectionEvent event) { } else if (event == Network::ConnectionEvent::Connected) { connected_ = true; ASSERT(!pending_requests_.empty()); - connect_or_op_timer_->enableTimer(config_.opTimeout()); + connect_or_op_timer_->enableTimer(config_->opTimeout()); } if (event == Network::ConnectionEvent::RemoteClose && !connected_) { @@ -193,7 +193,7 @@ void RawClientImpl::onRawResponse(std::string&& response) { if (pending_requests_.empty()) { connect_or_op_timer_->disableTimer(); } else { - connect_or_op_timer_->enableTimer(config_.opTimeout()); + connect_or_op_timer_->enableTimer(config_->opTimeout()); } putOutlierEvent(Upstream::Outlier::Result::ExtOriginRequestSuccess); @@ -239,7 +239,7 @@ void RawClientImpl::initialize(const std::string& auth_username, const std::stri makeRawRequest(select_request, null_raw_client_callbacks); } - if (config_.readPolicy() != Common::Redis::Client::ReadPolicy::Primary) { + if (config_->readPolicy() != Common::Redis::Client::ReadPolicy::Primary) { makeRawRequest(Utility::makeRawReadOnlyRequest(), null_raw_client_callbacks); } } @@ -247,7 +247,7 @@ void RawClientImpl::initialize(const std::string& auth_username, const std::stri RawClientFactoryImpl RawClientFactoryImpl::instance_; RawClientPtr RawClientFactoryImpl::create(Upstream::HostConstSharedPtr host, - Event::Dispatcher& dispatcher, const Config& config, + Event::Dispatcher& dispatcher, ConfigSharedPtr config, const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope, const std::string& auth_username, const std::string& auth_password, diff --git a/source/extensions/filters/network/common/redis/raw_client_impl.h b/source/extensions/filters/network/common/redis/raw_client_impl.h index 8aa7da6d807a1..f042972571c7b 100644 --- a/source/extensions/filters/network/common/redis/raw_client_impl.h +++ b/source/extensions/filters/network/common/redis/raw_client_impl.h @@ -17,12 +17,12 @@ class RawClientImpl : public RawClient, public: static RawClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, - const Config& config, + ConfigSharedPtr config, const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope); RawClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, - RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, const Config& config, + RawEncoderPtr&& encoder, RawDecoderFactory& decoder_factory, ConfigSharedPtr config, const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope); ~RawClientImpl() override; @@ -84,7 +84,7 @@ class RawClientImpl : public RawClient, RawEncoderPtr encoder_; Buffer::OwnedImpl encoder_buffer_; DecoderPtr decoder_; - const Config& config_; + ConfigSharedPtr config_; std::list pending_requests_; Event::TimerPtr connect_or_op_timer_; bool connected_{}; @@ -97,7 +97,8 @@ class RawClientImpl : public RawClient, class RawClientFactoryImpl : public RawClientFactory { public: RawClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, - const Config& config, const RedisCommandStatsSharedPtr& redis_command_stats, + ConfigSharedPtr config, + const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope, const std::string& auth_username, const std::string& auth_password, const std::map& params) override; diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index 3fe9f05962777..6391164e2317f 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -1262,7 +1262,7 @@ class RedisRawClientImplTest : public testing::Test, } void setup() { - config_ = std::make_unique(); + config_ = std::make_shared(); finishSetup(); } @@ -1291,7 +1291,7 @@ class RedisRawClientImplTest : public testing::Test, Common::Redis::RedisCommandStats::createRedisCommandStats(stats_.symbolTable()); client_ = RawClientImpl::create(host_, dispatcher_, Common::Redis::RawEncoderPtr{encoder_}, - *this, *config_, redis_command_stats_, *stats_.rootScope()); + *this, config_, redis_command_stats_, *stats_.rootScope()); EXPECT_EQ(1UL, host_->cluster_.traffic_stats_->upstream_cx_total_.value()); EXPECT_EQ(1UL, host_->stats_.cx_total_.value()); EXPECT_EQ(false, client_->active()); @@ -1346,7 +1346,7 @@ class RedisRawClientImplTest : public testing::Test, Common::Redis::RawDecoderCallbacks* callbacks_{}; NiceMock* upstream_connection_{}; Network::ReadFilterSharedPtr upstream_read_filter_; - std::unique_ptr config_; + ConfigSharedPtr config_; RawClientPtr client_; NiceMock stats_; Stats::ScopeSharedPtr stats_scope_; @@ -1416,7 +1416,7 @@ TEST(RedisRawClientFactoryImplTest, Basic) { EXPECT_CALL(*host, createConnection_(_, _)).WillOnce(Return(conn_info)); NiceMock dispatcher; - ConfigImpl config(createConnPoolSettings()); + ConfigSharedPtr config = std::make_shared(createConnPoolSettings()); Stats::IsolatedStoreImpl stats_; auto redis_command_stats = Common::Redis::RedisCommandStats::createRedisCommandStats(stats_.symbolTable()); From 3fe314c69802c18160065c30028c9f3f7a216e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 13 Nov 2025 19:22:44 +0800 Subject: [PATCH 273/274] feat: support parsing buffer config from params in AsyncClientConfig (#16) --- envoy/redis/async_client.h | 29 ++++- .../wasm/test_data/test_redis_call_cpp.cc | 9 +- .../filters/http/wasm/wasm_filter_test.cc | 110 ++++++++++++++++++ 3 files changed, 142 insertions(+), 6 deletions(-) diff --git a/envoy/redis/async_client.h b/envoy/redis/async_client.h index 98e8d88ebc00b..a565d2610b88e 100644 --- a/envoy/redis/async_client.h +++ b/envoy/redis/async_client.h @@ -1,10 +1,11 @@ #pragma once #include +#include +#include #include #include #include -#include namespace Envoy { @@ -20,17 +21,39 @@ struct AsyncClientConfig { AsyncClientConfig(std::string&& username, std::string&& password, int op_timeout_milliseconds, std::map&& params) : auth_username_(std::move(username)), auth_password_(std::move(password)), - op_timeout_(op_timeout_milliseconds), buffer_flush_timeout_(3), params_(std::move(params)) { + op_timeout_(op_timeout_milliseconds), + max_buffer_size_before_flush_(parseUint32FromParams(params, "max_buffer_size_before_flush", 1024)), + buffer_flush_timeout_(parseUint32FromParams(params, "buffer_flush_timeout", 3)), + params_(std::move(params)) { } + const std::string auth_username_; const std::string auth_password_; const std::chrono::milliseconds op_timeout_; - const uint32_t max_buffer_size_before_flush_{1024}; + const uint32_t max_buffer_size_before_flush_; const std::chrono::milliseconds buffer_flush_timeout_; const uint32_t max_upstream_unknown_connections_{100}; const bool enable_command_stats_{false}; const std::map params_; + +private: + // Helper function to parse uint32 from params map with default value + static uint32_t parseUint32FromParams(const std::map& params, + const std::string& key, uint32_t default_value) { + auto it = params.find(key); + if (it != params.end()) { + try { + unsigned long value = std::stoul(it->second); + if (value <= std::numeric_limits::max()) { + return static_cast(value); + } + } catch (const std::exception&) { + // If parsing fails, return default value + } + } + return default_value; + } }; /** diff --git a/test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc index 63f7d406fcb6a..c2c0c132f2292 100644 --- a/test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_redis_call_cpp.cc @@ -30,7 +30,8 @@ static RegisterContextFactory register_RedisCallContext(CONTEXT_FACTORY(RedisCal "redis_call"); bool RedisCallRootContext::onConfigure(size_t) { - redisInit("cluster?db=1", "admin", "123456", 1000); + // Test with buffer configuration parameters + redisInit("cluster?db=1&buffer_flush_timeout=1&max_buffer_size_before_flush=512", "admin", "123456", 1000); return true; } @@ -52,14 +53,16 @@ FilterHeadersStatus RedisCallContext::onRequestHeaders(uint32_t, bool) { auto query = "*3\r\n$3\r\nset\r\n$2\r\nid\r\n$1\r\n1\r\n"; auto path = getRequestHeader(":path"); if (path->view() == "/bad") { - if (root()->redisCall("cluster?db=1", query, callback) != WasmResult::Ok) { + // Test with different buffer params on runtime call + if (root()->redisCall("cluster?db=1&buffer_flush_timeout=2", query, callback) != WasmResult::Ok) { logInfo("redis_call rejected"); } } else { if (root()->redisCall("bogus cluster", query, callback) == WasmResult::Ok) { logError("bogus cluster found error"); } - root()->redisCall("cluster?db=1", query, callback); + // Test with buffer params in query string + root()->redisCall("cluster?db=1&buffer_flush_timeout=1&max_buffer_size_before_flush=512", query, callback); logInfo("onRequestHeaders"); } diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index a985af3113d36..2404c980ba1fb 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -1,4 +1,5 @@ #include "envoy/grpc/async_client.h" +#include "envoy/redis/async_client.h" #include "source/common/http/message_impl.h" #include "source/extensions/filters/http/wasm/wasm_filter.h" @@ -795,6 +796,115 @@ TEST_P(WasmHttpFilterTest, RedisCall) { EXPECT_NE(callbacks, nullptr); } +#if defined(HIGRESS) +// Unit test for AsyncClientConfig parameter parsing +TEST(RedisAsyncClientConfigTest, ParseBufferParamsFromQueryString) { + // Test with all parameters specified + { + std::map params = { + {"db", "1"}, + {"buffer_flush_timeout", "5"}, + {"max_buffer_size_before_flush", "2048"} + }; + Redis::AsyncClientConfig config("testuser", "testpass", 1000, std::move(params)); + + EXPECT_EQ(config.auth_username_, "testuser"); + EXPECT_EQ(config.auth_password_, "testpass"); + EXPECT_EQ(config.op_timeout_.count(), 1000); + EXPECT_EQ(config.buffer_flush_timeout_.count(), 5); + EXPECT_EQ(config.max_buffer_size_before_flush_, 2048); + EXPECT_EQ(config.params_.at("db"), "1"); + } + + // Test with only buffer_flush_timeout specified (max_buffer uses default) + { + std::map params = { + {"buffer_flush_timeout", "1"} + }; + Redis::AsyncClientConfig config("admin", "123456", 2000, std::move(params)); + + EXPECT_EQ(config.buffer_flush_timeout_.count(), 1); + EXPECT_EQ(config.max_buffer_size_before_flush_, 1024); // default value + } + + // Test with only max_buffer_size_before_flush specified (timeout uses default) + { + std::map params = { + {"max_buffer_size_before_flush", "512"} + }; + Redis::AsyncClientConfig config("admin", "123456", 2000, std::move(params)); + + EXPECT_EQ(config.buffer_flush_timeout_.count(), 3); // default value + EXPECT_EQ(config.max_buffer_size_before_flush_, 512); + } + + // Test with no buffer params (both use defaults) + { + std::map params = { + {"db", "0"} + }; + Redis::AsyncClientConfig config("user", "pass", 500, std::move(params)); + + EXPECT_EQ(config.buffer_flush_timeout_.count(), 3); // default 3ms + EXPECT_EQ(config.max_buffer_size_before_flush_, 1024); // default 1024 bytes + } + + // Test with invalid buffer_flush_timeout (should use default) + { + std::map params = { + {"buffer_flush_timeout", "invalid_number"} + }; + Redis::AsyncClientConfig config("user", "pass", 500, std::move(params)); + + EXPECT_EQ(config.buffer_flush_timeout_.count(), 3); // default due to parse error + } + + // Test with invalid max_buffer_size_before_flush (should use default) + { + std::map params = { + {"max_buffer_size_before_flush", "not_a_number"} + }; + Redis::AsyncClientConfig config("user", "pass", 500, std::move(params)); + + EXPECT_EQ(config.max_buffer_size_before_flush_, 1024); // default due to parse error + } + + // Test with zero values (edge case - disable buffering) + { + std::map params = { + {"buffer_flush_timeout", "0"}, + {"max_buffer_size_before_flush", "0"} + }; + Redis::AsyncClientConfig config("user", "pass", 500, std::move(params)); + + EXPECT_EQ(config.buffer_flush_timeout_.count(), 0); + EXPECT_EQ(config.max_buffer_size_before_flush_, 0); + } + + // Test with very large values (within uint32 range) + { + std::map params = { + {"buffer_flush_timeout", "10000"}, + {"max_buffer_size_before_flush", "1048576"} // 1MB + }; + Redis::AsyncClientConfig config("user", "pass", 500, std::move(params)); + + EXPECT_EQ(config.buffer_flush_timeout_.count(), 10000); + EXPECT_EQ(config.max_buffer_size_before_flush_, 1048576); + } + + // Test with value exceeding uint32 max (should use default) + { + std::map params = { + {"max_buffer_size_before_flush", "99999999999999"} // exceeds uint32::max + }; + Redis::AsyncClientConfig config("user", "pass", 500, std::move(params)); + + EXPECT_EQ(config.max_buffer_size_before_flush_, 1024); // default due to overflow + } +} +#endif + TEST_P(WasmHttpFilterTest, DisableClearRouteCache) { if (std::get<1>(GetParam()) == "rust") { // This feature is not supported in rust test code From 2b9a4e811d8ae174294d3f0ee8f5a6990d09fa21 Mon Sep 17 00:00:00 2001 From: johnlanni Date: Sat, 31 Jan 2026 18:58:32 +0800 Subject: [PATCH 274/274] fix: prevent nil pointer dereference in envoyGoFilterOnHttpData Replace getState() with getOrCreateState() to handle cases where OnHttpData is called before OnHttpHeader, preventing SIGSEGV panic when s.req is not yet initialized. Fixes: panic: runtime error: invalid memory address or nil pointer dereference --- contrib/golang/filters/http/source/go/pkg/http/shim.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/golang/filters/http/source/go/pkg/http/shim.go b/contrib/golang/filters/http/source/go/pkg/http/shim.go index 4d97a09255067..f11d8c8fa7ad3 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/shim.go +++ b/contrib/golang/filters/http/source/go/pkg/http/shim.go @@ -236,7 +236,7 @@ func envoyGoFilterOnHttpHeader(s *C.processState, endStream, headerNum, headerBy //export envoyGoFilterOnHttpData func envoyGoFilterOnHttpData(s *C.processState, endStream, buffer, length uint64) uint64 { - state := getState(s) + state := getOrCreateState(s) req := state.request if req.pInfo.paniced {