diff --git a/.azure-pipelines/cleanup.sh b/.azure-pipelines/cleanup.sh index 5611a7b212fc..3714f24cac1e 100755 --- a/.azure-pipelines/cleanup.sh +++ b/.azure-pipelines/cleanup.sh @@ -3,7 +3,7 @@ set -e # Temporary script to remove tools from Azure pipelines agent to create more disk space room. -sudo apt-get update -y +sudo apt-get update -y || true sudo apt-get purge -y --no-upgrade 'ghc-*' 'zulu-*-azure-jdk' 'libllvm*' 'mysql-*' 'dotnet-*' 'libgl1' \ 'adoptopenjdk-*' 'azure-cli' 'google-chrome-stable' 'firefox' 'hhvm' diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 8e26b1f61ff2..5afb77b97b20 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -41,6 +41,7 @@ stages: - job: docs dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel. + condition: ne(variables['PostSubmit'], true) pool: vmImage: "ubuntu-18.04" steps: @@ -59,34 +60,14 @@ stages: GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) displayName: "Generate docs" - - script: ci/run_envoy_docker.sh 'ci/upload_gcs_artifact.sh /source/generated/docs docs' + - script: | + ci/run_envoy_docker.sh 'ci/upload_gcs_artifact.sh /source/generated/docs docs' displayName: "Upload Docs to GCS" env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) GCS_ARTIFACT_BUCKET: $(GcsArtifactBucket) - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: "$(Build.SourcesDirectory)/generated/docs" - artifactName: docs - - - task: InstallSSHKey@0 - inputs: - hostName: "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" - sshPublicKey: "$(DocsPublicKey)" - sshPassphrase: "$(SshDeployKeyPassphrase)" - sshKeySecureFile: "$(DocsPrivateKey)" - condition: and(succeeded(), eq(variables['PostSubmit'], true), ne(variables['NoSync'], true)) - - - script: docs/publish.sh - displayName: "Publish to GitHub" - workingDirectory: $(Build.SourcesDirectory) - env: - AZP_BRANCH: $(Build.SourceBranch) - AZP_SHA1: $(Build.SourceVersion) - condition: and(succeeded(), eq(variables['PostSubmit'], true), ne(variables['NoSync'], true)) - - job: dependencies dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel. pool: @@ -305,6 +286,43 @@ stages: artifactName: docker condition: always() + - stage: docs + dependsOn: ["docker"] + condition: and(succeeded(), eq(variables['PostSubmit'], true), ne(variables['NoSync'], true)) + jobs: + - job: publish + pool: + vmImage: "ubuntu-18.04" + steps: + - task: Cache@2 + inputs: + key: "docs | ./WORKSPACE | **/*.bzl" + path: $(Build.StagingDirectory)/repository_cache + continueOnError: true + + - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs' + workingDirectory: $(Build.SourcesDirectory) + env: + ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) + BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com + BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) + displayName: "Generate docs" + + - task: InstallSSHKey@0 + inputs: + hostName: "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" + sshPublicKey: "$(DocsPublicKey)" + sshPassphrase: "$(SshDeployKeyPassphrase)" + sshKeySecureFile: "$(DocsPrivateKey)" + + - script: docs/publish.sh + displayName: "Publish to GitHub" + workingDirectory: $(Build.SourcesDirectory) + env: + AZP_BRANCH: $(Build.SourceBranch) + AZP_SHA1: $(Build.SourceVersion) + - stage: verify dependsOn: ["docker"] jobs: diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index dcef9acbef46..1d034283e680 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -10,6 +10,7 @@ tasks: test_flags: - "--config=remote-clang-libc++" - "--config=remote-ci" + - "--define=wasm=disabled" - "--jobs=75" coverage: name: "Coverage" diff --git a/.bazelrc b/.bazelrc index 42c3ab4a0a11..3f2fab533837 100644 --- a/.bazelrc +++ b/.bazelrc @@ -246,7 +246,7 @@ build:remote-clang-cl --config=rbe-toolchain-clang-cl # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/master/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:b480535e8423b5fd7c102fd30c92f4785519e33a +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:19a268cfe3d12625380e7c61d2467c8779b58b56 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 21f934c44944..3f0cbcc33911 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:b480535e8423b5fd7c102fd30c92f4785519e33a +FROM gcr.io/envoy-ci/envoy-build:19a268cfe3d12625380e7c61d2467c8779b58b56 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 97c37be6a676..b6ee2969559f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,6 +12,9 @@ // Uncomment next line if you have devcontainer.env // "--env-file=.devcontainer/devcontainer.env" ], + "containerEnv": { + "ENVOY_SRCDIR": "${containerWorkspaceFolder}", + }, "settings": { "terminal.integrated.shell.linux": "/bin/bash", "bazel.buildifierFixOnFormat": true, diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index d6110bbddca2..ba5fe0bc64de 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -24,7 +24,9 @@ jobs: - name: Get build targets run: | . .github/workflows/get_build_targets.sh - echo ::set-env name=BUILD_TARGETS::$(echo $BUILD_TARGETS_LOCAL) + echo 'BUILD_TARGETS<> $GITHUB_ENV + echo $BUILD_TARGETS_LOCAL >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 @@ -57,7 +59,7 @@ jobs: - name: Clean Artifacts run: | git clean -xdf - + - name: Perform CodeQL Analysis if: env.BUILD_TARGETS != '' uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/get_build_targets.sh b/.github/workflows/get_build_targets.sh index c8d63b8dad8c..25f67b74af50 100755 --- a/.github/workflows/get_build_targets.sh +++ b/.github/workflows/get_build_targets.sh @@ -18,8 +18,8 @@ function get_targets() { # This chain of commands from left to right are: # 1. Excluding the redundant .cc/.h targets that bazel query emits. # 2. Storing only the unique output. - # 3. Limiting to the first 10 targets. - done | grep -v '\.cc\|\.h' | sort -u | head -n 10 + # 3. Limiting to the first 3 targets. + done | grep -v '\.cc\|\.h' | sort -u | head -n 3 } # Fetching the upstream HEAD to compare with and stored in FETCH_HEAD. diff --git a/CODEOWNERS b/CODEOWNERS index 66d902367b36..c72b2900ec38 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,7 +37,7 @@ extensions/filters/common/original_src @snowp @klarose # alts transport socket extension /*/extensions/transport_sockets/alts @htuch @yangminzhu # tls transport socket extension -/*/extensions/transport_sockets/tls @PiotrSikora @lizan @asraa +/*/extensions/transport_sockets/tls @PiotrSikora @lizan @asraa @ggreenway # proxy protocol socket extension /*/extensions/transport_sockets/proxy_protocol @alyssawilk @wez470 # common transport socket @@ -50,12 +50,14 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/tracers/datadog @cgilmour @palazzem @mattklein123 # tracers.xray extension /*/extensions/tracers/xray @marcomagdy @lavignes @mattklein123 +# tracers.skywalking extension +/*/extensions/tracers/skywalking @wbpcode @dio @lizan # mysql_proxy extension /*/extensions/filters/network/mysql_proxy @rshriram @venilnoronha @mattklein123 # postgres_proxy extension /*/extensions/filters/network/postgres_proxy @fabriziomello @cpakulski @dio # quic extension -/*/extensions/quic_listeners/ @alyssawilk @danzh2010 @mattklein123 @mpwarres @wu-bin +/*/extensions/quic_listeners/ @alyssawilk @danzh2010 @mattklein123 @mpwarres @wu-bin @ggreenway # zookeeper_proxy extension /*/extensions/filters/network/zookeeper_proxy @rgs1 @snowp # redis cluster extension @@ -90,6 +92,8 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/filters/network/wasm @PiotrSikora @lizan # webassembly common extension /*/extensions/common/wasm @PiotrSikora @lizan +# webassembly runtimes +/*/extensions/wasm_runtime/ @PiotrSikora @lizan # common matcher /*/extensions/common/matcher @mattklein123 @yangminzhu # common crypto extension @@ -125,7 +129,7 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/retry/host @snowp @alyssawilk /*/extensions/filters/network/http_connection_manager @alyssawilk @mattklein123 /*/extensions/filters/network/ext_authz @gsagula @dio -/*/extensions/filters/network/tcp_proxy @alyssawilk @zuercher +/*/extensions/filters/network/tcp_proxy @alyssawilk @zuercher @ggreenway /*/extensions/filters/network/echo @htuch @alyssawilk /*/extensions/filters/udp/dns_filter @abaptiste @mattklein123 /*/extensions/filters/network/direct_response @kyessenov @zuercher @@ -144,9 +148,7 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/watchdog/profile_action @kbaichoo @antoniovicente # Core upstream code extensions/upstreams/http @alyssawilk @snowp @mattklein123 -extensions/upstreams/http/http @alyssawilk @snowp @mattklein123 -extensions/upstreams/http/tcp @alyssawilk @mattklein123 -extensions/upstreams/http/default @alyssawilk @snowp @mattklein123 +extensions/upstreams/tcp @alyssawilk @ggreenway @mattklein123 # OAuth2 extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp # HTTP Local Rate Limit diff --git a/api/BUILD b/api/BUILD index 345732128a0d..3df8b906b006 100644 --- a/api/BUILD +++ b/api/BUILD @@ -247,6 +247,7 @@ proto_library( "//envoy/extensions/upstreams/http/generic/v3:pkg", "//envoy/extensions/upstreams/http/http/v3:pkg", "//envoy/extensions/upstreams/http/tcp/v3:pkg", + "//envoy/extensions/upstreams/tcp/generic/v3:pkg", "//envoy/extensions/wasm/v3:pkg", "//envoy/extensions/watchdog/profile_action/v3alpha:pkg", "//envoy/service/accesslog/v3:pkg", diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index a12a0ea98b3a..983f15967b28 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -40,6 +40,10 @@ def api_dependencies(): name = "com_github_openzipkin_zipkinapi", build_file_content = ZIPKINAPI_BUILD_CONTENT, ) + external_http_archive( + name = "com_github_apache_skywalking_data_collect_protocol", + build_file_content = SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT, + ) PROMETHEUSMETRICS_BUILD_CONTENT = """ load("@envoy_api//bazel:api_build_system.bzl", "api_cc_py_proto_library") @@ -101,3 +105,30 @@ go_proto_library( visibility = ["//visibility:public"], ) """ + +SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT = """ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +proto_library( + name = "protocol", + srcs = [ + "common/Common.proto", + "language-agent/Tracing.proto", + ], + visibility = ["//visibility:public"], +) + +cc_proto_library( + name = "protocol_cc_proto", + deps = [":protocol"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "protocol_go_proto", + proto = ":protocol", + visibility = ["//visibility:public"], +) +""" diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index e46f7d77f8e5..d72069046b85 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -88,4 +88,15 @@ REPOSITORY_LOCATIONS_SPEC = dict( release_date = "2020-08-17", use_category = ["api"], ), + com_github_apache_skywalking_data_collect_protocol = dict( + project_name = "SkyWalking API", + project_desc = "SkyWalking's language independent model and gRPC API Definitions", + project_url = "https://github.com/apache/skywalking-data-collect-protocol", + version = "8.1.0", + sha256 = "ebea8a6968722524d1bcc4426fb6a29907ddc2902aac7de1559012d3eee90cf9", + strip_prefix = "skywalking-data-collect-protocol-{version}", + urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v{version}.tar.gz"], + release_date = "2020-07-29", + use_category = ["api"], + ), ) diff --git a/api/envoy/config/common/matcher/v3/BUILD b/api/envoy/config/common/matcher/v3/BUILD index 482d7fe987f2..2f90ace882d9 100644 --- a/api/envoy/config/common/matcher/v3/BUILD +++ b/api/envoy/config/common/matcher/v3/BUILD @@ -6,7 +6,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/core/v3:pkg", "//envoy/config/route/v3:pkg", + "//envoy/type/matcher/v3:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/api/envoy/config/common/matcher/v3/matcher.proto b/api/envoy/config/common/matcher/v3/matcher.proto index 119740547aaf..8a1cb4839c4a 100644 --- a/api/envoy/config/common/matcher/v3/matcher.proto +++ b/api/envoy/config/common/matcher/v3/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v3; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/route/v3/route_components.proto"; +import "envoy/type/matcher/v3/value.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; @@ -16,6 +18,129 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] +message Matcher { + // What to do if a match is successful. + message OnMatch { + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v3.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + // Predicate to determine if a match is successful. + message Predicate { + // Predicate for a single input field. + message SinglePredicate { + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v3.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] diff --git a/api/envoy/config/common/matcher/v4alpha/BUILD b/api/envoy/config/common/matcher/v4alpha/BUILD index 42a738be2e89..8c0f8a2e08d8 100644 --- a/api/envoy/config/common/matcher/v4alpha/BUILD +++ b/api/envoy/config/common/matcher/v4alpha/BUILD @@ -7,7 +7,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ "//envoy/config/common/matcher/v3:pkg", + "//envoy/config/core/v4alpha:pkg", "//envoy/config/route/v4alpha:pkg", + "//envoy/type/matcher/v4alpha:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/api/envoy/config/common/matcher/v4alpha/matcher.proto b/api/envoy/config/common/matcher/v4alpha/matcher.proto index 3be0d2aea3a8..15859474e6e1 100644 --- a/api/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/api/envoy/config/common/matcher/v4alpha/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v4alpha; +import "envoy/config/core/v4alpha/extension.proto"; import "envoy/config/route/v4alpha/route_components.proto"; +import "envoy/type/matcher/v4alpha/value.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -15,6 +17,156 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] +message Matcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher"; + + // What to do if a match is successful. + message OnMatch { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.OnMatch"; + + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v4alpha.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList"; + + // Predicate to determine if a match is successful. + message Predicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate"; + + // Predicate for a single input field. + message SinglePredicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.SinglePredicate"; + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v4alpha.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.PredicateList"; + + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.FieldMatcher"; + + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree"; + + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree.MatchMap"; + + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] diff --git a/api/envoy/config/core/v3/base.proto b/api/envoy/config/core/v3/base.proto index 4d7d69fae70b..5b5339ea5bc5 100644 --- a/api/envoy/config/core/v3/base.proto +++ b/api/envoy/config/core/v3/base.proto @@ -313,6 +313,13 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.DataSource"; diff --git a/api/envoy/config/core/v4alpha/base.proto b/api/envoy/config/core/v4alpha/base.proto index dc1104a219b7..27b0b356b1a7 100644 --- a/api/envoy/config/core/v4alpha/base.proto +++ b/api/envoy/config/core/v4alpha/base.proto @@ -308,6 +308,16 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.core.v3.WatchedDirectory"; + + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.DataSource"; diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index f50973f8e13d..62633012cf47 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -751,7 +751,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index 1808fa11fd78..9f3da5376ae0 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -747,7 +747,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/api/envoy/config/trace/v3/skywalking.proto b/api/envoy/config/trace/v3/skywalking.proto new file mode 100644 index 000000000000..224d474ccf98 --- /dev/null +++ b/api/envoy/config/trace/v3/skywalking.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package envoy.config.trace.v3; + +import "envoy/config/core/v3/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/migrate.proto"; +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.config.trace.v3"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_migrate).move_to_package = + "envoy.extensions.tracers.skywalking.v4alpha"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + // SkyWalking collector service. + core.v3.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto b/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto index 3082089202ee..007ccabc3e47 100644 --- a/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto +++ b/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto @@ -15,11 +15,27 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // gRPC-JSON transcoder :ref:`configuration overview `. // [#extension: envoy.filters.http.grpc_json_transcoder] -// [#next-free-field: 10] +// [#next-free-field: 11] message GrpcJsonTranscoder { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder"; + 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 PrintOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder.PrintOptions"; @@ -160,4 +176,11 @@ message GrpcJsonTranscoder { // the ``google/rpc/error_details.proto`` should be included in the configured // :ref:`proto descriptor set `. bool convert_grpc_status = 9; + + // URL unescaping policy. + // This spec is only applied when extracting variable with multiple segments. + // 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 = 10 [(validate.rules).enum = {defined_only: true}]; } 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 5588961bf512..a10fa68f3043 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. @@ -388,8 +392,18 @@ message RequirementRule { // config.route.v3.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +476,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtAuthentication"; @@ -528,4 +543,25 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } diff --git a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 12d4fa5fe1d3..2746640fa738 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. @@ -388,8 +392,18 @@ message RequirementRule { // config.route.v4alpha.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +476,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication"; @@ -528,4 +543,28 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig"; + + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } 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 a4c115c68da0..c2254c4c117a 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -37,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -360,6 +360,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index ceb7f4a65a1f..a44d35f86ae2 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -36,7 +36,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -359,6 +359,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/api/envoy/extensions/tracers/skywalking/v4alpha/BUILD b/api/envoy/extensions/tracers/skywalking/v4alpha/BUILD new file mode 100644 index 000000000000..1d56979cc466 --- /dev/null +++ b/api/envoy/extensions/tracers/skywalking/v4alpha/BUILD @@ -0,0 +1,13 @@ +# 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/v4alpha:pkg", + "//envoy/config/trace/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto b/api/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto new file mode 100644 index 000000000000..37936faa6133 --- /dev/null +++ b/api/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package envoy.extensions.tracers.skywalking.v4alpha; + +import "envoy/config/core/v4alpha/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.tracers.skywalking.v4alpha"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.trace.v3.SkyWalkingConfig"; + + // SkyWalking collector service. + config.core.v4alpha.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v3.ClientConfig"; + + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/api/envoy/extensions/transport_sockets/tls/v3/common.proto b/api/envoy/extensions/transport_sockets/tls/v3/common.proto index 2f45a754416b..2b545b35ee12 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -128,16 +128,37 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v3.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v3.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. + config.core.v3.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -191,7 +212,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CertificateValidationContext"; @@ -233,8 +254,22 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v3.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. + config.core.v3.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 1382863cb804..30859bc2a3eb 100644 --- a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -129,17 +129,38 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v4alpha.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v4alpha.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. + config.core.v4alpha.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -193,7 +214,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext"; @@ -235,8 +256,22 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v4alpha.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. + config.core.v4alpha.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/api/envoy/extensions/upstreams/tcp/generic/v3/BUILD b/api/envoy/extensions/upstreams/tcp/generic/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/extensions/upstreams/tcp/generic/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/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto b/api/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto new file mode 100644 index 000000000000..5754491b91d1 --- /dev/null +++ b/api/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package envoy.extensions.upstreams.tcp.generic.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.upstreams.tcp.generic.v3"; +option java_outer_classname = "GenericConnectionPoolProtoOuterClass"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Generic Connection Pool] + +// A connection pool which forwards downstream TCP as TCP or HTTP to upstream, +// based on CONNECT configuration. +// [#extension: envoy.upstreams.tcp.generic] +message GenericConnectionPoolProto { +} diff --git a/api/envoy/extensions/wasm/v3/wasm.proto b/api/envoy/extensions/wasm/v3/wasm.proto index b42fb75a0bf7..c6affb810611 100644 --- a/api/envoy/extensions/wasm/v3/wasm.proto +++ b/api/envoy/extensions/wasm/v3/wasm.proto @@ -28,7 +28,29 @@ message VmConfig { // See ref: "TODO: add ref" for details. string vm_id = 1; - // The Wasm runtime type (either "v8" or "null" for code compiled into Envoy). + // The Wasm runtime type. + // Available Wasm runtime types are registered as extensions. The following runtimes are included + // in Envoy code base: + // + // .. _extension_envoy.wasm.runtime.null: + // + // **envoy.wasm.runtime.null**: Null sandbox, the Wasm module must be compiled and linked into the + // Envoy binary. The registered name is given in the *code* field as *inline_string*. + // + // .. _extension_envoy.wasm.runtime.v8: + // + // **envoy.wasm.runtime.v8**: `V8 `_-based WebAssembly runtime. + // + // .. _extension_envoy.wasm.runtime.wavm: + // + // **envoy.wasm.runtime.wavm**: `WAVM `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // + // .. _extension_envoy.wasm.runtime.wasmtime: + // + // **envoy.wasm.runtime.wasmtime**: `Wasmtime `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // string runtime = 2 [(validate.rules).string = {min_len: 1}]; // The Wasm code that Envoy will execute. diff --git a/api/envoy/service/discovery/v3/discovery.proto b/api/envoy/service/discovery/v3/discovery.proto index 40479539213c..49bb7a931ef5 100644 --- a/api/envoy/service/discovery/v3/discovery.proto +++ b/api/envoy/service/discovery/v3/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v3; import "envoy/config/core/v3/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -252,10 +253,19 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + // The resource's name, to distinguish it from others of the same type of resource. string name = 3 [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; @@ -272,4 +282,23 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } diff --git a/api/envoy/service/discovery/v4alpha/discovery.proto b/api/envoy/service/discovery/v4alpha/discovery.proto index 3f5dca95cbb8..e6d15a5d8f93 100644 --- a/api/envoy/service/discovery/v4alpha/discovery.proto +++ b/api/envoy/service/discovery/v4alpha/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v4alpha; import "envoy/config/core/v4alpha/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -254,11 +255,23 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.service.discovery.v3.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + option (udpa.annotations.versioning).previous_message_type = + "envoy.service.discovery.v3.Resource.CacheControl"; + + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + oneof name_specifier { // The resource's name, to distinguish it from others of the same type of resource. string name = 3; @@ -276,4 +289,23 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 2e0a1cd4997d..dc1162bb93c7 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -130,6 +130,7 @@ proto_library( "//envoy/extensions/upstreams/http/generic/v3:pkg", "//envoy/extensions/upstreams/http/http/v3:pkg", "//envoy/extensions/upstreams/http/tcp/v3:pkg", + "//envoy/extensions/upstreams/tcp/generic/v3:pkg", "//envoy/extensions/wasm/v3:pkg", "//envoy/extensions/watchdog/profile_action/v3alpha:pkg", "//envoy/service/accesslog/v3:pkg", diff --git a/api/xds_protocol.rst b/api/xds_protocol.rst index 6a96001238de..182a71ddd1c8 100644 --- a/api/xds_protocol.rst +++ b/api/xds_protocol.rst @@ -656,6 +656,42 @@ adding/removing/updating clusters. On the other hand, routes are not warmed, i.e., the management plane must ensure that clusters referenced by a route are in place, before pushing the updates for a route. +.. _xds_protocol_TTL: + +TTL +~~~ + +In the event that the management server becomes unreachable, the last known configuration received +by Envoy will persist until the connection is reestablished. For some services, this may not be +desirable. For example, in the case of a fault injection service, a management server crash at the +wrong time may leave Envoy in an undesirable state. The TTL setting allows Envoy to remove a set of +resources after a specified period of time if contact with the management server is lost. This can +be used, for example, to terminate a fault injection test when the management server can no longer +be reached. + +For clients that support the *xds.config.supports-resource-ttl* client feature, A TTL field may +be specified on each :ref:`Resource `. Each resource will have its own TTL +expiry time, at which point the resource will be expired. Each xDS type may have different ways of +handling such an expiry. + +To update the TTL associated with a *Resource*, the management server resends the resource with a +new TTL. To remove the TTL, the management server resends the resource with the TTL field unset. + +To allow for lightweight TTL updates ("heartbeats"), a response can be sent that provides a +:ref:`Resource ` with the :ref:`resource ` +unset and version matching the most recently sent version can be used to update the TTL. These +resources will not be treated as resource updates, but only as TTL updates. + +SotW TTL +^^^^^^^^ + +In order to use TTL with SotW xDS, the relevant resources must be wrapped in a +:ref:`Resource `. This allows setting the same TTL field that is used for +Delta xDS with SotW, without changing the SotW API. Heartbeats are supported for SotW as well: +any resource within the response that look like a heartbeat resource will only be used to update the TTL. + +This feature is gated by the *xds.config.supports-resource-in-sotw* client feature. + .. _xds_protocol_ads: Aggregated Discovery Service diff --git a/bazel/BUILD b/bazel/BUILD index d03b931018a3..268cdb752ec6 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -188,7 +188,7 @@ config_setting( ) # As select() can't be nested we need these specialized settings to avoid ambiguity when choosing -# tcmalloc's flavor for x86_64 builds. +# tcmalloc's flavor for x86_64 and aarch64 builds. config_setting( name = "disable_tcmalloc_on_linux_x86_64", values = { @@ -213,6 +213,30 @@ config_setting( }, ) +config_setting( + name = "disable_tcmalloc_on_linux_aarch64", + values = { + "define": "tcmalloc=disabled", + "cpu": "aarch64", + }, +) + +config_setting( + name = "gperftools_tcmalloc_on_linux_aarch64", + values = { + "define": "tcmalloc=gperftools", + "cpu": "aarch64", + }, +) + +config_setting( + name = "debug_tcmalloc_on_linux_aarch64", + values = { + "define": "tcmalloc=debug", + "cpu": "aarch64", + }, +) + config_setting( name = "disable_signal_trace", values = {"define": "signal_trace=disabled"}, @@ -315,11 +339,6 @@ config_setting( ) # TODO: consider converting WAVM VM support to an extension (https://github.com/envoyproxy/envoy/issues/12574) -config_setting( - name = "wasm_all", - values = {"define": "wasm=enabled"}, -) - config_setting( name = "wasm_wavm", values = {"define": "wasm=wavm"}, @@ -330,6 +349,11 @@ config_setting( values = {"define": "wasm=v8"}, ) +config_setting( + name = "wasm_wasmtime", + values = {"define": "wasm=wasmtime"}, +) + config_setting( name = "wasm_none", values = {"define": "wasm=disabled"}, diff --git a/bazel/README.md b/bazel/README.md index 024e4b88b49d..f2b79eab5c00 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -83,10 +83,15 @@ for how to update or override dependencies. echo "build --config=clang" >> user.bazelrc ``` - Note: Either `libc++` or `libstdc++-7-dev` (or higher) must be installed. These are typically - available via a package manager, but may not be available in default repositories depending on - OS version. To build against `libc++` build with the `--config=libc++` instead of the - `--config=clang` flag. + Note: Either `libc++` or `libstdc++-7-dev` (or higher) must be installed. + + #### Config Flag Choices + Different [config](https://docs.bazel.build/versions/master/guide.html#--config) flags specify the compiler libraries: + + - `--config=libc++` means using `clang` + `libc++` + - `--config=clang` means using `clang` + `libstdc++` + - no config flag means using `gcc` + `libstdc++` + ### macOS On macOS, you'll need to install several dependencies. This can be accomplished via [Homebrew](https://brew.sh/): @@ -615,7 +620,7 @@ The following optional features can be disabled on the Bazel build command-line: * Backtracing on signals with `--define signal_trace=disabled` * Active stream state dump on signals with `--define signal_trace=disabled` or `--define disable_object_dump_on_signal_trace=disabled` * tcmalloc with `--define tcmalloc=disabled`. Also you can choose Gperftools' implementation of - tcmalloc with `--define tcmalloc=gperftools` which is the default for non-x86 builds. + tcmalloc with `--define tcmalloc=gperftools` which is the default for builds other than x86_64 and aarch64. * deprecated features with `--define deprecated_features=disabled` diff --git a/bazel/cel-cpp.patch b/bazel/cel-cpp.patch index aa8d795b1410..aee357153fdf 100644 --- a/bazel/cel-cpp.patch +++ b/bazel/cel-cpp.patch @@ -1,7 +1,7 @@ -diff --git a/eval/eval/field_backed_map_impl.cc b/eval/eval/field_backed_map_impl.cc +diff --git a/eval/public/containers/field_backed_map_impl.cc b/eval/public/containers/field_backed_map_impl.cc index cd56f51..4d2a546 100644 ---- a/eval/eval/field_backed_map_impl.cc -+++ b/eval/eval/field_backed_map_impl.cc +--- a/eval/public/containers/field_backed_map_impl.cc ++++ b/eval/public/containers/field_backed_map_impl.cc @@ -117,7 +117,9 @@ int FieldBackedMapImpl::size() const { const CelList* FieldBackedMapImpl::ListKeys() const { return key_list_.get(); } diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index e95329095dca..283cf130af22 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -20,8 +20,8 @@ load( _envoy_select_hot_restart = "envoy_select_hot_restart", _envoy_select_new_codecs_in_integration_tests = "envoy_select_new_codecs_in_integration_tests", _envoy_select_wasm = "envoy_select_wasm", - _envoy_select_wasm_all_v8_wavm_none = "envoy_select_wasm_all_v8_wavm_none", _envoy_select_wasm_v8 = "envoy_select_wasm_v8", + _envoy_select_wasm_wasmtime = "envoy_select_wasm_wasmtime", _envoy_select_wasm_wavm = "envoy_select_wasm_wavm", ) load( @@ -181,8 +181,8 @@ envoy_select_boringssl = _envoy_select_boringssl envoy_select_google_grpc = _envoy_select_google_grpc envoy_select_hot_restart = _envoy_select_hot_restart envoy_select_wasm = _envoy_select_wasm -envoy_select_wasm_all_v8_wavm_none = _envoy_select_wasm_all_v8_wavm_none envoy_select_wasm_wavm = _envoy_select_wasm_wavm +envoy_select_wasm_wasmtime = _envoy_select_wasm_wasmtime envoy_select_wasm_v8 = _envoy_select_wasm_v8 envoy_select_new_codecs_in_integration_tests = _envoy_select_new_codecs_in_integration_tests diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index 4e4694f03cdd..c122e36cd9c7 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -63,6 +63,8 @@ def envoy_copts(repository, test = False): }) + select({ repository + "//bazel:clang_build": ["-fno-limit-debug-info", "-Wgnu-conditional-omitted-operand", "-Wc++2a-extensions", "-Wrange-loop-analysis"], repository + "//bazel:gcc_build": ["-Wno-maybe-uninitialized"], + # TODO: Replace with /Zc:preprocessor for cl.exe versions >= 16.5 + repository + "//bazel:windows_x86_64": ["-experimental:preprocessor", "-Wv:19.4"], "//conditions:default": [], }) + select({ repository + "//bazel:no_debug_info": ["-g0"], @@ -70,11 +72,15 @@ def envoy_copts(repository, test = False): }) + select({ repository + "//bazel:disable_tcmalloc": ["-DABSL_MALLOC_HOOK_MMAP_DISABLE"], repository + "//bazel:disable_tcmalloc_on_linux_x86_64": ["-DABSL_MALLOC_HOOK_MMAP_DISABLE"], + repository + "//bazel:disable_tcmalloc_on_linux_aarch64": ["-DABSL_MALLOC_HOOK_MMAP_DISABLE"], repository + "//bazel:gperftools_tcmalloc": ["-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:gperftools_tcmalloc_on_linux_x86_64": ["-DGPERFTOOLS_TCMALLOC"], + repository + "//bazel:gperftools_tcmalloc_on_linux_aarch64": ["-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:debug_tcmalloc": ["-DENVOY_MEMORY_DEBUG_ENABLED=1", "-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:debug_tcmalloc_on_linux_x86_64": ["-DENVOY_MEMORY_DEBUG_ENABLED=1", "-DGPERFTOOLS_TCMALLOC"], + repository + "//bazel:debug_tcmalloc_on_linux_aarch64": ["-DENVOY_MEMORY_DEBUG_ENABLED=1", "-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:linux_x86_64": ["-DTCMALLOC"], + repository + "//bazel:linux_aarch64": ["-DTCMALLOC"], "//conditions:default": ["-DGPERFTOOLS_TCMALLOC"], }) + select({ repository + "//bazel:disable_signal_trace": [], @@ -131,11 +137,15 @@ def tcmalloc_external_dep(repository): return select({ repository + "//bazel:disable_tcmalloc": None, repository + "//bazel:disable_tcmalloc_on_linux_x86_64": None, + repository + "//bazel:disable_tcmalloc_on_linux_aarch64": None, repository + "//bazel:debug_tcmalloc": envoy_external_dep_path("gperftools"), repository + "//bazel:debug_tcmalloc_on_linux_x86_64": envoy_external_dep_path("gperftools"), + repository + "//bazel:debug_tcmalloc_on_linux_aarch64": envoy_external_dep_path("gperftools"), repository + "//bazel:gperftools_tcmalloc": envoy_external_dep_path("gperftools"), repository + "//bazel:gperftools_tcmalloc_on_linux_x86_64": envoy_external_dep_path("gperftools"), + repository + "//bazel:gperftools_tcmalloc_on_linux_aarch64": envoy_external_dep_path("gperftools"), repository + "//bazel:linux_x86_64": envoy_external_dep_path("tcmalloc"), + repository + "//bazel:linux_aarch64": envoy_external_dep_path("tcmalloc"), "//conditions:default": envoy_external_dep_path("gperftools"), }) diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index 5eb90df500c0..a2f7c6b0ae02 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -21,11 +21,15 @@ def tcmalloc_external_deps(repository): return select({ repository + "//bazel:disable_tcmalloc": [], repository + "//bazel:disable_tcmalloc_on_linux_x86_64": [], + repository + "//bazel:disable_tcmalloc_on_linux_aarch64": [], repository + "//bazel:debug_tcmalloc": [envoy_external_dep_path("gperftools")], repository + "//bazel:debug_tcmalloc_on_linux_x86_64": [envoy_external_dep_path("gperftools")], + repository + "//bazel:debug_tcmalloc_on_linux_aarch64": [envoy_external_dep_path("gperftools")], repository + "//bazel:gperftools_tcmalloc": [envoy_external_dep_path("gperftools")], repository + "//bazel:gperftools_tcmalloc_on_linux_x86_64": [envoy_external_dep_path("gperftools")], + repository + "//bazel:gperftools_tcmalloc_on_linux_aarch64": [envoy_external_dep_path("gperftools")], repository + "//bazel:linux_x86_64": [envoy_external_dep_path("tcmalloc")], + repository + "//bazel:linux_aarch64": [envoy_external_dep_path("tcmalloc")], "//conditions:default": [envoy_external_dep_path("gperftools")], }) @@ -140,7 +144,7 @@ def envoy_cc_library( hdrs = hdrs, copts = envoy_copts(repository) + copts, visibility = visibility, - tags = ["nocompdb"], + tags = ["nocompdb"] + tags, deps = [":" + name], strip_include_prefix = strip_include_prefix, ) diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index 5a33e4da515d..8cbecb26075c 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -41,6 +41,7 @@ def envoy_select_wasm(xs): def envoy_select_wasm_v8(xs): return select({ + "@envoy//bazel:wasm_wasmtime": [], "@envoy//bazel:wasm_wavm": [], "@envoy//bazel:wasm_none": [], "//conditions:default": xs, @@ -48,18 +49,14 @@ def envoy_select_wasm_v8(xs): def envoy_select_wasm_wavm(xs): return select({ - "@envoy//bazel:wasm_all": xs, "@envoy//bazel:wasm_wavm": xs, "//conditions:default": [], }) -def envoy_select_wasm_all_v8_wavm_none(xs1, xs2, xs3, xs4): +def envoy_select_wasm_wasmtime(xs): return select({ - "@envoy//bazel:wasm_all": xs1, - "@envoy//bazel:wasm_v8": xs2, - "@envoy//bazel:wasm_wavm": xs3, - "@envoy//bazel:wasm_none": xs4, - "//conditions:default": xs2, + "@envoy//bazel:wasm_wasmtime": xs, + "//conditions:default": [], }) # Select the given values if use legacy codecs in test is on in the current build. diff --git a/bazel/external/cargo/Cargo.toml b/bazel/external/cargo/Cargo.toml index f56a3f47ad31..610d35df4e60 100644 --- a/bazel/external/cargo/Cargo.toml +++ b/bazel/external/cargo/Cargo.toml @@ -46,6 +46,11 @@ name = "http_metadata_rust" path = "../../../test/extensions/filters/http/wasm/test_data/metadata_rust.rs" crate-type = ["cdylib"] +[[example]] +name = "http_resume_call_rust" +path = "../../../test/extensions/filters/http/wasm/test_data/resume_call_rust.rs" +crate-type = ["cdylib"] + [[example]] name = "http_shared_data_rust" path = "../../../test/extensions/filters/http/wasm/test_data/shared_data_rust.rs" diff --git a/bazel/external/proxy_wasm_cpp_host.BUILD b/bazel/external/proxy_wasm_cpp_host.BUILD index 1b3f0829d7b2..148635bc099d 100644 --- a/bazel/external/proxy_wasm_cpp_host.BUILD +++ b/bazel/external/proxy_wasm_cpp_host.BUILD @@ -1,10 +1,4 @@ load("@rules_cc//cc:defs.bzl", "cc_library") -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_select_wasm_all_v8_wavm_none", - "envoy_select_wasm_v8", - "envoy_select_wasm_wavm", -) licenses(["notice"]) # Apache 2 @@ -19,44 +13,14 @@ cc_library( ) cc_library( - name = "lib", - # Note that the select cannot appear in the glob. - srcs = envoy_select_wasm_all_v8_wavm_none( - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - ), - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - exclude = ["src/wavm/*"], - ), - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - exclude = ["src/v8/*"], - ), - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - exclude = [ - "src/wavm/*", - "src/v8/*", - ], - ), - ), - copts = envoy_select_wasm_wavm([ - '-DWAVM_API=""', - "-Wno-non-virtual-dtor", - "-Wno-old-style-cast", + name = "common_lib", + srcs = glob([ + "src/*.h", + "src/*.cc", + "src/common/*.h", + "src/common/*.cc", + "src/third_party/*.h", + "src/third_party/*.cc", ]), deps = [ ":include", @@ -68,9 +32,54 @@ cc_library( "//external:zlib", "@proxy_wasm_cpp_sdk//:api_lib", "@proxy_wasm_cpp_sdk//:common_lib", - ] + envoy_select_wasm_wavm([ - "@envoy//bazel/foreign_cc:wavm", - ]) + envoy_select_wasm_v8([ + ], +) + +cc_library( + name = "null_lib", + srcs = glob([ + "src/null/*.cc", + ]), + deps = [ + ":common_lib", + ], +) + +cc_library( + name = "v8_lib", + srcs = glob([ + "src/v8/*.cc", + ]), + deps = [ + ":common_lib", "//external:wee8", + ], +) + +cc_library( + name = "wavm_lib", + srcs = glob([ + "src/wavm/*.cc", ]), + copts = [ + '-DWAVM_API=""', + "-Wno-non-virtual-dtor", + "-Wno-old-style-cast", + ], + deps = [ + ":common_lib", + "@envoy//bazel/foreign_cc:wavm", + ], +) + +cc_library( + name = "wasmtime_lib", + srcs = glob([ + "src/wasmtime/*.h", + "src/wasmtime/*.cc", + ]), + deps = [ + ":common_lib", + "@com_github_wasm_c_api//:wasmtime_lib", + ], ) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 7541909aa191..a3c430cf9fa4 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -57,16 +57,12 @@ quiche_common_copts = [ "-Wno-unused-function", # quic_inlined_frame.h uses offsetof() to optimize memory usage in frames. "-Wno-invalid-offsetof", - "-Wno-range-loop-analysis", ] quiche_copts = select({ # Ignore unguarded #pragma GCC statements in QUICHE sources "@envoy//bazel:windows_x86_64": ["-wd4068"], # Remove these after upstream fix. - "@envoy//bazel:gcc_build": [ - "-Wno-sign-compare", - ] + quiche_common_copts, "//conditions:default": quiche_common_copts, }) @@ -737,7 +733,6 @@ envoy_cc_library( hdrs = [ "quiche/spdy/platform/api/spdy_bug_tracker.h", "quiche/spdy/platform/api/spdy_containers.h", - "quiche/spdy/platform/api/spdy_endianness_util.h", "quiche/spdy/platform/api/spdy_estimate_memory_usage.h", "quiche/spdy/platform/api/spdy_flags.h", "quiche/spdy/platform/api/spdy_logging.h", @@ -935,6 +930,7 @@ envoy_cc_library( copts = quiche_copts, repository = "@envoy", deps = [ + ":http2_hpack_huffman_hpack_huffman_encoder_lib", ":spdy_core_protocol_lib", ":spdy_platform", ], @@ -1049,19 +1045,16 @@ envoy_cc_library( envoy_cc_library( name = "quic_platform_base", hdrs = [ - "quiche/quic/platform/api/quic_aligned.h", "quiche/quic/platform/api/quic_bug_tracker.h", "quiche/quic/platform/api/quic_client_stats.h", "quiche/quic/platform/api/quic_containers.h", "quiche/quic/platform/api/quic_error_code_wrappers.h", "quiche/quic/platform/api/quic_estimate_memory_usage.h", "quiche/quic/platform/api/quic_exported_stats.h", - "quiche/quic/platform/api/quic_fallthrough.h", "quiche/quic/platform/api/quic_flag_utils.h", "quiche/quic/platform/api/quic_flags.h", "quiche/quic/platform/api/quic_iovec.h", "quiche/quic/platform/api/quic_logging.h", - "quiche/quic/platform/api/quic_macros.h", "quiche/quic/platform/api/quic_map_util.h", "quiche/quic/platform/api/quic_mem_slice.h", "quiche/quic/platform/api/quic_prefetch.h", @@ -1072,6 +1065,7 @@ envoy_cc_library( "quiche/quic/platform/api/quic_stream_buffer_allocator.h", "quiche/quic/platform/api/quic_string_utils.h", "quiche/quic/platform/api/quic_uint128.h", + "quiche/quic/platform/api/quic_testvalue.h", # TODO: uncomment the following files as implementations are added. # "quiche/quic/platform/api/quic_fuzzed_data_provider.h", # "quiche/quic/platform/api/quic_test_loopback.h", @@ -1147,7 +1141,6 @@ envoy_cc_test_library( hdrs = ["quiche/quic/platform/api/quic_port_utils.h"], repository = "@envoy", tags = ["nofips"], - deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_port_utils_impl_lib"], ) envoy_cc_library( @@ -1216,15 +1209,14 @@ envoy_cc_test_library( ) envoy_cc_library( - name = "quiche_common_platform_endian", - hdrs = ["quiche/common/platform/api/quiche_endian.h"], + name = "quiche_common_endian_lib", + hdrs = ["quiche/common/quiche_endian.h"], repository = "@envoy", tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":quiche_common_platform_export", - "@envoy//source/extensions/quic_listeners/quiche/platform:quiche_common_platform_endian_impl_lib", ], ) @@ -1932,6 +1924,7 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ ":quic_core_clock_lib", + ":quic_core_crypto_certificate_view_lib", ":quic_core_crypto_encryption_lib", ":quic_core_crypto_hkdf_lib", ":quic_core_crypto_proof_source_interface_lib", @@ -2167,6 +2160,15 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quic_core_flags_list_lib", + hdrs = ["quiche/quic/core/quic_flags_list.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], +) + envoy_cc_library( name = "quic_core_framer_lib", srcs = ["quiche/quic/core/quic_framer.cc"], @@ -2339,6 +2341,7 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ + ":http2_constants_lib", ":quic_core_data_lib", ":quic_core_error_codes_lib", ":quic_core_http_http_frames_lib", @@ -2723,6 +2726,27 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quic_core_path_validator_lib", + srcs = ["quiche/quic/core/quic_path_validator.cc"], + hdrs = ["quiche/quic/core/quic_path_validator.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_alarm_factory_interface_lib", + ":quic_core_alarm_interface_lib", + ":quic_core_arena_scoped_ptr_lib", + ":quic_core_clock_lib", + ":quic_core_constants_lib", + ":quic_core_crypto_random_lib", + ":quic_core_one_block_arena_lib", + ":quic_core_packet_writer_interface_lib", + ":quic_core_types_lib", + ":quic_platform", + ], +) + envoy_cc_library( name = "quic_core_process_packet_interface_lib", hdrs = ["quiche/quic/core/quic_process_packet_interface.h"], @@ -2735,6 +2759,15 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quic_core_protocol_flags_list_lib", + hdrs = ["quiche/quic/core/quic_protocol_flags_list.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], +) + envoy_cc_library( name = "quic_core_qpack_blocking_manager_lib", srcs = ["quiche/quic/core/qpack/qpack_blocking_manager.cc"], @@ -2896,6 +2929,7 @@ envoy_cc_library( deps = [ ":http2_decoder_decode_buffer_lib", ":http2_decoder_decode_status_lib", + ":quic_core_error_codes_lib", ":quic_core_qpack_qpack_instruction_decoder_lib", ":quic_core_qpack_qpack_instructions_lib", ":quic_core_qpack_qpack_stream_receiver_lib", @@ -3368,7 +3402,7 @@ envoy_cc_library( ":quic_core_error_codes_lib", ":quic_core_time_lib", ":quic_platform_base", - ":quiche_common_platform_endian", + ":quiche_common_endian_lib", ], ) @@ -3420,6 +3454,7 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ + ":quic_core_circular_deque_lib", ":quic_core_connection_stats_lib", ":quic_core_packets_lib", ":quic_core_session_notifier_interface_lib", @@ -3459,6 +3494,7 @@ envoy_cc_library( deps = [ ":quic_core_versions_lib", ":quic_platform_base", + ":quiche_common_endian_lib", ], ) @@ -3475,7 +3511,6 @@ envoy_cc_library( ":quic_core_tag_lib", ":quic_core_types_lib", ":quic_platform_base", - ":quiche_common_platform_endian", ], ) @@ -3619,6 +3654,35 @@ envoy_cc_test_library( ], ) +envoy_cc_test_library( + name = "quic_test_tools_qpack_qpack_encoder_test_utils_lib", + srcs = ["quiche/quic/test_tools/qpack/qpack_encoder_test_utils.cc"], + hdrs = ["quiche/quic/test_tools/qpack/qpack_encoder_test_utils.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_qpack_qpack_encoder_lib", + ":quic_platform_test", + ":quic_test_tools_qpack_qpack_test_utils_lib", + ":spdy_core_header_block_lib", + ":spdy_core_hpack_hpack_lib", + ], +) + +envoy_cc_test_library( + name = "quic_test_tools_qpack_qpack_test_utils_lib", + srcs = ["quiche/quic/test_tools/qpack/qpack_test_utils.cc"], + hdrs = ["quiche/quic/test_tools/qpack/qpack_test_utils.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_qpack_qpack_stream_sender_delegate_lib", + ":quic_platform_test", + ], +) + envoy_cc_test_library( name = "quic_test_tools_sent_packet_manager_peer_lib", srcs = ["quiche/quic/test_tools/quic_sent_packet_manager_peer.cc"], @@ -3746,6 +3810,7 @@ envoy_cc_test_library( ":quic_core_packet_creator_lib", ":quic_core_packet_writer_interface_lib", ":quic_core_packets_lib", + ":quic_core_path_validator_lib", ":quic_core_received_packet_manager_lib", ":quic_core_sent_packet_manager_lib", ":quic_core_server_id_lib", @@ -3836,25 +3901,10 @@ envoy_cc_test_library( deps = [":epoll_server_platform"], ) -envoy_cc_library( - name = "quiche_common_platform_optional", - hdrs = ["quiche/common/platform/api/quiche_optional.h"], - repository = "@envoy", - tags = ["nofips"], - visibility = ["//visibility:public"], - deps = [ - ":quiche_common_platform_export", - "@envoy//source/extensions/quic_listeners/quiche/platform:quiche_common_platform_optional_impl_lib", - ], -) - envoy_cc_library( name = "quiche_common_platform", hdrs = [ - "quiche/common/platform/api/quiche_arraysize.h", "quiche/common/platform/api/quiche_logging.h", - "quiche/common/platform/api/quiche_optional.h", - "quiche/common/platform/api/quiche_ptr_util.h", "quiche/common/platform/api/quiche_str_cat.h", "quiche/common/platform/api/quiche_string_piece.h", "quiche/common/platform/api/quiche_text_utils.h", @@ -3866,7 +3916,6 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ ":quiche_common_platform_export", - ":quiche_common_platform_optional", "@envoy//source/extensions/quic_listeners/quiche/platform:quiche_common_platform_impl_lib", ], ) @@ -3874,7 +3923,6 @@ envoy_cc_library( envoy_cc_test_library( name = "quiche_common_platform_test", srcs = [ - "quiche/common/platform/api/quiche_endian_test.cc", "quiche/common/platform/api/quiche_str_cat_test.cc", "quiche/common/platform/api/quiche_text_utils_test.cc", "quiche/common/platform/api/quiche_time_utils_test.cc", @@ -3884,7 +3932,6 @@ envoy_cc_test_library( tags = ["nofips"], deps = [ ":quiche_common_platform", - ":quiche_common_platform_endian", "@envoy//test/extensions/quic_listeners/quiche/platform:quiche_common_platform_test_impl_lib", ], ) @@ -3904,8 +3951,8 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ + ":quiche_common_endian_lib", ":quiche_common_platform", - ":quiche_common_platform_endian", ], ) @@ -3944,6 +3991,7 @@ envoy_cc_test( deps = [ ":http2_platform", ":http2_test_tools_random", + ":quiche_common_test_tools_test_utils_lib", ], ) diff --git a/bazel/external/wasm-c-api.BUILD b/bazel/external/wasm-c-api.BUILD new file mode 100644 index 000000000000..abff294cf9c3 --- /dev/null +++ b/bazel/external/wasm-c-api.BUILD @@ -0,0 +1,19 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "wasmtime_lib", + hdrs = [ + "include/wasm.h", + ], + defines = [ + "ENVOY_WASM_WASMTIME", + ], + include_prefix = "wasmtime", + deps = [ + "@com_github_wasmtime//:rust_c_api", + ], +) diff --git a/bazel/external/wasmtime.BUILD b/bazel/external/wasmtime.BUILD new file mode 100644 index 000000000000..edb2ab30367e --- /dev/null +++ b/bazel/external/wasmtime.BUILD @@ -0,0 +1,32 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@io_bazel_rules_rust//rust:rust.bzl", "rust_library") + +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "helpers_lib", + srcs = [ + "crates/runtime/src/helpers.c", + ], + visibility = ["//visibility:private"], +) + +rust_library( + name = "rust_c_api", + srcs = glob(["crates/c-api/src/**/*.rs"]), + crate_root = "crates/c-api/src/lib.rs", + crate_type = "staticlib", + edition = "2018", + proc_macro_deps = [ + "@proxy_wasm_cpp_host//bazel/cargo:wasmtime_c_api_macros", + ], + deps = [ + ":helpers_lib", + "@proxy_wasm_cpp_host//bazel/cargo:anyhow", + "@proxy_wasm_cpp_host//bazel/cargo:env_logger", + "@proxy_wasm_cpp_host//bazel/cargo:once_cell", + "@proxy_wasm_cpp_host//bazel/cargo:wasmtime", + ], +) diff --git a/bazel/external/wee8.BUILD b/bazel/external/wee8.BUILD index ce16c32af799..0ea13769ccd4 100644 --- a/bazel/external/wee8.BUILD +++ b/bazel/external/wee8.BUILD @@ -29,7 +29,10 @@ cc_library( genrule( name = "build", - srcs = glob(["wee8/**"]), + srcs = glob( + ["wee8/**"], + exclude = ["wee8/out/**"], + ), outs = [ "libwee8.a", ], diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 3df2774717ed..557657b89431 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -182,6 +182,8 @@ def envoy_dependencies(skip_targets = []): _org_llvm_llvm() _com_github_wavm_wavm() + _com_github_wasmtime() + _com_github_wasm_c_api() switched_rules_by_language( name = "com_google_googleapis_imports", @@ -864,6 +866,18 @@ def _com_github_wavm_wavm(): actual = "@envoy//bazel/foreign_cc:wavm", ) +def _com_github_wasmtime(): + external_http_archive( + name = "com_github_wasmtime", + build_file = "@envoy//bazel/external:wasmtime.BUILD", + ) + +def _com_github_wasm_c_api(): + external_http_archive( + name = "com_github_wasm_c_api", + build_file = "@envoy//bazel/external:wasm-c-api.BUILD", + ) + def _kafka_deps(): # This archive contains Kafka client source code. # We are using request/response message format files to generate parser code. diff --git a/bazel/repositories_extra.bzl b/bazel/repositories_extra.bzl index 70fe69b6fa40..dbfc5221d054 100644 --- a/bazel/repositories_extra.bzl +++ b/bazel/repositories_extra.bzl @@ -1,5 +1,6 @@ load("@rules_python//python:repositories.bzl", "py_repositories") load("@rules_python//python:pip.bzl", "pip3_import", "pip_repositories") +load("@proxy_wasm_cpp_host//bazel/cargo:crates.bzl", "proxy_wasm_cpp_host_raze__fetch_remote_crates") # Python dependencies. def _python_deps(): @@ -100,3 +101,4 @@ def _python_deps(): # Envoy deps that rely on a first stage of dependency loading in envoy_dependencies(). def envoy_dependencies_extra(): _python_deps() + proxy_wasm_cpp_host_raze__fetch_remote_crates() diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 974c9deea62b..02d9a62ddeb8 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -89,12 +89,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Abseil", project_desc = "Open source collection of C++ libraries drawn from the most fundamental pieces of Google’s internal codebase", project_url = "https://abseil.io/", - version = "093cc27604df1c4a179b73bc3f00d4d1ce2ce113", - sha256 = "55d33c75aff05a8c4a55bdf0eddad66c71a963107bc2add96cf8eb88ddb47a80", + version = "8f1c34a77a2ba04512b7f9cbc6013d405e6a0b31", + sha256 = "635367c5cac4bbab95d0485ba9e68fa422546b06ce050190c99be7e23aba3ce3", strip_prefix = "abseil-cpp-{version}", urls = ["https://github.com/abseil/abseil-cpp/archive/{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2020-10-01", + release_date = "2020-10-17", cpe = "N/A", ), com_github_c_ares_c_ares = dict( @@ -199,12 +199,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "tcmalloc", project_desc = "Fast, multi-threaded malloc implementation", project_url = "https://github.com/google/tcmalloc", - version = "d1311bf409db47c3441d3de6ea07d768c6551dec", - sha256 = "e22444b6544edd81f11c987dd5e482a2e00bbff717badb388779ca57525dad50", + version = "9f385356c34d4fc11f76a000b609e2b446c20667", + sha256 = "652e48e0b9ef645db04bff8a3d4841c60ce07275f5d98e18e698dc92bd111291", strip_prefix = "tcmalloc-{version}", urls = ["https://github.com/google/tcmalloc/archive/{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2020-09-16", + release_date = "2020-11-04", cpe = "N/A", ), com_github_gperftools_gperftools = dict( @@ -408,13 +408,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "jwt_verify_lib", project_desc = "JWT verification library for C++", project_url = "https://github.com/google/jwt_verify_lib", - version = "7276a339af8426724b744216f619c99152f8c141", - sha256 = "f1fde4f3ebb3b2d841332c7a02a4b50e0529a19709934c63bc6208d1bbe28fb1", + version = "28efec2e4df1072db0ed03597591360ec9f80aac", + sha256 = "7a5c35b7cbf633398503ae12cad8c2833e92b3a796eed68b6256d22d51ace5e1", strip_prefix = "jwt_verify_lib-{version}", urls = ["https://github.com/google/jwt_verify_lib/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.http.jwt_authn"], - release_date = "2020-07-10", + release_date = "2020-11-04", cpe = "N/A", ), com_github_nodejs_http_parser = dict( @@ -507,13 +507,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "grpc-httpjson-transcoding", project_desc = "Library that supports transcoding so that HTTP/JSON can be converted to gRPC", project_url = "https://github.com/grpc-ecosystem/grpc-httpjson-transcoding", - version = "faf8af1e9788cd4385b94c8f85edab5ea5d4b2d6", - sha256 = "62c8cb5ea2cca1142cde9d4a0778c52c6022345c3268c60ef81666946b958ad5", + version = "4d095f048889d4fc3b8d4579aa80ca4290319802", + sha256 = "7af66e0674340932683ab4f04ea6f03e2550849a54741738d94310b84d396a2c", strip_prefix = "grpc-httpjson-transcoding-{version}", urls = ["https://github.com/grpc-ecosystem/grpc-httpjson-transcoding/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.http.grpc_json_transcoder"], - release_date = "2020-03-02", + release_date = "2020-11-12", cpe = "N/A", ), io_bazel_rules_go = dict( @@ -587,13 +587,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/llvm-{version}.src.tar.xz"], release_date = "2020-03-23", use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - ], + extensions = ["envoy.wasm.runtime.wavm"], cpe = "cpe:2.3:a:llvm:*:*", ), com_github_wavm_wavm = dict( @@ -606,15 +600,37 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/WAVM/WAVM/archive/{version}.tar.gz"], release_date = "2020-09-17", use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - ], + extensions = ["envoy.wasm.runtime.wavm"], cpe = "cpe:2.3:a:webassembly_virtual_machine_project:webassembly_virtual_machine:*", ), + com_github_wasmtime = dict( + project_name = "wasmtime", + project_desc = "A standalone runtime for WebAssembly", + project_url = "https://github.com/bytecodealliance/wasmtime", + version = "0.21.0", + sha256 = "7874feb1026bbef06796bd5ab80e73f15b8e83752bde8dc93994f5bc039a4952", + strip_prefix = "wasmtime-{version}", + urls = ["https://github.com/bytecodealliance/wasmtime/archive/v{version}.tar.gz"], + release_date = "2020-11-05", + use_category = ["dataplane_ext"], + extensions = ["envoy.wasm.runtime.wasmtime"], + cpe = "N/A", + ), + com_github_wasm_c_api = dict( + project_name = "wasm-c-api", + project_desc = "WebAssembly C and C++ API", + project_url = "https://github.com/WebAssembly/wasm-c-api", + # this is the submodule's specific commit used by wasmtime + # https://github.com/bytecodealliance/wasmtime/tree/v0.21.0/crates/c-api + version = "d9a80099d496b5cdba6f3fe8fc77586e0e505ddc", + sha256 = "aea8cd095e9937f1e14f2c93e026317b197eb2345e7a817fe3932062eb7b792c", + strip_prefix = "wasm-c-api-{version}", + urls = ["https://github.com/WebAssembly/wasm-c-api/archive/{version}.tar.gz"], + release_date = "2019-11-14", + use_category = ["dataplane_ext"], + extensions = ["envoy.wasm.runtime.wasmtime"], + cpe = "N/A", + ), io_opencensus_cpp = dict( project_name = "OpenCensus C++", project_desc = "OpenCensus tracing library", @@ -657,13 +673,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( sha256 = "f22734640e0515bc34d1ca3772513aef24374fafa44d0489d3a9a57cadec69fb", urls = ["https://storage.googleapis.com/envoyproxy-wee8/wee8-{version}.tar.gz"], use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - ], + extensions = ["envoy.wasm.runtime.v8"], release_date = "2020-10-27", cpe = "cpe:2.3:a:google:v8:*", ), @@ -671,13 +681,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://quiche.googlesource.com/quiche", - # Static snapshot of https://quiche.googlesource.com/quiche/+archive/f555d99a084cdd086a349548c70fb558ac5847cf.tar.gz - version = "f555d99a084cdd086a349548c70fb558ac5847cf", - sha256 = "1833f08e7b0f18b49d7498b029b7f3e6559a82113ec82a98a9e945553756e351", + # Static snapshot of https://quiche.googlesource.com/quiche/+archive/ecc28c0d7428f3323ea26eb1ddb98a5e06b23dea.tar.gz + version = "ecc28c0d7428f3323ea26eb1ddb98a5e06b23dea", + sha256 = "52680dea984dbe899c27176155578b97276e1f1516b7c3a63fb16ba593061859", urls = ["https://storage.googleapis.com/quiche-envoy-integration/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.transport_sockets.quic"], - release_date = "2020-09-17", + release_date = "2020-11-10", cpe = "N/A", ), com_googlesource_googleurl = dict( @@ -697,8 +707,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Common Expression Language (CEL) C++ library", project_desc = "Common Expression Language (CEL) C++ library", project_url = "https://opensource.google/projects/cel", - version = "b9453a09b28a1531c4917e8792b3ea61f6b1a447", - sha256 = "cad7d01139947d78e413d112cb8f7431fbb33cf66b0adf9c280824803fc2a72e", + version = "47244a458e7739ad38e178a3f3892d197de4a574", + sha256 = "51b1af23cb703a94d18fe7a5e2696f96cde5bc35a279c6c844e6363aea3982fb", strip_prefix = "cel-cpp-{version}", urls = ["https://github.com/google/cel-cpp/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -711,7 +721,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", ], - release_date = "2020-07-14", + release_date = "2020-10-25", cpe = "N/A", ), com_github_google_flatbuffers = dict( @@ -822,6 +832,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.null", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2020-10-22", cpe = "N/A", @@ -830,8 +844,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/proxy-wasm/proxy-wasm-cpp-host", - version = "40fd3d03842c07d65fed907a6b6ed0f89d68d531", - sha256 = "b5ae746e66b6209ea0cce86d6c21de99dacbec1da9cdadd53a9ec46bc296a3ba", + version = "15827110ac35fdac9abdb6b05d04ee7ee2044dae", + sha256 = "77a2671205eb0973bee375a1bee4099edef991350433981f6e3508780318117d", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -841,8 +855,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.null", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], - release_date = "2020-10-27", + release_date = "2020-11-12", cpe = "N/A", ), emscripten_toolchain = dict( @@ -864,8 +882,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( sha256 = "426a7712af597d90301dcc29e63d62de5c2e09fb347692e89abb775ec97c74fe", strip_prefix = "rules_rust-{version}", urls = ["https://github.com/bazelbuild/rules_rust/archive/{version}.tar.gz"], - use_category = ["test_only"], + use_category = ["dataplane_ext"], + extensions = ["envoy.wasm.runtime.wasmtime"], release_date = "2020-10-21", + cpe = "N/A", ), rules_antlr = dict( project_name = "ANTLR Rules for Bazel", diff --git a/ci/Dockerfile-envoy-google-vrp b/ci/Dockerfile-envoy-google-vrp index 802e148851e0..abc84f1269ab 100644 --- a/ci/Dockerfile-envoy-google-vrp +++ b/ci/Dockerfile-envoy-google-vrp @@ -16,6 +16,9 @@ ADD configs/google-vrp/supervisor.conf /etc/supervisor.conf ADD test/config/integration/certs/serverkey.pem /etc/envoy/certs/serverkey.pem ADD test/config/integration/certs/servercert.pem /etc/envoy/certs/servercert.pem # ADD %local envoy bin% /usr/local/bin/envoy +RUN chmod 777 /var/log/supervisor +RUN chmod a+r /etc/supervisor.conf /etc/envoy/* /etc/envoy/certs/* +RUN chmod a+rx /usr/local/bin/launch_envoy.sh EXPOSE 10000 EXPOSE 10001 diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 8a9e68182115..4e3de2a2f2a5 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -280,7 +280,6 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then "--define" "boringssl=fips" "--define" "log_debug_assert_in_release=enabled" "--define" "quiche=enabled" - "--define" "wasm=wavm" "--define" "path_normalization_by_default=true" "--define" "deprecated_features=disabled" "--define" "use_new_codecs_in_integration_tests=false" @@ -297,19 +296,22 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then TEST_TARGETS=("@envoy//test/...") fi # Building all the dependencies from scratch to link them against libc++. - echo "Building and testing ${TEST_TARGETS[*]}" - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only + echo "Building and testing with wasm=wasmtime: ${TEST_TARGETS[*]}" + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wasmtime "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only + + echo "Building and testing with wasm=wavm: ${TEST_TARGETS[*]}" + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only # Legacy codecs "--define legacy_codecs_in_integration_tests=true" should also be tested in # integration tests with asan. - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//test/integration/... --config=clang-asan --build_tests_only + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//test/integration/... --config=clang-asan --build_tests_only # "--define log_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. - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c opt @envoy//test/common/common:assert_test @envoy//test/server:server_test + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c opt @envoy//test/common/common:assert_test @envoy//test/server:server_test - echo "Building binary..." - bazel build "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//source/exe:envoy-static --build_tag_filters=-nofips + echo "Building binary with wasm=wavm..." + bazel build "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//source/exe:envoy-static --build_tag_filters=-nofips collect_build_profile build exit 0 elif [[ "$CI_TARGET" == "bazel.api" ]]; then @@ -448,7 +450,7 @@ elif [[ "$CI_TARGET" == "verify_examples" ]]; then done docker images sudo apt-get update -y - sudo apt-get install -y -qq --no-install-recommends redis-tools + sudo apt-get install -y -qq --no-install-recommends expect redis-tools export DOCKER_NO_PULL=1 umask 027 chmod -R o-rwx examples/ diff --git a/ci/docker-entrypoint.sh b/ci/docker-entrypoint.sh index 4815acb1956a..6e584d37e3d7 100755 --- a/ci/docker-entrypoint.sh +++ b/ci/docker-entrypoint.sh @@ -24,7 +24,7 @@ if [ "$ENVOY_UID" != "0" ]; then fi # Ensure the envoy user is able to write to container logs chown envoy:envoy /dev/stdout /dev/stderr - su-exec envoy "${@}" + exec su-exec envoy "${@}" else exec "${@}" fi diff --git a/ci/filter_example_setup.sh b/ci/filter_example_setup.sh index 774464f15a7c..ae0113c2d085 100644 --- a/ci/filter_example_setup.sh +++ b/ci/filter_example_setup.sh @@ -5,7 +5,7 @@ set -e # This is the hash on https://github.com/envoyproxy/envoy-filter-example.git we pin to. -ENVOY_FILTER_EXAMPLE_GITSHA="bebd0b2422ea7739905f1793565681d7266491e6" +ENVOY_FILTER_EXAMPLE_GITSHA="30e5df3b73aec14ca3b70e4537e0b42f2e9d7fd0" ENVOY_FILTER_EXAMPLE_SRCDIR="${BUILD_DIR}/envoy-filter-example" # shellcheck disable=SC2034 diff --git a/ci/repokitteh/modules/newcontributor.star b/ci/repokitteh/modules/newcontributor.star index 4cf644bc200f..a192bd8613e7 100644 --- a/ci/repokitteh/modules/newcontributor.star +++ b/ci/repokitteh/modules/newcontributor.star @@ -14,7 +14,9 @@ def get_pr_author_association(issue_number): path="repos/envoyproxy/envoy/pulls/%s" % issue_number)["json"]["author_association"] def is_newcontributor(issue_number): - return get_pr_author_association(issue_number) == "FIRST_TIME_CONTRIBUTOR" + return ( + get_pr_author_association(issue_number) + in ["NONE", "FIRST_TIME_CONTRIBUTOR", "FIRST_TIMER"]) def should_message_newcontributor(action, issue_number): return ( diff --git a/ci/run_clang_tidy.sh b/ci/run_clang_tidy.sh index fe5d5fdd2047..b29a624c3f45 100755 --- a/ci/run_clang_tidy.sh +++ b/ci/run_clang_tidy.sh @@ -113,9 +113,7 @@ elif [[ "${RUN_FULL_CLANG_TIDY}" == 1 ]]; then run_clang_tidy else if [[ -z "${DIFF_REF}" ]]; then - if [[ "${BUILD_REASON}" == "PullRequest" ]]; then - DIFF_REF="remotes/origin/${SYSTEM_PULLREQUEST_TARGETBRANCH}" - elif [[ "${BUILD_REASON}" == *CI ]]; then + if [[ "${BUILD_REASON}" == *CI ]]; then DIFF_REF="HEAD^" else DIFF_REF=$("${ENVOY_SRCDIR}"/tools/git/last_github_commit.sh) diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index e0f204e67fcf..da5115db5274 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -83,7 +83,6 @@ docker run --rm \ -e ENVOY_BUILD_IMAGE \ -e ENVOY_SRCDIR \ -e ENVOY_BUILD_TARGET \ - -e SYSTEM_PULLREQUEST_TARGETBRANCH \ -e SYSTEM_PULLREQUEST_PULLREQUESTNUMBER \ -e GCS_ARTIFACT_BUCKET \ -e GITHUB_TOKEN \ diff --git a/ci/upload_gcs_artifact.sh b/ci/upload_gcs_artifact.sh index 755abf3a39d5..aea9dbc3f24f 100755 --- a/ci/upload_gcs_artifact.sh +++ b/ci/upload_gcs_artifact.sh @@ -18,9 +18,28 @@ if [ ! -d "${SOURCE_DIRECTORY}" ]; then exit 1 fi -BRANCH=${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}} -GCS_LOCATION="${GCS_ARTIFACT_BUCKET}/${BRANCH}/${TARGET_SUFFIX}" +if [[ "$BUILD_REASON" == "PullRequest" ]]; then + # non-master upload to the last commit sha (first 7 chars) in the developers branch + UPLOAD_PATH="$(git log --pretty=%P -n 1 | cut -d' ' -f2 | head -c7)" +else + UPLOAD_PATH="${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}}" +fi + +GCS_LOCATION="${GCS_ARTIFACT_BUCKET}/${UPLOAD_PATH}/${TARGET_SUFFIX}" echo "Uploading to gs://${GCS_LOCATION} ..." gsutil -mq rsync -dr "${SOURCE_DIRECTORY}" "gs://${GCS_LOCATION}" + +# For PR uploads, add a redirect `PR_NUMBER` -> `COMMIT_SHA` +if [[ "$BUILD_REASON" == "PullRequest" ]]; then + REDIRECT_PATH="${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}}" + TMP_REDIRECT="/tmp/redirect/${REDIRECT_PATH}/${TARGET_SUFFIX}" + mkdir -p "$TMP_REDIRECT" + echo "" \ + > "${TMP_REDIRECT}/index.html" + GCS_REDIRECT="${GCS_ARTIFACT_BUCKET}/${REDIRECT_PATH}/${TARGET_SUFFIX}" + echo "Uploading redirect to gs://${GCS_REDIRECT} ..." + gsutil -h "Cache-Control:no-cache,max-age=0" -mq rsync -dr "${TMP_REDIRECT}" "gs://${GCS_REDIRECT}" +fi + echo "Artifacts uploaded to: https://storage.googleapis.com/${GCS_LOCATION}/index.html" diff --git a/configs/BUILD b/configs/BUILD index 9e2b73195368..064ce28fd94c 100644 --- a/configs/BUILD +++ b/configs/BUILD @@ -31,11 +31,15 @@ filegroup( "using_deprecated_config.yaml", "**/*.template.yaml", "freebind/freebind.yaml", + "envoy-demo.yaml", ], ) + select({ - "//bazel:apple": [], + "//bazel:apple": ["envoy-demo.yaml"], "//bazel:windows_x86_64": [], - "//conditions:default": ["freebind/freebind.yaml"], + "//conditions:default": [ + "envoy-demo.yaml", + "freebind/freebind.yaml", + ], }), ) diff --git a/configs/Dockerfile b/configs/Dockerfile index bae9778af9f3..8167a41d4dd4 100644 --- a/configs/Dockerfile +++ b/configs/Dockerfile @@ -3,5 +3,5 @@ FROM envoyproxy/envoy-dev:latest RUN apt-get update -COPY envoyproxy_io_proxy.yaml /etc/envoy.yaml +COPY envoy-demo.yaml /etc/envoy.yaml CMD /usr/local/bin/envoy -c /etc/envoy.yaml diff --git a/configs/envoy-demo.yaml b/configs/envoy-demo.yaml new file mode 100644 index 000000000000..287e7faa42f5 --- /dev/null +++ b/configs/envoy-demo.yaml @@ -0,0 +1,63 @@ +admin: + access_log_path: /tmp/admin_access.log + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + 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: ingress_http + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + # For the demo config in the Docker container we use: + # - system logs -> `/dev/stderr` + # - (listener) access_logs -> `/dev/stdout` + path: /dev/stdout + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + http_filters: + - name: envoy.filters.http.router + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: www.envoyproxy.io diff --git a/configs/google-vrp/supervisor.conf b/configs/google-vrp/supervisor.conf index e019581d079c..1e1d09f33660 100644 --- a/configs/google-vrp/supervisor.conf +++ b/configs/google-vrp/supervisor.conf @@ -1,5 +1,6 @@ [supervisord] nodaemon=true +logfile=/var/log/supervisor/supervisord.log [program:envoy-edge] command=launch_envoy.sh -c /etc/envoy/envoy-edge.yaml %(ENV_ENVOY_EDGE_EXTRA_ARGS)s diff --git a/docs/generate_extension_db.py b/docs/generate_extension_db.py index 3a04aac76705..c6261977696e 100755 --- a/docs/generate_extension_db.py +++ b/docs/generate_extension_db.py @@ -63,5 +63,7 @@ def GetExtensionMetadata(target): '//source/extensions/transport_sockets/tls:config') extension_db['envoy.upstreams.http.generic'] = GetExtensionMetadata( '//source/extensions/upstreams/http/generic:config') + extension_db['envoy.upstreams.tcp.generic'] = GetExtensionMetadata( + '//source/extensions/upstreams/tcp/generic:config') pathlib.Path(output_path).write_text(json.dumps(extension_db)) diff --git a/docs/protodoc_manifest.yaml b/docs/protodoc_manifest.yaml index 2e2afff3264d..ecdf19115a34 100644 --- a/docs/protodoc_manifest.yaml +++ b/docs/protodoc_manifest.yaml @@ -42,6 +42,9 @@ fields: envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.stream_idle_timeout: edge_config: example: 300s # 5 mins + envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.request_headers_timeout: + edge_config: + example: 10s # 10 seconds envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.request_timeout: edge_config: note: > diff --git a/docs/requirements.txt b/docs/requirements.txt index c8e98061b50e..edff23bf3823 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -116,9 +116,9 @@ sphinxcontrib-jsmath==1.0.1 \ sphinxcontrib-qthelp==1.0.3 \ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 -sphinxext-rediraffe==0.2.4 \ - --hash=sha256:5428fb614d1fbc16964ba587aaa6b1c8ec92fd0b1d01bb6b369637446f43a27d \ - --hash=sha256:13e6474342df6643723976a3429edfc5e811e9f48b9f832c9fb6bdd9fe53fd83 +sphinxext-rediraffe==0.2.5 \ + --hash=sha256:7b706284d20602acecc1783cc58c8d0543937af1ee53f912bfdc4b258a7e649a \ + --hash=sha256:a17287cdee7763341b91762879e042b33a4916d6a2fc6d2f97a18107325bd2cc sphinxcontrib-serializinghtml==1.1.4 \ --hash=sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc \ --hash=sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a diff --git a/docs/root/_static/css/envoy.css b/docs/root/_static/css/envoy.css index 8021e5df6f21..ade3cb1b16a1 100644 --- a/docs/root/_static/css/envoy.css +++ b/docs/root/_static/css/envoy.css @@ -23,3 +23,8 @@ table.docutils div.line-block { margin-left: 4px; padding: 4px; } + +/* make inline sidebars flow down the right of page */ +.rst-content .sidebar { + clear: right; +} diff --git a/docs/root/_static/envoy-admin.png b/docs/root/_static/envoy-admin.png new file mode 100644 index 000000000000..d1063866a07c Binary files /dev/null and b/docs/root/_static/envoy-admin.png differ diff --git a/docs/root/api-v3/config/accesslog/accesslog.rst b/docs/root/api-v3/config/accesslog/accesslog.rst index e6839c5059c8..dae49773f788 100644 --- a/docs/root/api-v3/config/accesslog/accesslog.rst +++ b/docs/root/api-v3/config/accesslog/accesslog.rst @@ -1,3 +1,5 @@ +.. _api-v3_config_accesslog: + Access loggers ============== diff --git a/docs/root/api-v3/config/config.rst b/docs/root/api-v3/config/config.rst index 3d87b90373eb..db70d213b89f 100644 --- a/docs/root/api-v3/config/config.rst +++ b/docs/root/api-v3/config/config.rst @@ -1,3 +1,5 @@ +.. _api-v3_config: + Extensions ========== diff --git a/docs/root/api-v3/config/upstream/upstream.rst b/docs/root/api-v3/config/upstream/upstream.rst index 5047eaa92b28..49f3cf9a6db9 100644 --- a/docs/root/api-v3/config/upstream/upstream.rst +++ b/docs/root/api-v3/config/upstream/upstream.rst @@ -6,3 +6,4 @@ Upstream Configuration :maxdepth: 3 ../../extensions/upstreams/http/*/v3/** + ../../extensions/upstreams/tcp/*/v3/** diff --git a/docs/root/api/client_features.rst b/docs/root/api/client_features.rst index 4cd6594f0bdc..07f0bd259a53 100644 --- a/docs/root/api/client_features.rst +++ b/docs/root/api/client_features.rst @@ -23,3 +23,7 @@ Currently Defined Client Features - **envoy.lrs.supports_send_all_clusters**: This feature indicates that the client supports the *envoy_api_field_service.load_stats.v2.LoadStatsResponse.send_all_clusters* field in the LRS response. +- **xds.config.supports-resource-ttl**: This feature indicates that xDS client supports + per-resource or per SotW :ref:`TTL `. +- **xds.config.resource-in-sotw**: This feature indicates that the xDS client is able to unwrap + *Resource* wrappers within the SotW DiscoveryResponse. diff --git a/docs/root/configuration/http/http_conn_man/response_code_details.rst b/docs/root/configuration/http/http_conn_man/response_code_details.rst index 350c0767f93f..898b8523f185 100644 --- a/docs/root/configuration/http/http_conn_man/response_code_details.rst +++ b/docs/root/configuration/http/http_conn_man/response_code_details.rst @@ -26,6 +26,7 @@ Below are the list of reasons the HttpConnectionManager or Router filter may sen duration_timeout, The max connection duration was exceeded. direct_response, A direct response was generated by the router filter. filter_chain_not_found, The request was rejected due to no matching filter chain. + filter_removed_required_headers, The request was rejected in the filter manager because a configured filter removed required headers. internal_redirect, The original stream was replaced with an internal redirect. low_version, The HTTP/1.0 or HTTP/0.9 request was rejected due to HTTP/1.0 support not being configured. maintenance_mode, The request was rejected by the router filter because the cluster was in maintenance mode. diff --git a/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml b/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml index d9f51b804d48..a7a01517b3b1 100644 --- a/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml +++ b/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml @@ -40,7 +40,7 @@ static_resources: http_filters: - name: envoy.filters.http.dynamic_forward_proxy typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.dynamic_forward_proxy.v2alpha.FilterConfig + "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig dns_cache_config: name: dynamic_forward_proxy_cache_config dns_lookup_family: V4_ONLY diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 3f067fdb7ea0..84dc87a8b63f 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -124,8 +124,9 @@ follow: .. code-block:: yaml - per_filter_config: + typed_per_filter_config: envoy.filters.http.lua: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute disabled: true We can also refer to a Lua script in the filter configuration by specifying a name in LuaPerRoute. @@ -133,8 +134,9 @@ The ``GLOBAL`` Lua script will be overridden by the referenced script: .. code-block:: yaml - per_filter_config: + typed_per_filter_config: envoy.filters.http.lua: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute name: hello.lua .. attention:: @@ -148,8 +150,9 @@ Lua script as follows: .. code-block:: yaml - per_filter_config: + typed_per_filter_config: envoy.filters.http.lua: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute source_code: inline_string: | function envoy_on_response(response_handle) diff --git a/docs/root/configuration/http/http_filters/oauth2_filter.rst b/docs/root/configuration/http/http_filters/oauth2_filter.rst index acbdda6780c7..6b8b9789a5c7 100644 --- a/docs/root/configuration/http/http_filters/oauth2_filter.rst +++ b/docs/root/configuration/http/http_filters/oauth2_filter.rst @@ -7,6 +7,35 @@ OAuth2 * :ref:`v3 API reference ` * This filter should be configured with the name *envoy.filters.http.oauth2*. +The OAuth filter's flow involves: + +* An unauthenticated user arrives at myapp.com, and the oauth filter redirects them to the + :ref:`authorization_endpoint ` + for login. The :ref:`client_id ` + and the :ref:`redirect_uri ` + are sent as query string parameters in this first redirect. +* After a successful login, the authn server should be configured to redirect the user back to the + :ref:`redirect_uri ` + provided in the query string in the first step. In the below code example, we choose /callback as the configured match path. + An "authorization grant" is included in the query string for this second redirect. +* Using this new grant and the :ref:`token_secret `, + the filter then attempts to retrieve an access token from + the :ref:`token_endpoint `. The filter knows it has to do this + instead of reinitiating another login because the incoming request has a path that matches the + :ref:`redirect_path_matcher ` criteria. +* Upon receiving an access token, the filter sets cookies so that subseqeuent requests can skip the full + flow. These cookies are calculated using the + :ref:`hmac_secret ` + to assist in encoding. +* The filter calls continueDecoding() to unblock the filter chain. + +When the authn server validates the client and returns an authorization token back to the OAuth filter, +no matter what format that token is, if +:ref:`forward_bearer_token ` +is set to true the filter will send over a +cookie named `BearerToken` to the upstream. Additionally, the `Authorization` header will be populated +with the same value. + .. attention:: The OAuth2 filter is currently under active development. @@ -43,7 +72,7 @@ The following is an example configuring the filter. sds_config: path: "/etc/envoy/hmac.yaml" -And the below code block is an example of how we employ it as one of +Below is a complete code example of how we employ the filter as one of :ref:`HttpConnectionManager HTTP filters ` @@ -86,10 +115,34 @@ And the below code block is an example of how we employ it as one of sds_config: path: "/etc/envoy/hmac.yaml" - name: envoy.router + tracing: {} + codec_type: "AUTO" + stat_prefix: ingress_http + route_config: + virtual_hosts: + - name: service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: service + timeout: 5s clusters: - name: service - # ... + connect_timeout: 5s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 - name: auth connect_timeout: 5s type: LOGICAL_DNS @@ -106,13 +159,44 @@ And the below code block is an example of how we employ it as one of tls_context: sni: auth.example.com +Finally, the following code block illustrates sample contents inside a yaml file containing both credential secrets. +Both the :ref:`token_secret ` +and the :ref:`hmac_secret ` +can be defined in one shared file. + +.. code-block:: yaml + + static_resources: + secrets: + - name: token + generic_secret: + secret: + - name: hmac + generic_secret: + secret: + + Notes ----- -This module does not currently provide much Cross-Site-Request-Forgery protection for the redirect -loop to the OAuth server and back. +When enabled, the OAuth filter does not protect against Cross-Site-Request-Forgery attacks on domains with +cached authentication (in the form of cookies). +It is recommended to pair this filter with the :ref:`CSRF Filter ` +to prevent malicious social engineering. + +The service must be served over HTTPS for this filter to work properly, as the cookies use `;secure`. Without https, your +:ref:`authorization_endpoint ` +provider will likely reject the incoming request, and your access cookies will not be cached to bypass future logins. + +The signout path will redirect the current user to '/', and clear all authentication cookies related to +the HMAC validation. Consequently, the OAuth filter will then restart the full OAuth flow at the root path, +sending the user to the configured auth endpoint. + +:ref:`pass_through_matcher ` provides +an interface for users to provide specific header matching criteria such that, when applicable, the OAuth flow is entirely skipped. +When this occurs, the `oauth_success` metric is still incremented. -The service must be served over HTTPS for this filter to work, as the cookies use `;secure`. +Generally, allowlisting is inadvisable from a security standpoint. Statistics ---------- diff --git a/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml b/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml index 5fde76139391..02a631626071 100644 --- a/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml +++ b/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml @@ -8,6 +8,7 @@ admin: static_resources: listeners: - name: listener_0 + reuse_port: true address: socket_address: protocol: UDP diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index 75c0630285fa..73ca330f7333 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -187,6 +187,10 @@ The following command operators are supported: HTTP response code. Note that a response code of '0' means that the server never sent the beginning of a response. This generally means that the (downstream) client disconnected. + Note that in the case of 100-continue responses, only the response code of the final headers + will be logged. If a 100-continue is followed by a 200, the logged response will be 200. + If a 100-continue results in a disconnect, the 100 will be logged. + TCP Not implemented ("-"). diff --git a/docs/root/configuration/overview/xds_api.rst b/docs/root/configuration/overview/xds_api.rst index 428575afda8f..3217c000b209 100644 --- a/docs/root/configuration/overview/xds_api.rst +++ b/docs/root/configuration/overview/xds_api.rst @@ -348,3 +348,25 @@ To use delta, simply set the api_type field of your That works for both xDS and ADS; for ADS, it's the api_type field of :ref:`DynamicResources.ads_config `, as described in the previous section. + + +.. _config_overview_ttl: + +xDS TTL +^^^^^^^ + +When using xDS, users might find themself wanting to temporarily update certain xDS resources. In order to do +safely, xDS TTLs can be used to make sure that if the control plane becomes unavailable and is unable to revert +the xDS change, Envoy will remove the resource after a TTL specified by the server. See the +:ref:`protocol documentation ` for more information. + +Currently the behavior when a TTL expires is that the resource is *removed* (as opposed to reverted to the +previous version). As such, this feature should primarily be used for use cases where the absence of the resource +is preferred instead of the temporary version, e.g. when using RTDS to apply a temporary runtime override. + +The TTL is specified on the :ref:`Resource ` proto: for Delta xDS this is specified directly +within the response, while for SotW xDS the server may wrap individual resources listed in the response within a +:ref:`Resource ` in order to specify a TTL value. + +The server can refresh or modify the TTL by issuing another response for the same version. In this case the resource +itself does not have to be included. \ No newline at end of file diff --git a/docs/root/configuration/security/secret.rst b/docs/root/configuration/security/secret.rst index 087cf388b759..5ad3650cc19e 100644 --- a/docs/root/configuration/security/secret.rst +++ b/docs/root/configuration/security/secret.rst @@ -33,6 +33,30 @@ SDS Configuration *SdsSecretConfig* is used in two fields in :ref:`CommonTlsContext `. The first field is *tls_certificate_sds_secret_configs* to use SDS to get :ref:`TlsCertificate `. The second field is *validation_context_sds_secret_config* to use SDS to get :ref:`CertificateValidationContext `. +.. _sds_key_rotation: + +Key rotation +------------ + +It's usually preferrable to perform key rotation via gRPC SDS, but when this is not possible or +desired (e.g. during bootstrap of SDS credentials), SDS allows for filesystem rotation when secrets +refer to filesystem paths. This currently is supported for the following secret types: + +* :ref:`TlsCertificate ` +* :ref:`CertificateValidationContext ` + +By default, directories containing secrets are watched for filesystem move events. For example, a +key or trusted CA certificates at ``/foo/bar/baz/cert.pem`` will be watched at `/foo/bar/baz`. +Explicit control over the watched directory is possible by specifying a *watched_directory* path in +:ref:`TlsCertificate +` and +:ref:`CertificateValidationContext +`. +This allows watches to be established at path predecessors, e.g. ``/foo/bar``; this capability is +useful when implementing common key rotation schemes. + +An example of key rotation is provided :ref:`below `. + Example one: static_resource ----------------------------- @@ -211,9 +235,11 @@ In contrast, :ref:`sds_server_example` requires a restart to reload xDS certific "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext" common_tls_context: tls_certificate_sds_secret_configs: + name: tls_sds sds_config: path: /etc/envoy/tls_certificate_sds_secret.yaml validation_context_sds_secret_config: + name: validation_context_sds sds_config: path: /etc/envoy/validation_context_sds_secret.yaml @@ -239,6 +265,39 @@ Path to CA certificate bundle for validating the xDS server certificate is given trusted_ca: filename: /certs/cacert.pem +In the above example, a watch will be established on ``/certs``. File movement in this directory +will trigger an update. An alternative common key rotation scheme that provides improved atomicity +is to establish an active symlink ``/certs/current`` and use an atomic move operation to replace the +symlink. The watch in this case needs to be on the certificate's grandparent directory. Envoy +supports this scheme via the use of *watched_directory*. Continuing the above examples: + +.. code-block:: yaml + + resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + tls_certificate: + certificate_chain: + filename: /certs/current/sds_cert.pem + private_key: + filename: /certs/current/sds_key.pem + watched_directory: + path: /certs + +.. code-block:: yaml + + resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + validation_context: + trusted_ca: + filename: /certs/current/cacert.pem + watched_directory: + path: /certs + +Secret rotation can be performed with: + +.. code-block:: bash + + ln -s /certs/new && mv -Tf /certs/new /certs/current Statistics ---------- @@ -261,3 +320,12 @@ For upstream clusters, they are in the *cluster..client_ssl_socket ssl_context_update_by_sds, Total number of ssl context has been updated. upstream_context_secrets_not_ready, Total number of upstream connections reset due to empty ssl certificate. + +SDS has a :ref:`statistics ` tree rooted in the *sds..* +namespace. In addition, the following statistics are tracked in this namespace: + +.. csv-table:: + :header: Name, Description + :widths: 1, 2 + + key_rotation_failed, Total number of filesystem key rotations that failed outside of an SDS update. diff --git a/docs/root/faq/configuration/timeouts.rst b/docs/root/faq/configuration/timeouts.rst index bdf42877c33b..2b44ce135346 100644 --- a/docs/root/faq/configuration/timeouts.rst +++ b/docs/root/faq/configuration/timeouts.rst @@ -50,6 +50,12 @@ context request/stream is interchangeable. This timeout is not enforced by default as it is not compatible with streaming requests (requests that never end). See the stream idle timeout that follows. However, if using the :ref:`buffer filter `, it is recommended to configure this timeout. +* The HTTP connection manager :ref:`request_headers_timeout + ` + determines the amount of time the client has to send *only the headers* on the request stream + before the stream is cancelled. This can be used to prevent clients from consuming too much + memory by creating large numbers of mostly-idle streams waiting for headers. The request header + timeout is disabled by default. * The HTTP connection manager :ref:`stream_idle_timeout ` is the amount of time that the connection manager will allow a stream to exist with no upstream @@ -120,4 +126,4 @@ Transport Socket * The :ref:`transport_socket_connect_timeout ` specifies the amount of time Envoy will wait for a downstream client to complete transport-level negotiations. When configured on a filter chain with a TLS or ALTS transport socket, this limits - the amount of time allowed to finish the encrypted handshake after establishing a TCP connection. \ No newline at end of file + the amount of time allowed to finish the encrypted handshake after establishing a TCP connection. diff --git a/docs/root/intro/arch_overview/advanced/advanced.rst b/docs/root/intro/arch_overview/advanced/advanced.rst index c26e0fee562c..b8fa3a05d102 100644 --- a/docs/root/intro/arch_overview/advanced/advanced.rst +++ b/docs/root/intro/arch_overview/advanced/advanced.rst @@ -5,3 +5,4 @@ Advanced :maxdepth: 2 data_sharing_between_filters + attributes diff --git a/docs/root/intro/arch_overview/advanced/attributes.rst b/docs/root/intro/arch_overview/advanced/attributes.rst new file mode 100644 index 000000000000..767a75b2d4c4 --- /dev/null +++ b/docs/root/intro/arch_overview/advanced/attributes.rst @@ -0,0 +1,191 @@ +.. _arch_overview_attributes: + +Attributes +========== + +Attributes refer to contextual properties provided by Envoy during request and +connection processing. They are named by a dot-separated path (e.g. +`request.path`), have a fixed type (e.g. `string` or `int`), and may be +absent or present depending on the context. Attributes are exposed to CEL +runtime in :ref:`RBAC filter `, as well as Wasm extensions +via `get_property` ABI method. + +Attribute value types are limited to: + +* `string` for UTF-8 strings +* `bytes` for byte buffers +* `int` for 64-bit signed integers +* `uint` for 64-bit unsigned integers +* `bool` for booleans +* `list` for lists of values +* `map` for associative arrays with string keys +* `timestamp` for timestamps as specified by `Timestamp `_ +* `duration` for durations as specified by `Duration `_ +* Protocol buffer message types + +CEL provides standard helper functions for operating on abstract types such as +`getMonth` for `timestamp` values. Note that integer literals (e.g. `7`) are of +type `int`, which is distinct from `uint` (e.g. `7u`), and the arithmetic +conversion is not automatic (use `uint(7)` for explicit conversion). + +Wasm extensions receive the attribute values as a serialized buffer according +to the type of the attribute. Strings and bytes are passed as-is, integers are +passed as 64 bits directly, timestamps and durations are approximated to +nano-seconds, and structured values are converted to a sequence of pairs +recursively. + +.. _arch_overview_request_attributes: + +Request attributes +------------------ + +The following request attributes are generally available upon initial request +processing, which makes them suitable for RBAC policies: + +.. csv-table:: + :header: Attribute, Type, Description + :escape: ' + :widths: 1, 1, 4 + + request.path, string, The path portion of the URL + request.url_path, string, The path portion of the URL without the query string + request.host, string, The host portion of the URL + request.scheme, string, The scheme portion of the URL e.g. "http" + request.method, string, Request method e.g. "GET" + request.headers, "map", All request headers indexed by the lower-cased header name + request.referer, string, Referer request header + request.useragent, string, User agent request header + request.time, timestamp, Time of the first byte received + request.id, string, Request ID corresponding to `x-request-id` header value + request.protocol, string, "Request protocol ('"HTTP/1.0'", '"HTTP/1.1'", '"HTTP/2'", or '"HTTP/3'")" + +Header values in `request.headers` associative array are comma-concatenated in case of multiple values. + +Additional attributes are available once the request completes: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + request.duration, duration, Total duration of the request + request.size, int, Size of the request body. Content length header is used if available. + request.total_size, int, Total size of the request including the approximate uncompressed size of the headers + +Response attributes +------------------- + +Response attributes are only available after the request completes. + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + response.code, int, Response HTTP status code + response.code_details, string, Internal response code details (subject to change) + response.flags, int, Additional details about the response beyond the standard response code encoded as a bit-vector + response.grpc_status, int, Response gRPC status code + response.headers, "map", All response headers indexed by the lower-cased header name + response.trailers, "map", All response trailers indexed by the lower-cased trailer name + response.size, int, Size of the response body + response.total_size, int, Total size of the response including the approximate uncompressed size of the headers and the trailers + +Connection attributes +--------------------- + +The following attributes are available once the downstream connection is +established (which also applies to HTTP requests making them suitable for +RBAC): + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + source.address, string, Downstream connection remote address + source.port, int, Downstream connection remote port + destination.address, string, Downstream connection local address + destination.port, int, Downstream connection local port + connection.id, uint, Downstream connection ID + connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented + connection.requested_server_name, string, Requested server name in the downstream TLS connection + connection.tls_version, string, TLS version of the downstream TLS connection + connection.subject_local_certificate, string, The subject field of the local certificate in the downstream TLS connection + connection.subject_peer_certificate, string, The subject field of the peer certificate in the downstream TLS connection + connection.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the downstream TLS connection + connection.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the downstream TLS connection + connection.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the downstream TLS connection + connection.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the downstream TLS connection + +The following additional attributes are available upon the downstream connection termination: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + connection.termination_details, string, Internal termination details of the connection (subject to change) + +Upstream attributes +------------------- + +The following attributes are available once the upstream connection is established: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + upstream.address, string, Upstream connection remote address + upstream.port, int, Upstream connection remote port + upstream.tls_version, string, TLS version of the upstream TLS connection + upstream.subject_local_certificate, string, The subject field of the local certificate in the upstream TLS connection + upstream.subject_peer_certificate, string, The subject field of the peer certificate in the upstream TLS connection + upstream.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the upstream TLS connection + upstream.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the upstream TLS connection + upstream.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the upstream TLS connection + upstream.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the upstream TLS connection + upstream.local_address, string, The local address of the upstream connection + upstream.transport_failure_reason, string, The upstream transport failure reason e.g. certificate validation failed + +Metadata and filter state +------------------------- + +Data exchanged between filters is available as the following attributes: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + metadata, :ref:`Metadata`, Dynamic request metadata + filter_state, "map", Mapping from a filter state name to its serialized string value + +Note that these attributes may change during the life of a request as the data can be +updated by filters at any point. + +Wasm attributes +--------------- + +In addition to all above, the following extra attributes are available to Wasm extensions: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + plugin_name, string, Plugin name + plugin_root_id, string, Plugin root ID + plugin_vm_id, string, Plugin VM ID + node, :ref:`Node`, Local node description + cluster_name, string, Upstream cluster name + cluster_metadata, :ref:`Metadata`, Upstream cluster metadata + listener_direction, int, Enumeration value of the :ref:`listener traffic direction` + listener_metadata, :ref:`Metadata`, Listener metadata + route_name, string, Route name + route_metadata, :ref:`Metadata`, Route metadata + upstream_host_metadata, :ref:`Metadata`, Upstream host metadata + +Path expressions +---------------- + +Path expressions allow access to inner fields in structured attributes via a +sequence of field names, map, and list indexes following an attribute name. For +example, `get_property({"node", "id"})` in Wasm ABI extracts the value of `id` +field in `node` message attribute, while `get_property({"request", "headers", +"my-header"})` refers to the comma-concatenated value of a particular request +header. diff --git a/docs/root/intro/arch_overview/listeners/dns_filter.rst b/docs/root/intro/arch_overview/listeners/dns_filter.rst index 87295a8d0950..3e4c9d3cd35d 100644 --- a/docs/root/intro/arch_overview/listeners/dns_filter.rst +++ b/docs/root/intro/arch_overview/listeners/dns_filter.rst @@ -1,3 +1,5 @@ +.. _arch_overview_dns_filter: + DNS Filter ========== diff --git a/docs/root/intro/arch_overview/operations/dynamic_configuration.rst b/docs/root/intro/arch_overview/operations/dynamic_configuration.rst index e5d026c88f50..4b0fe43f5140 100644 --- a/docs/root/intro/arch_overview/operations/dynamic_configuration.rst +++ b/docs/root/intro/arch_overview/operations/dynamic_configuration.rst @@ -138,3 +138,9 @@ a resource from an update implying that the resource is gone. Envoy supports a " xDS (including ADS), where updates only contain resources added/changed/removed. Delta xDS is a new protocol, with request/response APIs different from SotW. :ref:`More details about delta `. + +xDS TTL +------- + +Certain xDS updates might want to set a TTL to guard against control plane unavailability, read more +:ref:`here `. \ No newline at end of file diff --git a/docs/root/intro/arch_overview/security/rbac_filter.rst b/docs/root/intro/arch_overview/security/rbac_filter.rst index b12d568a25ad..2826a7c3ae2d 100644 --- a/docs/root/intro/arch_overview/security/rbac_filter.rst +++ b/docs/root/intro/arch_overview/security/rbac_filter.rst @@ -58,63 +58,8 @@ whether the request path starts with `/v1/`: - const_expr: string_value: /v1/ -The following attributes are exposed to the language runtime: +Envoy provides a number of :ref:`request attributes ` +for expressive policies. Most attributes are optional and provide the default +value based on the type of the attribute. CEL supports presence checks for +attributes and maps using `has()` syntax, e.g. `has(request.referer)`. -.. csv-table:: - :header: Attribute, Type, Description - :widths: 1, 1, 2 - - request.path, string, The path portion of the URL - request.url_path, string, The path portion of the URL without the query string - request.host, string, The host portion of the URL - request.scheme, string, The scheme portion of the URL - request.method, string, Request method - request.headers, string map, All request headers - request.referer, string, Referer request header - request.useragent, string, User agent request header - request.time, timestamp, Time of the first byte received - request.duration, duration, Total duration of the request - request.id, string, Request ID - request.size, int, Size of the request body - request.total_size, int, Total size of the request including the headers - request.protocol, string, Request protocol e.g. "HTTP/2" - response.code, int, Response HTTP status code - response.code_details, string, Internal response code details (subject to change) - response.grpc_status, int, Response gRPC status code - response.headers, string map, All response headers - response.trailers, string map, All response trailers - response.size, int, Size of the response body - response.total_size, int, Total size of the response including the approximate uncompressed size of the headers and the trailers - response.flags, int, Additional details about the response beyond the standard response code - source.address, string, Downstream connection remote address - source.port, int, Downstream connection remote port - destination.address, string, Downstream connection local address - destination.port, int, Downstream connection local port - metadata, :ref:`Metadata`, Dynamic metadata - filter_state, map string to bytes, Filter state mapping data names to their serialized string value - connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented - connection.requested_server_name, string, Requested server name in the downstream TLS connection - connection.tls_version, string, TLS version of the downstream TLS connection - connection.subject_local_certificate, string, The subject field of the local certificate in the downstream TLS connection - connection.subject_peer_certificate, string, The subject field of the peer certificate in the downstream TLS connection - connection.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the downstream TLS connection - connection.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the downstream TLS connection - connection.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the downstream TLS connection - connection.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the downstream TLS connection - connection.id, uint, Downstream connection ID - upstream.address, string, Upstream connection remote address - upstream.port, int, Upstream connection remote port - upstream.tls_version, string, TLS version of the upstream TLS connection - upstream.subject_local_certificate, string, The subject field of the local certificate in the upstream TLS connection - upstream.subject_peer_certificate, string, The subject field of the peer certificate in the upstream TLS connection - upstream.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the upstream TLS connection - upstream.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the upstream TLS connection - upstream.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the upstream TLS connection - upstream.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the upstream TLS connection - upstream.local_address, string, The local address of the upstream connection - upstream.transport_failure_reason, string, The upstream transport failure reason e.g. certificate validation failed - - -Most attributes are optional and provide the default value based on the type of the attribute. -CEL supports presence checks for attributes and maps using `has()` syntax, e.g. -`has(request.referer)`. diff --git a/docs/root/intro/arch_overview/upstream/dns_resolution.rst b/docs/root/intro/arch_overview/upstream/dns_resolution.rst new file mode 100644 index 000000000000..def8b33841ea --- /dev/null +++ b/docs/root/intro/arch_overview/upstream/dns_resolution.rst @@ -0,0 +1,24 @@ +.. _arch_overview_dns_resolution: + +DNS Resolution +============== + +Many Envoy components resolve DNS: different cluster types ( +:ref:`strict dns `, +:ref:`logical dns `); +the :ref:`dynamic forward proxy ` system (which is +composed of a cluster and a filter); +the udp :ref:`dns filter `, etc. +Envoy uses `c-ares `_ as a third party DNS resolution library. +On Apple OSes Envoy additionally offers resolution using Apple specific APIs via the +``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime feature. + +The Apple-based DNS Resolver emits the following stats rooted in the ``dns.apple`` stats tree: + + .. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + connection_failure, Counter, Number of failed attempts to connect to the DNS server + socket_failure, Counter, Number of failed attempts to obtain a file descriptor to the socket to the DNS server + processing_failure, Counter, Number of failures when processing data from the DNS server \ No newline at end of file diff --git a/docs/root/intro/arch_overview/upstream/upstream.rst b/docs/root/intro/arch_overview/upstream/upstream.rst index 3c976f0212c3..0694cb533c6a 100644 --- a/docs/root/intro/arch_overview/upstream/upstream.rst +++ b/docs/root/intro/arch_overview/upstream/upstream.rst @@ -6,6 +6,7 @@ Upstream clusters cluster_manager service_discovery + dns_resolution health_checking connection_pooling load_balancing/load_balancing diff --git a/docs/root/operations/admin.rst b/docs/root/operations/admin.rst index 1816322afcfe..ae2dd425a28e 100644 --- a/docs/root/operations/admin.rst +++ b/docs/root/operations/admin.rst @@ -324,6 +324,8 @@ modify different aspects of the server: traffic direction are stopped, listener additions and modifications in that direction are not allowed. +.. _operations_admin_interface_server_info: + .. http:get:: /server_info Outputs a JSON message containing information about the running server. diff --git a/docs/root/operations/cli.rst b/docs/root/operations/cli.rst index cb2253321ae1..d4f6f2808213 100644 --- a/docs/root/operations/cli.rst +++ b/docs/root/operations/cli.rst @@ -116,9 +116,6 @@ following are the command line options that Envoy supports. *(optional)* The format string to use for laying out the log message metadata. If this is not set, a default format string ``"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"`` is used. - When used in conjunction with :option:`--log-format-prefix-with-location` set to 1, the logger can be - configured to prefix ``%v`` by a file path and a line number. - When used in conjunction with :option:`--log-format-escaped`, the logger can be configured to log in a format that is parsable by log viewers. Known integrations are documented in the :ref:`application logging configuration ` section. @@ -126,6 +123,7 @@ following are the command line options that Envoy supports. The supported format flags are (with example output): :%v: The actual message to log ("some user text") + :%_: The actual message to log, but with escaped newlines (from (if using ``%v``) "some user text\nbelow", to "some user text\\nbelow") :%t: Thread id ("1232") :%P: Process id ("3456") :%n: Logger's name ("filter") @@ -160,14 +158,6 @@ following are the command line options that Envoy supports. :%#: Source line ("123") :%!: Source function ("myFunc") -.. option:: --log-format-prefix-with-location <1|0> - - *(optional)* This temporary flag allows replacing all entries of ``"%v"`` in the log format by - ``"[%g:%#] %v"``. This flag is provided for migration purposes only. If this is not set, a - default value 0 is used. - - **NOTE**: The flag will be removed at 1.17.0 release. - .. option:: --log-format-escaped *(optional)* This flag enables application log sanitization to escape C-style escape sequences. @@ -188,8 +178,8 @@ following are the command line options that Envoy supports. *(optional)* Enables fine-grain logger with file level log control and runtime update at administration interface. If enabled, main log macros including `ENVOY_LOG`, `ENVOY_CONN_LOG`, `ENVOY_STREAM_LOG` and - `ENVOY_FLUSH_LOG` will use a per-file logger, and the usage doesn't need `Envoy::Logger::Loggable` any - more. The administration interface usage is similar. Please see `Administration interface + `ENVOY_FLUSH_LOG` will use a per-file logger, and the usage doesn't need `Envoy::Logger::Loggable` any + more. The administration interface usage is similar. Please see `Administration interface `_ for more detail. .. option:: --socket-path diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml new file mode 100644 index 000000000000..ab581b1ec0cf --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml @@ -0,0 +1,69 @@ +static_resources: + + listeners: + - 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: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + require_client_certificate: true + common_tls_context: + validation_context: + trusted_ca: + filename: certs/cacert.pem + match_subject_alt_names: + - exact: proxy-postgres-frontend.example.com + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/clientcert.pem + private_key: + filename: certs/clientkey.pem diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-sni.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-sni.yaml new file mode 100644 index 000000000000..68e7ab7569ed --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls-sni.yaml @@ -0,0 +1,61 @@ +static_resources: + + listeners: + - 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: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + filter_chain_match: + server_names: + - my-service-name.example.com + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: www.envoyproxy.io diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml new file mode 100644 index 000000000000..4260b1fca986 --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml @@ -0,0 +1,53 @@ +static_resources: + + listeners: + - 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: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + validation_context: + trusted_ca: + filename: certs/cacert.pem + match_subject_alt_names: + - exact: proxy-postgres-backend.example.com diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls.yaml new file mode 100644 index 000000000000..2bcf2acf2cb1 --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls.yaml @@ -0,0 +1,57 @@ +static_resources: + + listeners: + - 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: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/docs/root/start/quick-start/_include/envoy-demo.yaml b/docs/root/start/quick-start/_include/envoy-demo.yaml index b3697360e120..6419deef60c5 100644 --- a/docs/root/start/quick-start/_include/envoy-demo.yaml +++ b/docs/root/start/quick-start/_include/envoy-demo.yaml @@ -12,6 +12,11 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout http_filters: - name: envoy.filters.http.router route_config: @@ -46,10 +51,3 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: www.envoyproxy.io - -admin: - access_log_path: /dev/null - address: - socket_address: - address: 0.0.0.0 - port_value: 9901 diff --git a/docs/root/start/quick-start/admin.rst b/docs/root/start/quick-start/admin.rst new file mode 100644 index 000000000000..6b2534937fe6 --- /dev/null +++ b/docs/root/start/quick-start/admin.rst @@ -0,0 +1,267 @@ +.. _start_quick_start_admin: + +Envoy admin interface +===================== + +The optional admin interface provided by Envoy allows you to view configuration and statistics, change the +behaviour of the server, and tap traffic according to specific filter rules. + +.. note:: + + This guide provides configuration information, and some basic examples of using a couple of the admin + endpoints. + + See the :ref:`admin docs ` for information on all of the available endpoints. + +.. admonition:: Requirements + + Some of the examples below make use of the `jq `_ tool to parse the output + from the admin server. + +.. _start_quick_start_admin_config: + +``admin`` +--------- + +The :ref:`admin message ` is required to enable and configure +the administration server. + +The ``address`` key specifies the listening :ref:`address ` +which in the demo configuration is ``0.0.0.0:9901``. + +You must set the :ref:`access_log_path ` to +specify where to send admin access logs. + +In this example, the logs are simply discarded. + +.. code-block:: yaml + :emphasize-lines: 2, 5-6 + + admin: + access_log_path: /dev/null + address: + socket_address: + address: 0.0.0.0 + port_value: 9901 + +.. warning:: + + The Envoy admin endpoint can expose private information about the running service, allows modification + of runtime settings and can also be used to shut the server down. + + As the endpoint is not authenticated it is essential that you limit access to it. + + You may wish to restrict the network address the admin server listens to in your own deployment as part + of your strategy to limit access to this endpoint. + + +``stat_prefix`` +--------------- + +The Envoy +:ref:`HttpConnectionManager ` +must be configured with +:ref:`stat_prefix `. + +This provides a key that can be filtered when querying the stats interface +:ref:`as shown below ` + +In the :download:`envoy-demo.yaml <_include/envoy-demo.yaml>` the listener is configured with the +:ref:`stat_prefix ` +of ``ingress_http``. + +.. literalinclude:: _include/envoy-demo.yaml + :language: yaml + :linenos: + :lines: 1-29 + :emphasize-lines: 13-14 + +.. _start_quick_start_admin_config_dump: + +Admin endpoints: ``config_dump`` +-------------------------------- + +The :ref:`config_dump ` endpoint returns Envoy's runtime +configuration in ``json`` format. + +The following command allows you to see the types of configuration available: + +.. code-block:: console + + $ curl -s http://localhost:9901/config_dump | jq -r '.configs[] | .["@type"]' + type.googleapis.com/envoy.admin.v3.BootstrapConfigDump + type.googleapis.com/envoy.admin.v3.ClustersConfigDump + type.googleapis.com/envoy.admin.v3.ListenersConfigDump + type.googleapis.com/envoy.admin.v3.ScopedRoutesConfigDump + type.googleapis.com/envoy.admin.v3.RoutesConfigDump + type.googleapis.com/envoy.admin.v3.SecretsConfigDump + +To view the :ref:`socket_address ` of the first +:ref:`dynamic_listener ` currently configured, +you could: + +.. code-block:: console + + $ curl -s http://localhost:9901/config_dump?resource=dynamic_listeners | jq '.configs[0].active_state.listener.address' + { + "socket_address": { + "address": "0.0.0.0", + "port_value": 10000 + } + } + +.. note:: + + See the reference section for :ref:`config_dump ` for further information + on available parameters and responses. + +.. tip:: + + Enabling the :ref:`admin ` interface with + dynamic configuration can be particularly useful as it allows you to use the + :ref:`config_dump ` endpoint to see how Envoy is configured at + a particular point in time. + +.. _start_quick_start_admin_stats: + +Admin endpoints: ``stats`` +-------------------------- + +The :ref:`admin stats ` endpoint allows you to retrieve runtime information about Envoy. + +The stats are provided as ``key: value`` pairs, where the keys use a hierarchical dotted notation, +and the values are one of ``counter``, ``histogram`` or ``gauge`` types. + +To see the top-level categories of stats available, you can: + +.. code-block:: console + + $ curl -s http://localhost:9901/stats | cut -d. -f1 | sort | uniq + cluster + cluster_manager + filesystem + http + http1 + listener + listener_manager + main_thread + runtime + server + vhost + workers + +The stats endpoint accepts a :ref:`filter ` argument, which +is evaluated as a regular expression: + +.. code-block:: console + + $ curl -s http://localhost:9901/stats?filter='^http\.ingress_http' + http.ingress_http.downstream_cx_active: 0 + http.ingress_http.downstream_cx_delayed_close_timeout: 0 + http.ingress_http.downstream_cx_destroy: 3 + http.ingress_http.downstream_cx_destroy_active_rq: 0 + http.ingress_http.downstream_cx_destroy_local: 0 + http.ingress_http.downstream_cx_destroy_local_active_rq: 0 + http.ingress_http.downstream_cx_destroy_remote: 3 + http.ingress_http.downstream_cx_destroy_remote_active_rq: 0 + http.ingress_http.downstream_cx_drain_close: 0 + http.ingress_http.downstream_cx_http1_active: 0 + http.ingress_http.downstream_cx_http1_total: 3 + http.ingress_http.downstream_cx_http2_active: 0 + http.ingress_http.downstream_cx_http2_total: 0 + http.ingress_http.downstream_cx_http3_active: 0 + http.ingress_http.downstream_cx_http3_total: 0 + http.ingress_http.downstream_cx_idle_timeout: 0 + http.ingress_http.downstream_cx_max_duration_reached: 0 + http.ingress_http.downstream_cx_overload_disable_keepalive: 0 + http.ingress_http.downstream_cx_protocol_error: 0 + http.ingress_http.downstream_cx_rx_bytes_buffered: 0 + http.ingress_http.downstream_cx_rx_bytes_total: 250 + http.ingress_http.downstream_cx_ssl_active: 0 + http.ingress_http.downstream_cx_ssl_total: 0 + http.ingress_http.downstream_cx_total: 3 + http.ingress_http.downstream_cx_tx_bytes_buffered: 0 + http.ingress_http.downstream_cx_tx_bytes_total: 1117 + http.ingress_http.downstream_cx_upgrades_active: 0 + http.ingress_http.downstream_cx_upgrades_total: 0 + http.ingress_http.downstream_flow_control_paused_reading_total: 0 + http.ingress_http.downstream_flow_control_resumed_reading_total: 0 + http.ingress_http.downstream_rq_1xx: 0 + http.ingress_http.downstream_rq_2xx: 3 + http.ingress_http.downstream_rq_3xx: 0 + http.ingress_http.downstream_rq_4xx: 0 + http.ingress_http.downstream_rq_5xx: 0 + http.ingress_http.downstream_rq_active: 0 + http.ingress_http.downstream_rq_completed: 3 + http.ingress_http.downstream_rq_http1_total: 3 + http.ingress_http.downstream_rq_http2_total: 0 + http.ingress_http.downstream_rq_http3_total: 0 + http.ingress_http.downstream_rq_idle_timeout: 0 + http.ingress_http.downstream_rq_max_duration_reached: 0 + http.ingress_http.downstream_rq_non_relative_path: 0 + http.ingress_http.downstream_rq_overload_close: 0 + http.ingress_http.downstream_rq_response_before_rq_complete: 0 + http.ingress_http.downstream_rq_rx_reset: 0 + http.ingress_http.downstream_rq_timeout: 0 + http.ingress_http.downstream_rq_too_large: 0 + http.ingress_http.downstream_rq_total: 3 + http.ingress_http.downstream_rq_tx_reset: 0 + http.ingress_http.downstream_rq_ws_on_non_ws_route: 0 + http.ingress_http.no_cluster: 0 + http.ingress_http.no_route: 0 + http.ingress_http.passthrough_internal_redirect_bad_location: 0 + http.ingress_http.passthrough_internal_redirect_no_route: 0 + http.ingress_http.passthrough_internal_redirect_predicate: 0 + http.ingress_http.passthrough_internal_redirect_too_many_redirects: 0 + http.ingress_http.passthrough_internal_redirect_unsafe_scheme: 0 + http.ingress_http.rq_direct_response: 0 + http.ingress_http.rq_redirect: 0 + http.ingress_http.rq_reset_after_downstream_response_started: 0 + http.ingress_http.rq_total: 3 + http.ingress_http.rs_too_large: 0 + http.ingress_http.tracing.client_enabled: 0 + http.ingress_http.tracing.health_check: 0 + http.ingress_http.tracing.not_traceable: 0 + http.ingress_http.tracing.random_sampling: 0 + http.ingress_http.tracing.service_forced: 0 + http.ingress_http.downstream_cx_length_ms: P0(nan,2.0) P25(nan,2.075) P50(nan,3.05) P75(nan,17.25) P90(nan,17.7) P95(nan,17.85) P99(nan,17.97) P99.5(nan,17.985) P99.9(nan,17.997) P100(nan,18.0) + http.ingress_http.downstream_rq_time: P0(nan,1.0) P25(nan,1.075) P50(nan,2.05) P75(nan,16.25) P90(nan,16.7) P95(nan,16.85) P99(nan,16.97) P99.5(nan,16.985) P99.9(nan,16.997) P100(nan,17.0) + + +You can also pass a :ref:`format ` argument, for example to return ``json``: + +.. code-block:: console + + $ curl -s "http://localhost:9901/stats?filter=http.ingress_http.rq&format=json" | jq '.stats' + +.. code-block:: json + + [ + { + "value": 0, + "name": "http.ingress_http.rq_direct_response" + }, + { + "value": 0, + "name": "http.ingress_http.rq_redirect" + }, + { + "value": 0, + "name": "http.ingress_http.rq_reset_after_downstream_response_started" + }, + { + "value": 3, + "name": "http.ingress_http.rq_total" + } + ] + + +Envoy admin web UI +------------------ + +Envoy also has a web user interface that allows you to view and modify settings and +statistics. + +Point your browser to http://localhost:9901. + +.. image:: /_static/envoy-admin.png diff --git a/docs/root/start/quick-start/configuration-dynamic-control-plane.rst b/docs/root/start/quick-start/configuration-dynamic-control-plane.rst index 3ceb430abe36..914a057eafc0 100644 --- a/docs/root/start/quick-start/configuration-dynamic-control-plane.rst +++ b/docs/root/start/quick-start/configuration-dynamic-control-plane.rst @@ -19,7 +19,7 @@ At a minimum, you will need to start Envoy configured with the following section - :ref:`dynamic_resources ` to tell Envoy which configurations should be updated dynamically - :ref:`static_resources ` to specify where Envoy should retrieve its configuration from. -You can also add an :ref:`admin ` section if you wish to monitor Envoy or +You can also add an :ref:`admin ` section if you wish to monitor Envoy or retrieve stats or configuration information. The following sections walk through the dynamic configuration provided in the @@ -72,26 +72,3 @@ The ``xds_cluster`` is configured to query a control plane at http://my-control- :lines: 17-35 :lineno-start: 17 :emphasize-lines: 3-17 - -.. _start_quick_start_dynamic_admin: - -``admin`` ---------- - -Configuring the :ref:`admin ` section is -the same as for :ref:`static configuration `. - -Enabling the :ref:`admin ` interface with -dynamic configuration, allows you to use the :ref:`config_dump ` -endpoint to see how Envoy is currently configured. - -.. literalinclude:: _include/envoy-dynamic-control-plane-demo.yaml - :language: yaml - :linenos: - :lines: 33-40 - :lineno-start: 33 - :emphasize-lines: 3-8 - -.. warning:: - - You may wish to restrict the network address the admin server listens to in your own deployment. diff --git a/docs/root/start/quick-start/configuration-dynamic-filesystem.rst b/docs/root/start/quick-start/configuration-dynamic-filesystem.rst index f6d55cf7a43b..dc6c02100e1b 100644 --- a/docs/root/start/quick-start/configuration-dynamic-filesystem.rst +++ b/docs/root/start/quick-start/configuration-dynamic-filesystem.rst @@ -19,7 +19,7 @@ For the given example you will also need two dynamic configuration files: - :ref:`lds.yaml ` for listeners. - :ref:`cds.yaml ` for clusters. -You can also add an :ref:`admin ` section if you wish to monitor Envoy or +You can also add an :ref:`admin ` section if you wish to monitor Envoy or retrieve stats or configuration information. The following sections walk through the dynamic configuration provided in the @@ -90,26 +90,3 @@ proxies over ``TLS`` to https://www.envoyproxy.io. :language: yaml :linenos: :emphasize-lines: 8, 14-15, 19-20 - -.. _start_quick_start_dynamic_fs_admin: - -``admin`` ---------- - -Configuring the :ref:`admin ` section is -the same as for :ref:`static configuration `. - -Enabling the :ref:`admin ` interface with -dynamic configuration, allows you to use the :ref:`config_dump ` -endpoint to see how Envoy is currently configured. - -.. literalinclude:: _include/envoy-dynamic-filesystem-demo.yaml - :language: yaml - :linenos: - :lines: 9-16 - :lineno-start: 9 - :emphasize-lines: 3-8 - -.. warning:: - - You may wish to restrict the network address the admin server listens to in your own deployment. diff --git a/docs/root/start/quick-start/configuration-static.rst b/docs/root/start/quick-start/configuration-static.rst index 74e539b529d0..3b9a1516e24d 100644 --- a/docs/root/start/quick-start/configuration-static.rst +++ b/docs/root/start/quick-start/configuration-static.rst @@ -7,7 +7,7 @@ To start Envoy with static configuration, you will need to specify :ref:`listene and :ref:`clusters ` as :ref:`static_resources `. -You can also add an :ref:`admin ` section if you wish to monitor Envoy +You can also add an :ref:`admin ` section if you wish to monitor Envoy or retrieve stats. The following sections walk through the static configuration provided in the @@ -41,8 +41,8 @@ All paths are matched and routed to the ``service_envoyproxy_io`` .. literalinclude:: _include/envoy-demo.yaml :language: yaml :linenos: - :lines: 1-29 - :emphasize-lines: 3-27 + :lines: 1-34 + :emphasize-lines: 3-32 .. _start_quick_start_static_clusters: @@ -54,27 +54,6 @@ proxies over ``TLS`` to https://www.envoyproxy.io. .. literalinclude:: _include/envoy-demo.yaml :language: yaml - :lineno-start: 27 - :lines: 27-50 + :lineno-start: 32 + :lines: 32-53 :emphasize-lines: 3-22 - -.. _start_quick_start_static_admin: - -``admin`` ---------- - -The :ref:`admin message ` is required to enable and configure -the administration server. - -The ``address`` key specifies the listening :ref:`address ` -which in the demo configuration is ``0.0.0.0:9901``. - -.. literalinclude:: _include/envoy-demo.yaml - :language: yaml - :lineno-start: 48 - :lines: 48-55 - :emphasize-lines: 3-8 - -.. warning:: - - You may wish to restrict the network address the admin server listens to in your own deployment. diff --git a/docs/root/start/quick-start/index.rst b/docs/root/start/quick-start/index.rst index 566b82f0a084..e9daf8cf96ef 100644 --- a/docs/root/start/quick-start/index.rst +++ b/docs/root/start/quick-start/index.rst @@ -13,4 +13,6 @@ provides an introduction to the types of configuration Envoy can be used with. configuration-static configuration-dynamic-filesystem configuration-dynamic-control-plane + admin + securing next-steps diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index b3188c395522..d9db5a40691f 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -20,15 +20,17 @@ Once you have :ref:`installed Envoy `, you can check the version inform .. code-block:: console - $ envoy --version + $ envoy --version + ... .. tab:: Docker .. substitution-code-block:: console - $ docker run --rm \ - envoyproxy/|envoy_docker_image| \ - --version + $ docker run --rm \ + envoyproxy/|envoy_docker_image| \ + --version + ... .. _start_quick_start_help: @@ -44,15 +46,17 @@ flag: .. code-block:: console - $ envoy --help + $ envoy --help + ... .. tab:: Docker .. substitution-code-block:: console - $ docker run --rm \ - envoyproxy/|envoy_docker_image| \ - --help + $ docker run --rm \ + envoyproxy/|envoy_docker_image| \ + --help + ... .. _start_quick_start_config: @@ -70,7 +74,8 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu .. code-block:: console - $ envoy -c envoy-demo.yaml + $ envoy -c envoy-demo.yaml + ... .. tab:: Docker @@ -79,10 +84,11 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu .. substitution-code-block:: console - $ docker run --rm -d \ + $ docker run --rm -it \ -p 9901:9901 \ - -p 10000:10000 \ - envoyproxy/|envoy_docker_image| + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| + ... To specify a custom configuration you can mount the config into the container, and specify the path with ``-c``. @@ -90,44 +96,52 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu .. substitution-code-block:: console - $ docker run --rm -d \ - -v $(pwd)/envoy-custom.yaml:/envoy-custom.yaml \ - -p 9901:9901 \ - -p 10000:10000 \ - envoyproxy/|envoy_docker_image| \ - -c /envoy-custom.yaml + $ docker run --rm -it \ + -v $(pwd)/envoy-custom.yaml:/envoy-custom.yaml \ + -p 9901:9901 \ + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| \ + -c /envoy-custom.yaml + ... -Check Envoy is proxying on http://localhost:10000 +Check Envoy is proxying on http://localhost:10000. .. code-block:: console $ curl -v localhost:10000 + ... -The Envoy admin endpoint should also be available at http://localhost:9901 +The Envoy admin endpoint should also be available at http://localhost:9901. .. code-block:: console $ curl -v localhost:9901 + ... + +You can exit the server with `Ctrl-c`. + +See the :ref:`admin quick start guide ` for more information about the Envoy admin interface. .. _start_quick_start_override: -Override the default configuration by merging a config file ------------------------------------------------------------ +Override the default configuration +---------------------------------- -You can provide a configuration override file using ``--config-yaml`` which will merge with the main +You can provide an override configuration using :option:`--config-yaml` which will merge with the main configuration. +This option can only be specified once. + Save the following snippet to ``envoy-override.yaml``: .. code-block:: yaml - listeners: - - name: listener_0 - address: - socket_address: - port_value: 20000 + admin: + address: + socket_address: + port_value: 9902 -Next, start the Envoy server using the override configuration. +Next, start the Envoy server using the override configuration: .. tabs:: @@ -135,26 +149,199 @@ Next, start the Envoy server using the override configuration. .. code-block:: console - $ envoy -c envoy-demo.yaml --config-yaml envoy-override.yaml + $ envoy -c envoy-demo.yaml --config-yaml "$(cat envoy-override.yaml)" + ... .. tab:: Docker .. substitution-code-block:: console - $ docker run --rm -d \ - -v $(pwd)/envoy-override.yaml:/envoy-override.yaml \ - -p 20000:20000 \ - envoyproxy/|envoy_docker_image| \ - --config-yaml /envoy-override.yaml + $ docker run --rm -it \ + -p 9902:9902 \ + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| \ + -c /etc/envoy/envoy.yaml \ + --config-yaml "$(cat envoy-override.yaml)" + ... -Envoy should now be proxying on http://localhost:20000 +The Envoy admin interface should now be available on http://localhost:9902. .. code-block:: console - $ curl -v localhost:20000 + $ curl -v localhost:9902 + ... -The Envoy admin endpoint should also be available at http://localhost:9901 +.. note:: -.. code-block:: console + When merging ``yaml`` lists (e.g. :ref:`listeners ` + or :ref:`clusters `) the merged configurations + are appended. - $ curl -v localhost:9901 + You cannot therefore use an override file to change the configurations of previously specified + :ref:`listeners ` or + :ref:`clusters ` + +Validating your Envoy configuration +----------------------------------- + +You can start Envoy in :option:`validate mode <--mode>`. + +This allows you to check that Envoy is able to start with your configuration, without actually starting +or restarting the service, or making any network connections. + +If the configuration is valid the process will print ``OK`` and exit with a return code of ``0``. + +For invalid configuration the process will print the errors and exit with ``1``. + +.. tabs:: + + .. tab:: System + + .. code-block:: console + + $ envoy --mode validate -c my-envoy-config.yaml + [2020-11-08 12:36:06.543][11][info][main] [source/server/server.cc:583] runtime: layers: + - name: base + static_layer: + {} + - name: admin + admin_layer: + {} + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:95] loading tracing configuration + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:70] loading 0 static secret(s) + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:76] loading 1 cluster(s) + [2020-11-08 12:36:06.546][11][info][config] [source/server/configuration_impl.cc:80] loading 1 listener(s) + [2020-11-08 12:36:06.549][11][info][config] [source/server/configuration_impl.cc:121] loading stats sink configuration + configuration 'my-envoy-config.yaml' OK + + .. tab:: Docker + + .. substitution-code-block:: console + + $ docker run --rm \ + -v $(pwd)/my-envoy-config.yaml:/my-envoy-config.yaml \ + envoyproxy/|envoy_docker_image| \ + --mode validate \ + -c my-envoy-config.yaml + [2020-11-08 12:36:06.543][11][info][main] [source/server/server.cc:583] runtime: layers: + - name: base + static_layer: + {} + - name: admin + admin_layer: + {} + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:95] loading tracing configuration + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:70] loading 0 static secret(s) + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:76] loading 1 cluster(s) + [2020-11-08 12:36:06.546][11][info][config] [source/server/configuration_impl.cc:80] loading 1 listener(s) + [2020-11-08 12:36:06.549][11][info][config] [source/server/configuration_impl.cc:121] loading stats sink configuration + configuration 'my-envoy-config.yaml' OK + +Envoy logging +------------- + +By default Envoy system logs are sent to ``/dev/stderr``. + +This can be overridden using :option:`--log-path`. + +.. tabs:: + + .. tab:: System + + .. code-block:: console + + $ mkdir logs + $ envoy -c envoy-demo.yaml --log-path logs/custom.log + + .. tab:: Docker + + .. substitution-code-block:: console + + $ mkdir logs + $ chmod go+rwx logs/ + $ docker run --rm -it \ + -p 10000:10000 \ + -v $(pwd)/logs:/logs \ + envoyproxy/|envoy_docker_image| \ + -c /etc/envoy/envoy.yaml \ + --log-path logs/custom.log + +:ref:`Access log ` paths can be set for the +:ref:`admin interface `, and for configured +:ref:`listeners `. + +The :download:`demo configuration <_include/envoy-demo.yaml>` is configured with a +:ref:`listener ` that logs access +to ``/dev/stdout``: + +.. literalinclude:: _include/envoy-demo.yaml + :language: yaml + :linenos: + :lineno-start: 12 + :lines: 12-22 + :emphasize-lines: 4-8 + +The default configuration in the Envoy Docker container also logs access in this way. + +Logging to ``/dev/stderr`` and ``/dev/stdout`` for system and access logs respectively can +be useful when running Envoy inside a container as the streams can be separated, and logging requires no +additional files or directories to be mounted. + +Some Envoy :ref:`filters and extensions ` may also have additional logging capabilities. + +Envoy can be configured to log to :ref:`different formats `, and to +:ref:`different outputs ` in addition to files and ``stdout/err``. + +.. note:: + + If you are running Envoy on a Windows system Envoy will output to ``CON`` by default. + + This can also be used as a logging path when configuring logging. + +Debugging Envoy +--------------- + +The log level for Envoy system logs can be set using the :option:`-l or --log-level <--log-level>` option. + +The available log levels are: + +- ``trace`` +- ``debug`` +- ``info`` +- ``warning/warn`` +- ``error`` +- ``critical`` +- ``off`` + +The default is ``info``. + +You can also set the log level for specific components using the :option:`--component-log-level` option. + +The following example inhibits all logging except for the ``upstream`` and ``connection`` components, +which are set to ``debug`` and ``trace`` respectively. + +.. tabs:: + + .. tab:: System + + .. code-block:: console + + $ envoy -c envoy-demo.yaml -l off --component-log-level upstream:debug,connection:trace + ... + + .. tab:: Docker + + .. substitution-code-block:: console + + $ docker run --rm -d \ + -p 9901:9901 \ + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| \ + -c /etc/envoy/envoy.yaml \ + -l off \ + --component-log-level upstream:debug,connection:trace + ... + +.. tip:: + + See ``ALL_LOGGER_IDS`` in :repo:`logger.h ` for a list of components. diff --git a/docs/root/start/quick-start/securing.rst b/docs/root/start/quick-start/securing.rst new file mode 100644 index 000000000000..cb9d50b52ae4 --- /dev/null +++ b/docs/root/start/quick-start/securing.rst @@ -0,0 +1,206 @@ +.. _start_quick_start_securing: + +Securing Envoy +============== + +Envoy provides a number of features to secure traffic in and out of your network, and +between proxies and services within your network. + +Transport Layer Security (``TLS``) can be used to secure all types of ``HTTP`` traffic, including ``WebSockets``. + +Envoy also has support for transmitting and receiving generic ``TCP`` traffic with ``TLS``. + +Envoy also offers a number of other ``HTTP``-based protocols for authentication and authorization +such as :ref:`JWT `, :ref:`RBAC ` +and :ref:`OAuth `. + +.. warning:: + + The following guide takes you through individual aspects of securing traffic. + + To secure traffic over a network that is untrusted, you are strongly advised to make use of encryption + and mutual authentication wherever you control both sides of the connection or where relevant protocols are available. + + Here we provide a guide to using :ref:`mTLS ` which provides both encryption + and mutual authentication. + + When using ``TLS``, you are strongly encouraged to :ref:`validate ` + all certificates wherever possible. + + It is your responsibility to ensure the integrity of your certificate chain, and outside the scope of this guide. + +.. _start_quick_start_securing_contexts: + +Upstream and downstream ``TLS`` contexts +---------------------------------------- + +Machines connecting to Envoy to proxy traffic are "downstream" in relation to Envoy. + +Specifying a ``TLS`` context that clients can connect to is achieved by setting the +:ref:`DownstreamTLSContext ` +in the :ref:`transport_socket ` of a +:ref:`listener `. + +You will also need to provide valid certificates. + +.. literalinclude:: _include/envoy-demo-tls.yaml + :language: yaml + :linenos: + :lines: 1-37 + :emphasize-lines: 28-37 + :caption: :download:`envoy-demo-tls.yaml <_include/envoy-demo-tls.yaml>` + +Connecting to an "upstream" ``TLS`` service is conversely done by adding an +:ref:`UpstreamTLSContext ` +to the :ref:`transport_socket ` of a +:ref:`cluster `. + +.. literalinclude:: _include/envoy-demo-tls.yaml + :language: yaml + :linenos: + :lineno-start: 39 + :lines: 39-57 + :emphasize-lines: 16-19 + :caption: :download:`envoy-demo-tls.yaml <_include/envoy-demo-tls.yaml>` + +.. _start_quick_start_securing_validation: + +Validate an endpoint's certificates when connecting +--------------------------------------------------- + +When Envoy connects to an upstream ``TLS`` service, it does not, by default, validate the certificates +that it is presented with. + +You can use the :ref:`validation_context ` +to specify how Envoy should validate these certificates. + +Firstly, you can ensure that the certificates are from a mutually trusted certificate authority: + +.. literalinclude:: _include/envoy-demo-tls-validation.yaml + :language: yaml + :linenos: + :lineno-start: 43 + :lines: 43-53 + :emphasize-lines: 6-9 + :caption: :download:`envoy-demo-tls-validation.yaml <_include/envoy-demo-tls-validation.yaml>` + +You can also ensure that the "Subject Alternative Names" for the cerficate match. + +This is commonly used by web certificates (X.509) to identify the domain or domains that a +certificate is valid for. + +.. literalinclude:: _include/envoy-demo-tls-validation.yaml + :language: yaml + :linenos: + :lineno-start: 43 + :lines: 43-53 + :emphasize-lines: 6-7, 10-11 + :caption: :download:`envoy-demo-tls-validation.yaml <_include/envoy-demo-tls-validation.yaml>` + +.. note:: + + If the "Subject Alternative Names" for a certificate are for a wildcard domain, eg ``*.example.com``, + this is what you should use when matching with ``match_subject_alt_names``. + +.. note:: + + See :ref:`here ` to view all + of the possible configurations for certificate validation. + +.. _start_quick_start_securing_mtls: + +Use mututal ``TLS`` (``mTLS``) to enforce client certificate authentication +--------------------------------------------------------------------------- + +With mutual ``TLS`` (``mTLS``), Envoy also provides a way to authenticate connecting clients. + +At a minimum you will need to set +:ref:`require_client_certificate ` +and specify a mutually trusted certificate authority: + +.. literalinclude:: _include/envoy-demo-tls-client-auth.yaml + :language: yaml + :linenos: + :lineno-start: 27 + :lines: 27-39 + :emphasize-lines: 6, 8-10 + :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>` + +You can further restrict the authentication of connecting clients by specifying the allowed +"Subject Alternative Names" in +:ref:`match_subject_alt_names `, +similar to validating upstream certificates :ref:`described above `. + +.. literalinclude:: _include/envoy-demo-tls-client-auth.yaml + :language: yaml + :linenos: + :lineno-start: 27 + :lines: 27-39 + :emphasize-lines: 7, 11-12 + :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>` + +.. note:: + + See :ref:`here ` to view all + of the possible configurations for certificate validation. + +.. _start_quick_start_securing_mtls_client: + +Use mututal ``TLS`` (``mTLS``) to connect with client certificates +------------------------------------------------------------------ + +When connecting to an upstream with client certificates you can set them as follows: + +.. literalinclude:: _include/envoy-demo-tls-client-auth.yaml + :language: yaml + :linenos: + :lineno-start: 45 + :lines: 45-69 + :emphasize-lines: 21-25 + :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>` + +.. _start_quick_start_securing_sni: + +Provide multiple ``TLS`` domains at the same ``IP`` address with ``SNI`` +------------------------------------------------------------------------ + +``SNI`` is an extension to the ``TLS`` protocol which allows multiple domains served +from the same ``IP`` address to be secured with ``TLS``. + +To secure specific domains on a listening connection with ``SNI``, you should set the +:ref:`filter_chain_match ` of the +:ref:`listener `: + +.. literalinclude:: _include/envoy-demo-tls-sni.yaml + :language: yaml + :linenos: + :lineno-start: 27 + :lines: 27-35 + :emphasize-lines: 2-4 + :caption: :download:`envoy-demo-tls-sni.yaml <_include/envoy-demo-tls-sni.yaml>` + +See here for :ref:`more info about creating multiple endpoints with SNI ` + +.. _start_quick_start_securing_sni_client: + +Connect to an endpoint with ``SNI`` +----------------------------------- + +When connecting to a ``TLS`` endpoint that uses ``SNI`` you should set +:ref:`sni ` in the configuration +of the :ref:`UpstreamTLSContext `. + +This will usually be the DNS name of the service you are connecting to. + +.. literalinclude:: _include/envoy-demo-tls-sni.yaml + :language: yaml + :linenos: + :lineno-start: 56 + :lines: 56-61 + :emphasize-lines: 6 + :caption: :download:`envoy-demo-tls-sni.yaml <_include/envoy-demo-tls-sni.yaml>` + +When connecting to an Envoy endpoint that is protected by ``SNI``, this must match one of the +:ref:`server_names ` set in the endpoint's +:ref:`filter_chain_match `, as +:ref:`described above `. diff --git a/docs/root/start/sandboxes/_include/docker-env-setup-link.rst b/docs/root/start/sandboxes/_include/docker-env-setup-link.rst new file mode 100644 index 000000000000..c30a4edae344 --- /dev/null +++ b/docs/root/start/sandboxes/_include/docker-env-setup-link.rst @@ -0,0 +1,3 @@ +:ref:`Sandbox environment ` + Setup your sandbox environment with Docker and Docker Compose, + and clone the Envoy repository with Git. diff --git a/docs/root/start/sandboxes/_include/docker-env-setup.rst b/docs/root/start/sandboxes/_include/docker-env-setup.rst deleted file mode 100644 index a1ee8dfede6f..000000000000 --- a/docs/root/start/sandboxes/_include/docker-env-setup.rst +++ /dev/null @@ -1,23 +0,0 @@ -The following documentation runs through the setup of Envoy described above. - -Step 1: Install Docker -********************** - -Ensure that you have a recent versions of ``docker`` and ``docker-compose`` installed. - -A simple way to achieve this is via the `Docker Desktop `_. - -Step 2: Clone the Envoy repo -**************************** - -If you have not cloned the Envoy repo, clone it with: - -.. tabs:: - - .. code-tab:: console SSH - - git clone git@github.com:envoyproxy/envoy - - .. code-tab:: console HTTPS - - git clone https://github.com/envoyproxy/envoy.git diff --git a/docs/root/start/sandboxes/cache.rst b/docs/root/start/sandboxes/cache.rst index 8d34e7305490..d58eb7d41dfe 100644 --- a/docs/root/start/sandboxes/cache.rst +++ b/docs/root/start/sandboxes/cache.rst @@ -1,20 +1,32 @@ .. _install_sandboxes_cache_filter: -Cache Filter +Cache filter ============ .. TODO(yosrym93): When a documentation is written for a production-ready Cache Filter, link to it through this doc. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + In this example, we demonstrate how HTTP caching can be utilized in Envoy by using the Cache Filter. The setup of this sandbox is based on the setup of the :ref:`Front Proxy sandbox `. All incoming requests are routed via the front Envoy, which acts as a reverse proxy sitting on -the edge of the ``envoymesh`` network. Port ``8000`` is exposed by docker -compose (see :repo:`/examples/cache/docker-compose.yaml`) to handle ``HTTP`` calls +the edge of the ``envoymesh`` network. + +Port ``8000`` is exposed by :download:`docker-compose.yaml <_include/cache/docker-compose.yaml>` to handle ``HTTP`` calls to the services. Two backend services are deployed behind the front Envoy, each with a sidecar Envoy. The front Envoy is configured to run the Cache Filter, which stores cacheable responses in an in-memory cache, -and serves it to subsequent requests. In this demo, the responses that are served by the deployed services are stored in :repo:`/examples/cache/responses.yaml`. -This file is mounted to both services' containers, so any changes made to the stored responses while the services are running should be instantly effective (no need to rebuild or rerun). +and serves it to subsequent requests. + +In this demo, the responses that are served by the deployed services are stored in :download:`responses.yaml <_include/cache/responses.yaml>`. + +This file is mounted to both services' containers, so any changes made to the stored responses while the services are +running should be instantly effective (no need to rebuild or rerun). For the purposes of the demo, a response's date of creation is appended to its body before being served. An Etag is computed for every response for validation purposes, which only depends on the response body in the yaml file (i.e. the appended date is not taken into account). @@ -22,11 +34,11 @@ Cached responses can be identified by having an ``age`` header. Validated respon as when a response is validated the ``date`` header is updated, while the body stays the same. Validated responses do not have an ``age`` header. Responses served from the backend service have no ``age`` header, and their ``date`` header is the same as their generation date. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** +Change to the ``examples/cache`` directory. + .. code-block:: console $ pwd @@ -41,7 +53,7 @@ Step 3: Start all of our containers cache_service1_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 8000/tcp cache_service2_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 8000/tcp -Step 4: Test Envoy's HTTP caching capabilities +Step 2: Test Envoy's HTTP caching capabilities ********************************************** You can now send a request to both services via the ``front-envoy``. Note that since the two services have different routes, @@ -54,7 +66,8 @@ To send a request: ``service_no``: The service to send the request to, 1 or 2. -``response``: The response that is being requested. The responses are found in :repo:`/examples/cache/responses.yaml`. +``response``: The response that is being requested. The responses are found in +:download:`responses.yaml <_include/cache/responses.yaml>`. The provided example responses are: @@ -222,5 +235,8 @@ You will receive a new response that's served from the backend service. The new response will be cached for subsequent requests. You can also add new responses to the yaml file with different ``cache-control`` headers and start experimenting! -To learn more about caching and ``cache-control`` headers visit -the `MDN Web Docs `_. + +.. seealso:: + + `MDN Web Docs `_. + Learn more about caching and ``cache-control`` on the web. diff --git a/docs/root/start/sandboxes/cors.rst b/docs/root/start/sandboxes/cors.rst index cf016484a417..df639cfc17a9 100644 --- a/docs/root/start/sandboxes/cors.rst +++ b/docs/root/start/sandboxes/cors.rst @@ -1,8 +1,12 @@ .. _install_sandboxes_cors: -CORS Filter +CORS filter =========== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + Cross-Origin Resource Sharing (CORS) is a method of enforcing client-side access controls on resources by specifying external domains that are able to access certain or all routes of your domain. Browsers use the presence of HTTP @@ -26,12 +30,10 @@ The CORS enforcement choices are: * Restricted: CORS is enabled on the route requested and the only allowed origin is ``envoyproxy.io``. This will result in a client-side CORS error. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** -Switch to the ``frontend`` directory in the ``cors`` example, and start the containers: +Change to the ``examples/cors/frontend`` directory, and start the containers: .. code-block:: console @@ -61,7 +63,7 @@ Now, switch to the ``backend`` directory in the ``cors`` example, and start the backend_backend-service_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp backend_front-envoy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8002->8000/tcp, 0.0.0.0:8003->8001/tcp -Step 4: Test Envoy's CORS capabilities +Step 2: Test Envoy's CORS capabilities ************************************** You can now open a browser to view your frontend service at http://localhost:8000. @@ -77,7 +79,7 @@ For example: Access to XMLHttpRequest at 'http://192.168.99.100:8002/cors/disabled' from origin 'http://192.168.99.101:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. -Step 5: Check stats of backend via admin +Step 3: Check stats of backend via admin **************************************** When Envoy runs, it can listen to ``admin`` requests if a port is configured. @@ -92,3 +94,8 @@ invalid and valid origins increment as you make requests from the frontend clust http.ingress_http.cors.origin_invalid: 2 http.ingress_http.cors.origin_valid: 7 + +.. seealso:: + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. diff --git a/docs/root/start/sandboxes/csrf.rst b/docs/root/start/sandboxes/csrf.rst index e8bf316d8bb4..22beb26b064c 100644 --- a/docs/root/start/sandboxes/csrf.rst +++ b/docs/root/start/sandboxes/csrf.rst @@ -1,8 +1,12 @@ .. _install_sandboxes_csrf: -CSRF Filter +CSRF filter =========== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + Cross-Site Request Forgery (CSRF) is an attack that occurs when a malicious third-party website exploits a vulnerability that allows them to submit an undesired request on a user's behalf. To mitigate this attack this filter @@ -27,12 +31,10 @@ enforcement. The CSRF enforcement choices are: * Ignored: CSRF is enabled but the request type is a GET. This should bypass the CSRF filter and return successfully. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** -Switch to the ``samesite`` directory in the ``csrf`` example, and start the containers: +Change to the ``examples/csrf/samesite`` directory, and start the containers: .. code-block:: console @@ -61,7 +63,7 @@ Now, switch to the ``crosssite`` directory in the ``csrf`` example, and start th crosssite_front-envoy_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 0.0.0.0:8002->8000/tcp crosssite_service_1 /docker-entrypoint.sh /bin ... Up 10000/tcp -Step 4: Test Envoy's CSRF capabilities +Step 2: Test Envoy's CSRF capabilities ************************************** You can now open a browser at http://localhost:8002 to view your ``crosssite`` frontend service. @@ -86,7 +88,7 @@ For example: If you change the destination to be the same as one displaying the website and set the ``CSRF`` enforcement to enabled the request will go through successfully. -Step 5: Check stats of backend via admin +Step 3: Check stats of backend via admin **************************************** When Envoy runs, it can listen to ``admin`` requests if a port is configured. In @@ -101,3 +103,8 @@ invalid and valid origins increment as you make requests from the frontend clust http.ingress_http.csrf.missing_source_origin: 0 http.ingress_http.csrf.request_invalid: 1 http.ingress_http.csrf.request_valid: 0 + +.. seealso:: + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. diff --git a/docs/root/start/sandboxes/double-proxy.rst b/docs/root/start/sandboxes/double-proxy.rst new file mode 100644 index 000000000000..75563a600a66 --- /dev/null +++ b/docs/root/start/sandboxes/double-proxy.rst @@ -0,0 +1,190 @@ +.. _install_sandboxes_double_proxy: + +Double proxy (with ``mTLS`` encryption) +======================================= + +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + + :ref:`openssl ` + Generate ``SSL`` keys and certificates. + +This sandbox demonstrates a basic "double proxy" configuration, in which a simple Flask app +connects to a PostgreSQL database, with two Envoy proxies in between. + +``Envoy (front)`` -> ``Flask`` -> ``Envoy (postgres-front)`` -> ``Envoy (postgres-back)`` -> ``PostgreSQL`` + +This type of setup is common in a service mesh where Envoy acts as a "sidecar" between individual services. + +It can also be useful as a way of providing access for application servers to upstream services or +databases that may be in a different location or subnet, outside of a service mesh or sidecar-based setup. + +Another common use case is with Envoy configured to provide "Points of presence" at the edges of the cloud, +and to relay requests to upstream servers and services. + +This example encrypts the transmission of data between the two middle proxies and provides mutual authentication +using ``mTLS``. + +This can be useful if the proxies are physically separated or transmit data over untrusted networks. + +In order to use the sandbox you will first need to generate the necessary ``SSL`` keys and certificates. + +This example walks through creating a certificate authority, and using it to create a domain key and sign +certificates for the proxies. + +Change to the ``examples/double-proxy`` directory. + +Step 1: Create a certificate authority +************************************** + +First create a key for the certificate authority: + +.. code-block:: console + + $ pwd + envoy/examples/double-proxy + $ mkdir -p certs + $ openssl genrsa -out certs/ca.key 4096 + Generating RSA private key, 4096 bit long modulus (2 primes) + ..........++++ + ..........................................................................................................++++ + e is 65537 (0x010001) + +Now use the key to generate a certificate authority certificate. + +If you wish, you can interactively alter the fields in the certificate. + +For the purpose of this example, the defaults should be sufficient. + +.. code-block:: console + + $ openssl req -x509 -new -nodes -key certs/ca.key -sha256 -days 1024 -out certs/ca.crt + + You are about to be asked to enter information that will be incorporated + into your certificate request. + What you are about to enter is what is called a Distinguished Name or a DN. + There are quite a few fields but you can leave some blank + For some fields there will be a default value, + If you enter '.', the field will be left blank. + ----- + Country Name (2 letter code) [AU]: + State or Province Name (full name) [Some-State]: + Locality Name (eg, city) []: + Organization Name (eg, company) [Internet Widgits Pty Ltd]: + Organizational Unit Name (eg, section) []: + Common Name (e.g. server FQDN or YOUR name) []: + Email Address []: + +Step 2: Create a domain key +*************************** + +Create a key for the example domain: + +.. code-block:: console + + $ openssl genrsa -out certs/example.com.key 2048 + Generating RSA private key, 2048 bit long modulus (2 primes) + ..+++++ + .................................................+++++ + e is 65537 (0x010001) + +Step 3: Generate certificate signing requests for the proxies +************************************************************* + +Use the domain key to generate certificate signing requests for each of the proxies: + +.. code-block:: console + + $ openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-frontend.example.com" \ + -out certs/proxy-postgres-frontend.example.com.csr + $ openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-backend.example.com" \ + -out certs/proxy-postgres-backend.example.com.csr + +Step 4: Sign the proxy certificates +*********************************** + +You can now use the certificate authority that you created to sign the certificate requests. + +Note the ``subjectAltName``. This is used for reciprocally matching and validating the certificates. + +.. code-block:: console + + $ openssl x509 -req \ + -in certs/proxy-postgres-frontend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-frontend.example.com") \ + -out certs/postgres-frontend.example.com.crt \ + -days 500 \ + -sha256 + Signature ok + subject=C = US, ST = CA, O = "MyExample, Inc.", CN = proxy-postgres-frontend.example.com + Getting CA Private Key + + $ openssl x509 -req \ + -in certs/proxy-postgres-backend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-backend.example.com") \ + -out certs/postgres-backend.example.com.crt \ + -days 500 \ + -sha256 + Signature ok + subject=C = US, ST = CA, O = "MyExample, Inc.", CN = proxy-postgres-backend.example.com + Getting CA Private Key + +At this point you should have the necessary keys and certificates to secure the connection between +the proxies. + +They keys and certificates are stored in the ``certs/`` directory. + +Step 5: Start all of our containers +*********************************** + +Build and start the containers. + +This will load the required keys and certificates into the frontend and backend proxies. + +.. code-block:: console + + $ pwd + envoy/examples/double-proxy + $ docker-compose build --pull + $ docker-compose up -d + $ docker-compose ps + + Name Command State Ports + -------------------------------------------------------------------------------------------------------- + double-proxy_app_1 python3 /code/service.py Up + double-proxy_postgres_1 docker-entrypoint.sh postgres Up 5432/tcp + double-proxy_proxy-frontend_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10000->10000/tcp + double-proxy_proxy-postgres-backend_1 /docker-entrypoint.sh /usr ... Up 10000/tcp + double-proxy_proxy-postgres-frontend_1 /docker-entrypoint.sh /usr ... Up 10000/tcp + +Step 6: Check the flask app can connect to the database +******************************************************* + +Checking the response at http://localhost:10000, you should see the output from the Flask app: + +.. code-block:: console + + $ curl -s http://localhost:10000 + Connected to Postgres, version: PostgreSQL 13.0 (Debian 13.0-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit + +.. seealso:: + + :ref:`Securing Envoy quick start guide ` + Outline of key concepts for securing Envoy. + + :ref:`TLS sandbox ` + Examples of various ``TLS`` termination patterns with Envoy. diff --git a/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst b/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst index 6874a9c3158c..c6b9319b099e 100644 --- a/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst +++ b/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst @@ -3,19 +3,27 @@ Dynamic configuration (control plane) ===================================== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + + :ref:`jq ` + Parse ``json`` output from the upstream echo servers. + This example walks through configuring Envoy using the `Go Control Plane `_ reference implementation. It demonstrates how configuration provided to Envoy persists, even when the control plane is not available, and provides a trivial example of how to update Envoy's configuration dynamically. -.. include:: _include/docker-env-setup.rst +Step 1: Start the proxy container +********************************* Change directory to ``examples/dynamic-config-cp`` in the Envoy repository. -Step 3: Start the proxy container -********************************* - First build the containers and start the ``proxy`` container. This should also start two upstream ``HTTP`` echo servers, ``service1`` and ``service2``. @@ -36,7 +44,7 @@ The control plane has not yet been started. dynamic-config-cp_service1_1 /bin/echo-server Up 8080/tcp dynamic-config-cp_service2_1 /bin/echo-server Up 8080/tcp -Step 4: Check initial config and web response +Step 2: Check initial config and web response ********************************************* As we have not yet started the control plane, nothing should be responding on port ``10000``. @@ -46,8 +54,8 @@ As we have not yet started the control plane, nothing should be responding on po $ curl http://localhost:10000 curl: (56) Recv failure: Connection reset by peer -Dump the proxy's ``static_clusters`` configuration and you should see the cluster named ``xds_cluster`` -configured for the control plane: +Dump the proxy's :ref:`static_clusters ` +configuration and you should see the cluster named ``xds_cluster`` configured for the control plane: .. code-block:: console @@ -57,14 +65,15 @@ configured for the control plane: :language: json :emphasize-lines: 10, 18-19 -No ``dynamic_active_clusters`` have been configured yet: +No :ref:`dynamic_active_clusters ` +have been configured yet: .. code-block:: console $ curl -s http://localhost:19000/config_dump | jq '.configs[1].dynamic_active_clusters' null -Step 5: Start the control plane +Step 3: Start the control plane ******************************* Start up the ``go-control-plane`` service. @@ -83,7 +92,7 @@ You may need to wait a moment or two for it to become ``healthy``. dynamic-config-cp_service1_1 /bin/echo-server Up 8080/tcp dynamic-config-cp_service2_1 /bin/echo-server Up 8080/tcp -Step 6: Query the proxy +Step 4: Query the proxy *********************** Once the control plane has started and is ``healthy``, you should be able to make a request to port ``10000``, @@ -107,8 +116,8 @@ which will be served by ``service1``. Step 5: Dump Envoy's ``dynamic_active_clusters`` config ******************************************************* -If you now dump the proxy's ``dynamic_active_clusters`` configuration, you should see it is configured -with the ``example_proxy_cluster`` pointing to ``service1``, and a version of ``1``. +If you now dump the proxy's :ref:`dynamic_active_clusters ` +configuration, you should see it is configured with the ``example_proxy_cluster`` pointing to ``service1``, and a version of ``1``. .. code-block:: console @@ -134,11 +143,11 @@ The Envoy proxy should continue proxying responses from ``service1``. $ curl http://localhost:10000 | grep "served by" Request served by service1 -Step 7: Edit resource.go and restart the go-control-plane -********************************************************* +Step 7: Edit ``go`` file and restart the control plane +****************************************************** -The example setup starts `go-control-plane `_ -with a custom :download:`resource.go <_include/dynamic-config-cp/resource.go>` file which +The example setup starts the ``go-control-plane`` +service with a custom :download:`resource.go <_include/dynamic-config-cp/resource.go>` file which specifies the configuration provided to Envoy. Update this to have Envoy proxy instead to ``service2``. @@ -181,8 +190,9 @@ Now when you make a request to the proxy it should be served by the ``service2`` $ curl http://localhost:10000 | grep "served by" Request served by service2 -Dumping the ``dynamic_active_clusters`` you should see the cluster configuration now has a version -of ``2``, and ``example_proxy_cluster`` is configured to proxy to ``service2``: +Dumping the :ref:`dynamic_active_clusters ` +you should see the cluster configuration now has a version of ``2``, and ``example_proxy_cluster`` +is configured to proxy to ``service2``: .. code-block:: console @@ -191,3 +201,17 @@ of ``2``, and ``example_proxy_cluster`` is configured to proxy to ``service2``: .. literalinclude:: _include/dynamic-config-cp/response-config-active-clusters-updated.json :language: json :emphasize-lines: 3, 11, 19-20 + +.. seealso:: + + :ref:`Dynamic configuration (control plane) quick start guide ` + Quick start guide to dynamic configuration of Envoy with a control plane. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + :ref:`Dynamic configuration (filesystem) sandbox ` + Configure Envoy using filesystem-based dynamic configuration. + + `Go control plane `_ + Reference implementation of Envoy control plane written in ``go``. diff --git a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst index d71bee0e9673..9364e98f40aa 100644 --- a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst +++ b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst @@ -3,31 +3,25 @@ Dynamic configuration (filesystem) ================================== -This example walks through configuring Envoy using filesystem-based dynamic configuration. +.. sidebar:: Requirements -It demonstrates how configuration provided to Envoy dynamically can be updated without -restarting the server. + .. include:: _include/docker-env-setup-link.rst -.. include:: _include/docker-env-setup.rst - -Change directory to ``examples/dynamic-config-fs`` in the Envoy repository. + :ref:`curl ` + Used to make ``HTTP`` requests. -Step 3: Start the proxy container -********************************* + :ref:`jq ` + Parse ``json`` output from the upstream echo servers. -.. note:: +This example walks through configuring Envoy using filesystem-based dynamic configuration. - If you are running on a system with strict ``umask`` you will need to ``chmod`` the dynamic config - files which are mounted into the container: +It demonstrates how configuration provided to Envoy dynamically can be updated without +restarting the server. - .. code-block:: console +Step 1: Start the proxy container +********************************* - $ umask - 027 - $ pwd - envoy/examples/dynamic-config-fs - $ chmod go+r configs/* - $ chmod go+x configs +Change directory to ``examples/dynamic-config-fs`` in the Envoy repository. Build and start the containers. @@ -47,7 +41,7 @@ This should also start two upstream ``HTTP`` echo servers, ``service1`` and ``se dynamic-config-fs_service1_1 /bin/echo-server Up 8080/tcp dynamic-config-fs_service2_1 /bin/echo-server Up 8080/tcp -Step 4: Check web response +Step 2: Check web response ************************** You should be able to make a request to port ``10000``, which will be served by ``service1``. @@ -66,11 +60,11 @@ You should be able to make a request to port ``10000``, which will be served by X-Request-Id: 6672902d-56ca-456c-be6a-992a603cab9a X-Envoy-Expected-Rq-Timeout-Ms: 15000 -Step 5: Dump Envoy's ``dynamic_active_clusters`` config +Step 3: Dump Envoy's ``dynamic_active_clusters`` config ******************************************************* -If you now dump the proxy’s ``dynamic_active_clusters`` configuration, you should see it is configured with -the ``example_proxy_cluster`` pointing to ``service1``. +If you now dump the proxy’s :ref:`dynamic_active_clusters ` +configuration, you should see it is configured with the ``example_proxy_cluster`` pointing to ``service1``. .. code-block:: console @@ -80,17 +74,17 @@ the ``example_proxy_cluster`` pointing to ``service1``. :language: json :emphasize-lines: 10, 18-19 -Step 5: Edit ``configs/cds.yaml`` file to update upstream cluster -***************************************************************** +Step 4: Edit ``cds.yaml`` inside the container to update upstream cluster +************************************************************************* -The example setup provides two dynamic configuration files: +The example setup provides Envoy with two dynamic configuration files: - :download:`configs/cds.yaml <_include/dynamic-config-fs/configs/cds.yaml>` to provide a :ref:`Cluster discovery service (CDS) `. - :download:`configs/lds.yaml <_include/dynamic-config-fs/configs/lds.yaml>` to provide a :ref:`Listener discovery service (CDS) `. -Edit ``configs/cds.yaml`` in the dynamic configuration example folder and change the cluster address +Edit ``cds.yaml`` inside the container and change the cluster address from ``service1`` to ``service2``: .. literalinclude:: _include/dynamic-config-fs/configs/cds.yaml @@ -100,7 +94,13 @@ from ``service1`` to ``service2``: :lineno-start: 7 :emphasize-lines: 8 -Step 6: Check Envoy uses updated configuration +You can do this using ``sed`` inside the container: + +.. code-block:: console + + docker-compose exec -T proxy sed -i s/service1/service2/ /var/lib/envoy/cds.yaml + +Step 5: Check Envoy uses updated configuration ********************************************** Checking the web response again, the request should now be handled by ``service2``: @@ -110,8 +110,8 @@ Checking the web response again, the request should now be handled by ``service2 $ curl http://localhost:10000 | grep "served by" Request served by service2 -Dumping the ``dynamic_active_clusters``, the ``example_proxy_cluster`` should now be -configured to proxy to ``service2``: +Dumping the :ref:`dynamic_active_clusters `, +the ``example_proxy_cluster`` should now be configured to proxy to ``service2``: .. code-block:: console @@ -120,3 +120,14 @@ configured to proxy to ``service2``: .. literalinclude:: _include/dynamic-config-fs/response-config-active-clusters-updated.json :language: json :emphasize-lines: 10, 18-19 + +.. seealso:: + + :ref:`Dynamic configuration (filesystem) quick start guide ` + Quick start guide to filesystem-based dynamic configuration of Envoy. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + :ref:`Dynamic configuration (control plane) sandbox ` + Configure Envoy dynamically with the Go Control Plane. diff --git a/docs/root/start/sandboxes/ext_authz.rst b/docs/root/start/sandboxes/ext_authz.rst index f9e687cc03a0..35114beca8ea 100644 --- a/docs/root/start/sandboxes/ext_authz.rst +++ b/docs/root/start/sandboxes/ext_authz.rst @@ -1,7 +1,14 @@ .. _install_sandboxes_ext_authz: -External Authorization Filter -============================= +External authorization (``ext_authz``) filter +============================================= + +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. The External Authorization sandbox demonstrates Envoy's :ref:`ext_authz filter ` capability to delegate authorization of incoming requests through Envoy to an external services. @@ -14,11 +21,11 @@ service behind the proxy will be checked by an external HTTP or gRPC service. In for every authorized call, the external authorization service adds additional ``x-current-user`` header entry to the original request headers to be forwarded to the upstream service. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** +Change to the ``examples/ext_authz`` directory. + To build this sandbox example and start the example services, run the following commands: .. code-block:: console @@ -41,11 +48,16 @@ To build this sandbox example and start the example services, run the following This sandbox has multiple setup controlled by ``FRONT_ENVOY_YAML`` environment variable which points to the effective Envoy configuration to be used. The default value of ``FRONT_ENVOY_YAML`` can be defined in the ``.env`` file or provided inline when running the ``docker-compose up`` - command. For more information, pease take a look at `environment variables in Compose documentation `_. + command. + + For more information, please take a look at + `environment variables in Compose documentation `_. -By default, ``FRONT_ENVOY_YAML`` points to ``config/grpc-service/v3.yaml`` file which bootstraps -front-envoy with ext_authz HTTP filter with gRPC service ``V3`` (this is specified by :ref:`transport_api_version field`). -The possible values of ``FRONT_ENVOY_YAML`` can be found inside the ``envoy/examples/ext_authz/config`` +By default, ``FRONT_ENVOY_YAML`` points to :download:`config/grpc-service/v3.yaml <_include/ext_authz/config/grpc-service/v3.yaml>` +file which bootstraps front-envoy with ext_authz HTTP filter with gRPC service ``V3`` (this is specified by +:ref:`transport_api_version field`). + +The possible values of ``FRONT_ENVOY_YAML`` can be found inside the ``config`` directory. For example, to run Envoy with ext_authz HTTP filter with HTTP service will be: @@ -87,8 +99,8 @@ to provide a ``Bearer`` token via the ``Authorization`` header. .. note:: - A complete list of users is defined in ``envoy/examples/ext_authz/auth/users.json`` file. For - example, the ``token1`` used in the below example is corresponding to ``user1``. + A complete list of users is defined in :download:`auth/users.json <_include/ext_authz/auth/users.json>` + file. For example, the ``token1`` used in the below example is corresponding to ``user1``. An example of successful requests can be observed as follows: @@ -115,7 +127,7 @@ An example of successful requests can be observed as follows: Hello user1 from behind Envoy! We can also employ `Open Policy Agent `_ server -(with `envoy_ext_authz_grpc `_ plugin enabled) +(with `envoy_ext_authz_grpc `_ plugin enabled) as the authorization server. To run this example: .. code-block:: console @@ -151,7 +163,8 @@ And sending a request to the upstream service (via the Front Envoy) gives: Hello OPA from behind Envoy! From the logs, we can observe the policy decision message from the Open Policy Agent server (for -the above request against the defined policy in ``config/opa-service/policy.rego``): +the above request against the defined policy in +:download:`config/opa-service/policy.rego <_include/ext_authz/config/opa-service/policy.rego>`): .. code-block:: console @@ -205,3 +218,17 @@ Trying to send a request with method other than ``GET`` gives a rejection: < date: Thu, 02 Jul 2020 06:46:13 GMT < server: envoy < content-length: 0 + +.. seealso:: + + :ref:`ext_authz filter ` + Learn more about using Envoy's ``ext_authz`` filter. + + `Open Policy Agent `_ + Policy-based control for cloud native environments. + + `envoy_ext_authz_grpc `_ + Open Policy Agent Envoy plugin. + + `environment variables in Compose documentation `_. + Further information about using env variables with Docker Compose. diff --git a/docs/root/start/sandboxes/fault_injection.rst b/docs/root/start/sandboxes/fault_injection.rst index 2faae8c3ac57..c699110c3d68 100644 --- a/docs/root/start/sandboxes/fault_injection.rst +++ b/docs/root/start/sandboxes/fault_injection.rst @@ -1,15 +1,23 @@ .. _install_sandboxes_fault_injection: -Fault Injection Filter +Fault injection filter ====================== -This simple example demonstrates Envoy's :ref:`fault injection ` capability using Envoy's :ref:`runtime support ` to control the feature. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst -Step 3: Start all of our containers +This simple example demonstrates Envoy's :ref:`fault injection ` capability +using Envoy's :ref:`runtime support ` to control the feature. + +It demonstrates fault injection that cause the request to abort and fail, and also faults that simply delay the +response. + +Step 1: Start all of our containers *********************************** +Change to the ``examples/fault_injection`` directory. + Terminal 1 .. code-block:: console @@ -25,7 +33,7 @@ Terminal 1 fault-injection_backend_1 gunicorn -b 0.0.0.0:80 htt Up 0.0.0.0:8080->80/tcp fault-injection_envoy_1 /docker-entrypoint.sh /usr Up 10000/tcp, 0.0.0.0:9211->9211/tcp -Step 4: Start sending continuous stream of HTTP requests +Step 2: Start sending continuous stream of HTTP requests ******************************************************** Terminal 2 @@ -37,9 +45,14 @@ Terminal 2 $ docker-compose exec envoy bash $ bash send_request.sh -The script above (``send_request.sh``) sends a continuous stream of HTTP requests to Envoy, which in turn forwards the requests to the backend container. Fauilt injection is configured in Envoy but turned off (i.e. affects 0% of requests). Consequently, you should see a continuous sequence of HTTP 200 response codes. +The script above (:download:`send_request.sh <_include/fault-injection/send_request.sh>`) sends a continuous stream +of HTTP requests to Envoy, which in turn forwards the requests to the backend container. + +Fault injection is configured in Envoy but turned off (i.e. affects 0% of requests). + +Consequently, you should see a continuous sequence of ``HTTP 200`` response codes. -Step 5: Test Envoy's abort fault injection +Step 3: Test Envoy's abort fault injection ****************************************** Turn on *abort* fault injection via the runtime using the commands below. @@ -51,8 +64,9 @@ Terminal 3 $ docker-compose exec envoy bash $ bash enable_abort_fault_injection.sh -The script above enables HTTP aborts for 100% of requests. So, you should now see a continuous sequence of HTTP 503 -responses for all requests. +The script above enables ``HTTP`` aborts for 100% of requests. + +You should now see a continuous sequence of ``HTTP 503`` responses for all requests. To disable the abort injection: @@ -62,7 +76,7 @@ Terminal 3 $ bash disable_abort_fault_injection.sh -Step 6: Test Envoy's delay fault injection +Step 4: Test Envoy's delay fault injection ****************************************** Turn on *delay* fault injection via the runtime using the commands below. @@ -74,7 +88,10 @@ Terminal 3 $ docker-compose exec envoy bash $ bash enable_delay_fault_injection.sh -The script above will add a 3-second delay to 50% of HTTP requests. You should now see a continuous sequence of HTTP 200 responses for all requests, but half of the requests will take 3 seconds to complete. +The script above will add a 3-second delay to 50% of ``HTTP`` requests. + +You should now see a continuous sequence of ``HTTP 200`` responses for all requests, but half of the requests +will take 3 seconds to complete. To disable the delay injection: @@ -84,7 +101,7 @@ Terminal 3 $ bash disable_delay_fault_injection.sh -Step 7: Check the current runtime filesystem +Step 5: Check the current runtime filesystem ******************************************** To see the current runtime filesystem overview: @@ -94,3 +111,8 @@ Terminal 3 .. code-block:: console $ tree /srv/runtime + +.. seealso:: + + :ref:`Fault injection ` + Learn more about Envoy's ``HTTP`` fault injection filter. diff --git a/docs/root/start/sandboxes/front_proxy.rst b/docs/root/start/sandboxes/front_proxy.rst index afd0d213953b..5b3652b47fe5 100644 --- a/docs/root/start/sandboxes/front_proxy.rst +++ b/docs/root/start/sandboxes/front_proxy.rst @@ -1,12 +1,20 @@ .. _install_sandboxes_front_proxy: -Front Proxy +Front proxy =========== -To get a flavor of what Envoy has to offer as a front proxy, we are releasing a `docker compose `_ -sandbox that deploys a front Envoy and a couple of services (simple Flask apps) colocated with a -running service Envoy. The three containers will be deployed inside a virtual network called -``envoymesh``. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + +To get a flavor of what Envoy has to offer as a front proxy, we are releasing a +`docker compose `_ sandbox that deploys a front Envoy and a +couple of services (simple Flask apps) colocated with a running service Envoy. + +The three containers will be deployed inside a virtual network called ``envoymesh``. Below you can see a graphic showing the docker compose deployment: @@ -15,22 +23,22 @@ Below you can see a graphic showing the docker compose deployment: All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8080``, ``8443``, and ``8001`` are exposed by docker -compose (see :repo:`/examples/front-proxy/docker-compose.yaml`) to handle ``HTTP``, ``HTTPS`` calls -to the services and requests to ``/admin`` respectively. +compose (see :download:`docker-compose.yaml <_include/front-proxy/docker-compose.yaml>`) to handle +``HTTP``, ``HTTPS`` calls to the services and requests to ``/admin`` respectively. Moreover, notice that all traffic routed by the front Envoy to the service containers is actually -routed to the service Envoys (routes setup in :repo:`/examples/front-proxy/front-envoy.yaml`). +routed to the service Envoys (routes setup in :download:`front-envoy.yaml <_include/front-proxy/front-envoy.yaml>`). In turn the service Envoys route the request to the Flask app via the loopback -address (routes setup in :repo:`/examples/front-proxy/service-envoy.yaml`). This +address (routes setup in :download:`service-envoy.yaml <_include/front-proxy/service-envoy.yaml>`). This setup illustrates the advantage of running service Envoys collocated with your services: all requests are handled by the service Envoy, and efficiently routed to your services. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** +Change to the ``examples/front-proxy`` directory. + .. code-block:: console $ pwd @@ -45,7 +53,7 @@ Step 3: Start all of our containers front-proxy_service1_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp front-proxy_service2_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp -Step 4: Test Envoy's routing capabilities +Step 2: Test Envoy's routing capabilities ***************************************** You can now send a request to both services via the ``front-envoy``. @@ -142,7 +150,7 @@ We can also use ``HTTPS`` to call services behind the front Envoy. For example, < Hello from behind Envoy (service 1)! hostname: 36418bc3c824 resolvedhostname: 192.168.160.4 -Step 5: Test Envoy's load balancing capabilities +Step 3: Test Envoy's load balancing capabilities ************************************************ Now let's scale up our ``service1`` nodes to demonstrate the load balancing abilities of Envoy: @@ -211,7 +219,7 @@ requests by doing a round robin of the three ``service1`` machines: < Hello from behind Envoy (service 1)! hostname: 36418bc3c824 resolvedhostname: 192.168.160.4 -Step 6: Enter containers and curl services +Step 4: Enter containers and curl services ****************************************** In addition of using ``curl`` from your host machine, you can also enter the @@ -231,7 +239,7 @@ enter the ``front-envoy`` container, and ``curl`` for services locally: root@81288499f9d7:/# curl localhost:8080/service/2 Hello from behind Envoy (service 2)! hostname: 92f4a3737bbc resolvedhostname: 172.19.0.2 -Step 7: Enter container and curl admin +Step 5: Enter container and curl admin ************************************** When Envoy runs it also attaches an ``admin`` to your desired port. @@ -240,8 +248,8 @@ In the example configs the admin is bound to port ``8001``. We can ``curl`` it to gain useful information: -- ``/server_info`` provides information about the Envoy version you are running. -- ``/stats`` provides statistics about the Envoy server. +- :ref:`/server_info ` provides information about the Envoy version you are running. +- :ref:`/stats ` provides statistics about the Envoy server. In the example we can we can enter the ``front-envoy`` container to query admin: @@ -318,3 +326,8 @@ In the example we can we can enter the ``front-envoy`` container to query admin: Notice that we can get the number of members of upstream clusters, number of requests fulfilled by them, information about http ingress, and a plethora of other useful stats. + +.. seealso:: + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. diff --git a/docs/root/start/sandboxes/grpc_bridge.rst b/docs/root/start/sandboxes/grpc_bridge.rst index 7e5dd8cb45ce..7d729bcad428 100644 --- a/docs/root/start/sandboxes/grpc_bridge.rst +++ b/docs/root/start/sandboxes/grpc_bridge.rst @@ -1,8 +1,12 @@ .. _install_sandboxes_grpc_bridge: -gRPC Bridge +gRPC bridge =========== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + The gRPC bridge sandbox is an example usage of Envoy's :ref:`gRPC bridge filter `. @@ -16,16 +20,17 @@ The client send messages through a proxy that upgrades the HTTP requests from `` Another Envoy feature demonstrated in this example is Envoy's ability to do authority base routing via its route configuration. -.. include:: _include/docker-env-setup.rst - -Step 3: Generate the protocol stubs +Step 1: Generate the protocol stubs *********************************** +Change to the ``examples/grpc-bridge`` directory. + A docker-compose file is provided that generates the stubs for both ``client`` and ``server`` from the specification in the ``protos`` directory. -Inspecting the ``docker-compose-protos.yaml`` file, you will see that it contains both the ``python`` -and ``go`` gRPC protoc commands necessary for generating the protocol stubs. +Inspecting the :download:`docker-compose-protos.yaml <_include/grpc-bridge/docker-compose-protos.yaml>` file, +you will see that it contains both the ``python`` and ``go`` gRPC protoc commands necessary for generating the +protocol stubs. Generate the stubs as follows: @@ -59,7 +64,7 @@ respective directories: These generated ``python`` and ``go`` stubs can be included as external modules. -Step 4: Start all of our containers +Step 2: Start all of our containers *********************************** To build this sandbox example and start the example services, run the following commands: @@ -79,7 +84,7 @@ To build this sandbox example and start the example services, run the following grpc-bridge_grpc-server-proxy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8811->8811/tcp grpc-bridge_grpc-server_1 /bin/sh -c /bin/server Up 0.0.0.0:8081->8081/tcp -Step 5: Send requests to the Key/Value store +Step 3: Send requests to the Key/Value store ******************************************** To use the Python service and send gRPC requests: @@ -126,3 +131,8 @@ In the running docker-compose container, you should see the gRPC service printin grpc_1 | 2017/05/30 12:05:09 set: foo = bar grpc_1 | 2017/05/30 12:05:12 get: foo grpc_1 | 2017/05/30 12:05:18 set: foo = baz + +.. seealso:: + + :ref:`gRPC bridge filter `. + Learn more about configuring Envoy's gRPC bridge filter. diff --git a/docs/root/start/sandboxes/index.rst b/docs/root/start/sandboxes/index.rst index 88db6644e856..d1b686d01820 100644 --- a/docs/root/start/sandboxes/index.rst +++ b/docs/root/start/sandboxes/index.rst @@ -3,17 +3,52 @@ Sandboxes --------- -We've created a number of sandboxes using Docker Compose that set up different -environments to test out Envoy's features and show sample configurations. As we -gauge peoples' interests we will add more sandboxes demonstrating different -features. The following sandboxes are available: +.. sidebar:: Contributing + + If there are other sandboxes you would like to see demonstrated here + please + `open a ticket on github `_. + + :repo:`The Envoy project welcomes contributions ` and would be happy to review a + `Pull Request `_ with the necessary changes + should you be able to create one. + + :repo:`See the sandbox developer documentation ` for more information about + creating your own sandbox. + +.. sidebar:: Compatibility + + As the examples use the pre-built :ref:`Envoy Docker images ` they should work + on the following architectures: + + - x86_64 + - ARM 64 + + Some of the examples may use pre-built (x86) binaries and will therefore have more limited + compatibility. + +We have created a number of sandboxes using `Docker Compose `_ +that set up environments to test out Envoy's features and show sample configurations. + +These can be used to learn Envoy and model your own configurations. + + +Before you begin you will need to install the sandbox environment. .. toctree:: :maxdepth: 2 + setup + +The following sandboxes are available: + +.. toctree:: + :maxdepth: 1 + cache cors csrf + double-proxy dynamic-configuration-filesystem dynamic-configuration-control-plane ext_authz @@ -27,5 +62,8 @@ features. The following sandboxes are available: mysql postgres redis + skywalking_tracing + tls wasm-cc + websocket zipkin_tracing diff --git a/docs/root/start/sandboxes/jaeger_native_tracing.rst b/docs/root/start/sandboxes/jaeger_native_tracing.rst index e99531c21d7b..6853380704dc 100644 --- a/docs/root/start/sandboxes/jaeger_native_tracing.rst +++ b/docs/root/start/sandboxes/jaeger_native_tracing.rst @@ -1,8 +1,20 @@ .. _install_sandboxes_jaeger_native_tracing: -Jaeger Native Tracing +Jaeger native tracing ===================== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + +.. sidebar:: Compatibility + + The jaeger native tracing sandbox uses a binary built for ``x86_64``, and will therefore + only work on that architecture. + The Jaeger tracing sandbox demonstrates Envoy's :ref:`request tracing ` capabilities using `Jaeger `_ as the tracing provider and Jaeger's native `C++ client `_ as a plugin. Using Jaeger with its @@ -14,22 +26,17 @@ native client instead of with Envoy's builtin Zipkin client has the following ad can be used, including probabilistic or remote where sampling can be centrally controlled from Jaeger's backend. - Spans are sent to the collector in a more efficient binary encoding. - This sandbox is very similar to the front proxy architecture described above, with one difference: service1 makes an API call to service2 before returning a response. The three containers will be deployed inside a virtual network called ``envoymesh``. -.. note:: - - The jaeger native tracing sandbox only works on x86-64. - All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :repo:`/examples/jaeger-native-tracing/docker-compose.yaml`). Notice that +by docker compose (see :download:`docker-compose.yaml <_include/jaeger-native-tracing/docker-compose.yaml>`). Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:repo:`/examples/jaeger-native-tracing/front-envoy-jaeger.yaml`) and setup to propagate the spans generated +:download:`front-envoy-jaeger.yaml <_include/jaeger-native-tracing/front-envoy-jaeger.yaml>`) and setup to propagate the spans generated by the Jaeger tracer to a Jaeger cluster (trace driver setup -in :repo:`/examples/jaeger-native-tracing/front-envoy-jaeger.yaml`). +in :download:`front-envoy-jaeger.yaml <_include/jaeger-native-tracing/front-envoy-jaeger.yaml>`). Before routing a request to the appropriate service Envoy or the application, Envoy will take care of generating the appropriate spans for tracing (parent/child context spans). @@ -40,11 +47,9 @@ One of the most important benefits of tracing from Envoy is that it will take ca propagating the traces to the Jaeger service cluster. However, in order to fully take advantage of tracing, the application has to propagate trace headers that Envoy generates, while making calls to other services. In the sandbox we have provided, the simple flask app -(see trace function in :repo:`/examples/front-proxy/service.py`) acting as service1 propagates +(see trace function in :download:`front-proxy/service.py <_include/front-proxy/service.py>`) acting as service1 propagates the trace headers while making an outbound call to service2. -.. include:: _include/docker-env-setup.rst - Step 3: Build the sandbox ************************* @@ -97,3 +102,20 @@ Point your browser to http://localhost:16686 . You should see the Jaeger dashboa Set the service to "front-proxy" and hit 'Find Traces'. You should see traces from the front-proxy. Click on a trace to explore the path taken by the request from front-proxy to service1 to service2, as well as the latency incurred at each hop. + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + `Jaeger `_ + Jaeger tracing website. + + `Jaeger C++ client `_ + The Jaeger C++ cient. + + `Jaeger Go client `_ + The Jaeger Go client. + + `Jaeger sampling strategies `_ + More information about Jaeger sampling strategies. diff --git a/docs/root/start/sandboxes/jaeger_tracing.rst b/docs/root/start/sandboxes/jaeger_tracing.rst index 6029185d5ab5..63fd1e77f6ea 100644 --- a/docs/root/start/sandboxes/jaeger_tracing.rst +++ b/docs/root/start/sandboxes/jaeger_tracing.rst @@ -1,8 +1,15 @@ .. _install_sandboxes_jaeger_tracing: -Jaeger Tracing +Jaeger tracing ============== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + The Jaeger tracing sandbox demonstrates Envoy's :ref:`request tracing ` capabilities using `Jaeger `_ as the tracing provider. This sandbox is very similar to the front proxy architecture described above, with one difference: @@ -11,11 +18,11 @@ The three containers will be deployed inside a virtual network called ``envoymes All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :repo:`/examples/jaeger-tracing/docker-compose.yaml`). Notice that +by docker compose (see :download:`docker-compose.yaml <_include/jaeger-tracing/docker-compose.yaml>`). Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:repo:`/examples/jaeger-tracing/front-envoy-jaeger.yaml`) and setup to propagate the spans generated +:download:`front-envoy-jaeger.yaml <_include/jaeger-tracing/front-envoy-jaeger.yaml>`) and setup to propagate the spans generated by the Jaeger tracer to a Jaeger cluster (trace driver setup -in :repo:`/examples/jaeger-tracing/front-envoy-jaeger.yaml`). +in :download:`front-envoy-jaeger.yaml <_include/jaeger-tracing/front-envoy-jaeger.yaml>`). Before routing a request to the appropriate service Envoy or the application, Envoy will take care of generating the appropriate spans for tracing (parent/child context spans). @@ -26,12 +33,10 @@ One of the most important benefits of tracing from Envoy is that it will take ca propagating the traces to the Jaeger service cluster. However, in order to fully take advantage of tracing, the application has to propagate trace headers that Envoy generates, while making calls to other services. In the sandbox we have provided, the simple flask app -(see trace function in :repo:`/examples/front-proxy/service.py`) acting as service1 propagates +(see trace function in :download:`service.py <_include/front-proxy/service.py>`) acting as service1 propagates the trace headers while making an outbound call to service2. -.. include:: _include/docker-env-setup.rst - -Step 3: Build the sandbox +Step 1: Build the sandbox ************************* To build this sandbox example, and start the example apps run the following commands: @@ -51,7 +56,7 @@ To build this sandbox example, and start the example apps run the following comm jaeger-tracing_service1_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp jaeger-tracing_service2_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp -Step 4: Generate some load +Step 2: Generate some load ************************** You can now send a request to service1 via the front-envoy as follows: @@ -76,10 +81,21 @@ You can now send a request to service1 via the front-envoy as follows: Hello from behind Envoy (service 1)! hostname: f26027f1ce28 resolvedhostname: 172.19.0.6 * Connection #0 to host 192.168.99.100 left intact -Step 5: View the traces in Jaeger UI +Step 3: View the traces in Jaeger UI ************************************ Point your browser to http://localhost:16686 . You should see the Jaeger dashboard. Set the service to "front-proxy" and hit 'Find Traces'. You should see traces from the front-proxy. Click on a trace to explore the path taken by the request from front-proxy to service1 to service2, as well as the latency incurred at each hop. + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + :ref:`Jaeger native tracing sandbox ` + An example of using Jaeger natively with Envoy. + + `Jaeger `_ + Jaeger tracing website. diff --git a/docs/root/start/sandboxes/load_reporting_service.rst b/docs/root/start/sandboxes/load_reporting_service.rst index 4258cf57fcc0..59d1bdfd19f8 100644 --- a/docs/root/start/sandboxes/load_reporting_service.rst +++ b/docs/root/start/sandboxes/load_reporting_service.rst @@ -1,9 +1,14 @@ .. _install_sandboxes_load_reporting_service: -Load Reporting Service (LRS) -============================ +Load reporting service (``LRS``) +================================ -This simple example demonstrates Envoy's Load Reporting Service (LRS) capability and how to use it. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + +This simple example demonstrates Envoy's :ref:`Load reporting service (LRS) ` +capability and how to use it. Lets say Cluster A (downstream) talks to Cluster B (Upstream) and Cluster C (Upstream). When enabling Load Report for Cluster A, LRS server should be sending LoadStatsResponse to Cluster A with LoadStatsResponse.Clusters to be B and C. @@ -12,13 +17,14 @@ from Cluster A to Cluster C. In this example, all incoming requests are routed via Envoy to a simple goLang web server aka http_server. We scale up two containers and randomly send requests to each. Envoy is configured to initiate the connection with LRS Server. -LRS Server enables the stats by sending LoadStatsResponse. Sending requests to http_server will be counted towards successful requests and will be visible in LRS Server logs. +LRS Server enables the stats by sending LoadStatsResponse. Sending requests to http_server will be counted towards successful +requests and will be visible in LRS Server logs. -.. include:: _include/docker-env-setup.rst - -Step 3: Build the sandbox +Step 1: Build the sandbox ************************* +Change to the ``examples/load-reporting-service`` directory. + Terminal 1 :: $ pwd @@ -39,7 +45,7 @@ Terminal 2 :: load-reporting-service_http_service_2 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 0.0.0.0:80->80/tcp load-reporting-service_lrs_server_1 go run main.go Up 0.0.0.0:18000->18000/tcp -Step 4: Start sending stream of HTTP requests +Step 2: Start sending stream of HTTP requests ********************************************* Terminal 2 :: @@ -48,9 +54,10 @@ Terminal 2 :: envoy/examples/load_reporting_service $ bash send_requests.sh -The script above (``send_requests.sh``) sends requests randomly to each Envoy, which in turn forwards the requests to the backend service. +The script above (:download:`send_requests.sh <_include/load-reporting-service/send_requests.sh>`) sends requests +randomly to each Envoy, which in turn forwards the requests to the backend service. -Step 5: See Envoy Stats +Step 3: See Envoy Stats *********************** You should see @@ -73,3 +80,11 @@ Terminal 1 :: ............................ lrs_server_1 | 2020/02/12 17:09:09 Got stats from cluster `http_service` node `0022a319e1e2` - cluster_name:"local_service" upstream_locality_stats: total_successful_requests:3 total_issued_requests:3 > load_report_interval: lrs_server_1 | 2020/02/12 17:09:09 Got stats from cluster `http_service` node `2417806c9d9a` - cluster_name:"local_service" upstream_locality_stats: total_successful_requests:9 total_issued_requests:9 > load_report_interval: + +.. seealso:: + + :ref:`Load reporting service ` + Overview of Envoy's Load reporting service. + + :ref:`Load reporting service API(V3) ` + The Load reporting service API. diff --git a/docs/root/start/sandboxes/lua.rst b/docs/root/start/sandboxes/lua.rst index a3d5ab30a479..b8538f2f5872 100644 --- a/docs/root/start/sandboxes/lua.rst +++ b/docs/root/start/sandboxes/lua.rst @@ -1,19 +1,31 @@ .. _install_sandboxes_lua: -Lua Filter +Lua filter ========== -In this example, we show how a Lua filter can be used with the Envoy -proxy. The Envoy proxy configuration includes a Lua -filter that contains two functions namely -``envoy_on_request(request_handle)`` and -``envoy_on_response(response_handle)`` as documented :ref:`here `. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst -Step 3: Build the sandbox + :ref:`curl ` + Used to make ``HTTP`` requests. + +In this example, we show how a `Lua `_ filter can be used with the Envoy +proxy. + +The Envoy proxy configuration includes a Lua filter that contains two functions: + +- ``envoy_on_request(request_handle)`` +- ``envoy_on_response(response_handle)`` + +:ref:`See here ` for an overview of Envoy's Lua filter and documentation +regarding these functions. + +Step 1: Build the sandbox ************************* +Change to the ``examples/lua`` directory. + .. code-block:: console $ pwd @@ -27,7 +39,7 @@ Step 3: Build the sandbox lua_proxy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8000->8000/tcp lua_web_service_1 node ./index.js Up 0.0.0.0:8080->80/tcp -Step 4: Send a request to the service +Step 2: Send a request to the service ************************************* The output from the ``curl`` command below should include the headers ``foo``. @@ -83,3 +95,11 @@ Terminal 1 } * Connection #0 to host localhost left intact }* Closing connection 0 + +.. seealso:: + + :ref:`Envoy Lua filter ` + Learn more about the Envoy Lua filter. + + `Lua `_ + The Lua programming language. diff --git a/docs/root/start/sandboxes/mysql.rst b/docs/root/start/sandboxes/mysql.rst index ab5d384c5424..ca588f94faa0 100644 --- a/docs/root/start/sandboxes/mysql.rst +++ b/docs/root/start/sandboxes/mysql.rst @@ -1,16 +1,27 @@ .. _install_sandboxes_mysql: -MySQL Filter +MySQL filter ============ -In this example, we show how the :ref:`MySQL filter ` can be used with the Envoy proxy. The Envoy proxy configuration includes a MySQL filter that parses queries and collects MySQL-specific -metrics. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. -.. include:: _include/docker-env-setup.rst +In this example, we show how the :ref:`MySQL filter ` can be used with the Envoy proxy. -Step 3: Build the sandbox +The Envoy proxy configuration includes a MySQL filter that parses queries and collects MySQL-specific +metrics. + +Step 1: Build the sandbox ************************* +Change to the ``examples/mysql`` directory. + +Build and start the containers. + Terminal 1 .. code-block:: console @@ -26,7 +37,7 @@ Terminal 1 mysql_mysql_1 docker-entrypoint.sh mysqld Up 3306/tcp mysql_proxy_1 /docker-entrypoint.sh /bin Up 10000/tcp, 0.0.0.0:1999->1999/tcp, 0.0.0.0:8001->8001/tcp -Step 4: Issue commands using mysql +Step 2: Issue commands using mysql ********************************** Use ``mysql`` to issue some commands and verify they are routed via Envoy. Note @@ -71,7 +82,7 @@ Terminal 1 mysql> exit Bye -Step 5: Check egress stats +Step 3: Check egress stats ************************** Check egress stats were updated. @@ -91,7 +102,7 @@ Terminal 1 mysql.egress_mysql.sessions: 1 mysql.egress_mysql.upgraded_to_ssl: 0 -Step 6: Check TCP stats +Step 4: Check TCP stats *********************** Check TCP stats were updated. @@ -112,3 +123,14 @@ Terminal 1 tcp.mysql_tcp.idle_timeout: 0 tcp.mysql_tcp.upstream_flush_active: 0 tcp.mysql_tcp.upstream_flush_total: 0 + +.. seealso:: + + :ref:`Envoy MySQL filter ` + Learn more about using the Envoy MySQL filter. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `MySQL `_ + The MySQL database. diff --git a/docs/root/start/sandboxes/postgres.rst b/docs/root/start/sandboxes/postgres.rst index 0419533cff08..0e11ecb1e502 100644 --- a/docs/root/start/sandboxes/postgres.rst +++ b/docs/root/start/sandboxes/postgres.rst @@ -1,18 +1,28 @@ .. _install_sandboxes_postgres: -Postgres Filter -=============== +PostgreSQL filter +================= -In this example, we show how the :ref:`Postgres filter ` -can be used with the Envoy proxy. The Envoy proxy configuration includes a Postgres filter that -parses queries and collects Postgres-specific metrics. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst + :ref:`curl ` + Used to make ``HTTP`` requests. -Step 3: Build the sandbox +In this example, we show how the :ref:`PostgreSQL filter ` +can be used with the Envoy proxy. + +The Envoy proxy configuration includes a PostgreSQL filter that parses queries and collects Postgres-specific metrics. + + +Step 1: Build the sandbox ************************* +Change to the ``examples/postgres`` directory. + +Build and start the containers. + .. code-block:: console $ pwd @@ -26,7 +36,7 @@ Step 3: Build the sandbox postgres_postgres_1 docker-entrypoint.sh postgres Up 5432/tcp postgres_proxy_1 /docker-entrypoint.sh /usr ... Up 10000/tcp, 0.0.0.0:1999->1999/tcp, 0.0.0.0:8001->8001/tcp -Step 4: Issue commands using psql +Step 2: Issue commands using psql ********************************* This example uses ``psql`` client inside a container to issue some commands and @@ -65,7 +75,7 @@ filter can't decode encrypted sessions. testdb=# \q -Step 5: Check egress stats +Step 3: Check egress stats ************************** Check egress stats were updated. @@ -105,7 +115,7 @@ Check egress stats were updated. postgres.egress_postgres.transactions_rollback: 0 -Step 6: Check TCP stats +Step 4: Check TCP stats *********************** Check TCP stats were updated. @@ -125,3 +135,14 @@ Check TCP stats were updated. tcp.postgres_tcp.max_downstream_connection_duration: 0 tcp.postgres_tcp.upstream_flush_active: 0 tcp.postgres_tcp.upstream_flush_total: 0 + +.. seealso:: + + :ref:`Envoy PostgreSQL filter ` + Learn more about using the Envoy PostgreSQL filter. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `PostgreSQL `_ + The PostgreSQL database. diff --git a/docs/root/start/sandboxes/redis.rst b/docs/root/start/sandboxes/redis.rst index 4faf9ef93120..5d304337ec63 100644 --- a/docs/root/start/sandboxes/redis.rst +++ b/docs/root/start/sandboxes/redis.rst @@ -1,15 +1,26 @@ .. _install_sandboxes_redis_filter: -Redis Filter +Redis filter ============ -In this example, we show how a :ref:`Redis filter ` can be used with the Envoy proxy. The Envoy proxy configuration includes a Redis filter that routes egress requests to redis server. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst -Step 3: Build the sandbox + :ref:`redis ` + Redis client to query the server. + +In this example, we show how a :ref:`Redis filter ` can be used with the Envoy proxy. + +The Envoy proxy configuration includes a Redis filter that routes egress requests to redis server. + +Step 1: Build the sandbox ************************* +Change to the ``examples/redis`` directory. + +Build and start the containers. + Terminal 1 .. code-block:: console @@ -25,7 +36,7 @@ Terminal 1 redis_proxy_1 /docker-entrypoint.sh /bin Up 10000/tcp, 0.0.0.0:1999->1999/tcp, 0.0.0.0:8001->8001/tcp redis_redis_1 docker-entrypoint.sh redis Up 6379/tcp -Step 4: Issue Redis commands +Step 2: Issue Redis commands **************************** Issue Redis commands using your favourite Redis client, such as ``redis-cli``, and verify they are routed via Envoy. @@ -43,7 +54,7 @@ Terminal 1 $ redis-cli -h localhost -p 1999 get bar "bar" -Step 5: Verify egress stats +Step 3: Verify egress stats *************************** Go to ``http://localhost:8001/stats?usedonly&filter=redis.egress_redis.command`` and verify the following stats: @@ -52,3 +63,14 @@ Go to ``http://localhost:8001/stats?usedonly&filter=redis.egress_redis.command`` redis.egress_redis.command.get.total: 2 redis.egress_redis.command.set.total: 2 + +.. seealso:: + + :ref:`Envoy Redis filter ` + Learn more about using the Envoy Redis filter. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `Redis `_ + The Redis in-memory data structure store. diff --git a/docs/root/start/sandboxes/setup.rst b/docs/root/start/sandboxes/setup.rst new file mode 100644 index 000000000000..a679426b0525 --- /dev/null +++ b/docs/root/start/sandboxes/setup.rst @@ -0,0 +1,137 @@ +.. _start_sandboxes_setup: + +Setup the sandbox environment +============================= + +Before you can run the Envoy sandboxes you will need to set up your environment +with :ref:`Docker ` and +:ref:`Docker Compose `. + +You should also clone the :ref:`Envoy repository ` with +:ref:`Git ` + +Some of the examples require the installation of +:ref:`additional dependencies `. + +It is indicated in the sandbox documentation where this is the case. + +.. tip:: + + If you are working on a Mac OS or Windows system, a simple way to install both + :ref:`Docker ` and + :ref:`Docker Compose ` is with + `Docker Desktop `_. + +.. _start_sandboxes_setup_docker: + +Install Docker +-------------- + +Ensure that you have a recent versions of ``docker`` installed. + +You will need a minimum version of ``18.06.0+``. + +Version ``19.03`` is well tested. + +The user account running the examples will need to have permission to use Docker on your system. + +Full instructions for installing Docker can be found on the `Docker website `_ + +.. _start_sandboxes_setup_docker_compose: + +Install Docker Compose +---------------------- + +The examples use +`Docker compose configuration version 3.7 `_. + +You will need to install a fairly recent version of `Docker Compose `_. + +Version ``1.27.4`` is well tested. + +Docker Compose is a `python application `_ and can be +installed through a variety of methods including `pip `_ and +`native operating system installation `_. + +.. _start_sandboxes_setup_git: + +Install Git +----------- + +The Envoy project is managed using `Git `_. + +You can `find instructions for installing Git on various operating systems here `_. + +.. _start_sandboxes_setup_envoy: + +Clone the Envoy repository +-------------------------- + +If you have not cloned the `Envoy repository `_ already, +clone it with: + +.. tabs:: + + .. code-tab:: console SSH + + git clone git@github.com:envoyproxy/envoy + + .. code-tab:: console HTTPS + + git clone https://github.com/envoyproxy/envoy.git + +.. _start_sandboxes_setup_additional: + +Additional dependencies +----------------------- + +The following utilities are used in only some of the sandbox examples, and installation is +therefore optional. + +.. _start_sandboxes_setup_curl: + +curl +~~~~ + +Many of the examples use the `curl `_ utility to make ``HTTP`` requests. + +Instructions for installing `curl `_ on many platforms and operating systems +can be `found on the curl website `_. + +.. _start_sandboxes_setup_jq: + +jq +~~~ + +The `jq `_ tool is very useful for parsing ``json`` data, +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_openssl: + +openssl +~~~~~~~ + +`OpenSSL `_ is a robust, commercial-grade, and full-featured toolkit for +the Transport Layer Security (``TLS``) and Secure Sockets Layer (``SSL``) protocols. + +Binary distributions of `OpenSSL `_ are available for Mac OS with `brew `_ +and in most if not all flavours of Linux. + +Windows users can either use an `unofficial binary `_ or compile from source. + +Check for installation instructions specific to your operating system. + +.. _start_sandboxes_setup_redis: + +redis +~~~~~ + +Binary distributions of `Redis `_ are available for Mac OS with `brew `_ +and in most flavours of Linux. + +Windows users should check out the `Windows port of Redis `_. + +Check for installation instructions specific to your operating system. diff --git a/docs/root/start/sandboxes/skywalking_tracing.rst b/docs/root/start/sandboxes/skywalking_tracing.rst new file mode 100644 index 000000000000..fa8c31ff46a9 --- /dev/null +++ b/docs/root/start/sandboxes/skywalking_tracing.rst @@ -0,0 +1,108 @@ +.. _install_sandboxes_skywalking_tracing: + +SkyWalking tracing +================== + +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + +The SkyWalking tracing sandbox demonstrates Envoy's :ref:`request tracing ` +capabilities using `SkyWalking `_ as the tracing provider. This sandbox +is very similar to the Zipkin sandbox. All containers will be deployed inside a virtual network +called ``envoymesh``. + +All incoming requests are routed via the front Envoy, which is acting as a reverse proxy +sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed +by docker compose (see :download:`docker-compose.yaml <_include/skywalking-tracing/docker-compose.yaml>`). + +Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in +:download:`front-envoy-skywalking.yaml <_include/skywalking-tracing/front-envoy-skywalking.yaml>`) +and setup to propagate the spans generated by the SkyWalking tracer to a SkyWalking cluster (trace driver setup +in :download:`front-envoy-skywalking.yaml <_include/skywalking-tracing/front-envoy-skywalking.yaml>`). + +When service1 accepts the request forwarded from front envoy, it will make an API call to service2 before +returning a response. + +Step 1: Build the sandbox +************************* + +Change to the ``examples/skywalking-tracing`` directory. + +To build this sandbox example, and start the example apps run the following commands: + +.. code-block:: console + + $ pwd + envoy/examples/skywalking-tracing + $ docker-compose pull + $ docker-compose up --build -d + $ docker-compose ps + + Name Command State Ports + -------------------------------------------------------------------------------------------------------------------------------------------------- + skywalking-tracing_elasticsearch_1 /tini -- /usr/local/bin/do ... Up (healthy) 0.0.0.0:9200->9200/tcp, 9300/tcp + skywalking-tracing_front-envoy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8000->8000/tcp, 0.0.0.0:8001->8001/tcp + skywalking-tracing_service1_1 /bin/sh /usr/local/bin/sta ... Up 10000/tcp + skywalking-tracing_service2_1 /bin/sh /usr/local/bin/sta ... Up 10000/tcp + skywalking-tracing_skywalking-oap_1 bash docker-entrypoint.sh Up (healthy) 0.0.0.0:11800->11800/tcp, 1234/tcp, 0.0.0.0:12800->12800/tcp + skywalking-tracing_skywalking-ui_1 bash docker-entrypoint.sh Up 0.0.0.0:8080->8080/tcp + +Step 2: Generate some load +************************** + +You can now send a request to service1 via the front-envoy as follows: + +.. code-block:: console + + $ curl -v localhost:8000/trace/1 + * Trying ::1... + * TCP_NODELAY set + * Connected to localhost (::1) port 8000 (#0) + > GET /trace/1 HTTP/1.1 + > Host: localhost:8000 + > User-Agent: curl/7.58.0 + > Accept: */* + > + < HTTP/1.1 200 OK + < content-type: text/html; charset=utf-8 + < content-length: 89 + < server: envoy + < date: Sat, 10 Oct 2020 01:56:08 GMT + < x-envoy-upstream-service-time: 27 + < + Hello from behind Envoy (service 1)! hostname: 1a2ba43d6d84 resolvedhostname: 172.19.0.6 + * Connection #0 to host localhost left intact + +You can get SkyWalking stats of front-envoy after some requests as follows: + +.. code-block:: console + + $ curl -s localhost:8001/stats | grep tracing.skywalking + tracing.skywalking.cache_flushed: 0 + tracing.skywalking.segments_dropped: 0 + tracing.skywalking.segments_flushed: 0 + tracing.skywalking.segments_sent: 13 + +Step 3: View the traces in SkyWalking UI +**************************************** + +Point your browser to http://localhost:8080 . You should see the SkyWalking dashboard. +Set the service to "front-envoy" and set the start time to a few minutes before +the start of the test and hit enter. You should see traces from the front-proxy. +Click on a trace to explore the path taken by the request from front-proxy to service1 +to service2, as well as the latency incurred at each hop. + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `Apache SkyWalking `_ + SkyWalking observability analysis platform and application performance management system. diff --git a/docs/root/start/sandboxes/tls.rst b/docs/root/start/sandboxes/tls.rst new file mode 100644 index 000000000000..84942094bb81 --- /dev/null +++ b/docs/root/start/sandboxes/tls.rst @@ -0,0 +1,175 @@ +.. _install_sandboxes_tls: + +Transport layer security (``TLS``) +================================== + +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + + :ref:`jq ` + Parse ``json`` output from the upstream echo servers. + +This example walks through some of the ways that Envoy can be configured to make +use of encrypted connections using ``HTTP`` over ``TLS``. + +It demonstrates a number of commonly used proxying and ``TLS`` termination patterns: + +- ``https`` -> ``http`` +- ``https`` -> ``https`` +- ``http`` -> ``https`` +- ``https`` passthrough + +To better understand the provided examples, and for a description of how ``TLS`` is +configured with Envoy, please see the :ref:`securing Envoy quick start guide `. + +.. warning:: + + For the sake of simplicity, the examples provided here do not authenticate any client certificates, + or validate any of the provided certificates. + + When using ``TLS``, you are strongly encouraged to :ref:`validate ` + all certificates wherever possible. + + You should also :ref:`authenticate clients ` + where you control both sides of the connection, or relevant protocols are available. + +Step 1: Build the sandbox +************************* + +Change directory to ``examples/tls`` in the Envoy repository. + +This starts four proxies listening on ``localhost`` ports ``10000-10003``. + +It also starts two upstream services, one ``HTTP`` and one ``HTTPS``, which echo back received headers +in ``json`` format. + +The upstream services listen on the internal Docker network on ports ``80`` and ``443`` respectively. + +.. code-block:: console + + $ pwd + envoy/examples/tls + $ docker-compose pull + $ docker-compose up --build -d + $ docker-compose ps + + Name Command State Ports + ----------------------------------------------------------------------------------------------- + tls_proxy-https-to-http_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10000->10000/tcp + tls_proxy-https-to-https_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10001->10000/tcp + tls_proxy-http-to-https_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10002->10000/tcp + tls_proxy-https-passthrough_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10003->10000/tcp + tls_service-http_1 node ./index.js Up + tls_service-https_1 node ./index.js Up + +Step 2: Test proxying ``https`` -> ``http`` +******************************************* + +The Envoy proxy listening on https://localhost:10000 terminates ``HTTPS`` and proxies to the upstream ``HTTP`` service. + +The :download:`https -> http configuration <_include/tls/envoy-https-http.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to the +:ref:`listener `. + +Querying the service at port ``10000`` you should see an ``x-forwarded-proto`` header of ``https`` has +been added: + +.. code-block:: console + + $ curl -sk https://localhost:10000 | jq -r '.headers["x-forwarded-proto"]' + https + +The upstream ``service-http`` handles the request. + +.. code-block:: console + + $ curl -sk https://localhost:10000 | jq -r '.os.hostname' + service-http + +Step 3: Test proxying ``https`` -> ``https`` +******************************************** + +The Envoy proxy listening on https://localhost:10001 terminates ``HTTPS`` and proxies to the upstream ``HTTPS`` service. + +The :download:`https -> https configuration <_include/tls/envoy-https-https.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to both the +:ref:`listener ` and the +:ref:`cluster `. + +Querying the service at port ``10001`` you should see an ``x-forwarded-proto`` header of ``https`` has +been added: + +.. code-block:: console + + $ curl -sk https://localhost:10001 | jq -r '.headers["x-forwarded-proto"]' + https + +The upstream ``service-https`` handles the request. + +.. code-block:: console + + $ curl -sk https://localhost:10001 | jq -r '.os.hostname' + service-https + +Step 4: Test proxying ``http`` -> ``https`` +******************************************* + +The Envoy proxy listening on http://localhost:10002 terminates ``HTTP`` and proxies to the upstream ``HTTPS`` service. + +The :download:`http -> https configuration <_include/tls/envoy-http-https.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to the +:ref:`cluster `. + +Querying the service at port ``10002`` you should see an ``x-forwarded-proto`` header of ``http`` has +been added: + +.. code-block:: console + + $ curl -s http://localhost:10002 | jq -r '.headers["x-forwarded-proto"]' + http + +The upstream ``service-https`` handles the request. + +.. code-block:: console + + $ curl -s http://localhost:10002 | jq -r '.os.hostname' + service-https + + +Step 5: Test proxying ``https`` passthrough +******************************************* + +The Envoy proxy listening on https://localhost:10003 proxies directly to the upstream ``HTTPS`` service which +does the ``TLS`` termination. + +The :download:`https passthrough configuration <_include/tls/envoy-https-passthrough.yaml>` requires no ``TLS`` +or ``HTTP`` setup, and instead uses a simple +:ref:`tcp_proxy `. + +Querying the service at port ``10003`` you should see that no ``x-forwarded-proto`` header has been +added: + +.. code-block:: console + + $ curl -sk https://localhost:10003 | jq -r '.headers["x-forwarded-proto"]' + null + +The upstream ``service-https`` handles the request. + +.. code-block:: console + + $ curl -sk https://localhost:10003 | jq -r '.os.hostname' + service-https + +.. seealso:: + + :ref:`Securing Envoy quick start guide ` + Outline of key concepts for securing Envoy. + + :ref:`Double proxy sandbox ` + An example of securing traffic between proxies with validation and + mutual authentication using ``mTLS`` with non-``HTTP`` traffic. diff --git a/docs/root/start/sandboxes/wasm-cc.rst b/docs/root/start/sandboxes/wasm-cc.rst index b28541a15a4e..8a0c9517bb14 100644 --- a/docs/root/start/sandboxes/wasm-cc.rst +++ b/docs/root/start/sandboxes/wasm-cc.rst @@ -1,22 +1,28 @@ .. _install_sandboxes_wasm_filter: -WASM C++ filter +Wasm C++ filter =============== -This sandbox demonstrates a basic C++ Wasm filter which injects content into the body of an ``HTTP`` response, and adds -and updates some headers. +.. sidebar:: Requirements -It also takes you through the steps required to build your own C++ Wasm filter, and run it with Envoy. + .. include:: _include/docker-env-setup-link.rst -.. include:: _include/docker-env-setup.rst + :ref:`curl ` + Used to make ``HTTP`` requests. -Step 3: Start all of our containers -*********************************** - -.. note:: +.. sidebar:: Compatibility The provided Wasm binary was compiled for the ``x86_64`` architecture. If you would like to use this sandbox - with the ``arm64`` architecture, skip to Step 5. + with the ``arm64`` architecture, change directory to ``examples/wasm-cc`` and skip to Step 3. + +This sandbox demonstrates a basic :ref:`Envoy Wasm filter ` written in C++ which injects +content into the body of an ``HTTP`` response, and adds and updates some headers. + +It also takes you through the steps required to build your own C++ :ref:`Wasm filter `, +and run it with Envoy. + +Step 1: Start all of our containers +*********************************** First lets start the containers - an Envoy proxy which uses a Wasm Filter, and a backend which echos back our request. @@ -35,7 +41,7 @@ Change to the ``examples/wasm-cc`` folder in the Envoy repo, and start the compo wasm_proxy_1 /docker-entrypoint.sh /usr ... Up 10000/tcp, 0.0.0.0:8000->8000/tcp wasm_web_service_1 node ./index.js Up -Step 4: Check web response +Step 2: Check web response ************************** The Wasm filter should inject "Hello, world" at the end of the response body when you make a request to the proxy. @@ -55,7 +61,7 @@ The filter also sets the ``content-type`` header to ``text/plain``, and adds a c $ curl -v http://localhost:8000 | grep "x-wasm-custom: " x-wasm-custom: FOO -Step 5: Compile the updated filter +Step 3: Compile the updated filter ********************************** There are two source code files provided for the Wasm filter. @@ -93,10 +99,10 @@ The compiled binary should now be in the ``lib`` folder. -r-xr-xr-x 1 root root 59641 Oct 20 00:00 envoy_filter_http_wasm_example.wasm -r-xr-xr-x 1 root root 59653 Oct 20 10:16 envoy_filter_http_wasm_updated_example.wasm -Step 6: Edit the Dockerfile and restart the proxy +Step 4: Edit the Dockerfile and restart the proxy ************************************************* -Edit the ``Dockerfile-proxy`` recipe provided in the example to use the updated binary you created in step 5. +Edit the ``Dockerfile-proxy`` recipe provided in the example to use the updated binary you created in step 3. Find the ``COPY`` line that adds the Wasm binary to the image: @@ -117,7 +123,7 @@ Now, rebuild and start the proxy container. $ docker-compose up --build -d proxy -Step 7: Check the proxy has been updated +Step 5: Check the proxy has been updated **************************************** The Wasm filter should instead inject "Hello, Wasm world" at the end of the response body. @@ -136,3 +142,14 @@ The ``content-type`` and ``x-wasm-custom`` headers should also have changed $ curl -v http://localhost:8000 | grep "x-wasm-custom: " x-wasm-custom: BAR + +.. seealso:: + + :ref:`Envoy Wasm filter ` + Further information about the Envoy Wasm filter. + + :ref:`Envoy Wasm API(V3) ` + The Envoy Wasm API - version 3. + + `Proxy Wasm C++ SDK `_ + WebAssembly for proxies (C++ SDK) diff --git a/docs/root/start/sandboxes/websocket.rst b/docs/root/start/sandboxes/websocket.rst new file mode 100644 index 000000000000..7f28770c7eae --- /dev/null +++ b/docs/root/start/sandboxes/websocket.rst @@ -0,0 +1,159 @@ +.. _install_sandboxes_websocket: + +WebSockets +========== + +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`openssl ` + Generate ``SSL`` keys and certificates. + +This example walks through some of the ways that Envoy can be configured to proxy WebSockets. + +It demonstrates terminating a WebSocket connection with and without ``TLS``, and provides some basic examples +of proxying to encrypted and non-encrypted upstream sockets. + +.. warning:: + + For the sake of simplicity, the examples provided here do not authenticate any client certificates, + or validate any of the provided certificates. + + When using ``TLS``, you are strongly encouraged to :ref:`validate ` + all certificates wherever possible. + + You should also :ref:`authenticate clients ` + where you control both sides of the connection, or relevant protocols are available. + +Step 1: Create a certificate file for wss +***************************************** + +Change directory to ``examples/websocket`` in the Envoy repository. + +.. code-block:: console + + $ pwd + envoy/examples/websocket + $ mkdir -p certs + $ openssl req -batch -new -x509 -nodes -keyout certs/key.pem -out certs/cert.pem + Generating a RSA private key + ..................................................................................................................+++++ + ......+++++ + writing new private key to 'certs/key.pem' + ----- + $ openssl pkcs12 -export -passout pass: -out certs/output.pkcs12 -inkey certs/key.pem -in certs/cert.pem + +Step 2: Build and start the sandbox +*********************************** + +This starts three proxies listening on ``localhost`` ports ``10000-30000``. + +It also starts two upstream services, one ``ws`` and one ``wss``. + +The upstream services listen on the internal Docker network on ports ``80`` and ``443`` respectively. + +The socket servers are very trivial implementations, that simply output ``[ws] HELO`` and +``[wss] HELO`` in response to any input. + +.. code-block:: console + + $ docker-compose pull + $ docker-compose up --build -d + $ docker-compose ps + + Name Command State Ports + --------------------------------------------------------------------------------------------------- + websocket_proxy-ws_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10000->10000/tcp + websocket_proxy-wss_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:20000->10000/tcp + websocket_proxy-wss-passthrough_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:30000->10000/tcp + websocket_service-ws_1 websocat -E ws-listen:0.0. ... Up + websocket_service-wss_1 websocat wss-listen:0.0.0. ... Up + +Step 3: Test proxying ``ws`` -> ``ws`` +************************************** + +The proxy listening on port ``10000`` terminates the WebSocket connection without ``TLS`` and then proxies +to an upstream socket, also without ``TLS``. + +In order for Envoy to terminate the WebSocket connection, the +:ref:`upgrade_configs ` +in :ref:`HttpConnectionManager ` +must be set, as can be seen in the provided :download:`ws -> ws configuration <_include/websocket/envoy-ws.yaml>`: + +.. literalinclude:: _include/websocket/envoy-ws.yaml + :language: yaml + :lines: 1-29 + :linenos: + :emphasize-lines: 13-14 + +You can start an interactive session with the socket as follows: + +.. code-block:: console + + $ docker run -ti --network=host solsson/websocat ws://localhost:10000 + HELO + [ws] HELO + GOODBYE + [ws] HELO + +Type ``Ctrl-c`` to exit the socket session. + +Step 4: Test proxying ``wss`` -> ``wss`` +**************************************** + +The proxy listening on port ``20000`` terminates the WebSocket connection with ``TLS`` and then proxies +to an upstream ``TLS`` WebSocket. + +In addition to the +:ref:`upgrade_configs ` +in :ref:`HttpConnectionManager `, +the :download:`wss -> wss configuration <_include/websocket/envoy-wss.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to both the +:ref:`listener ` and the +:ref:`cluster `. + +You can start an interactive session with the socket as follows: + +.. code-block:: console + + $ docker run -ti --network=host solsson/websocat --insecure wss://localhost:20000 + HELO + [wss] HELO + GOODBYE + [wss] HELO + +Type ``Ctrl-c`` to exit the socket session. + +Step 5: Test proxying ``wss`` passthrough +***************************************** + +The proxy listening on port ``30000`` passes through all ``TCP`` traffic to an upstream ``TLS`` WebSocket. + +The :download:`wss passthrough configuration <_include/websocket/envoy-wss-passthrough.yaml>` requires no ``TLS`` +or ``HTTP`` setup, and instead uses a simple +:ref:`tcp_proxy `. + +You can start an interactive session with the socket as follows: + +.. code-block:: console + + $ docker run -ti --network=host solsson/websocat --insecure wss://localhost:30000 + HELO + [wss] HELO + GOODBYE + [wss] HELO + +Type ``Ctrl-c`` to exit the socket session. + +.. seealso:: + + :ref:`Securing Envoy quick start guide ` + Outline of key concepts for securing Envoy. + + :ref:`Double proxy sandbox ` + An example of securing traffic between proxies with validation and + mutual authentication using ``mTLS`` with non-``HTTP`` traffic. + + :ref:`TLS sandbox ` + Examples of various ``TLS`` termination patterns with Envoy. diff --git a/docs/root/start/sandboxes/zipkin_tracing.rst b/docs/root/start/sandboxes/zipkin_tracing.rst index 24571b51278a..e2f9a10412a2 100644 --- a/docs/root/start/sandboxes/zipkin_tracing.rst +++ b/docs/root/start/sandboxes/zipkin_tracing.rst @@ -1,8 +1,15 @@ .. _install_sandboxes_zipkin_tracing: -Zipkin Tracing +Zipkin tracing ============== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + The Zipkin tracing sandbox demonstrates Envoy's :ref:`request tracing ` capabilities using `Zipkin `_ as the tracing provider. This sandbox is very similar to the front proxy architecture described above, with one difference: @@ -11,11 +18,11 @@ The three containers will be deployed inside a virtual network called ``envoymes All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :repo:`/examples/zipkin-tracing/docker-compose.yaml`). Notice that -all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:repo:`/examples/zipkin-tracing/front-envoy-zipkin.yaml`) and setup to propagate the spans generated -by the Zipkin tracer to a Zipkin cluster (trace driver setup -in :repo:`/examples/zipkin-tracing/front-envoy-zipkin.yaml`). +by docker compose (see :download:`docker-compose.yaml <_include/zipkin-tracing/docker-compose.yaml>`). +Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing +setup in :download:`front-envoy-zipkin.yaml <_include/zipkin-tracing/front-envoy-zipkin.yaml>`) and setup +to propagate the spans generated by the Zipkin tracer to a Zipkin cluster (trace driver setup +in :download:`front-envoy-zipkin.yaml <_include/zipkin-tracing/front-envoy-zipkin.yaml>`). Before routing a request to the appropriate service Envoy or the application, Envoy will take care of generating the appropriate spans for tracing (parent/child/shared context spans). @@ -26,14 +33,14 @@ One of the most important benefits of tracing from Envoy is that it will take ca propagating the traces to the Zipkin service cluster. However, in order to fully take advantage of tracing, the application has to propagate trace headers that Envoy generates, while making calls to other services. In the sandbox we have provided, the simple flask app -(see trace function in :repo:`/examples/front-proxy/service.py`) acting as service1 propagates +(see trace function in :download:`service.py <_include/front-proxy/service.py>`) acting as service1 propagates the trace headers while making an outbound call to service2. -.. include:: _include/docker-env-setup.rst - Step 3: Build the sandbox ************************* +Change directory to ``examples/zipkin-tracing`` in the Envoy repository. + To build this sandbox example, and start the example apps run the following commands: .. code-block:: console @@ -84,3 +91,12 @@ Set the service to "front-proxy" and set the start time to a few minutes before the start of the test (step 2) and hit enter. You should see traces from the front-proxy. Click on a trace to explore the path taken by the request from front-proxy to service1 to service2, as well as the latency incurred at each hop. + + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + `Zipkin `_ + Zipkin tracing website. diff --git a/docs/root/start/start.rst b/docs/root/start/start.rst index 2747baa3e334..2f7fdb876499 100644 --- a/docs/root/start/start.rst +++ b/docs/root/start/start.rst @@ -8,9 +8,7 @@ This section gets you started with a very simple configuration and provides some The fastest way to get started using Envoy is :ref:`installing pre-built binaries `. You can also :ref:`build it ` from source. -These examples use the :ref:`v3 Envoy API `, but use only the static configuration -feature of the API, which is most useful for simple requirements. For more complex requirements -:ref:`Dynamic Configuration ` is supported. +These examples use the :ref:`v3 Envoy API `. .. toctree:: :maxdepth: 3 diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 2b2f82283110..062c4255f3cc 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -5,16 +5,24 @@ Incompatible Behavior Changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +* config: v2 is now fatal-by-default. This may be overridden by setting :option:`--bootstrap-version` 2 on the CLI for a v2 bootstrap file and also enabling the runtime `envoy.reloadable_features.enable_deprecated_v2_api` feature. + Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* * build: the Alpine based debug images are no longer built in CI, use Ubuntu based images instead. * cluster manager: the cluster which can't extract secret entity by SDS to be warming and never activate. This feature is disabled by default and is controlled by runtime guard `envoy.reloadable_features.cluster_keep_warming_no_secret_entity`. +* expr filter: added `connection.termination_details` property support. * ext_authz filter: disable `envoy.reloadable_features.ext_authz_measure_timeout_on_check_created` by default. * ext_authz filter: the deprecated field :ref:`use_alpha ` is no longer supported and cannot be set anymore. * grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. +* jwt_authn filter: added support of Jwt time constraint verification with a clock skew (default to 60 seconds) and added a filter config field :ref:`clock_skew_seconds ` to configure it. +* memory: enable new tcmalloc with restartable sequences for aarch64 builds. +* tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults. * watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. +* xds: to support TTLs, heartbeating has been added to xDS. As a result, responses that contain empty resources without updating the version will no longer be propagated to the + subscribers. To undo this for VHDS (which is the only subscriber that wants empty resources), the `envoy.reloadable_features.vhds_heartbeats` can be set to "false". Bug Fixes --------- @@ -22,10 +30,16 @@ Bug Fixes * active http health checks: properly handles HTTP/2 GOAWAY frames from the upstream. Previously a GOAWAY frame due to a graceful listener drain could cause improper failed health checks due to streams being refused by the upstream on a connection that is going away. To revert to old GOAWAY handling behavior, set the runtime feature `envoy.reloadable_features.health_check.graceful_goaway_handling` to false. +* config: validate that upgrade configs have a non-empty :ref:`upgrade_type `, fixing a bug where an errant "-" could result in unexpected behavior. * dns: fix a bug where custom resolvers provided in configuration were not preserved after network issues. +* dns_filter: correctly associate DNS response IDs when multiple queries are received. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. +* http: reject requests with missing required headers after filter chain processing. * http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. +* proxy_proto: fixed a bug where the wrong downstream address got sent to upstream connections. +* tls: fix detection of the upstream connection close event. * tls: fix read resumption after triggering buffer high-watermark and all remaining request/response bytes are stored in the SSL connection's internal buffers. +* watchdog: touch the watchdog before most event loop operations to avoid misses when handling bursts of callbacks. Removed Config or Runtime ------------------------- @@ -39,19 +53,31 @@ New Features ------------ * config: added new runtime feature `envoy.features.enable_all_deprecated_features` that allows the use of all deprecated features. * grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. +* grpc-json: added support for configuring :ref:`unescaping behavior ` for path components. * hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. * health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. +* http: added HCM :ref:`timeout config field ` to control how long a downstream has to finish sending headers before the stream is cancelled. * http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to true. +* jwt_authn: added support for :ref:`per-route config `. * listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. +* log: added a new custom flag ``%_`` to the log pattern to print the actual message to log, but with escaped newlines. * lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() `. * mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. * network: added a :ref:`timeout ` for incoming connections completing transport-level negotiation, including TLS and ALTS hanshakes. * overload: add :ref:`envoy.overload_actions.reduce_timeouts ` overload action to enable scaling timeouts down with load. * ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. * ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. +* sds: improved support for atomic :ref:`key rotations ` and added configurable rotation triggers for + :ref:`TlsCertificate ` and + :ref:`CertificateValidationContext `. * tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. +* tls: added support for RSA certificates with 4096-bit keys in FIPS mode. +* tracing: added SkyWalking tracer. +* xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded + in the list of resources to specify the TTL. Deprecated ---------- * gzip: :ref:`HTTP Gzip filter ` is rejected now unless explicitly allowed with :ref:`runtime override ` `envoy.deprecated_features.allow_deprecated_gzip_http_filter` set to `true`. +* logging: the `--log-format-prefix-with-location` option is removed. * ratelimit: the :ref:`dynamic metadata ` action is deprecated in favor of the more generic :ref:`metadata ` action. diff --git a/docs/root/version_history/v1.15.0.rst b/docs/root/version_history/v1.15.0.rst index f8943b9a9d77..d97953aac32c 100644 --- a/docs/root/version_history/v1.15.0.rst +++ b/docs/root/version_history/v1.15.0.rst @@ -108,7 +108,7 @@ New Features * listener: added in place filter chain update flow for tcp listener update which doesn't close connections if the corresponding network filter chain is equivalent during the listener update. Can be disabled by setting runtime feature `envoy.reloadable_features.listener_in_place_filterchain_update` to false. Also added additional draining filter chain stat for :ref:`listener manager ` to track the number of draining filter chains and the number of in place update attempts. -* logger: added :option:`--log-format-prefix-with-location` command line option to prefix '%v' with file path and line number. +* logger: added `--log-format-prefix-with-location` command line option to prefix '%v' with file path and line number. * lrs: added new *envoy_api_field_service.load_stats.v2.LoadStatsResponse.send_all_clusters* field in LRS response, which allows management servers to avoid explicitly listing all clusters it is interested in; behavior is allowed based on new "envoy.lrs.supports_send_all_clusters" capability diff --git a/docs/root/version_history/v1.16.0.rst b/docs/root/version_history/v1.16.0.rst index 003dd17ba1f0..d9c2d97d7f25 100644 --- a/docs/root/version_history/v1.16.0.rst +++ b/docs/root/version_history/v1.16.0.rst @@ -42,7 +42,7 @@ Minor Behavior Changes * http: the per-stream FilterState maintained by the HTTP connection manager will now provide read/write access to the downstream connection FilterState. As such, code that relies on interacting with this might see a change in behavior. * logging: added fine-grain logging for file level log control with logger management at administration interface. It can be enabled by option :option:`--enable-fine-grain-logging`. -* logging: changed default log format to `"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"` and default value of :option:`--log-format-prefix-with-location` to `0`. +* logging: changed default log format to `"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"` and default value of `--log-format-prefix-with-location` to `0`. * logging: nghttp2 log messages no longer appear at trace level unless `ENVOY_NGHTTP2_TRACE` is set in the environment. * lua: changed the response body returned by `httpCall()` API to raw data. Previously, the returned data was string. diff --git a/examples/DEVELOPER.md b/examples/DEVELOPER.md index dd2950829eb3..88e9a9b9f39c 100644 --- a/examples/DEVELOPER.md +++ b/examples/DEVELOPER.md @@ -85,6 +85,18 @@ responds_with \ -H 'Origin: https://example-service.com' ``` +#### Utility functions: `responds_without` + +You can also check that a request *does not* respond with given `HTTP` content: + +```bash +responds_without \ + "Anything unexpected" \ + "http://localhost:8000" +``` + +`responds_without` can accept additional curl arguments like `responds_with` + #### Utility functions: `responds_with_header` You can check that a request responds with an expected header as follows: diff --git a/examples/double-proxy/Dockerfile-app b/examples/double-proxy/Dockerfile-app new file mode 100644 index 000000000000..f843db1f8a53 --- /dev/null +++ b/examples/double-proxy/Dockerfile-app @@ -0,0 +1,7 @@ +FROM python:3.8-alpine + +RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev +RUN pip3 install -q Flask==0.11.1 requests==2.18.4 psycopg2-binary +RUN mkdir /code +ADD ./service.py /code +ENTRYPOINT ["python3", "/code/service.py"] diff --git a/examples/double-proxy/Dockerfile-proxy b/examples/double-proxy/Dockerfile-proxy new file mode 100644 index 000000000000..f70f44311461 --- /dev/null +++ b/examples/double-proxy/Dockerfile-proxy @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/double-proxy/Dockerfile-proxy-backend b/examples/double-proxy/Dockerfile-proxy-backend new file mode 100644 index 000000000000..875e76ad80ff --- /dev/null +++ b/examples/double-proxy/Dockerfile-proxy-backend @@ -0,0 +1,9 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-backend.yaml /etc/envoy.yaml +COPY ./certs/ca.crt /certs/cacert.pem +COPY ./certs/postgres-backend.example.com.crt /certs/servercert.pem +COPY ./certs/example.com.key /certs/serverkey.pem + +RUN chmod go+r /etc/envoy.yaml /certs/cacert.pem /certs/serverkey.pem /certs/servercert.pem +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/double-proxy/Dockerfile-proxy-frontend b/examples/double-proxy/Dockerfile-proxy-frontend new file mode 100644 index 000000000000..4b39ef030f9f --- /dev/null +++ b/examples/double-proxy/Dockerfile-proxy-frontend @@ -0,0 +1,9 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-frontend.yaml /etc/envoy.yaml +COPY ./certs/ca.crt /certs/cacert.pem +COPY ./certs/postgres-frontend.example.com.crt /certs/clientcert.pem +COPY ./certs/example.com.key /certs/clientkey.pem + +RUN chmod go+r /etc/envoy.yaml /certs/cacert.pem /certs/clientkey.pem /certs/clientcert.pem +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/double-proxy/README.md b/examples/double-proxy/README.md new file mode 100644 index 000000000000..42ac1ea14aab --- /dev/null +++ b/examples/double-proxy/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/double-proxy.html). diff --git a/examples/double-proxy/docker-compose.yaml b/examples/double-proxy/docker-compose.yaml new file mode 100644 index 000000000000..5b4d7b6452ee --- /dev/null +++ b/examples/double-proxy/docker-compose.yaml @@ -0,0 +1,64 @@ +version: "3.7" +services: + + proxy-frontend: + build: + context: . + dockerfile: Dockerfile-proxy + networks: + edge: + ports: + - "10000:10000" + + app: + build: + context: . + dockerfile: Dockerfile-app + networks: + edge: + postgres-frontend: + + proxy-postgres-frontend: + build: + context: . + dockerfile: Dockerfile-proxy-frontend + networks: + postgres-frontend: + aliases: + - postgres + postgres-in-between: + + proxy-postgres-backend: + build: + context: . + dockerfile: Dockerfile-proxy-backend + networks: + postgres-backend: + postgres-in-between: + aliases: + - proxy-postgres-backend.example.com + + postgres: + image: postgres:latest + networks: + postgres-backend: + environment: + # WARNING! Do not use it on production environments because this will + # allow anyone with access to the Postgres port to access your + # database without a password, even if POSTGRES_PASSWORD is set. + # See PostgreSQL documentation about "trust": + # https://www.postgresql.org/docs/current/auth-trust.html + POSTGRES_HOST_AUTH_METHOD: trust + +networks: + edge: + name: edge + + postgres-backend: + name: postgres-backend + + postgres-frontend: + name: postgres-frontend + + postgres-in-between: + name: postgres-in-between diff --git a/examples/double-proxy/envoy-backend.yaml b/examples/double-proxy/envoy-backend.yaml new file mode 100644 index 000000000000..9095875fa01d --- /dev/null +++ b/examples/double-proxy/envoy-backend.yaml @@ -0,0 +1,51 @@ +static_resources: + listeners: + - name: postgres_listener + address: + socket_address: + address: 0.0.0.0 + port_value: 5432 + listener_filters: + - name: "envoy.filters.listener.tls_inspector" + typed_config: {} + filter_chains: + - filters: + - name: envoy.filters.network.postgres_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.postgres_proxy.v3alpha.PostgresProxy + stat_prefix: egress_postgres + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: postgres_tcp + cluster: postgres_cluster + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + require_client_certificate: true + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + validation_context: + match_subject_alt_names: + - exact: proxy-postgres-frontend.example.com + trusted_ca: + filename: certs/cacert.pem + + clusters: + - name: postgres_cluster + connect_timeout: 1s + type: strict_dns + load_assignment: + cluster_name: postgres_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: postgres + port_value: 5432 diff --git a/examples/double-proxy/envoy-frontend.yaml b/examples/double-proxy/envoy-frontend.yaml new file mode 100644 index 000000000000..b8900f0d6f3d --- /dev/null +++ b/examples/double-proxy/envoy-frontend.yaml @@ -0,0 +1,47 @@ +static_resources: + listeners: + - name: postgres_listener + address: + socket_address: + address: 0.0.0.0 + port_value: 5432 + filter_chains: + - filters: + - name: envoy.filters.network.postgres_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.postgres_proxy.v3alpha.PostgresProxy + stat_prefix: egress_postgres + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: postgres_tcp + cluster: postgres_cluster + + clusters: + - name: postgres_cluster + connect_timeout: 1s + type: strict_dns + load_assignment: + cluster_name: postgres_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: proxy-postgres-backend.example.com + port_value: 5432 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/clientcert.pem + private_key: + filename: certs/clientkey.pem + validation_context: + match_subject_alt_names: + - exact: proxy-postgres-backend.example.com + trusted_ca: + filename: certs/cacert.pem diff --git a/examples/double-proxy/envoy.yaml b/examples/double-proxy/envoy.yaml new file mode 100644 index 000000000000..f63fc12697ee --- /dev/null +++ b/examples/double-proxy/envoy.yaml @@ -0,0 +1,41 @@ +static_resources: + listeners: + - 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 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service1 + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: service1 + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: app + port_value: 8000 diff --git a/examples/double-proxy/service.py b/examples/double-proxy/service.py new file mode 100644 index 000000000000..40c693c11efc --- /dev/null +++ b/examples/double-proxy/service.py @@ -0,0 +1,21 @@ +import sys + +from flask import Flask + +import psycopg2 + +app = Flask(__name__) + + +@app.route('/') +def hello(): + conn = psycopg2.connect("host=postgres user=postgres") + cur = conn.cursor() + cur.execute('SELECT version()') + msg = 'Connected to Postgres, version: %s' % cur.fetchone() + cur.close() + return msg + + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=8000, debug=True) diff --git a/examples/double-proxy/verify.sh b/examples/double-proxy/verify.sh new file mode 100755 index 000000000000..e933d8ece68c --- /dev/null +++ b/examples/double-proxy/verify.sh @@ -0,0 +1,54 @@ +#!/bin/bash -e + +export NAME=double-proxy +export MANUAL=true +export DELAY=5 + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + +mkdir -p certs + +run_log "Create a cert authority" +openssl genrsa -out certs/ca.key 4096 +openssl req -batch -x509 -new -nodes -key certs/ca.key -sha256 -days 1024 -out certs/ca.crt + +run_log "Create a domain key" +openssl genrsa -out certs/example.com.key 2048 + +run_log "Generate signing requests for each proxy" +openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-frontend.example.com" \ + -out certs/proxy-postgres-frontend.example.com.csr +openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-backend.example.com" \ + -out certs/proxy-postgres-backend.example.com.csr + +run_log "Generate certificates for each proxy" +openssl x509 -req \ + -in certs/proxy-postgres-frontend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-frontend.example.com") \ + -out certs/postgres-frontend.example.com.crt \ + -days 500 \ + -sha256 +openssl x509 -req \ + -in certs/proxy-postgres-backend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-backend.example.com") \ + -out certs/postgres-backend.example.com.crt \ + -days 500 \ + -sha256 + +bring_up_example + +run_log "Test app/db connection" +responds_with \ + "Connected to Postgres, version: PostgreSQL" \ + http://localhost:10000 diff --git a/examples/dynamic-config-cp/verify.sh b/examples/dynamic-config-cp/verify.sh index 0cc02650a8dc..37bd30d66182 100755 --- a/examples/dynamic-config-cp/verify.sh +++ b/examples/dynamic-config-cp/verify.sh @@ -56,8 +56,8 @@ curl -s http://localhost:19000/config_dump \ | grep '"address": "service1"' run_log "Edit resource.go" -sed -i s/service1/service2/ resource.go -sed -i s/\"1\",/\"2\",/ resource.go +sed -i'.bak' s/service1/service2/ resource.go +sed -i'.bak' s/\"1\",/\"2\",/ resource.go run_log "Bring back up the control plane" docker-compose up --build -d go-control-plane diff --git a/examples/dynamic-config-fs/Dockerfile-proxy b/examples/dynamic-config-fs/Dockerfile-proxy index f70f44311461..1728cbefb07c 100644 --- a/examples/dynamic-config-fs/Dockerfile-proxy +++ b/examples/dynamic-config-fs/Dockerfile-proxy @@ -1,5 +1,7 @@ FROM envoyproxy/envoy-dev:latest COPY ./envoy.yaml /etc/envoy.yaml -RUN chmod go+r /etc/envoy.yaml +COPY ./configs /var/lib/envoy +RUN chmod go+x /var/lib/envoy \ + && chmod go+r /etc/envoy.yaml /var/lib/envoy/* CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/dynamic-config-fs/docker-compose.yaml b/examples/dynamic-config-fs/docker-compose.yaml index b3aed8c7e8ad..1df3161e59f0 100644 --- a/examples/dynamic-config-fs/docker-compose.yaml +++ b/examples/dynamic-config-fs/docker-compose.yaml @@ -11,8 +11,6 @@ services: ports: - 10000:10000 - 19000:19000 - volumes: - - ./configs:/var/lib/envoy service1: image: jmalloc/echo-server diff --git a/examples/dynamic-config-fs/verify.sh b/examples/dynamic-config-fs/verify.sh index 1799bd9575f9..f9371cdf30b0 100755 --- a/examples/dynamic-config-fs/verify.sh +++ b/examples/dynamic-config-fs/verify.sh @@ -19,7 +19,7 @@ curl -s http://localhost:19000/config_dump \ | grep '"address": "service1"' run_log "Set upstream to service2" -sed -i s/service1/service2/ configs/cds.yaml +docker-compose exec -T proxy sed -i s/service1/service2/ /var/lib/envoy/cds.yaml run_log "Check for response comes from service2 upstream" responds_with \ diff --git a/examples/front-proxy/service.py b/examples/front-proxy/service.py index 1d5d5920a8e3..c0e008d73404 100644 --- a/examples/front-proxy/service.py +++ b/examples/front-proxy/service.py @@ -19,7 +19,10 @@ 'X-B3-Flags', # Jaeger header (for native client) - "uber-trace-id" + "uber-trace-id", + + # SkyWalking headers. + "sw8" ] diff --git a/examples/skywalking-tracing/Dockerfile-frontenvoy b/examples/skywalking-tracing/Dockerfile-frontenvoy new file mode 100644 index 000000000000..86d0a6b91b8b --- /dev/null +++ b/examples/skywalking-tracing/Dockerfile-frontenvoy @@ -0,0 +1,7 @@ +FROM envoyproxy/envoy-dev:latest + +RUN apt-get update && apt-get -q install -y \ + curl +COPY ./front-envoy-skywalking.yaml /etc/front-envoy.yaml +RUN chmod go+r /etc/front-envoy.yaml +CMD /usr/local/bin/envoy -c /etc/front-envoy.yaml --service-cluster front-proxy diff --git a/examples/skywalking-tracing/README.md b/examples/skywalking-tracing/README.md new file mode 100644 index 000000000000..5a9375b74006 --- /dev/null +++ b/examples/skywalking-tracing/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/skywalking_tracing) diff --git a/examples/skywalking-tracing/docker-compose.yaml b/examples/skywalking-tracing/docker-compose.yaml new file mode 100644 index 000000000000..5ac0e647a7fe --- /dev/null +++ b/examples/skywalking-tracing/docker-compose.yaml @@ -0,0 +1,85 @@ +version: "3.7" +services: + # Front envoy. + front-envoy: + build: + context: . + dockerfile: Dockerfile-frontenvoy + networks: + - envoymesh + ports: + - 8000:8000 + - 8001:8001 + depends_on: + - skywalking-oap + # First service. + service1: + build: + context: ../front-proxy + dockerfile: Dockerfile-service + volumes: + - ./service1-envoy-skywalking.yaml:/etc/service-envoy.yaml + networks: + - envoymesh + environment: + - SERVICE_NAME=1 + depends_on: + - skywalking-oap + # Second service. + service2: + build: + context: ../front-proxy + dockerfile: Dockerfile-service + volumes: + - ./service2-envoy-skywalking.yaml:/etc/service-envoy.yaml + networks: + - envoymesh + environment: + - SERVICE_NAME=2 + depends_on: + - skywalking-oap + # Skywalking components. + elasticsearch: + image: elasticsearch:7.9.2 + networks: + - envoymesh + healthcheck: + test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + environment: + discovery.type: single-node + ulimits: + memlock: + soft: -1 + hard: -1 + skywalking-oap: + image: apache/skywalking-oap-server:8.1.0-es7 + networks: + - envoymesh + depends_on: + - elasticsearch + environment: + SW_STORAGE: elasticsearch7 + SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200 + healthcheck: + test: ["CMD-SHELL", "/skywalking/bin/swctl"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + restart: on-failure + skywalking-ui: + image: apache/skywalking-ui:8.1.0 + networks: + - envoymesh + depends_on: + - skywalking-oap + ports: + - 8080:8080 + environment: + SW_OAP_ADDRESS: skywalking-oap:12800 +networks: + envoymesh: {} diff --git a/examples/skywalking-tracing/front-envoy-skywalking.yaml b/examples/skywalking-tracing/front-envoy-skywalking.yaml new file mode 100644 index 000000000000..32d7de94dd07 --- /dev/null +++ b/examples/skywalking-tracing/front-envoy-skywalking.yaml @@ -0,0 +1,80 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + traffic_direction: OUTBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + generate_request_id: true + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: front-envoy + instance_name: front-envoy-1 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service1 + decorator: + operation: checkAvailability + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + clusters: + - name: service1 + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: service1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service1 + port_value: 8000 + - name: skywalking + connect_timeout: 1s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: skywalking + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: skywalking-oap + port_value: 11800 +admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 diff --git a/examples/skywalking-tracing/service1-envoy-skywalking.yaml b/examples/skywalking-tracing/service1-envoy-skywalking.yaml new file mode 100644 index 000000000000..a030c63f8c9f --- /dev/null +++ b/examples/skywalking-tracing/service1-envoy-skywalking.yaml @@ -0,0 +1,128 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + traffic_direction: INBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: service1-envoy + instance_name: service1-envoy-1 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: service1_route + virtual_hosts: + - name: service1 + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: local_service + decorator: + operation: checkAvailability + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + - address: + socket_address: + address: 0.0.0.0 + port_value: 9000 + traffic_direction: OUTBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: service1-envoy + instance_name: service1-envoy-1 + codec_type: auto + stat_prefix: egress_http + route_config: + name: service2_route + virtual_hosts: + - name: service2 + domains: + - "*" + routes: + - match: + prefix: "/trace/2" + route: + cluster: service2 + decorator: + operation: checkStock + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + clusters: + - name: local_service + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + - name: service2 + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: service2 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service2 + port_value: 8000 + - name: skywalking + connect_timeout: 1s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: skywalking + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: skywalking-oap + port_value: 11800 diff --git a/examples/skywalking-tracing/service2-envoy-skywalking.yaml b/examples/skywalking-tracing/service2-envoy-skywalking.yaml new file mode 100644 index 000000000000..5f0ee1b834ea --- /dev/null +++ b/examples/skywalking-tracing/service2-envoy-skywalking.yaml @@ -0,0 +1,72 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + traffic_direction: INBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: service2-envoy + instance_name: service2-envoy-1 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: service2 + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: local_service + decorator: + operation: checkStock + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + clusters: + - name: local_service + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + - name: skywalking + connect_timeout: 1s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: skywalking + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: skywalking-oap + port_value: 11800 diff --git a/examples/skywalking-tracing/verify.sh b/examples/skywalking-tracing/verify.sh new file mode 100755 index 000000000000..3c5c4799ca90 --- /dev/null +++ b/examples/skywalking-tracing/verify.sh @@ -0,0 +1,51 @@ +#!/bin/bash -e + +export NAME=skywalking +export DELAY=200 + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + +run_log "Test connection" +responds_with \ + "Hello from behind Envoy (service 1)!" \ + http://localhost:8000/trace/1 + +run_log "Test stats" +responds_with \ + "tracing.skywalking.segments_sent: 1" \ + http://localhost:8001/stats + +run_log "Test dashboard" +responds_with \ + "" \ + http://localhost:8080 + +run_log "Test OAP Server" +responds_with \ + "getEndpoints" \ + http://localhost:8080/graphql \ + -X POST \ + -H "Content-Type:application/json" \ + -d "{ \"query\": \"query queryEndpoints(\$serviceId: ID!, \$keyword: String!) { + getEndpoints: searchEndpoint(serviceId: \$serviceId, keyword: \$keyword, limit: 100) { + key: id + label: name + } + }\", + \"variables\": { \"serviceId\": \"\", \"keyword\": \"\" } + }" + +responds_with \ + "currentTimestamp" \ + http://localhost:8080/graphql \ + -X POST \ + -H "Content-Type:application/json" \ + -d "{ \"query\": \"query queryOAPTimeInfo { + getTimeInfo { + timezone + currentTimestamp + } + }\", + \"variables\": {} + }" diff --git a/examples/tls/Dockerfile-proxy-http-https b/examples/tls/Dockerfile-proxy-http-https new file mode 100644 index 000000000000..1d13a8c4821e --- /dev/null +++ b/examples/tls/Dockerfile-proxy-http-https @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-http-https.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/Dockerfile-proxy-https-http b/examples/tls/Dockerfile-proxy-https-http new file mode 100644 index 000000000000..3ab63a86915a --- /dev/null +++ b/examples/tls/Dockerfile-proxy-https-http @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-https-http.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/Dockerfile-proxy-https-https b/examples/tls/Dockerfile-proxy-https-https new file mode 100644 index 000000000000..a34183bd6e34 --- /dev/null +++ b/examples/tls/Dockerfile-proxy-https-https @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-https-https.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/Dockerfile-proxy-https-passthrough b/examples/tls/Dockerfile-proxy-https-passthrough new file mode 100644 index 000000000000..a460c25de9d0 --- /dev/null +++ b/examples/tls/Dockerfile-proxy-https-passthrough @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-https-passthrough.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/README.md b/examples/tls/README.md new file mode 100644 index 000000000000..61d68e1757a7 --- /dev/null +++ b/examples/tls/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/tls.html). diff --git a/examples/tls/docker-compose.yaml b/examples/tls/docker-compose.yaml new file mode 100644 index 000000000000..1db7809a2220 --- /dev/null +++ b/examples/tls/docker-compose.yaml @@ -0,0 +1,42 @@ +version: "3.7" +services: + + proxy-https-to-http: + build: + context: . + dockerfile: Dockerfile-proxy-https-http + ports: + - "10000:10000" + + proxy-https-to-https: + build: + context: . + dockerfile: Dockerfile-proxy-https-https + ports: + - "10001:10000" + + proxy-http-to-https: + build: + context: . + dockerfile: Dockerfile-proxy-http-https + ports: + - "10002:10000" + + proxy-https-passthrough: + build: + context: . + dockerfile: Dockerfile-proxy-https-passthrough + ports: + - "10003:10000" + + service-http: + image: mendhak/http-https-echo + hostname: service-http + environment: + - HTTPS_PORT=0 + + service-https: + image: mendhak/http-https-echo + hostname: service-https + environment: + - HTTP_PORT=0 diff --git a/examples/tls/envoy-http-https.yaml b/examples/tls/envoy-http-https.yaml new file mode 100644 index 000000000000..2e896de2e78b --- /dev/null +++ b/examples/tls/envoy-http-https.yaml @@ -0,0 +1,45 @@ +static_resources: + listeners: + - 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 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service-https + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: service-https + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-https + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-https + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/examples/tls/envoy-https-http.yaml b/examples/tls/envoy-https-http.yaml new file mode 100644 index 000000000000..46dccfff0d6e --- /dev/null +++ b/examples/tls/envoy-https-http.yaml @@ -0,0 +1,104 @@ +static_resources: + listeners: + - 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 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service-http + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + # The following self-signed certificate pair is generated using: + # $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy' + # + # Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy + # via filename. Reference: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource. + # + # Or in a dynamic configuration scenario, certificate pair can be fetched remotely via + # Secret Discovery Service (SDS). Reference: https://www.envoyproxy.io/docs/envoy/latest/configuration/security/secret. + certificate_chain: + inline_string: | + -----BEGIN CERTIFICATE----- + MIICqDCCAZACCQCquzpHNpqBcDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtm + cm9udC1lbnZveTAeFw0yMDA3MDgwMTMxNDZaFw0zMDA3MDYwMTMxNDZaMBYxFDAS + BgNVBAMMC2Zyb250LWVudm95MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAthnYkqVQBX+Wg7aQWyCCb87hBce1hAFhbRM8Y9dQTqxoMXZiA2n8G089hUou + oQpEdJgitXVS6YMFPFUUWfwcqxYAynLK4X5im26Yfa1eO8La8sZUS+4Bjao1gF5/ + VJxSEo2yZ7fFBo8M4E44ZehIIocipCRS+YZehFs6dmHoq/MGvh2eAHIa+O9xssPt + ofFcQMR8rwBHVbKy484O10tNCouX4yUkyQXqCRy6HRu7kSjOjNKSGtjfG+h5M8bh + 10W7ZrsJ1hWhzBulSaMZaUY3vh5ngpws1JATQVSK1Jm/dmMRciwlTK7KfzgxHlSX + 58ENpS7yPTISkEICcLbXkkKGEQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmj6Hg + vwOxWz0xu+6fSfRL6PGJUGq6wghCfUvjfwZ7zppDUqU47fk+yqPIOzuGZMdAqi7N + v1DXkeO4A3hnMD22Rlqt25vfogAaZVToBeQxCPd/ALBLFrvLUFYuSlS3zXSBpQqQ + Ny2IKFYsMllz5RSROONHBjaJOn5OwqenJ91MPmTAG7ujXKN6INSBM0PjX9Jy4Xb9 + zT+I85jRDQHnTFce1WICBDCYidTIvJtdSSokGSuy4/xyxAAc/BpZAfOjBQ4G1QRe + 9XwOi790LyNUYFJVyeOvNJwveloWuPLHb9idmY5YABwikUY6QNcXwyHTbRCkPB2I + m+/R4XnmL4cKQ+5Z + -----END CERTIFICATE----- + private_key: + inline_string: | + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2GdiSpVAFf5aD + tpBbIIJvzuEFx7WEAWFtEzxj11BOrGgxdmIDafwbTz2FSi6hCkR0mCK1dVLpgwU8 + VRRZ/ByrFgDKcsrhfmKbbph9rV47wtryxlRL7gGNqjWAXn9UnFISjbJnt8UGjwzg + Tjhl6EgihyKkJFL5hl6EWzp2Yeir8wa+HZ4Achr473Gyw+2h8VxAxHyvAEdVsrLj + zg7XS00Ki5fjJSTJBeoJHLodG7uRKM6M0pIa2N8b6HkzxuHXRbtmuwnWFaHMG6VJ + oxlpRje+HmeCnCzUkBNBVIrUmb92YxFyLCVMrsp/ODEeVJfnwQ2lLvI9MhKQQgJw + tteSQoYRAgMBAAECggEAeDGdEkYNCGQLe8pvg8Z0ccoSGpeTxpqGrNEKhjfi6NrB + NwyVav10iq4FxEmPd3nobzDPkAftfvWc6hKaCT7vyTkPspCMOsQJ39/ixOk+jqFx + lNa1YxyoZ9IV2DIHR1iaj2Z5gB367PZUoGTgstrbafbaNY9IOSyojCIO935ubbcx + DWwL24XAf51ez6sXnI8V5tXmrFlNXhbhJdH8iIxNyM45HrnlUlOk0lCK4gmLJjy9 + 10IS2H2Wh3M5zsTpihH1JvM56oAH1ahrhMXs/rVFXXkg50yD1KV+HQiEbglYKUxO + eMYtfaY9i2CuLwhDnWp3oxP3HfgQQhD09OEN3e0IlQKBgQDZ/3poG9TiMZSjfKqL + xnCABMXGVQsfFWNC8THoW6RRx5Rqi8q08yJrmhCu32YKvccsOljDQJQQJdQO1g09 + e/adJmCnTrqxNtjPkX9txV23Lp6Ak7emjiQ5ICu7iWxrcO3zf7hmKtj7z+av8sjO + mDI7NkX5vnlE74nztBEjp3eC0wKBgQDV2GeJV028RW3b/QyP3Gwmax2+cKLR9PKR + nJnmO5bxAT0nQ3xuJEAqMIss/Rfb/macWc2N/6CWJCRT6a2vgy6xBW+bqG6RdQMB + xEZXFZl+sSKhXPkc5Wjb4lQ14YWyRPrTjMlwez3k4UolIJhJmwl+D7OkMRrOUERO + EtUvc7odCwKBgBi+nhdZKWXveM7B5N3uzXBKmmRz3MpPdC/yDtcwJ8u8msUpTv4R + JxQNrd0bsIqBli0YBmFLYEMg+BwjAee7vXeDFq+HCTv6XMva2RsNryCO4yD3I359 + XfE6DJzB8ZOUgv4Dvluie3TB2Y6ZQV/p+LGt7G13yG4hvofyJYvlg3RPAoGAcjDg + +OH5zLN2eqah8qBN0CYa9/rFt0AJ19+7/smLTJ7QvQq4g0gwS1couplcCEnNGWiK + 72y1n/ckvvplmPeAE19HveMvR9UoCeV5ej86fACy8V/oVpnaaLBvL2aCMjPLjPP9 + DWeCIZp8MV86cvOrGfngf6kJG2qZTueXl4NAuwkCgYEArKkhlZVXjwBoVvtHYmN2 + o+F6cGMlRJTLhNc391WApsgDZfTZSdeJsBsvvzS/Nc0burrufJg0wYioTlpReSy4 + ohhtprnQQAddfjHP7rh2LGt+irFzhdXXQ1ybGaGM9D764KUNCXLuwdly0vzXU4HU + q5sGxGrC1RECGB5Zwx2S2ZY= + -----END PRIVATE KEY----- + + clusters: + - name: service-http + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-http + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-http + port_value: 80 diff --git a/examples/tls/envoy-https-https.yaml b/examples/tls/envoy-https-https.yaml new file mode 100644 index 000000000000..e838895d903a --- /dev/null +++ b/examples/tls/envoy-https-https.yaml @@ -0,0 +1,108 @@ +static_resources: + listeners: + - 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 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service-https + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + # The following self-signed certificate pair is generated using: + # $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy' + # + # Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy + # via filename. Reference: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource. + # + # Or in a dynamic configuration scenario, certificate pair can be fetched remotely via + # Secret Discovery Service (SDS). Reference: https://www.envoyproxy.io/docs/envoy/latest/configuration/security/secret. + certificate_chain: + inline_string: | + -----BEGIN CERTIFICATE----- + MIICqDCCAZACCQCquzpHNpqBcDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtm + cm9udC1lbnZveTAeFw0yMDA3MDgwMTMxNDZaFw0zMDA3MDYwMTMxNDZaMBYxFDAS + BgNVBAMMC2Zyb250LWVudm95MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAthnYkqVQBX+Wg7aQWyCCb87hBce1hAFhbRM8Y9dQTqxoMXZiA2n8G089hUou + oQpEdJgitXVS6YMFPFUUWfwcqxYAynLK4X5im26Yfa1eO8La8sZUS+4Bjao1gF5/ + VJxSEo2yZ7fFBo8M4E44ZehIIocipCRS+YZehFs6dmHoq/MGvh2eAHIa+O9xssPt + ofFcQMR8rwBHVbKy484O10tNCouX4yUkyQXqCRy6HRu7kSjOjNKSGtjfG+h5M8bh + 10W7ZrsJ1hWhzBulSaMZaUY3vh5ngpws1JATQVSK1Jm/dmMRciwlTK7KfzgxHlSX + 58ENpS7yPTISkEICcLbXkkKGEQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmj6Hg + vwOxWz0xu+6fSfRL6PGJUGq6wghCfUvjfwZ7zppDUqU47fk+yqPIOzuGZMdAqi7N + v1DXkeO4A3hnMD22Rlqt25vfogAaZVToBeQxCPd/ALBLFrvLUFYuSlS3zXSBpQqQ + Ny2IKFYsMllz5RSROONHBjaJOn5OwqenJ91MPmTAG7ujXKN6INSBM0PjX9Jy4Xb9 + zT+I85jRDQHnTFce1WICBDCYidTIvJtdSSokGSuy4/xyxAAc/BpZAfOjBQ4G1QRe + 9XwOi790LyNUYFJVyeOvNJwveloWuPLHb9idmY5YABwikUY6QNcXwyHTbRCkPB2I + m+/R4XnmL4cKQ+5Z + -----END CERTIFICATE----- + private_key: + inline_string: | + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2GdiSpVAFf5aD + tpBbIIJvzuEFx7WEAWFtEzxj11BOrGgxdmIDafwbTz2FSi6hCkR0mCK1dVLpgwU8 + VRRZ/ByrFgDKcsrhfmKbbph9rV47wtryxlRL7gGNqjWAXn9UnFISjbJnt8UGjwzg + Tjhl6EgihyKkJFL5hl6EWzp2Yeir8wa+HZ4Achr473Gyw+2h8VxAxHyvAEdVsrLj + zg7XS00Ki5fjJSTJBeoJHLodG7uRKM6M0pIa2N8b6HkzxuHXRbtmuwnWFaHMG6VJ + oxlpRje+HmeCnCzUkBNBVIrUmb92YxFyLCVMrsp/ODEeVJfnwQ2lLvI9MhKQQgJw + tteSQoYRAgMBAAECggEAeDGdEkYNCGQLe8pvg8Z0ccoSGpeTxpqGrNEKhjfi6NrB + NwyVav10iq4FxEmPd3nobzDPkAftfvWc6hKaCT7vyTkPspCMOsQJ39/ixOk+jqFx + lNa1YxyoZ9IV2DIHR1iaj2Z5gB367PZUoGTgstrbafbaNY9IOSyojCIO935ubbcx + DWwL24XAf51ez6sXnI8V5tXmrFlNXhbhJdH8iIxNyM45HrnlUlOk0lCK4gmLJjy9 + 10IS2H2Wh3M5zsTpihH1JvM56oAH1ahrhMXs/rVFXXkg50yD1KV+HQiEbglYKUxO + eMYtfaY9i2CuLwhDnWp3oxP3HfgQQhD09OEN3e0IlQKBgQDZ/3poG9TiMZSjfKqL + xnCABMXGVQsfFWNC8THoW6RRx5Rqi8q08yJrmhCu32YKvccsOljDQJQQJdQO1g09 + e/adJmCnTrqxNtjPkX9txV23Lp6Ak7emjiQ5ICu7iWxrcO3zf7hmKtj7z+av8sjO + mDI7NkX5vnlE74nztBEjp3eC0wKBgQDV2GeJV028RW3b/QyP3Gwmax2+cKLR9PKR + nJnmO5bxAT0nQ3xuJEAqMIss/Rfb/macWc2N/6CWJCRT6a2vgy6xBW+bqG6RdQMB + xEZXFZl+sSKhXPkc5Wjb4lQ14YWyRPrTjMlwez3k4UolIJhJmwl+D7OkMRrOUERO + EtUvc7odCwKBgBi+nhdZKWXveM7B5N3uzXBKmmRz3MpPdC/yDtcwJ8u8msUpTv4R + JxQNrd0bsIqBli0YBmFLYEMg+BwjAee7vXeDFq+HCTv6XMva2RsNryCO4yD3I359 + XfE6DJzB8ZOUgv4Dvluie3TB2Y6ZQV/p+LGt7G13yG4hvofyJYvlg3RPAoGAcjDg + +OH5zLN2eqah8qBN0CYa9/rFt0AJ19+7/smLTJ7QvQq4g0gwS1couplcCEnNGWiK + 72y1n/ckvvplmPeAE19HveMvR9UoCeV5ej86fACy8V/oVpnaaLBvL2aCMjPLjPP9 + DWeCIZp8MV86cvOrGfngf6kJG2qZTueXl4NAuwkCgYEArKkhlZVXjwBoVvtHYmN2 + o+F6cGMlRJTLhNc391WApsgDZfTZSdeJsBsvvzS/Nc0burrufJg0wYioTlpReSy4 + ohhtprnQQAddfjHP7rh2LGt+irFzhdXXQ1ybGaGM9D764KUNCXLuwdly0vzXU4HU + q5sGxGrC1RECGB5Zwx2S2ZY= + -----END PRIVATE KEY----- + + clusters: + - name: service-https + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-https + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-https + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/examples/tls/envoy-https-passthrough.yaml b/examples/tls/envoy-https-passthrough.yaml new file mode 100644 index 000000000000..8ce160addf4a --- /dev/null +++ b/examples/tls/envoy-https-passthrough.yaml @@ -0,0 +1,28 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: service-https + stat_prefix: https_passthrough + + clusters: + - name: service-https + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-https + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-https + port_value: 443 diff --git a/examples/tls/verify.sh b/examples/tls/verify.sh new file mode 100755 index 000000000000..96c92992ce26 --- /dev/null +++ b/examples/tls/verify.sh @@ -0,0 +1,33 @@ +#!/bin/bash -e + +export NAME=tls + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + +run_log "Test https -> http" +responds_with \ + '"x-forwarded-proto": "https",' \ + -k \ + https://localhost:10000 +curl -sk https://localhost:10000 | jq '.os.hostname' | grep '"service-http"' + +run_log "Test https -> https" +responds_with \ + '"x-forwarded-proto": "https",' \ + -k \ + https://localhost:10001 +curl -sk https://localhost:10001 | jq '.os.hostname' | grep '"service-https"' + +run_log "Test http -> https" +responds_with \ + '"x-forwarded-proto": "http",' \ + http://localhost:10002 +curl -s http://localhost:10002 | jq '.os.hostname' | grep '"service-https"' + +run_log "Test https passthrough" +responds_without \ + '"x-forwarded-proto"' \ + -k \ + https://localhost:10003 +curl -sk https://localhost:10003 | jq '.os.hostname' | grep '"service-https"' diff --git a/examples/verify-common.sh b/examples/verify-common.sh index 509faf3a846f..277336170f82 100644 --- a/examples/verify-common.sh +++ b/examples/verify-common.sh @@ -95,6 +95,16 @@ responds_with () { } } +responds_without () { + local expected + expected="$1" + shift + _curl "${@}" | grep "$expected" | [[ "$(wc -l)" -eq 0 ]] || { + echo "ERROR: curl without (${*}): $expected" >&2 + return 1 + } +} + responds_with_header () { local expected expected="$1" diff --git a/examples/wasm-cc/BUILD b/examples/wasm-cc/BUILD index 3d139a68e0ea..a6f1975445ca 100644 --- a/examples/wasm-cc/BUILD +++ b/examples/wasm-cc/BUILD @@ -11,7 +11,7 @@ envoy_package() selects.config_setting_group( name = "include_wasm_config", - match_all = ["//bazel:x86", "//bazel:wasm_all"], + match_all = ["//bazel:x86", "//bazel:wasm_v8"], ) filegroup( diff --git a/examples/wasm-cc/docker-compose-wasm.yaml b/examples/wasm-cc/docker-compose-wasm.yaml index f0f834ebe801..43c051200fed 100644 --- a/examples/wasm-cc/docker-compose-wasm.yaml +++ b/examples/wasm-cc/docker-compose-wasm.yaml @@ -3,7 +3,7 @@ version: "3.7" services: wasm_compile_update: - image: envoyproxy/envoy-build-ubuntu:b480535e8423b5fd7c102fd30c92f4785519e33a + image: envoyproxy/envoy-build-ubuntu:19a268cfe3d12625380e7c61d2467c8779b58b56 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_updated_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" @@ -13,7 +13,7 @@ services: - ./lib:/build wasm_compile: - image: envoyproxy/envoy-build-ubuntu:b480535e8423b5fd7c102fd30c92f4785519e33a + image: envoyproxy/envoy-build-ubuntu:19a268cfe3d12625380e7c61d2467c8779b58b56 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" diff --git a/examples/wasm-cc/verify.sh b/examples/wasm-cc/verify.sh index 17e5c6f878f2..c7bedbdfb396 100755 --- a/examples/wasm-cc/verify.sh +++ b/examples/wasm-cc/verify.sh @@ -30,7 +30,7 @@ run_log "Check for the compiled update" ls -l lib/*updated*wasm run_log "Edit the Docker recipe to use the updated binary" -sed -i s/\\.\\/lib\\/envoy_filter_http_wasm_example.wasm/.\\/lib\\/envoy_filter_http_wasm_updated_example.wasm/ Dockerfile-proxy +sed -i'.bak' s/\\.\\/lib\\/envoy_filter_http_wasm_example.wasm/.\\/lib\\/envoy_filter_http_wasm_updated_example.wasm/ Dockerfile-proxy run_log "Bring the proxy back up" docker-compose up --build -d proxy diff --git a/examples/websocket/Dockerfile-proxy-ws b/examples/websocket/Dockerfile-proxy-ws new file mode 100644 index 000000000000..a8d9d19ed1b8 --- /dev/null +++ b/examples/websocket/Dockerfile-proxy-ws @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-ws.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/websocket/Dockerfile-proxy-wss b/examples/websocket/Dockerfile-proxy-wss new file mode 100644 index 000000000000..c0114fb559b4 --- /dev/null +++ b/examples/websocket/Dockerfile-proxy-wss @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-wss.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/websocket/Dockerfile-proxy-wss-passthrough b/examples/websocket/Dockerfile-proxy-wss-passthrough new file mode 100644 index 000000000000..66e4eb5d2629 --- /dev/null +++ b/examples/websocket/Dockerfile-proxy-wss-passthrough @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-wss-passthrough.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/websocket/README.md b/examples/websocket/README.md new file mode 100644 index 000000000000..8473888649c3 --- /dev/null +++ b/examples/websocket/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/websocket.html). diff --git a/examples/websocket/docker-compose.yaml b/examples/websocket/docker-compose.yaml new file mode 100644 index 000000000000..0435bfddaec2 --- /dev/null +++ b/examples/websocket/docker-compose.yaml @@ -0,0 +1,35 @@ +version: "3.7" +services: + + proxy-ws: + build: + context: . + dockerfile: Dockerfile-proxy-ws + ports: + - "10000:10000" + + proxy-wss-wss: + build: + context: . + dockerfile: Dockerfile-proxy-wss + ports: + - "20000:10000" + + proxy-wss-passthrough: + build: + context: . + dockerfile: Dockerfile-proxy-wss-passthrough + ports: + - "30000:10000" + + service-ws: + image: solsson/websocat + hostname: service-ws + command: -E ws-listen:0.0.0.0:80 literalreply:'[ws] HELO' + + service-wss: + image: solsson/websocat + hostname: service-wss + command: wss-listen:0.0.0.0:443 literalreply:"[wss] HELO" --pkcs12-der /certs/output.pkcs12 + volumes: + - ./certs/output.pkcs12:/certs/output.pkcs12 diff --git a/examples/websocket/envoy-ws.yaml b/examples/websocket/envoy-ws.yaml new file mode 100644 index 000000000000..b04013066a9c --- /dev/null +++ b/examples/websocket/envoy-ws.yaml @@ -0,0 +1,42 @@ +static_resources: + listeners: + - 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: ingress_ws_to_ws + upgrade_configs: + - upgrade_type: websocket + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service_ws + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: service_ws + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service_ws + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-ws + port_value: 80 diff --git a/examples/websocket/envoy-wss-passthrough.yaml b/examples/websocket/envoy-wss-passthrough.yaml new file mode 100644 index 000000000000..4da386464106 --- /dev/null +++ b/examples/websocket/envoy-wss-passthrough.yaml @@ -0,0 +1,28 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: service_wss_passthrough + stat_prefix: wss_passthrough + + clusters: + - name: service_wss_passthrough + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service_wss_passthrough + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-wss + port_value: 443 diff --git a/examples/websocket/envoy-wss.yaml b/examples/websocket/envoy-wss.yaml new file mode 100644 index 000000000000..9993916e1851 --- /dev/null +++ b/examples/websocket/envoy-wss.yaml @@ -0,0 +1,109 @@ +static_resources: + listeners: + - 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: ingress_wss_to_wss + upgrade_configs: + - upgrade_type: websocket + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service_wss + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + # The following self-signed certificate pair is generated using: + # $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy' + # + # Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy + # via filename. Reference: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource. + # + # Or in a dynamic configuration scenario, certificate pair can be fetched remotely via + # Secret Discovery Service (SDS). Reference: https://www.envoyproxy.io/docs/envoy/latest/configuration/security/secret. + certificate_chain: + inline_string: | + -----BEGIN CERTIFICATE----- + MIICqDCCAZACCQCquzpHNpqBcDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtm + cm9udC1lbnZveTAeFw0yMDA3MDgwMTMxNDZaFw0zMDA3MDYwMTMxNDZaMBYxFDAS + BgNVBAMMC2Zyb250LWVudm95MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAthnYkqVQBX+Wg7aQWyCCb87hBce1hAFhbRM8Y9dQTqxoMXZiA2n8G089hUou + oQpEdJgitXVS6YMFPFUUWfwcqxYAynLK4X5im26Yfa1eO8La8sZUS+4Bjao1gF5/ + VJxSEo2yZ7fFBo8M4E44ZehIIocipCRS+YZehFs6dmHoq/MGvh2eAHIa+O9xssPt + ofFcQMR8rwBHVbKy484O10tNCouX4yUkyQXqCRy6HRu7kSjOjNKSGtjfG+h5M8bh + 10W7ZrsJ1hWhzBulSaMZaUY3vh5ngpws1JATQVSK1Jm/dmMRciwlTK7KfzgxHlSX + 58ENpS7yPTISkEICcLbXkkKGEQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmj6Hg + vwOxWz0xu+6fSfRL6PGJUGq6wghCfUvjfwZ7zppDUqU47fk+yqPIOzuGZMdAqi7N + v1DXkeO4A3hnMD22Rlqt25vfogAaZVToBeQxCPd/ALBLFrvLUFYuSlS3zXSBpQqQ + Ny2IKFYsMllz5RSROONHBjaJOn5OwqenJ91MPmTAG7ujXKN6INSBM0PjX9Jy4Xb9 + zT+I85jRDQHnTFce1WICBDCYidTIvJtdSSokGSuy4/xyxAAc/BpZAfOjBQ4G1QRe + 9XwOi790LyNUYFJVyeOvNJwveloWuPLHb9idmY5YABwikUY6QNcXwyHTbRCkPB2I + m+/R4XnmL4cKQ+5Z + -----END CERTIFICATE----- + private_key: + inline_string: | + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2GdiSpVAFf5aD + tpBbIIJvzuEFx7WEAWFtEzxj11BOrGgxdmIDafwbTz2FSi6hCkR0mCK1dVLpgwU8 + VRRZ/ByrFgDKcsrhfmKbbph9rV47wtryxlRL7gGNqjWAXn9UnFISjbJnt8UGjwzg + Tjhl6EgihyKkJFL5hl6EWzp2Yeir8wa+HZ4Achr473Gyw+2h8VxAxHyvAEdVsrLj + zg7XS00Ki5fjJSTJBeoJHLodG7uRKM6M0pIa2N8b6HkzxuHXRbtmuwnWFaHMG6VJ + oxlpRje+HmeCnCzUkBNBVIrUmb92YxFyLCVMrsp/ODEeVJfnwQ2lLvI9MhKQQgJw + tteSQoYRAgMBAAECggEAeDGdEkYNCGQLe8pvg8Z0ccoSGpeTxpqGrNEKhjfi6NrB + NwyVav10iq4FxEmPd3nobzDPkAftfvWc6hKaCT7vyTkPspCMOsQJ39/ixOk+jqFx + lNa1YxyoZ9IV2DIHR1iaj2Z5gB367PZUoGTgstrbafbaNY9IOSyojCIO935ubbcx + DWwL24XAf51ez6sXnI8V5tXmrFlNXhbhJdH8iIxNyM45HrnlUlOk0lCK4gmLJjy9 + 10IS2H2Wh3M5zsTpihH1JvM56oAH1ahrhMXs/rVFXXkg50yD1KV+HQiEbglYKUxO + eMYtfaY9i2CuLwhDnWp3oxP3HfgQQhD09OEN3e0IlQKBgQDZ/3poG9TiMZSjfKqL + xnCABMXGVQsfFWNC8THoW6RRx5Rqi8q08yJrmhCu32YKvccsOljDQJQQJdQO1g09 + e/adJmCnTrqxNtjPkX9txV23Lp6Ak7emjiQ5ICu7iWxrcO3zf7hmKtj7z+av8sjO + mDI7NkX5vnlE74nztBEjp3eC0wKBgQDV2GeJV028RW3b/QyP3Gwmax2+cKLR9PKR + nJnmO5bxAT0nQ3xuJEAqMIss/Rfb/macWc2N/6CWJCRT6a2vgy6xBW+bqG6RdQMB + xEZXFZl+sSKhXPkc5Wjb4lQ14YWyRPrTjMlwez3k4UolIJhJmwl+D7OkMRrOUERO + EtUvc7odCwKBgBi+nhdZKWXveM7B5N3uzXBKmmRz3MpPdC/yDtcwJ8u8msUpTv4R + JxQNrd0bsIqBli0YBmFLYEMg+BwjAee7vXeDFq+HCTv6XMva2RsNryCO4yD3I359 + XfE6DJzB8ZOUgv4Dvluie3TB2Y6ZQV/p+LGt7G13yG4hvofyJYvlg3RPAoGAcjDg + +OH5zLN2eqah8qBN0CYa9/rFt0AJ19+7/smLTJ7QvQq4g0gwS1couplcCEnNGWiK + 72y1n/ckvvplmPeAE19HveMvR9UoCeV5ej86fACy8V/oVpnaaLBvL2aCMjPLjPP9 + DWeCIZp8MV86cvOrGfngf6kJG2qZTueXl4NAuwkCgYEArKkhlZVXjwBoVvtHYmN2 + o+F6cGMlRJTLhNc391WApsgDZfTZSdeJsBsvvzS/Nc0burrufJg0wYioTlpReSy4 + ohhtprnQQAddfjHP7rh2LGt+irFzhdXXQ1ybGaGM9D764KUNCXLuwdly0vzXU4HU + q5sGxGrC1RECGB5Zwx2S2ZY= + -----END PRIVATE KEY----- + + clusters: + - name: service_wss + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service_wss + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-wss + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/examples/websocket/verify.sh b/examples/websocket/verify.sh new file mode 100755 index 000000000000..68b463b077ff --- /dev/null +++ b/examples/websocket/verify.sh @@ -0,0 +1,54 @@ +#!/bin/bash -e +# +# Requirements: expect +# + +export NAME=tls +export MANUAL=true + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + + +interact_ws () { + local port="$1" \ + protocol="$2" \ + backend="$3" \ + insecure="" + if [ "$protocol" == "wss" ]; then + insecure="--insecure" + fi + expect < ws" +interact_ws 10000 ws ws + +run_log "Interact with web socket wss -> wss" +interact_ws 20000 wss wss + +run_log "Interact with web socket wss passthrough" +interact_ws 30000 wss wss diff --git a/generated_api_shadow/bazel/repositories.bzl b/generated_api_shadow/bazel/repositories.bzl index a12a0ea98b3a..983f15967b28 100644 --- a/generated_api_shadow/bazel/repositories.bzl +++ b/generated_api_shadow/bazel/repositories.bzl @@ -40,6 +40,10 @@ def api_dependencies(): name = "com_github_openzipkin_zipkinapi", build_file_content = ZIPKINAPI_BUILD_CONTENT, ) + external_http_archive( + name = "com_github_apache_skywalking_data_collect_protocol", + build_file_content = SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT, + ) PROMETHEUSMETRICS_BUILD_CONTENT = """ load("@envoy_api//bazel:api_build_system.bzl", "api_cc_py_proto_library") @@ -101,3 +105,30 @@ go_proto_library( visibility = ["//visibility:public"], ) """ + +SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT = """ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +proto_library( + name = "protocol", + srcs = [ + "common/Common.proto", + "language-agent/Tracing.proto", + ], + visibility = ["//visibility:public"], +) + +cc_proto_library( + name = "protocol_cc_proto", + deps = [":protocol"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "protocol_go_proto", + proto = ":protocol", + visibility = ["//visibility:public"], +) +""" diff --git a/generated_api_shadow/bazel/repository_locations.bzl b/generated_api_shadow/bazel/repository_locations.bzl index e46f7d77f8e5..d72069046b85 100644 --- a/generated_api_shadow/bazel/repository_locations.bzl +++ b/generated_api_shadow/bazel/repository_locations.bzl @@ -88,4 +88,15 @@ REPOSITORY_LOCATIONS_SPEC = dict( release_date = "2020-08-17", use_category = ["api"], ), + com_github_apache_skywalking_data_collect_protocol = dict( + project_name = "SkyWalking API", + project_desc = "SkyWalking's language independent model and gRPC API Definitions", + project_url = "https://github.com/apache/skywalking-data-collect-protocol", + version = "8.1.0", + sha256 = "ebea8a6968722524d1bcc4426fb6a29907ddc2902aac7de1559012d3eee90cf9", + strip_prefix = "skywalking-data-collect-protocol-{version}", + urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v{version}.tar.gz"], + release_date = "2020-07-29", + use_category = ["api"], + ), ) diff --git a/generated_api_shadow/envoy/config/common/matcher/v3/BUILD b/generated_api_shadow/envoy/config/common/matcher/v3/BUILD index 482d7fe987f2..2f90ace882d9 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v3/BUILD +++ b/generated_api_shadow/envoy/config/common/matcher/v3/BUILD @@ -6,7 +6,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/core/v3:pkg", "//envoy/config/route/v3:pkg", + "//envoy/type/matcher/v3:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto index 119740547aaf..8a1cb4839c4a 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v3; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/route/v3/route_components.proto"; +import "envoy/type/matcher/v3/value.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; @@ -16,6 +18,129 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] +message Matcher { + // What to do if a match is successful. + message OnMatch { + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v3.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + // Predicate to determine if a match is successful. + message Predicate { + // Predicate for a single input field. + message SinglePredicate { + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v3.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] diff --git a/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD b/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD index 42a738be2e89..8c0f8a2e08d8 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD +++ b/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD @@ -7,7 +7,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ "//envoy/config/common/matcher/v3:pkg", + "//envoy/config/core/v4alpha:pkg", "//envoy/config/route/v4alpha:pkg", + "//envoy/type/matcher/v4alpha:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto index 3be0d2aea3a8..15859474e6e1 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v4alpha; +import "envoy/config/core/v4alpha/extension.proto"; import "envoy/config/route/v4alpha/route_components.proto"; +import "envoy/type/matcher/v4alpha/value.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -15,6 +17,156 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] +message Matcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher"; + + // What to do if a match is successful. + message OnMatch { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.OnMatch"; + + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v4alpha.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList"; + + // Predicate to determine if a match is successful. + message Predicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate"; + + // Predicate for a single input field. + message SinglePredicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.SinglePredicate"; + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v4alpha.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.PredicateList"; + + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.FieldMatcher"; + + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree"; + + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree.MatchMap"; + + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] diff --git a/generated_api_shadow/envoy/config/core/v3/base.proto b/generated_api_shadow/envoy/config/core/v3/base.proto index 35b015710e5c..1184c89de6e2 100644 --- a/generated_api_shadow/envoy/config/core/v3/base.proto +++ b/generated_api_shadow/envoy/config/core/v3/base.proto @@ -311,6 +311,13 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.DataSource"; diff --git a/generated_api_shadow/envoy/config/core/v4alpha/base.proto b/generated_api_shadow/envoy/config/core/v4alpha/base.proto index 03fcc5a461e0..95ca4f77a2bc 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/base.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/base.proto @@ -315,6 +315,16 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.core.v3.WatchedDirectory"; + + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.DataSource"; diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index 279d7e72f984..6b97d3ff4a12 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -762,7 +762,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index 07eeeefed423..5c9c2c46a202 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -756,7 +756,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/generated_api_shadow/envoy/config/trace/v3/skywalking.proto b/generated_api_shadow/envoy/config/trace/v3/skywalking.proto new file mode 100644 index 000000000000..224d474ccf98 --- /dev/null +++ b/generated_api_shadow/envoy/config/trace/v3/skywalking.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package envoy.config.trace.v3; + +import "envoy/config/core/v3/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/migrate.proto"; +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.config.trace.v3"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_migrate).move_to_package = + "envoy.extensions.tracers.skywalking.v4alpha"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + // SkyWalking collector service. + core.v3.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto b/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto index 3082089202ee..007ccabc3e47 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto @@ -15,11 +15,27 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // gRPC-JSON transcoder :ref:`configuration overview `. // [#extension: envoy.filters.http.grpc_json_transcoder] -// [#next-free-field: 10] +// [#next-free-field: 11] message GrpcJsonTranscoder { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder"; + 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 PrintOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder.PrintOptions"; @@ -160,4 +176,11 @@ message GrpcJsonTranscoder { // the ``google/rpc/error_details.proto`` should be included in the configured // :ref:`proto descriptor set `. bool convert_grpc_status = 9; + + // URL unescaping policy. + // This spec is only applied when extracting variable with multiple segments. + // 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 = 10 [(validate.rules).enum = {defined_only: true}]; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 5588961bf512..a10fa68f3043 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. @@ -388,8 +392,18 @@ message RequirementRule { // config.route.v3.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +476,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtAuthentication"; @@ -528,4 +543,25 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 12d4fa5fe1d3..2746640fa738 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. @@ -388,8 +392,18 @@ message RequirementRule { // config.route.v4alpha.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +476,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication"; @@ -528,4 +543,28 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig"; + + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index d26ce2ffee96..250c91077fa1 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -37,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -362,6 +362,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index ceb7f4a65a1f..a44d35f86ae2 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -36,7 +36,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -359,6 +359,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/BUILD b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/BUILD new file mode 100644 index 000000000000..1d56979cc466 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/BUILD @@ -0,0 +1,13 @@ +# 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/v4alpha:pkg", + "//envoy/config/trace/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto new file mode 100644 index 000000000000..37936faa6133 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package envoy.extensions.tracers.skywalking.v4alpha; + +import "envoy/config/core/v4alpha/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.tracers.skywalking.v4alpha"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.trace.v3.SkyWalkingConfig"; + + // SkyWalking collector service. + config.core.v4alpha.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v3.ClientConfig"; + + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto index da834e8afc54..2ddca5720fc8 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -127,16 +127,37 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v3.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v3.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. + config.core.v3.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -190,7 +211,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CertificateValidationContext"; @@ -230,8 +251,22 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v3.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. + config.core.v3.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 1382863cb804..30859bc2a3eb 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -129,17 +129,38 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v4alpha.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v4alpha.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. + config.core.v4alpha.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -193,7 +214,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext"; @@ -235,8 +256,22 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v4alpha.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. + config.core.v4alpha.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/BUILD b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/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/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto new file mode 100644 index 000000000000..5754491b91d1 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package envoy.extensions.upstreams.tcp.generic.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.upstreams.tcp.generic.v3"; +option java_outer_classname = "GenericConnectionPoolProtoOuterClass"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Generic Connection Pool] + +// A connection pool which forwards downstream TCP as TCP or HTTP to upstream, +// based on CONNECT configuration. +// [#extension: envoy.upstreams.tcp.generic] +message GenericConnectionPoolProto { +} diff --git a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto index b42fb75a0bf7..c6affb810611 100644 --- a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto +++ b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto @@ -28,7 +28,29 @@ message VmConfig { // See ref: "TODO: add ref" for details. string vm_id = 1; - // The Wasm runtime type (either "v8" or "null" for code compiled into Envoy). + // The Wasm runtime type. + // Available Wasm runtime types are registered as extensions. The following runtimes are included + // in Envoy code base: + // + // .. _extension_envoy.wasm.runtime.null: + // + // **envoy.wasm.runtime.null**: Null sandbox, the Wasm module must be compiled and linked into the + // Envoy binary. The registered name is given in the *code* field as *inline_string*. + // + // .. _extension_envoy.wasm.runtime.v8: + // + // **envoy.wasm.runtime.v8**: `V8 `_-based WebAssembly runtime. + // + // .. _extension_envoy.wasm.runtime.wavm: + // + // **envoy.wasm.runtime.wavm**: `WAVM `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // + // .. _extension_envoy.wasm.runtime.wasmtime: + // + // **envoy.wasm.runtime.wasmtime**: `Wasmtime `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // string runtime = 2 [(validate.rules).string = {min_len: 1}]; // The Wasm code that Envoy will execute. diff --git a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto index 40479539213c..49bb7a931ef5 100644 --- a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v3; import "envoy/config/core/v3/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -252,10 +253,19 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + // The resource's name, to distinguish it from others of the same type of resource. string name = 3 [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; @@ -272,4 +282,23 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } diff --git a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto index 3f5dca95cbb8..e6d15a5d8f93 100644 --- a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v4alpha; import "envoy/config/core/v4alpha/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -254,11 +255,23 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.service.discovery.v3.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + option (udpa.annotations.versioning).previous_message_type = + "envoy.service.discovery.v3.Resource.CacheControl"; + + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + oneof name_specifier { // The resource's name, to distinguish it from others of the same type of resource. string name = 3; @@ -276,4 +289,23 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } diff --git a/include/envoy/api/api.h b/include/envoy/api/api.h index e9b3506c0312..aa310d2c40c2 100644 --- a/include/envoy/api/api.h +++ b/include/envoy/api/api.h @@ -57,9 +57,9 @@ class Api { virtual TimeSource& timeSource() PURE; /** - * @return a constant reference to the root Stats::Scope + * @return a reference to the root Stats::Scope */ - virtual const Stats::Scope& rootScope() PURE; + virtual Stats::Scope& rootScope() PURE; /** * @return a reference to the RandomGenerator. diff --git a/include/envoy/common/BUILD b/include/envoy/common/BUILD index de79fdf0e689..d120b7c0cc54 100644 --- a/include/envoy/common/BUILD +++ b/include/envoy/common/BUILD @@ -13,6 +13,7 @@ envoy_basic_cc_library( name = "base_includes", hdrs = [ "exception.h", + "optref.h", "platform.h", "pure.h", ], diff --git a/include/envoy/common/optref.h b/include/envoy/common/optref.h new file mode 100644 index 000000000000..cf51cdaa52ea --- /dev/null +++ b/include/envoy/common/optref.h @@ -0,0 +1,39 @@ +#pragma once + +#include "absl/types/optional.h" + +namespace Envoy { + +// Helper class to make it easier to work with optional references, allowing: +// foo(OptRef t) { +// if (t.has_value()) { +// t->method(); +// } +// } +// +// Using absl::optional directly you must write optref.value().method() which is +// a bit more awkward. +template struct OptRef : public absl::optional> { + OptRef(T& t) : absl::optional>(t) {} + OptRef() = default; + + /** + * Helper to call a method on T. The caller is responsible for ensuring + * has_value() is true. + */ + T* operator->() { + T& ref = **this; + return &ref; + } + + /** + * Helper to call a const method on T. The caller is responsible for ensuring + * has_value() is true. + */ + const T* operator->() const { + const T& ref = **this; + return &ref; + } +}; + +} // namespace Envoy diff --git a/include/envoy/config/subscription.h b/include/envoy/config/subscription.h index 0506ed46dcb7..e768482f2ee5 100644 --- a/include/envoy/config/subscription.h +++ b/include/envoy/config/subscription.h @@ -53,6 +53,8 @@ class DecodedResource { */ virtual const Protobuf::Message& resource() const PURE; + virtual absl::optional ttl() const PURE; + /** * @return bool does the xDS discovery response have a set resource payload? */ diff --git a/include/envoy/event/BUILD b/include/envoy/event/BUILD index 0d22a7747ae1..a51c9345e55f 100644 --- a/include/envoy/event/BUILD +++ b/include/envoy/event/BUILD @@ -31,6 +31,7 @@ envoy_cc_library( "//include/envoy/network:listen_socket_interface", "//include/envoy/network:listener_interface", "//include/envoy/network:transport_socket_interface", + "//include/envoy/server:watchdog_interface", "//include/envoy/thread:thread_interface", ], ) diff --git a/include/envoy/event/dispatcher.h b/include/envoy/event/dispatcher.h index 96b1d12310fb..bd4e9512aa4b 100644 --- a/include/envoy/event/dispatcher.h +++ b/include/envoy/event/dispatcher.h @@ -19,6 +19,7 @@ #include "envoy/network/listen_socket.h" #include "envoy/network/listener.h" #include "envoy/network/transport_socket.h" +#include "envoy/server/watchdog.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" #include "envoy/stream_info/stream_info.h" @@ -63,6 +64,15 @@ class Dispatcher { */ virtual const std::string& name() PURE; + /** + * Register a watchdog for this dispatcher. The dispatcher is responsible for touching the + * watchdog at least once per touch interval. Dispatcher implementations may choose to touch more + * often to avoid spurious miss events when processing long callback queues. + * @param min_touch_interval Touch interval for the watchdog. + */ + virtual void registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) PURE; + /** * Returns a time-source to use with this dispatcher. */ diff --git a/include/envoy/http/codec.h b/include/envoy/http/codec.h index 5e977c8a7d09..9ffa360bd72e 100644 --- a/include/envoy/http/codec.h +++ b/include/envoy/http/codec.h @@ -105,10 +105,12 @@ class RequestEncoder : public virtual StreamEncoder { public: /** * Encode headers, optionally indicating end of stream. - * @param headers supplies the header map to encode. + * @param headers supplies the header map to encode. Must have required HTTP headers. * @param end_stream supplies whether this is a header only request. + * @return Status indicating whether encoding succeeded. Encoding will fail if request + * headers are missing required HTTP headers (method, path for non-CONNECT, host for CONNECT). */ - virtual void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) PURE; + virtual Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) PURE; /** * Encode trailers. This implicitly ends the stream. diff --git a/include/envoy/http/filter.h b/include/envoy/http/filter.h index bd46d32f49d4..f9710a45eb0a 100644 --- a/include/envoy/http/filter.h +++ b/include/envoy/http/filter.h @@ -495,19 +495,24 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks { */ virtual uint32_t decoderBufferLimit() PURE; - // Takes a stream, and acts as if the headers are newly arrived. - // On success, this will result in a creating a new filter chain and likely upstream request - // associated with the original downstream stream. - // On failure, if the preconditions outlined below are not met, the caller is - // responsible for handling or terminating the original stream. - // - // This is currently limited to - // - streams which are completely read - // - streams which do not have a request body. - // - // Note that HttpConnectionManager sanitization will *not* be performed on the - // recreated stream, as it is assumed that sanitization has already been done. - virtual bool recreateStream() PURE; + /** + * Takes a stream, and acts as if the headers are newly arrived. + * On success, this will result in a creating a new filter chain and likely + * upstream request associated with the original downstream stream. On + * failure, if the preconditions outlined below are not met, the caller is + * responsible for handling or terminating the original stream. + * + * This is currently limited to + * - streams which are completely read + * - streams which do not have a request body. + * + * Note that HttpConnectionManager sanitization will *not* be performed on the + * recreated stream, as it is assumed that sanitization has already been done. + * + * @param original_response_headers Headers used for logging in the access logs and for charging + * stats. Ignored if null. + */ + virtual bool recreateStream(const ResponseHeaderMap* original_response_headers) PURE; /** * Adds socket options to be applied to any connections used for upstream requests. Note that diff --git a/include/envoy/network/proxy_protocol.h b/include/envoy/network/proxy_protocol.h index 52c111859b11..12f7323c4ccc 100644 --- a/include/envoy/network/proxy_protocol.h +++ b/include/envoy/network/proxy_protocol.h @@ -8,7 +8,11 @@ namespace Network { struct ProxyProtocolData { const Network::Address::InstanceConstSharedPtr src_addr_; const Network::Address::InstanceConstSharedPtr dst_addr_; + std::string asStringForHash() const { + return std::string(src_addr_ ? src_addr_->asString() : "null") + + (dst_addr_ ? dst_addr_->asString() : "null"); + } }; } // namespace Network -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/include/envoy/network/transport_socket.h b/include/envoy/network/transport_socket.h index 63169d3624e7..4244e6247767 100644 --- a/include/envoy/network/transport_socket.h +++ b/include/envoy/network/transport_socket.h @@ -14,6 +14,7 @@ namespace Envoy { namespace Network { +class TransportSocketFactory; class Connection; enum class ConnectionEvent; @@ -179,7 +180,7 @@ class TransportSocketOptions { virtual const std::vector& applicationProtocolListOverride() const PURE; /** - * The application protocol to use when negotiating an upstream connection and no other + * The application protocol(s) to use when negotiating an upstream connection and no other * application protocol has been configured. Both * TransportSocketOptions::applicationProtocolListOverride and application protocols configured * in the CommonTlsContext on the Cluster will take precedence. @@ -187,10 +188,10 @@ class TransportSocketOptions { * Note that this option is intended for intermediate code (e.g. the HTTP connection pools) to * specify a default ALPN when no specific values are specified elsewhere. As such, providing a * value here might not make sense prior to load balancing. - * @return the optional fallback for application protocols, for when they are not specified in the - * TLS configuration. + * @return the optional fallback(s) for application protocols, for when they are not specified in + * the TLS configuration. */ - virtual const absl::optional& applicationProtocolFallback() const PURE; + virtual const std::vector& applicationProtocolFallback() const PURE; /** * @return optional PROXY protocol address information. @@ -198,11 +199,13 @@ class TransportSocketOptions { virtual absl::optional proxyProtocolOptions() const PURE; /** - * @param vector of bytes to which the option should append hash key data that will be used - * to separate connections based on the option. Any data already in the key vector must - * not be modified. + * @param key supplies a vector of bytes to which the option should append hash key data that will + * be used to separate connections based on the option. Any data already in the key vector + * must not be modified. + * @param factory supplies the factor which will be used for creating the transport socket. */ - virtual void hashKey(std::vector& key) const PURE; + virtual void hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const PURE; }; // TODO(mattklein123): Rename to TransportSocketOptionsConstSharedPtr in a dedicated follow up. @@ -228,9 +231,9 @@ class TransportSocketFactory { createTransportSocket(TransportSocketOptionsSharedPtr options) const PURE; /** - * Check whether matched transport socket which required to use secret information is available. + * @return bool whether the transport socket will use proxy protocol options. */ - virtual bool isReady() const PURE; + virtual bool usesProxyProtocolOptions() const PURE; }; using TransportSocketFactoryPtr = std::unique_ptr; diff --git a/include/envoy/registry/registry.h b/include/envoy/registry/registry.h index b52686036074..b90e86ca52f3 100644 --- a/include/envoy/registry/registry.h +++ b/include/envoy/registry/registry.h @@ -346,6 +346,7 @@ template class FactoryRegistry : public Logger::Loggable>(); for (const auto& [factory_name, factory] : factories()) { + UNREFERENCED_PARAMETER(factory_name); if (factory == nullptr) { continue; } diff --git a/include/envoy/router/router.h b/include/envoy/router/router.h index b4b3be9f3c37..f9454eee452d 100644 --- a/include/envoy/router/router.h +++ b/include/envoy/router/router.h @@ -1274,8 +1274,10 @@ class GenericUpstream { * Encode headers, optionally indicating end of stream. * @param headers supplies the header map to encode. * @param end_stream supplies whether this is a header only request. + * @return status indicating success. Encoding will fail if headers do not have required HTTP + * headers. */ - virtual void encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) PURE; + virtual Http::Status encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) PURE; /** * Encode trailers. This implicitly ends the stream. * @param trailers supplies the trailers to encode. diff --git a/include/envoy/runtime/BUILD b/include/envoy/runtime/BUILD index b80d180dedaa..f28a5af6a50e 100644 --- a/include/envoy/runtime/BUILD +++ b/include/envoy/runtime/BUILD @@ -17,7 +17,7 @@ envoy_cc_library( ], deps = [ "//include/envoy/stats:stats_interface", - "//include/envoy/thread_local:thread_local_interface", + "//include/envoy/thread_local:thread_local_object", "//source/common/common:assert_lib", "//source/common/singleton:threadsafe_singleton", "@envoy_api//envoy/type/v3:pkg_cc_proto", diff --git a/include/envoy/runtime/runtime.h b/include/envoy/runtime/runtime.h index 72aa2e90fb28..27264a1f9e58 100644 --- a/include/envoy/runtime/runtime.h +++ b/include/envoy/runtime/runtime.h @@ -9,7 +9,7 @@ #include "envoy/common/pure.h" #include "envoy/stats/store.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" #include "envoy/type/v3/percent.pb.h" #include "common/common/assert.h" diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index 2aa968e03874..a9f7854ba1c2 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -68,6 +68,7 @@ envoy_cc_library( name = "guarddog_interface", hdrs = ["guarddog.h"], deps = [ + "//include/envoy/event:dispatcher_interface", "//include/envoy/server:watchdog_interface", "//include/envoy/stats:stats_interface", "//include/envoy/thread:thread_interface", @@ -126,7 +127,7 @@ envoy_cc_library( "//include/envoy/local_info:local_info_interface", "//include/envoy/runtime:runtime_interface", "//include/envoy/secret:secret_manager_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/ssl:context_manager_interface", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/tracing:http_tracer_interface", @@ -150,8 +151,8 @@ envoy_cc_library( name = "worker_interface", hdrs = ["worker.h"], deps = [ - ":overload_manager_interface", "//include/envoy/server:guarddog_interface", + "//include/envoy/server/overload:overload_manager_interface", ], ) @@ -159,8 +160,6 @@ envoy_cc_library( name = "watchdog_interface", hdrs = ["watchdog.h"], deps = [ - "//include/envoy/event:dispatcher_interface", - "//include/envoy/network:address_interface", "//include/envoy/thread:thread_interface", ], ) @@ -184,7 +183,7 @@ envoy_cc_library( "//include/envoy/local_info:local_info_interface", "//include/envoy/network:drain_decision_interface", "//include/envoy/runtime:runtime_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/singleton:manager_interface", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/tracing:http_tracer_interface", @@ -208,8 +207,8 @@ envoy_cc_library( "//include/envoy/config:typed_config_interface", "//include/envoy/http:codes_interface", "//include/envoy/http:filter_interface", - "//include/envoy/server:overload_manager_interface", "//include/envoy/server:transport_socket_config_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/singleton:manager_interface", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/tracing:http_tracer_interface", @@ -303,16 +302,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "overload_manager_interface", - hdrs = ["overload_manager.h"], - deps = [ - "//include/envoy/event:timer_interface", - "//include/envoy/thread_local:thread_local_interface", - "//source/common/singleton:const_singleton", - ], -) - envoy_cc_library( name = "tracer_config_interface", hdrs = ["tracer_config.h"], diff --git a/include/envoy/server/factory_context.h b/include/envoy/server/factory_context.h index 7d496a6d2eb4..01ffee80f763 100644 --- a/include/envoy/server/factory_context.h +++ b/include/envoy/server/factory_context.h @@ -19,7 +19,7 @@ #include "envoy/server/admin.h" #include "envoy/server/drain_manager.h" #include "envoy/server/lifecycle_notifier.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/server/process_context.h" #include "envoy/singleton/manager.h" #include "envoy/stats/scope.h" diff --git a/include/envoy/server/guarddog.h b/include/envoy/server/guarddog.h index 08b8f53646a0..4b818f222086 100644 --- a/include/envoy/server/guarddog.h +++ b/include/envoy/server/guarddog.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/common/pure.h" +#include "envoy/event/dispatcher.h" #include "envoy/server/watchdog.h" namespace Envoy { @@ -28,9 +29,11 @@ class GuardDog { * * @param thread_id a Thread::ThreadId containing the system thread id * @param thread_name supplies the name of the thread which is used for per-thread miss stats. + * @param dispatcher dispatcher responsible for petting the watchdog. */ virtual WatchDogSharedPtr createWatchDog(Thread::ThreadId thread_id, - const std::string& thread_name) PURE; + const std::string& thread_name, + Event::Dispatcher& dispatcher) PURE; /** * Tell the GuardDog to forget about this WatchDog. diff --git a/include/envoy/server/instance.h b/include/envoy/server/instance.h index 8bba884e8b47..338479f5ea2a 100644 --- a/include/envoy/server/instance.h +++ b/include/envoy/server/instance.h @@ -23,7 +23,7 @@ #include "envoy/server/lifecycle_notifier.h" #include "envoy/server/listener_manager.h" #include "envoy/server/options.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/ssl/context_manager.h" #include "envoy/thread_local/thread_local.h" #include "envoy/tracing/http_tracer.h" diff --git a/include/envoy/server/overload/BUILD b/include/envoy/server/overload/BUILD new file mode 100644 index 000000000000..990a306c7fd4 --- /dev/null +++ b/include/envoy/server/overload/BUILD @@ -0,0 +1,29 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "overload_manager_interface", + hdrs = ["overload_manager.h"], + deps = [ + ":thread_local_overload_state", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/thread_local:thread_local_interface", + "//source/common/singleton:const_singleton", + ], +) + +envoy_cc_library( + name = "thread_local_overload_state", + hdrs = ["thread_local_overload_state.h"], + deps = [ + "//include/envoy/event:timer_interface", + "//include/envoy/thread_local:thread_local_object", + ], +) diff --git a/include/envoy/server/overload_manager.h b/include/envoy/server/overload/overload_manager.h similarity index 57% rename from include/envoy/server/overload_manager.h rename to include/envoy/server/overload/overload_manager.h index 47e9fe0af51e..a4b42a7c9fad 100644 --- a/include/envoy/server/overload_manager.h +++ b/include/envoy/server/overload/overload_manager.h @@ -3,64 +3,13 @@ #include #include "envoy/common/pure.h" -#include "envoy/event/timer.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/event/dispatcher.h" +#include "envoy/server/overload/thread_local_overload_state.h" -#include "common/common/macros.h" #include "common/singleton/const_singleton.h" namespace Envoy { namespace Server { - -/** - * Tracks the state of an overload action. The state is a number between 0 and 1 that represents the - * level of saturation. The values are categorized in two groups: - * - Saturated (value = 1): indicates that an overload action is active because at least one of its - * triggers has reached saturation. - * - Scaling (0 <= value < 1): indicates that an overload action is not saturated. - */ -class OverloadActionState { -public: - static constexpr OverloadActionState inactive() { return OverloadActionState(0); } - - static constexpr OverloadActionState saturated() { return OverloadActionState(1.0); } - - explicit constexpr OverloadActionState(float value) - : action_value_(std::min(1.0f, std::max(0.0f, value))) {} - - float value() const { return action_value_; } - bool isSaturated() const { return action_value_ == 1; } - -private: - float action_value_; -}; - -/** - * Callback invoked when an overload action changes state. - */ -using OverloadActionCb = std::function; - -enum class OverloadTimerType { - // Timers created with this type will never be scaled. This should only be used for testing. - UnscaledRealTimerForTest, - // The amount of time an HTTP connection to a downstream client can remain idle (no streams). This - // corresponds to the HTTP_DOWNSTREAM_CONNECTION_IDLE TimerType in overload.proto. - HttpDownstreamIdleConnectionTimeout, -}; - -/** - * Thread-local copy of the state of each configured overload action. - */ -class ThreadLocalOverloadState : public ThreadLocal::ThreadLocalObject { -public: - // Get a thread-local reference to the value for the given action key. - virtual const OverloadActionState& getState(const std::string& action) PURE; - - // Get a scaled timer whose minimum corresponds to the configured value for the given timer type. - virtual Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, - Event::TimerCb callback) PURE; -}; - /** * Well-known overload action names. */ diff --git a/include/envoy/server/overload/thread_local_overload_state.h b/include/envoy/server/overload/thread_local_overload_state.h new file mode 100644 index 000000000000..6c94fab5cd42 --- /dev/null +++ b/include/envoy/server/overload/thread_local_overload_state.h @@ -0,0 +1,63 @@ + +#pragma once + +#include + +#include "envoy/common/pure.h" +#include "envoy/event/timer.h" +#include "envoy/thread_local/thread_local_object.h" + +namespace Envoy { +namespace Server { + +/** + * Tracks the state of an overload action. The state is a number between 0 and 1 that represents the + * level of saturation. The values are categorized in two groups: + * - Saturated (value = 1): indicates that an overload action is active because at least one of its + * triggers has reached saturation. + * - Scaling (0 <= value < 1): indicates that an overload action is not saturated. + */ +class OverloadActionState { +public: + static constexpr OverloadActionState inactive() { return OverloadActionState(0); } + + static constexpr OverloadActionState saturated() { return OverloadActionState(1.0); } + + explicit constexpr OverloadActionState(float value) + : action_value_(std::min(1.0f, std::max(0.0f, value))) {} + + float value() const { return action_value_; } + bool isSaturated() const { return action_value_ == 1; } + +private: + float action_value_; +}; + +/** + * Callback invoked when an overload action changes state. + */ +using OverloadActionCb = std::function; + +enum class OverloadTimerType { + // Timers created with this type will never be scaled. This should only be used for testing. + UnscaledRealTimerForTest, + // The amount of time an HTTP connection to a downstream client can remain idle (no streams). This + // corresponds to the HTTP_DOWNSTREAM_CONNECTION_IDLE TimerType in overload.proto. + HttpDownstreamIdleConnectionTimeout, +}; + +/** + * Thread-local copy of the state of each configured overload action. + */ +class ThreadLocalOverloadState : public ThreadLocal::ThreadLocalObject { +public: + // Get a thread-local reference to the value for the given action key. + virtual const OverloadActionState& getState(const std::string& action) PURE; + + // Get a scaled timer whose minimum corresponds to the configured value for the given timer type. + virtual Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, + Event::TimerCb callback) PURE; +}; + +} // namespace Server +} // namespace Envoy diff --git a/include/envoy/server/watchdog.h b/include/envoy/server/watchdog.h index cd76f552e244..86f9c0c23b31 100644 --- a/include/envoy/server/watchdog.h +++ b/include/envoy/server/watchdog.h @@ -3,7 +3,6 @@ #include #include "envoy/common/pure.h" -#include "envoy/event/dispatcher.h" #include "envoy/thread/thread.h" namespace Envoy { @@ -19,21 +18,12 @@ class WatchDog { public: virtual ~WatchDog() = default; - /** - * Start a recurring touch timer in the dispatcher passed as argument. - * - * This will automatically call the touch() method at the interval specified - * during construction. - * - * The timer object is stored within the WatchDog object. It will go away if - * the object goes out of scope and stop the timer. - */ - virtual void startWatchdog(Event::Dispatcher& dispatcher) PURE; - /** * Manually indicate that you are still alive by calling this. * - * This can be used if this is later used on a thread where there is no dispatcher. + * When the watchdog is registered with a dispatcher, the dispatcher will periodically call this + * method to indicate the thread is still alive. It should be called directly by the application + * code in cases where the watchdog is not registered with a dispatcher. */ virtual void touch() PURE; virtual Thread::ThreadId threadId() const PURE; diff --git a/include/envoy/server/worker.h b/include/envoy/server/worker.h index 9d6ed578cfbe..762cf83005e7 100644 --- a/include/envoy/server/worker.h +++ b/include/envoy/server/worker.h @@ -3,7 +3,7 @@ #include #include "envoy/server/guarddog.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" namespace Envoy { namespace Server { diff --git a/include/envoy/ssl/context_config.h b/include/envoy/ssl/context_config.h index 91bdddf57a36..675bddde27de 100644 --- a/include/envoy/ssl/context_config.h +++ b/include/envoy/ssl/context_config.h @@ -111,11 +111,6 @@ class ClientContextConfig : public virtual ContextConfig { * for names. */ virtual const std::string& signingAlgorithmsForTest() const PURE; - - /** - * Check whether TLS certificate entity and certificate validation context entity is available - */ - virtual bool isSecretReady() const PURE; }; using ClientContextConfigPtr = std::unique_ptr; diff --git a/include/envoy/stats/BUILD b/include/envoy/stats/BUILD index c810ac7ad30c..484115c77691 100644 --- a/include/envoy/stats/BUILD +++ b/include/envoy/stats/BUILD @@ -34,6 +34,7 @@ envoy_cc_library( ":refcount_ptr_interface", ":symbol_table_interface", "//include/envoy/common:interval_set_interface", + "//include/envoy/common:time_interface", ], ) diff --git a/include/envoy/stats/sink.h b/include/envoy/stats/sink.h index 1303c9fd67b8..ff0e607ffaa8 100644 --- a/include/envoy/stats/sink.h +++ b/include/envoy/stats/sink.h @@ -4,6 +4,7 @@ #include #include "envoy/common/pure.h" +#include "envoy/common/time.h" #include "envoy/stats/histogram.h" #include "envoy/stats/stats.h" @@ -40,6 +41,11 @@ class MetricSnapshot { * @return a snapshot of all text readouts. */ virtual const std::vector>& textReadouts() PURE; + + /** + * @return the time in UTC since epoch when the snapshot was created. + */ + virtual SystemTime snapshotTime() const PURE; }; /** diff --git a/include/envoy/stream_info/stream_info.h b/include/envoy/stream_info/stream_info.h index 858ff0f0952f..dc4a67973976 100644 --- a/include/envoy/stream_info/stream_info.h +++ b/include/envoy/stream_info/stream_info.h @@ -115,6 +115,8 @@ struct ResponseCodeDetailValues { const std::string MaxDurationTimeout = "max_duration_timeout"; // The per-stream total request timeout was exceeded. const std::string RequestOverallTimeout = "request_overall_timeout"; + // The per-stream request header timeout was exceeded. + const std::string RequestHeaderTimeout = "request_header_timeout"; // The request was rejected due to the Overload Manager reaching configured resource limits. const std::string Overload = "overload"; // The HTTP/1.0 or HTTP/0.9 request was rejected due to HTTP/1.0 support not being configured. @@ -168,10 +170,14 @@ struct ResponseCodeDetailValues { const std::string DownstreamLocalDisconnect = "downstream_local_disconnect"; // The max connection duration was exceeded. const std::string DurationTimeout = "duration_timeout"; + // The max request downstream header duration was exceeded. + const std::string DownstreamHeaderTimeout = "downstream_header_timeout"; // The response was generated by the admin filter. const std::string AdminFilterResponse = "admin_filter_response"; // The original stream was replaced with an internal redirect. const std::string InternalRedirect = "internal_redirect"; + // The request was rejected because configured filters erroneously removed required headers. + const std::string FilterRemovedRequiredHeaders = "filter_removed_required_headers"; // Changes or additions to details should be reflected in // docs/root/configuration/http/http_conn_man/response_code_details_details.rst }; diff --git a/include/envoy/tcp/BUILD b/include/envoy/tcp/BUILD index bbf990581003..2ab93df03996 100644 --- a/include/envoy/tcp/BUILD +++ b/include/envoy/tcp/BUILD @@ -18,3 +18,13 @@ envoy_cc_library( "//include/envoy/upstream:upstream_interface", ], ) + +envoy_cc_library( + name = "upstream_interface", + hdrs = ["upstream.h"], + deps = [ + "//include/envoy/tcp:conn_pool_interface", + "//include/envoy/upstream:upstream_interface", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", + ], +) diff --git a/include/envoy/tcp/upstream.h b/include/envoy/tcp/upstream.h new file mode 100644 index 000000000000..71d266b9f1fc --- /dev/null +++ b/include/envoy/tcp/upstream.h @@ -0,0 +1,136 @@ +#pragma once + +#include "envoy/buffer/buffer.h" +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" +#include "envoy/stream_info/stream_info.h" +#include "envoy/tcp/conn_pool.h" +#include "envoy/upstream/upstream.h" + +namespace Envoy { + +namespace Upstream { +class LoadBalancerContext; +} // namespace Upstream + +namespace TcpProxy { + +class GenericConnectionPoolCallbacks; +class GenericUpstream; + +// An API for wrapping either a TCP or an HTTP connection pool. +class GenericConnPool : public Logger::Loggable { +public: + virtual ~GenericConnPool() = default; + + /** + * Called to create a TCP connection or HTTP stream for "CONNECT" streams. + * + * The implementation is then responsible for calling either onGenericPoolReady or + * onGenericPoolFailure on the supplied GenericConnectionPoolCallbacks. + * + * @param callbacks callbacks to communicate stream failure or creation on. + */ + virtual void newStream(GenericConnectionPoolCallbacks& callbacks) PURE; +}; + +// An API for the UpstreamRequest to get callbacks from either an HTTP or TCP +// connection pool. +class GenericConnectionPoolCallbacks { +public: + virtual ~GenericConnectionPoolCallbacks() = default; + + /** + * Called when GenericConnPool::newStream has established a new stream. + * + * @param info supplies the stream info object associated with the upstream connection. + * @param upstream supplies the generic upstream for the stream. + * @param host supplies the description of the host that will carry the request. + * @param upstream_local_address supplies the local address of the upstream connection. + * @param ssl_info supplies the ssl information of the upstream connection. + */ + virtual void + onGenericPoolReady(StreamInfo::StreamInfo* info, std::unique_ptr&& upstream, + Upstream::HostDescriptionConstSharedPtr& host, + const Network::Address::InstanceConstSharedPtr& upstream_local_address, + Ssl::ConnectionInfoConstSharedPtr ssl_info) PURE; + + /** + * Called to indicate a failure for GenericConnPool::newStream to establish a stream. + * + * @param reason supplies the failure reason. + * @param host supplies the description of the host that caused the failure. This may be nullptr + * if no host was involved in the failure (for example overflow). + */ + virtual void onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, + Upstream::HostDescriptionConstSharedPtr host) PURE; +}; + +// Interface for a generic Upstream, which can communicate with a TCP or HTTP +// upstream. +class GenericUpstream { +public: + virtual ~GenericUpstream() = default; + + /** + * Enable/disable further data from this stream. + * + * @param disable true if the stream should be read disabled, false otherwise. + * @return returns true if the disable is performed, false otherwise + * (e.g. if the connection is closed) + */ + virtual bool readDisable(bool disable) PURE; + + /** + * Encodes data upstream. + * @param data supplies the data to encode. The data may be moved by the encoder. + * @param end_stream supplies whether this is the last data to encode. + */ + virtual void encodeData(Buffer::Instance& data, bool end_stream) PURE; + + /** + * Adds a callback to be called when the data is sent to the kernel. + * @param cb supplies the callback to be called + */ + virtual void addBytesSentCallback(Network::Connection::BytesSentCb cb) PURE; + + /** + * Called when an event is received on the downstream connection + * @param event supplies the event which occurred. + * @return the underlying ConnectionData if the event is not "Connected" and draining + is supported for this upstream. + */ + virtual Tcp::ConnectionPool::ConnectionData* + onDownstreamEvent(Network::ConnectionEvent event) PURE; +}; + +using GenericConnPoolPtr = std::unique_ptr; + +/* + * A factory for creating generic connection pools. + */ +class GenericConnPoolFactory : public Envoy::Config::TypedFactory { +public: + ~GenericConnPoolFactory() override = default; + + using TunnelingConfig = + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; + + /* + * @param cluster_name the name of the cluster to use + * @param cm the cluster manager to get the connection pool from + * @param config the tunneling config, if doing connect tunneling. + * @param context the load balancing context for this connection. + * @param upstream_callbacks the callbacks to provide to the connection if successfully created. + * @return may be null if there is no cluster with the given name. + */ + virtual GenericConnPoolPtr + createGenericConnPool(const std::string& cluster_name, Upstream::ClusterManager& cm, + const absl::optional& config, + Upstream::LoadBalancerContext* context, + Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) const PURE; +}; + +using GenericConnPoolFactoryPtr = std::unique_ptr; + +} // namespace TcpProxy +} // namespace Envoy diff --git a/include/envoy/thread_local/BUILD b/include/envoy/thread_local/BUILD index 3b23de4e0175..9a5e61235494 100644 --- a/include/envoy/thread_local/BUILD +++ b/include/envoy/thread_local/BUILD @@ -11,5 +11,15 @@ envoy_package() envoy_cc_library( name = "thread_local_interface", hdrs = ["thread_local.h"], - deps = ["//include/envoy/event:dispatcher_interface"], + deps = [ + ":thread_local_object", + "//include/envoy/event:dispatcher_interface", + "//source/common/common:assert_lib", + ], +) + +envoy_cc_library( + name = "thread_local_object", + hdrs = ["thread_local_object.h"], + deps = ["//source/common/common:assert_lib"], ) diff --git a/include/envoy/thread_local/thread_local.h b/include/envoy/thread_local/thread_local.h index b4828d6e6550..8ff2ca99c8c0 100644 --- a/include/envoy/thread_local/thread_local.h +++ b/include/envoy/thread_local/thread_local.h @@ -4,30 +4,16 @@ #include #include +#include "envoy/common/optref.h" #include "envoy/common/pure.h" #include "envoy/event/dispatcher.h" +#include "envoy/thread_local/thread_local_object.h" + +#include "common/common/assert.h" namespace Envoy { namespace ThreadLocal { -/** - * All objects that are stored via the ThreadLocal interface must derive from this type. - */ -class ThreadLocalObject { -public: - virtual ~ThreadLocalObject() = default; - - /** - * Return the object casted to a concrete type. See getTyped() below for comments on the casts. - */ - template T& asType() { - ASSERT(dynamic_cast(this) != nullptr); - return *static_cast(this); - } -}; - -using ThreadLocalObjectSharedPtr = std::shared_ptr; - /** * An individual allocated TLS slot. When the slot is destroyed the stored thread local will * be freed on each thread. @@ -69,28 +55,28 @@ class Slot { * a shared_ptr. Thus, this is a flexible mechanism that can be used to share * the same data across all threads or to share different data on each thread. * - * NOTE: The initialize callback is not supposed to capture the Slot, or its owner. As the owner + * NOTE: The initialize callback is not supposed to capture the Slot, or its owner, as the owner * may be destructed in main thread before the update_cb gets called in a worker thread. */ using InitializeCb = std::function; virtual void set(InitializeCb cb) PURE; +protected: + template friend class TypedSlot; + /** - * UpdateCb takes the current stored data, and must return the same data. The - * API was designed to allow replacement of the object via this API, but this - * is not currently used, and thus we are removing the functionality. In a future - * PR, the API will be removed. - * - * TLS will run the callback and assert the returned returned value matches - * the current value. + * UpdateCb takes is passed a shared point to the current stored data. Use of + * this API is deprecated; please use TypedSlot::runOnAllThreads instead. * * NOTE: The update callback is not supposed to capture the Slot, or its - * owner. As the owner may be destructed in main thread before the update_cb + * owner, as the owner may be destructed in main thread before the update_cb * gets called in a worker thread. **/ - using UpdateCb = std::function; + using UpdateCb = std::function; + + // Callers must use the TypedSlot API, below. virtual void runOnAllThreads(const UpdateCb& update_cb) PURE; - virtual void runOnAllThreads(const UpdateCb& update_cb, Event::PostCb complete_cb) PURE; + virtual void runOnAllThreads(const UpdateCb& update_cb, const Event::PostCb& complete_cb) PURE; }; using SlotPtr = std::unique_ptr; @@ -108,7 +94,9 @@ class SlotAllocator { virtual SlotPtr allocateSlot() PURE; }; -// Provides a typesafe API for slots. +// Provides a typesafe API for slots. The slot data must be derived from +// ThreadLocalObject. If there is no slot data, you can instantiated TypedSlot +// with the default type param: TypedSlot<> tls_; // // TODO(jmarantz): Rename the Slot class to something like RawSlot, where the // only reference is from TypedSlot, which we can then rename to Slot. @@ -144,46 +132,67 @@ template class TypedSlot { * a shared_ptr. Thus, this is a flexible mechanism that can be used to share * the same data across all threads or to share different data on each thread. * - * NOTE: The initialize callback is not supposed to capture the Slot, or its owner. As the owner + * NOTE: The initialize callback is not supposed to capture the Slot, or its owner, as the owner * may be destructed in main thread before the update_cb gets called in a worker thread. */ using InitializeCb = std::function(Event::Dispatcher& dispatcher)>; void set(InitializeCb cb) { slot_->set(cb); } /** - * @return a reference to the thread local object. + * @return an optional reference to the thread local object. */ - T& get() { return slot_->getTyped(); } + OptRef get() { return getOpt(slot_->get()); } + const OptRef get() const { return getOpt(slot_->get()); } /** + * Helper function to call methods on T. The caller is responsible + * for ensuring that get().has_value() is true. + * * @return a pointer to the thread local object. */ - T* operator->() { return &get(); } + T* operator->() { return &(slot_->getTyped()); } + const T* operator->() const { return &(slot_->getTyped()); } /** - * UpdateCb is passed a mutable reference to the current stored data. + * Helper function to get access to a T&. The caller is responsible for + * ensuring that get().has_value() is true. * - * NOTE: The update callback is not supposed to capture the TypedSlot, or its owner. As the owner - * may be destructed in main thread before the update_cb gets called in a worker thread. + * @return a reference to the thread local object. + */ + T& operator*() { return slot_->getTyped(); } + const T& operator*() const { return slot_->getTyped(); } + + /** + * UpdateCb is passed a mutable pointer to the current stored data. Callers + * can assume that the passed-in OptRef has a value if they have called set(), + * yielding a non-null shared_ptr, prior to runOnAllThreads(). + * + * NOTE: The update callback is not supposed to capture the TypedSlot, or its + * owner, as the owner may be destructed in main thread before the update_cb + * gets called in a worker thread. */ - using UpdateCb = std::function; + using UpdateCb = std::function obj)>; void runOnAllThreads(const UpdateCb& cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb)); } - void runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) { + void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb), complete_cb); } private: + static OptRef getOpt(ThreadLocalObjectSharedPtr obj) { + if (obj) { + return OptRef(obj->asType()); + } + return OptRef(); + } + Slot::UpdateCb makeSlotUpdateCb(UpdateCb cb) { - return [cb](ThreadLocalObjectSharedPtr obj) -> ThreadLocalObjectSharedPtr { - cb(obj->asType()); - return obj; - }; + return [cb](ThreadLocalObjectSharedPtr obj) { cb(getOpt(obj)); }; } const SlotPtr slot_; }; -template using TypedSlotPtr = std::unique_ptr>; +template using TypedSlotPtr = std::unique_ptr>; /** * Interface for getting and setting thread local data as well as registering a thread diff --git a/include/envoy/thread_local/thread_local_object.h b/include/envoy/thread_local/thread_local_object.h new file mode 100644 index 000000000000..e305102f7118 --- /dev/null +++ b/include/envoy/thread_local/thread_local_object.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include "common/common/assert.h" + +namespace Envoy { +namespace ThreadLocal { + +/** + * All objects that are stored via the ThreadLocal interface must derive from this type. + */ +class ThreadLocalObject { +public: + virtual ~ThreadLocalObject() = default; + + /** + * Return the object casted to a concrete type. See getTyped() below for comments on the casts. + */ + template T& asType() { + ASSERT(dynamic_cast(this) != nullptr); + return *static_cast(this); + } +}; + +using ThreadLocalObjectSharedPtr = std::shared_ptr; + +template class TypedSlot; + +} // namespace ThreadLocal +} // namespace Envoy diff --git a/include/envoy/upstream/cluster_manager.h b/include/envoy/upstream/cluster_manager.h index 5939092a371b..beb88299da28 100644 --- a/include/envoy/upstream/cluster_manager.h +++ b/include/envoy/upstream/cluster_manager.h @@ -126,12 +126,15 @@ class ClusterManager { initializeSecondaryClusters(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) PURE; using ClusterInfoMap = absl::node_hash_map>; + struct ClusterInfoMaps { + ClusterInfoMap active_clusters_; + ClusterInfoMap warming_clusters_; + }; /** - * @return ClusterInfoMap all current clusters. These are the primary (not thread local) - * clusters which should only be used for stats/admin. + * @return ClusterInfoMap all current clusters including active and warming. */ - virtual ClusterInfoMap clusters() PURE; + virtual ClusterInfoMaps clusters() PURE; using ClusterSet = absl::flat_hash_set; diff --git a/source/common/api/api_impl.h b/source/common/api/api_impl.h index 89dde910abd3..fa97e23870a9 100644 --- a/source/common/api/api_impl.h +++ b/source/common/api/api_impl.h @@ -28,7 +28,7 @@ class Impl : public Api { Thread::ThreadFactory& threadFactory() override { return thread_factory_; } Filesystem::Instance& fileSystem() override { return file_system_; } TimeSource& timeSource() override { return time_system_; } - const Stats::Scope& rootScope() override { return store_; } + Stats::Scope& rootScope() override { return store_; } Random::RandomGenerator& randomGenerator() override { return random_generator_; } ProcessContextOptRef processContext() override { return process_context_; } diff --git a/source/common/common/BUILD b/source/common/common/BUILD index 8c645fd32a91..89e41ae0c7b1 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -405,6 +405,6 @@ envoy_cc_library( name = "statusor_lib", hdrs = ["statusor.h"], deps = [ - "//third_party/statusor:statusor_lib", + "@com_google_absl//absl/status:statusor", ], ) diff --git a/source/common/common/logger.cc b/source/common/common/logger.cc index e0f79783f88f..c20dbcecbe84 100644 --- a/source/common/common/logger.cc +++ b/source/common/common/logger.cc @@ -202,7 +202,12 @@ void Registry::setLogLevel(spdlog::level::level_enum log_level) { void Registry::setLogFormat(const std::string& log_format) { for (Logger& logger : allLoggers()) { - logger.logger_->set_pattern(log_format); + auto formatter = std::make_unique(); + formatter + ->add_flag( + CustomFlagFormatter::EscapeMessageNewLine::Placeholder) + .set_pattern(log_format); + logger.logger_->set_formatter(std::move(formatter)); } } @@ -217,5 +222,15 @@ Logger* Registry::logger(const std::string& log_name) { return logger_to_return; } +namespace CustomFlagFormatter { + +void EscapeMessageNewLine::format(const spdlog::details::log_msg& msg, const std::tm&, + spdlog::memory_buf_t& dest) { + auto escaped = absl::StrReplaceAll(absl::string_view(msg.payload.data(), msg.payload.size()), + replacements()); + dest.append(escaped.data(), escaped.data() + escaped.size()); +} + +} // namespace CustomFlagFormatter } // namespace Logger } // namespace Envoy diff --git a/source/common/common/logger.h b/source/common/common/logger.h index c22b29b873db..72a919bf4d4d 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -329,6 +329,33 @@ template class Loggable { } }; +// Contains custom flags to introduce user defined flags in log pattern. Reference: +// https://github.com/gabime/spdlog#user-defined-flags-in-the-log-pattern. +namespace CustomFlagFormatter { + +/** + * When added to a formatter, this adds '_' as a user defined flag in the log pattern that escapes + * newlines. + */ +class EscapeMessageNewLine : public spdlog::custom_flag_formatter { +public: + void format(const spdlog::details::log_msg& msg, const std::tm& tm, + spdlog::memory_buf_t& dest) override; + + std::unique_ptr clone() const override { + return spdlog::details::make_unique(); + } + + constexpr static char Placeholder = '_'; + +private: + using ReplacementMap = absl::flat_hash_map; + const static ReplacementMap& replacements() { + CONSTRUCT_ON_FIRST_USE(ReplacementMap, ReplacementMap{{"\n", "\\n"}}); + } +}; + +} // namespace CustomFlagFormatter } // namespace Logger /** diff --git a/source/common/common/regex.h b/source/common/common/regex.h index 68cb7ff8074d..2fdcd52ebc1c 100644 --- a/source/common/common/regex.h +++ b/source/common/common/regex.h @@ -9,6 +9,8 @@ namespace Envoy { namespace Regex { +enum class Type { Re2, StdRegex }; + /** * Utilities for constructing regular expressions. */ diff --git a/source/common/common/statusor.h b/source/common/common/statusor.h index bb7201d3c4b6..474eb1548060 100644 --- a/source/common/common/statusor.h +++ b/source/common/common/statusor.h @@ -1,6 +1,6 @@ #pragma once -#include "third_party/statusor/statusor.h" +#include "absl/status/statusor.h" /** * Facility for returning either a valid value or an error in a form of Envoy::Status. diff --git a/source/common/config/BUILD b/source/common/config/BUILD index 61d0bf4b4445..baf4f851e998 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -68,6 +68,19 @@ envoy_cc_library( "//include/envoy/config:subscription_interface", "//source/common/protobuf:utility_lib", "@com_github_cncf_udpa//udpa/core/v1:pkg_cc_proto", + "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "ttl_lib", + srcs = ["ttl.cc"], + hdrs = ["ttl.h"], + deps = [ + "//include/envoy/config:subscription_interface", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/event:timer_interface", + "@com_google_absl//absl/container:btree", ], ) @@ -78,6 +91,7 @@ envoy_cc_library( deps = [ ":api_version_lib", ":pausable_ack_queue_lib", + ":ttl_lib", ":utility_lib", ":watch_map_lib", "//include/envoy/config:subscription_interface", @@ -137,6 +151,7 @@ envoy_cc_library( ":api_version_lib", ":decoded_resource_lib", ":grpc_stream_lib", + ":ttl_lib", ":utility_lib", "//include/envoy/config:grpc_mux_interface", "//include/envoy/config:subscription_interface", @@ -429,12 +444,24 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "watched_directory_lib", + srcs = ["watched_directory.cc"], + hdrs = ["watched_directory.h"], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/filesystem:watcher_interface", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "well_known_names", srcs = ["well_known_names.cc"], hdrs = ["well_known_names.h"], deps = [ "//source/common/common:assert_lib", + "//source/common/common:regex_lib", "//source/common/singleton:const_singleton", ], ) diff --git a/source/common/config/config_provider_impl.cc b/source/common/config/config_provider_impl.cc index 3be865d2642d..78eddb0ffe10 100644 --- a/source/common/config/config_provider_impl.cc +++ b/source/common/config/config_provider_impl.cc @@ -25,11 +25,8 @@ ConfigSubscriptionCommonBase::~ConfigSubscriptionCommonBase() { } void ConfigSubscriptionCommonBase::applyConfigUpdate(const ConfigUpdateCb& update_fn) { - tls_->runOnAllThreads([update_fn](ThreadLocal::ThreadLocalObjectSharedPtr previous) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - auto prev_thread_local_config = std::dynamic_pointer_cast(previous); - prev_thread_local_config->config_ = update_fn(prev_thread_local_config->config_); - return previous; + tls_.runOnAllThreads([update_fn](OptRef thread_local_config) { + thread_local_config->config_ = update_fn(thread_local_config->config_); }); } diff --git a/source/common/config/config_provider_impl.h b/source/common/config/config_provider_impl.h index 144332fe23b0..7334796b377a 100644 --- a/source/common/config/config_provider_impl.h +++ b/source/common/config/config_provider_impl.h @@ -166,9 +166,7 @@ class ConfigSubscriptionCommonBase : protected Logger::Loggable& configInfo() const { return config_info_; } - ConfigProvider::ConfigConstSharedPtr getConfig() const { - return tls_->getTyped().config_; - } + ConfigProvider::ConfigConstSharedPtr getConfig() { return tls_->config_; } /** * Must be called by derived classes when the onConfigUpdate() callback associated with the @@ -199,7 +197,7 @@ class ConfigSubscriptionCommonBase : protected Logger::Loggable config_info_; // This slot holds a Config implementation in each thread, which is intended to be shared between // config providers from the same config source. - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; private: // Local init target which signals first RPC interaction with management server. @@ -283,7 +281,7 @@ class ConfigSubscriptionInstance : public ConfigSubscriptionCommonBase { * with the underlying subscription, shared across all providers and workers. */ void initialize(const ConfigProvider::ConfigConstSharedPtr& initial_config) { - tls_->set([initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.set([initial_config](Event::Dispatcher&) { return std::make_shared(initial_config); }); } @@ -328,9 +326,8 @@ class DeltaConfigSubscriptionInstance : public ConfigSubscriptionCommonBase { * underlying subscription for each worker thread. */ void initialize(const std::function& init_cb) { - tls_->set([init_cb](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(init_cb()); - }); + tls_.set( + [init_cb](Event::Dispatcher&) { return std::make_shared(init_cb()); }); } }; diff --git a/source/common/config/decoded_resource_impl.h b/source/common/config/decoded_resource_impl.h index 559eef874c5b..983cca19a606 100644 --- a/source/common/config/decoded_resource_impl.h +++ b/source/common/config/decoded_resource_impl.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/config/subscription.h" +#include "envoy/service/discovery/v3/discovery.pb.h" #include "common/protobuf/utility.h" @@ -20,25 +21,45 @@ repeatedPtrFieldToVector(const Protobuf::RepeatedPtrField& xs) { } // namespace +class DecodedResourceImpl; +using DecodedResourceImplPtr = std::unique_ptr; + class DecodedResourceImpl : public DecodedResource { public: - DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, const ProtobufWkt::Any& resource, - const std::string& version) - : DecodedResourceImpl(resource_decoder, {}, Protobuf::RepeatedPtrField(), - resource, true, version) {} + static DecodedResourceImplPtr fromResource(OpaqueResourceDecoder& resource_decoder, + const ProtobufWkt::Any& resource, + const std::string& version) { + if (resource.Is()) { + envoy::service::discovery::v3::Resource r; + MessageUtil::unpackTo(resource, r); + + r.set_version(version); + + return std::make_unique(resource_decoder, r); + } + + return std::unique_ptr(new DecodedResourceImpl( + resource_decoder, absl::nullopt, Protobuf::RepeatedPtrField(), resource, true, + version, absl::nullopt)); + } + DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, const envoy::service::discovery::v3::Resource& resource) : DecodedResourceImpl(resource_decoder, resource.name(), resource.aliases(), - resource.resource(), resource.has_resource(), resource.version()) {} + resource.resource(), resource.has_resource(), resource.version(), + resource.has_ttl() + ? absl::make_optional(std::chrono::milliseconds( + DurationUtil::durationToMilliseconds(resource.ttl()))) + : absl::nullopt) {} DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, const udpa::core::v1::CollectionEntry::InlineEntry& inline_entry) : DecodedResourceImpl(resource_decoder, inline_entry.name(), Protobuf::RepeatedPtrField(), inline_entry.resource(), - true, inline_entry.version()) {} + true, inline_entry.version(), absl::nullopt) {} DecodedResourceImpl(ProtobufTypes::MessagePtr resource, const std::string& name, const std::vector& aliases, const std::string& version) : resource_(std::move(resource)), has_resource_(true), name_(name), aliases_(aliases), - version_(version) {} + version_(version), ttl_(absl::nullopt) {} // Config::DecodedResource const std::string& name() const override { return name_; } @@ -46,32 +67,33 @@ class DecodedResourceImpl : public DecodedResource { const std::string& version() const override { return version_; }; const Protobuf::Message& resource() const override { return *resource_; }; bool hasResource() const override { return has_resource_; } + absl::optional ttl() const override { return ttl_; } private: DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, absl::optional name, const Protobuf::RepeatedPtrField& aliases, const ProtobufWkt::Any& resource, bool has_resource, - const std::string& version) + const std::string& version, absl::optional ttl) : resource_(resource_decoder.decodeResource(resource)), has_resource_(has_resource), name_(name ? *name : resource_decoder.resourceName(*resource_)), - aliases_(repeatedPtrFieldToVector(aliases)), version_(version) {} + aliases_(repeatedPtrFieldToVector(aliases)), version_(version), ttl_(ttl) {} const ProtobufTypes::MessagePtr resource_; const bool has_resource_; const std::string name_; const std::vector aliases_; const std::string version_; + // Per resource TTL. + const absl::optional ttl_; }; -using DecodedResourceImplPtr = std::unique_ptr; - struct DecodedResourcesWrapper { DecodedResourcesWrapper() = default; DecodedResourcesWrapper(OpaqueResourceDecoder& resource_decoder, const Protobuf::RepeatedPtrField& resources, const std::string& version) { for (const auto& resource : resources) { - pushBack(std::make_unique(resource_decoder, resource, version)); + pushBack((DecodedResourceImpl::fromResource(resource_decoder, resource, version))); } } diff --git a/source/common/config/delta_subscription_state.cc b/source/common/config/delta_subscription_state.cc index c0a6a5502cb0..7702081602a1 100644 --- a/source/common/config/delta_subscription_state.cc +++ b/source/common/config/delta_subscription_state.cc @@ -1,18 +1,36 @@ #include "common/config/delta_subscription_state.h" +#include "envoy/event/dispatcher.h" #include "envoy/service/discovery/v3/discovery.pb.h" #include "common/common/assert.h" #include "common/common/hash.h" #include "common/config/utility.h" +#include "common/runtime/runtime_features.h" namespace Envoy { namespace Config { DeltaSubscriptionState::DeltaSubscriptionState(std::string type_url, UntypedConfigUpdateCallbacks& watch_map, - const LocalInfo::LocalInfo& local_info) - : type_url_(std::move(type_url)), watch_map_(watch_map), local_info_(local_info) {} + const LocalInfo::LocalInfo& local_info, + Event::Dispatcher& dispatcher) + // TODO(snowp): Hard coding VHDS here is temporary until we can move it away from relying on + // empty resources as updates. + : supports_heartbeats_(type_url != "envoy.config.route.v3.VirtualHost"), + ttl_( + [this](const auto& expired) { + Protobuf::RepeatedPtrField removed_resources; + for (const auto& resource : expired) { + setResourceWaitingForServer(resource); + removed_resources.Add(std::string(resource)); + } + + watch_map_.onConfigUpdate({}, removed_resources, ""); + }, + dispatcher, dispatcher.timeSource()), + type_url_(std::move(type_url)), watch_map_(watch_map), local_info_(local_info), + dispatcher_(dispatcher) {} void DeltaSubscriptionState::updateSubscriptionInterest(const std::set& cur_added, const std::set& cur_removed) { @@ -25,7 +43,7 @@ void DeltaSubscriptionState::updateSubscriptionInterest(const std::setsecond.waitingForServer() && + resource.version() == itr->second.version(); +} + void DeltaSubscriptionState::handleGoodResponse( const envoy::service::discovery::v3::DeltaDiscoveryResponse& message) { absl::flat_hash_set names_added_removed; + Protobuf::RepeatedPtrField non_heartbeat_resources; for (const auto& resource : message.resources()) { if (!names_added_removed.insert(resource.name()).second) { throw EnvoyException( fmt::format("duplicate name {} found among added/updated resources", resource.name())); } + if (isHeartbeatResponse(resource)) { + continue; + } + non_heartbeat_resources.Add()->CopyFrom(resource); // DeltaDiscoveryResponses for unresolved aliases don't contain an actual resource if (!resource.has_resource() && resource.aliases_size() > 0) { continue; @@ -81,11 +119,17 @@ void DeltaSubscriptionState::handleGoodResponse( fmt::format("duplicate name {} found in the union of added+removed resources", name)); } } - watch_map_.onConfigUpdate(message.resources(), message.removed_resources(), - message.system_version_info()); - for (const auto& resource : message.resources()) { - setResourceVersion(resource.name(), resource.version()); + + { + const auto scoped_update = ttl_.scopedTtlUpdate(); + for (const auto& resource : message.resources()) { + addResourceState(resource); + } } + + watch_map_.onConfigUpdate(non_heartbeat_resources, message.removed_resources(), + message.system_version_info()); + // If a resource is gone, there is no longer a meaningful version for it that makes sense to // provide to the server upon stream reconnect: either it will continue to not exist, in which // case saying nothing is fine, or the server will bring back something new, which we should @@ -124,12 +168,12 @@ DeltaSubscriptionState::getNextRequestAckless() { // initial_resource_versions "must be populated for first request in a stream". // Also, since this might be a new server, we must explicitly state *all* of our subscription // interest. - for (auto const& [resource_name, resource_version] : resource_versions_) { + for (auto const& [resource_name, resource_state] : resource_state_) { // Populate initial_resource_versions with the resource versions we currently have. // Resources we are interested in, but are still waiting to get any version of from the // server, do not belong in initial_resource_versions. (But do belong in new subscriptions!) - if (!resource_version.waitingForServer()) { - (*request.mutable_initial_resource_versions())[resource_name] = resource_version.version(); + if (!resource_state.waitingForServer()) { + (*request.mutable_initial_resource_versions())[resource_name] = resource_state.version(); } // As mentioned above, fill resource_names_subscribe with everything, including names we // have yet to receive any resource for. @@ -160,19 +204,26 @@ DeltaSubscriptionState::getNextRequestWithAck(const UpdateAck& ack) { return request; } -void DeltaSubscriptionState::setResourceVersion(const std::string& resource_name, - const std::string& resource_version) { - resource_versions_[resource_name] = ResourceVersion(resource_version); - resource_names_.insert(resource_name); +void DeltaSubscriptionState::addResourceState( + const envoy::service::discovery::v3::Resource& resource) { + if (resource.has_ttl()) { + ttl_.add(std::chrono::milliseconds(DurationUtil::durationToMilliseconds(resource.ttl())), + resource.name()); + } else { + ttl_.clear(resource.name()); + } + + resource_state_[resource.name()] = ResourceState(resource); + resource_names_.insert(resource.name()); } void DeltaSubscriptionState::setResourceWaitingForServer(const std::string& resource_name) { - resource_versions_[resource_name] = ResourceVersion(); + resource_state_[resource_name] = ResourceState(); resource_names_.insert(resource_name); } -void DeltaSubscriptionState::setLostInterestInResource(const std::string& resource_name) { - resource_versions_.erase(resource_name); +void DeltaSubscriptionState::removeResourceState(const std::string& resource_name) { + resource_state_.erase(resource_name); resource_names_.erase(resource_name); } diff --git a/source/common/config/delta_subscription_state.h b/source/common/config/delta_subscription_state.h index 1e21ba3a8efd..089b632c9a01 100644 --- a/source/common/config/delta_subscription_state.h +++ b/source/common/config/delta_subscription_state.h @@ -11,6 +11,7 @@ #include "common/common/logger.h" #include "common/config/api_version.h" #include "common/config/pausable_ack_queue.h" +#include "common/config/ttl.h" #include "common/config/watch_map.h" #include "absl/container/node_hash_map.h" @@ -25,7 +26,7 @@ namespace Config { class DeltaSubscriptionState : public Logger::Loggable { public: DeltaSubscriptionState(std::string type_url, UntypedConfigUpdateCallbacks& watch_map, - const LocalInfo::LocalInfo& local_info); + const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher); // Update which resources we're interested in subscribing to. void updateSubscriptionInterest(const std::set& cur_added, @@ -44,6 +45,7 @@ class DeltaSubscriptionState : public Logger::Loggable { // Returns the next gRPC request proto to be sent off to the server, based on this object's // understanding of the current protocol state, and new resources that Envoy wants to request. envoy::service::discovery::v3::DeltaDiscoveryRequest getNextRequestAckless(); + // The WithAck version first calls the Ack-less version, then adds in the passed-in ack. envoy::service::discovery::v3::DeltaDiscoveryRequest getNextRequestWithAck(const UpdateAck& ack); @@ -51,18 +53,22 @@ class DeltaSubscriptionState : public Logger::Loggable { DeltaSubscriptionState& operator=(const DeltaSubscriptionState&) = delete; private: + bool isHeartbeatResponse(const envoy::service::discovery::v3::Resource& resource) const; void handleGoodResponse(const envoy::service::discovery::v3::DeltaDiscoveryResponse& message); void handleBadResponse(const EnvoyException& e, UpdateAck& ack); - class ResourceVersion { + class ResourceState { public: - explicit ResourceVersion(absl::string_view version) : version_(version) {} - // Builds a ResourceVersion in the waitingForServer state. - ResourceVersion() = default; + ResourceState(const envoy::service::discovery::v3::Resource& resource) + : version_(resource.version()) {} + + // Builds a ResourceState in the waitingForServer state. + ResourceState() = default; // If true, we currently have no version of this resource - we are waiting for the server to // provide us with one. bool waitingForServer() const { return version_ == absl::nullopt; } + // Must not be called if waitingForServer() == true. std::string version() const { ASSERT(version_.has_value()); @@ -73,17 +79,24 @@ class DeltaSubscriptionState : public Logger::Loggable { absl::optional version_; }; - // Use these helpers to ensure resource_versions_ and resource_names_ get updated together. - void setResourceVersion(const std::string& resource_name, const std::string& resource_version); + // Use these helpers to ensure resource_state_ and resource_names_ get updated together. + void addResourceState(const envoy::service::discovery::v3::Resource& resource); void setResourceWaitingForServer(const std::string& resource_name); - void setLostInterestInResource(const std::string& resource_name); + void removeResourceState(const std::string& resource_name); + void populateDiscoveryRequest(envoy::service::discovery::v3::DeltaDiscoveryResponse& request); // A map from resource name to per-resource version. The keys of this map are exactly the resource // names we are currently interested in. Those in the waitingForServer state currently don't have // any version for that resource: we need to inform the server if we lose interest in them, but we // also need to *not* include them in the initial_resource_versions map upon a reconnect. - absl::node_hash_map resource_versions_; + absl::node_hash_map resource_state_; + + // Not all xDS resources supports heartbeats due to there being specific information encoded in + // an empty response, which is indistinguishable from a heartbeat in some cases. For now we just + // disable heartbeats for these resources (currently only VHDS). + const bool supports_heartbeats_; + TtlManager ttl_; // The keys of resource_versions_. Only tracked separately because std::map does not provide an // iterator into just its keys, e.g. for use in std::set_difference. std::set resource_names_; @@ -91,6 +104,7 @@ class DeltaSubscriptionState : public Logger::Loggable { const std::string type_url_; UntypedConfigUpdateCallbacks& watch_map_; const LocalInfo::LocalInfo& local_info_; + Event::Dispatcher& dispatcher_; std::chrono::milliseconds init_fetch_timeout_; bool any_request_sent_yet_in_current_stream_{}; diff --git a/source/common/config/grpc_mux_impl.cc b/source/common/config/grpc_mux_impl.cc index b415776d6b8e..c45aab8d0f56 100644 --- a/source/common/config/grpc_mux_impl.cc +++ b/source/common/config/grpc_mux_impl.cc @@ -24,6 +24,7 @@ GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, rate_limit_settings), local_info_(local_info), skip_subsequent_node_(skip_subsequent_node), first_stream_request_(true), transport_api_version_(transport_api_version), + dispatcher_(dispatcher), enable_type_url_downgrade_and_upgrade_(Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade")) { Config::Utility::checkLocalInfo("ads", local_info); @@ -32,7 +33,7 @@ GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, void GrpcMuxImpl::start() { grpc_stream_.establishNewStream(); } void GrpcMuxImpl::sendDiscoveryRequest(const std::string& type_url) { - ApiState& api_state = api_state_[type_url]; + ApiState& api_state = apiStateFor(type_url); auto& request = api_state.request_; request.mutable_resource_names()->Clear(); @@ -56,8 +57,8 @@ void GrpcMuxImpl::sendDiscoveryRequest(const std::string& type_url) { first_stream_request_ = false; // clear error_detail after the request is sent if it exists. - if (api_state_[type_url].request_.has_error_detail()) { - api_state_[type_url].request_.clear_error_detail(); + if (apiStateFor(type_url).request_.has_error_detail()) { + apiStateFor(type_url).request_.clear_error_detail(); } } @@ -73,10 +74,10 @@ GrpcMuxWatchPtr GrpcMuxImpl::addWatch(const std::string& type_url, // convenient side-effect that we order messages on the channel based on // Envoy's internal dependency ordering. // TODO(gsagula): move TokenBucketImpl params to a config. - if (!api_state_[type_url].subscribed_) { - api_state_[type_url].request_.set_type_url(type_url); - api_state_[type_url].request_.mutable_node()->MergeFrom(local_info_.node()); - api_state_[type_url].subscribed_ = true; + if (!apiStateFor(type_url).subscribed_) { + apiStateFor(type_url).request_.set_type_url(type_url); + apiStateFor(type_url).request_.mutable_node()->MergeFrom(local_info_.node()); + apiStateFor(type_url).subscribed_ = true; subscriptions_.emplace_back(type_url); if (enable_type_url_downgrade_and_upgrade_) { registerVersionedTypeUrl(type_url); @@ -98,14 +99,14 @@ ScopedResume GrpcMuxImpl::pause(const std::string& type_url) { ScopedResume GrpcMuxImpl::pause(const std::vector type_urls) { for (const auto& type_url : type_urls) { - ApiState& api_state = api_state_[type_url]; + ApiState& api_state = apiStateFor(type_url); ENVOY_LOG(debug, "Pausing discovery requests for {} (previous count {})", type_url, api_state.pauses_); ++api_state.pauses_; } return std::make_unique([this, type_urls]() { for (const auto& type_url : type_urls) { - ApiState& api_state = api_state_[type_url]; + ApiState& api_state = apiStateFor(type_url); ENVOY_LOG(debug, "Resuming discovery requests for {} (previous count {})", type_url, api_state.pauses_); ASSERT(api_state.paused()); @@ -155,16 +156,17 @@ void GrpcMuxImpl::onDiscoveryResponse( type_url); return; } - if (api_state_[type_url].watches_.empty()) { + + if (apiStateFor(type_url).watches_.empty()) { // update the nonce as we are processing this response. - api_state_[type_url].request_.set_response_nonce(message->nonce()); + apiStateFor(type_url).request_.set_response_nonce(message->nonce()); if (message->resources().empty()) { // No watches and no resources. This can happen when envoy unregisters from a // resource that's removed from the server as well. For example, a deleted cluster // triggers un-watching the ClusterLoadAssignment watch, and at the same time the // xDS server sends an empty list of ClusterLoadAssignment resources. we'll accept // this update. no need to send a discovery request, as we don't watch for anything. - api_state_[type_url].request_.set_version_info(message->version_info()); + apiStateFor(type_url).request_.set_version_info(message->version_info()); } else { // No watches and we have resources - this should not happen. send a NACK (by not // updating the version). @@ -189,19 +191,36 @@ void GrpcMuxImpl::onDiscoveryResponse( absl::btree_map resource_ref_map; std::vector all_resource_refs; OpaqueResourceDecoder& resource_decoder = - api_state_[type_url].watches_.front()->resource_decoder_; + apiStateFor(type_url).watches_.front()->resource_decoder_; + + const auto scoped_ttl_update = apiStateFor(type_url).ttl_.scopedTtlUpdate(); + for (const auto& resource : message->resources()) { - if (message->type_url() != resource.type_url()) { + // TODO(snowp): Check the underlying type when the resource is a Resource. + if (!resource.Is() && + message->type_url() != resource.type_url()) { throw EnvoyException( fmt::format("{} does not match the message-wide type URL {} in DiscoveryResponse {}", resource.type_url(), message->type_url(), message->DebugString())); } - resources.emplace_back( - new DecodedResourceImpl(resource_decoder, resource, message->version_info())); - all_resource_refs.emplace_back(*resources.back()); - resource_ref_map.emplace(resources.back()->name(), *resources.back()); + + auto decoded_resource = + DecodedResourceImpl::fromResource(resource_decoder, resource, message->version_info()); + + if (decoded_resource->ttl()) { + apiStateFor(type_url).ttl_.add(*decoded_resource->ttl(), decoded_resource->name()); + } else { + apiStateFor(type_url).ttl_.clear(decoded_resource->name()); + } + + if (!isHeartbeatResource(type_url, *decoded_resource)) { + resources.emplace_back(std::move(decoded_resource)); + all_resource_refs.emplace_back(*resources.back()); + resource_ref_map.emplace(resources.back()->name(), *resources.back()); + } } - for (auto watch : api_state_[type_url].watches_) { + + for (auto watch : apiStateFor(type_url).watches_) { // onConfigUpdate should be called in all cases for single watch xDS (Cluster and // Listener) even if the message does not have resources so that update_empty stat // is properly incremented and state-of-the-world semantics are maintained. @@ -216,6 +235,7 @@ void GrpcMuxImpl::onDiscoveryResponse( found_resources.emplace_back(it->second); } } + // onConfigUpdate should be called only on watches(clusters/routes) that have // updates in the message for EDS/RDS. if (!found_resources.empty()) { @@ -224,19 +244,19 @@ void GrpcMuxImpl::onDiscoveryResponse( } // TODO(mattklein123): In the future if we start tracking per-resource versions, we // would do that tracking here. - api_state_[type_url].request_.set_version_info(message->version_info()); + apiStateFor(type_url).request_.set_version_info(message->version_info()); Memory::Utils::tryShrinkHeap(); } catch (const EnvoyException& e) { - for (auto watch : api_state_[type_url].watches_) { + for (auto watch : apiStateFor(type_url).watches_) { watch->callbacks_.onConfigUpdateFailed( Envoy::Config::ConfigUpdateFailureReason::UpdateRejected, &e); } - ::google::rpc::Status* error_detail = api_state_[type_url].request_.mutable_error_detail(); + ::google::rpc::Status* error_detail = apiStateFor(type_url).request_.mutable_error_detail(); error_detail->set_code(Grpc::Status::WellKnownGrpcStatus::Internal); error_detail->set_message(Config::Utility::truncateGrpcStatusMessage(e.what())); } - api_state_[type_url].request_.set_response_nonce(message->nonce()); - ASSERT(api_state_[type_url].paused()); + apiStateFor(type_url).request_.set_response_nonce(message->nonce()); + ASSERT(apiStateFor(type_url).paused()); queueDiscoveryRequest(type_url); } @@ -253,7 +273,7 @@ void GrpcMuxImpl::onStreamEstablished() { void GrpcMuxImpl::onEstablishmentFailure() { for (const auto& api_state : api_state_) { - for (auto watch : api_state.second.watches_) { + for (auto watch : api_state.second->watches_) { watch->callbacks_.onConfigUpdateFailed( Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, nullptr); } @@ -265,7 +285,7 @@ void GrpcMuxImpl::queueDiscoveryRequest(const std::string& queue_item) { ENVOY_LOG(debug, "No stream available to queueDiscoveryRequest for {}", queue_item); return; // Drop this request; the reconnect will enqueue a new one. } - ApiState& api_state = api_state_[queue_item]; + ApiState& api_state = apiStateFor(queue_item); if (api_state.paused()) { ENVOY_LOG(trace, "API {} paused during queueDiscoveryRequest(), setting pending.", queue_item); api_state.pending_ = true; @@ -275,6 +295,44 @@ void GrpcMuxImpl::queueDiscoveryRequest(const std::string& queue_item) { drainRequests(); } +void GrpcMuxImpl::expiryCallback(const std::string& type_url, + const std::vector& expired) { + // The TtlManager triggers a callback with a list of all the expired elements, which we need + // to compare against the various watched resources to return the subset that each watch is + // subscribed to. + + // We convert the incoming list into a set in order to more efficiently perform this + // comparison when there are a lot of watches. + absl::flat_hash_set all_expired; + all_expired.insert(expired.begin(), expired.end()); + + // Note: We can blindly dereference the lookup here since the only time we call this is in a + // callback that is created at the same time as we insert the ApiState for this type. + for (auto watch : api_state_.find(type_url)->second->watches_) { + Protobuf::RepeatedPtrField found_resources_for_watch; + + for (const auto& resource : expired) { + if (all_expired.find(resource) != all_expired.end()) { + found_resources_for_watch.Add(std::string(resource)); + } + } + + watch->callbacks_.onConfigUpdate({}, found_resources_for_watch, ""); + } +} + +GrpcMuxImpl::ApiState& GrpcMuxImpl::apiStateFor(const std::string& type_url) { + auto itr = api_state_.find(type_url); + if (itr == api_state_.end()) { + api_state_.emplace( + type_url, std::make_unique(dispatcher_, [this, type_url](const auto& expired) { + expiryCallback(type_url, expired); + })); + } + + return *api_state_.find(type_url)->second; +} + void GrpcMuxImpl::drainRequests() { while (!request_queue_->empty() && grpc_stream_.checkRateLimitAllowsDrain()) { // Process the request, if rate limiting is not enabled at all or if it is under rate limit. diff --git a/source/common/config/grpc_mux_impl.h b/source/common/config/grpc_mux_impl.h index 1006b165c5ba..bd554891461d 100644 --- a/source/common/config/grpc_mux_impl.h +++ b/source/common/config/grpc_mux_impl.h @@ -19,6 +19,7 @@ #include "common/common/utility.h" #include "common/config/api_version.h" #include "common/config/grpc_stream.h" +#include "common/config/ttl.h" #include "common/config/utility.h" #include "common/runtime/runtime_features.h" @@ -83,7 +84,7 @@ class GrpcMuxImpl : public GrpcMux, OpaqueResourceDecoder& resource_decoder, const std::string& type_url, GrpcMuxImpl& parent) : resources_(resources), callbacks_(callbacks), resource_decoder_(resource_decoder), - type_url_(type_url), parent_(parent), watches_(parent.api_state_[type_url].watches_) { + type_url_(type_url), parent_(parent), watches_(parent.apiStateFor(type_url).watches_) { watches_.emplace(watches_.begin(), this); } @@ -117,6 +118,10 @@ class GrpcMuxImpl : public GrpcMux, // Per muxed API state. struct ApiState { + ApiState(Event::Dispatcher& dispatcher, + std::function&)> callback) + : ttl_(callback, dispatcher, dispatcher.timeSource()) {} + bool paused() const { return pauses_ > 0; } // Watches on the returned resources for the API; @@ -129,8 +134,14 @@ class GrpcMuxImpl : public GrpcMux, bool pending_{}; // Has this API been tracked in subscriptions_? bool subscribed_{}; + TtlManager ttl_; }; + bool isHeartbeatResource(const std::string& type_url, const DecodedResource& resource) { + return !resource.hasResource() && + resource.version() == apiStateFor(type_url).request_.version_info(); + } + void expiryCallback(const std::string& type_url, const std::vector& expired); // Request queue management logic. void queueDiscoveryRequest(const std::string& queue_item); @@ -140,7 +151,12 @@ class GrpcMuxImpl : public GrpcMux, const LocalInfo::LocalInfo& local_info_; const bool skip_subsequent_node_; bool first_stream_request_; - absl::node_hash_map api_state_; + + // Helper function for looking up and potentially allocating a new ApiState. + ApiState& apiStateFor(const std::string& type_url); + + absl::node_hash_map> api_state_; + // Envoy's dependency ordering. std::list subscriptions_; @@ -149,6 +165,8 @@ class GrpcMuxImpl : public GrpcMux, // This string is a type URL. std::unique_ptr> request_queue_; const envoy::config::core::v3::ApiVersion transport_api_version_; + + Event::Dispatcher& dispatcher_; bool enable_type_url_downgrade_and_upgrade_; }; diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index 0015a2689971..dc8d5c3b14dc 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -24,6 +24,7 @@ NewGrpcMuxImpl::NewGrpcMuxImpl(Grpc::RawAsyncClientPtr&& async_client, : grpc_stream_(this, std::move(async_client), service_method, random, dispatcher, scope, rate_limit_settings), local_info_(local_info), transport_api_version_(transport_api_version), + dispatcher_(dispatcher), enable_type_url_downgrade_and_upgrade_(Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade")) {} @@ -90,6 +91,7 @@ void NewGrpcMuxImpl::onDiscoveryResponse( void NewGrpcMuxImpl::onStreamEstablished() { for (auto& [type_url, subscription] : subscriptions_) { + UNREFERENCED_PARAMETER(type_url); subscription->sub_state_.markStreamFresh(); } trySendDiscoveryRequests(); @@ -192,8 +194,8 @@ void NewGrpcMuxImpl::removeWatch(const std::string& type_url, Watch* watch) { void NewGrpcMuxImpl::addSubscription(const std::string& type_url, const bool use_namespace_matching) { - subscriptions_.emplace( - type_url, std::make_unique(type_url, local_info_, use_namespace_matching)); + subscriptions_.emplace(type_url, std::make_unique( + type_url, local_info_, use_namespace_matching, dispatcher_)); subscription_ordering_.emplace_back(type_url); } diff --git a/source/common/config/new_grpc_mux_impl.h b/source/common/config/new_grpc_mux_impl.h index 8e64f3e4399f..f39308748df6 100644 --- a/source/common/config/new_grpc_mux_impl.h +++ b/source/common/config/new_grpc_mux_impl.h @@ -68,8 +68,9 @@ class NewGrpcMuxImpl struct SubscriptionStuff { SubscriptionStuff(const std::string& type_url, const LocalInfo::LocalInfo& local_info, - const bool use_namespace_matching) - : watch_map_(use_namespace_matching), sub_state_(type_url, watch_map_, local_info) {} + const bool use_namespace_matching, Event::Dispatcher& dispatcher) + : watch_map_(use_namespace_matching), + sub_state_(type_url, watch_map_, local_info, dispatcher) {} WatchMap watch_map_; DeltaSubscriptionState sub_state_; @@ -154,6 +155,7 @@ class NewGrpcMuxImpl const LocalInfo::LocalInfo& local_info_; const envoy::config::core::v3::ApiVersion transport_api_version_; + Event::Dispatcher& dispatcher_; const bool enable_type_url_downgrade_and_upgrade_; }; diff --git a/source/common/config/ttl.cc b/source/common/config/ttl.cc new file mode 100644 index 000000000000..3b54a788ae28 --- /dev/null +++ b/source/common/config/ttl.cc @@ -0,0 +1,73 @@ +#include "common/config/ttl.h" + +namespace Envoy { +namespace Config { + +TtlManager::TtlManager(std::function&)> callback, + Event::Dispatcher& dispatcher, TimeSource& time_source) + : callback_(callback), dispatcher_(dispatcher), time_source_(time_source) { + timer_ = dispatcher_.createTimer([this]() { + ScopedTtlUpdate scoped_update(*this); + + std::vector expired; + last_scheduled_time_ = absl::nullopt; + + const auto now = time_source_.monotonicTime(); + auto itr = ttls_.begin(); + while (itr != ttls_.end() && itr->first <= now) { + expired.push_back(itr->second); + ttl_lookup_.erase(itr->second); + itr++; + } + + if (itr != ttls_.begin()) { + ttls_.erase(ttls_.begin(), itr); + } + + if (!expired.empty()) { + callback_(expired); + } + }); +} +void TtlManager::add(std::chrono::milliseconds ttl, const std::string& name) { + ScopedTtlUpdate scoped_update(*this); + + clear(name); + + auto itr_and_inserted = ttls_.insert({time_source_.monotonicTime() + ttl, name}); + ttl_lookup_[name] = itr_and_inserted.first; +} + +void TtlManager::clear(const std::string& name) { + ScopedTtlUpdate scoped_update(*this); + + auto lookup_itr = ttl_lookup_.find(name); + if (lookup_itr != ttl_lookup_.end()) { + ttls_.erase(lookup_itr->second); + ttl_lookup_.erase(lookup_itr); + } +} + +void TtlManager::refreshTimer() { + // No TTLs, so just disable the timer. + if (ttls_.empty()) { + timer_->disableTimer(); + return; + } + + auto next_ttl_expiry = ttls_.begin()->first; + + // The currently scheduled timer execution matches the next TTL expiry, so do nothing. + if (timer_->enabled() && last_scheduled_time_ == next_ttl_expiry) { + return; + } + + auto timer_duration = std::chrono::duration_cast( + next_ttl_expiry - time_source_.monotonicTime()); + + // The time until the next TTL changed, so reset the timer to match the new value. + last_scheduled_time_ = next_ttl_expiry; + timer_->enableTimer(timer_duration, nullptr); +} +} // namespace Config +} // namespace Envoy \ No newline at end of file diff --git a/source/common/config/ttl.h b/source/common/config/ttl.h new file mode 100644 index 000000000000..aa7091c63765 --- /dev/null +++ b/source/common/config/ttl.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include "envoy/event/dispatcher.h" +#include "envoy/event/timer.h" + +namespace Envoy { +namespace Config { + +/** + * Class for managing TTL expiration of xDS resources. TTLs are managed with a single timer that + * will always be set to the time until the next TTL, ensuring that we have a constant amount of + * timers per xDS resource type. + * + * We use a combination of two data structures here: a std::set that ensures that we can iterate + * over the pending TTLs in sorted over, and a map from resource name to the set iterator which + * allows us to efficiently clear or update TTLs. As iterator stability is required to track the + * iterators, we use std::set over something like ``absl::btree_set``. + * + * As a result of these two data structures, all lookups and modifications can be performed in + * O(log (number of TTL entries)). This comes at the cost of a higher memory overhead versus just + * using a single data structure. + */ +class TtlManager { +public: + TtlManager(std::function&)> callback, + Event::Dispatcher& dispatcher, TimeSource& time_source); + + // RAII tracker to simplify managing when we should be running the update callbacks. + class ScopedTtlUpdate { + public: + ~ScopedTtlUpdate() { + if (--parent_.scoped_update_counter_ == 0) { + parent_.refreshTimer(); + } + } + + private: + ScopedTtlUpdate(TtlManager& parent) : parent_(parent) { parent_.scoped_update_counter_++; } + + friend TtlManager; + + TtlManager& parent_; + }; + + ScopedTtlUpdate scopedTtlUpdate() { return ScopedTtlUpdate(*this); } + + void add(std::chrono::milliseconds ttl, const std::string& name); + + void clear(const std::string& name); + +private: + void refreshTimer(); + + using TtlSet = std::set>; + TtlSet ttls_; + absl::flat_hash_map ttl_lookup_; + + Event::TimerPtr timer_; + absl::optional last_scheduled_time_; + uint8_t scoped_update_counter_{}; + + std::function&)> callback_; + Event::Dispatcher& dispatcher_; + TimeSource& time_source_; +}; +} // namespace Config +} // namespace Envoy \ No newline at end of file diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 5ffbe637d3d7..e9f206842849 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -212,8 +212,8 @@ class Utility { /** * Get a Factory from the registry with a particular name (and templated type) with error checking * to ensure the name and factory are valid. - * @param name string identifier for the particular implementation. Note: this is a proto string - * because it is assumed that this value will be pulled directly from the configuration proto. + * @param name string identifier for the particular implementation. + * @return factory the factory requested or nullptr if it does not exist. */ template static Factory& getAndCheckFactoryByName(const std::string& name) { if (name.empty()) { @@ -230,6 +230,32 @@ class Utility { return *factory; } + /** + * Get a Factory from the registry with a particular name or return nullptr. + * @param name string identifier for the particular implementation. + */ + template static Factory* getFactoryByName(const std::string& name) { + if (name.empty()) { + return nullptr; + } + + return Registry::FactoryRegistry::getFactory(name); + } + + /** + * Get a Factory from the registry or return nullptr. + * @param message proto that contains fields 'name' and 'typed_config'. + */ + template + static Factory* getFactory(const ProtoMessage& message) { + Factory* factory = Utility::getFactoryByType(message.typed_config()); + if (factory != nullptr) { + return factory; + } + + return Utility::getFactoryByName(message.name()); + } + /** * Get a Factory from the registry with error checking to ensure the name and the factory are * valid. diff --git a/source/common/config/version_converter.cc b/source/common/config/version_converter.cc index db2bd1cfc216..0477c29bcf86 100644 --- a/source/common/config/version_converter.cc +++ b/source/common/config/version_converter.cc @@ -78,20 +78,28 @@ void VersionConverter::annotateWithOriginalType(const Protobuf::Descriptor& prev void onMessage(Protobuf::Message& message, const void* ctxt) override { const Protobuf::Descriptor* descriptor = message.GetDescriptor(); const Protobuf::Reflection* reflection = message.GetReflection(); - const Protobuf::Descriptor& prev_descriptor = *static_cast(ctxt); + const Protobuf::Descriptor* prev_descriptor = static_cast(ctxt); + // If there is no previous descriptor for this message, we don't need to annotate anything. + if (prev_descriptor == nullptr) { + return; + } // If they are the same type, there's no possibility of any different type // further down, so we're done. - if (descriptor->full_name() == prev_descriptor.full_name()) { + if (descriptor->full_name() == prev_descriptor->full_name()) { return; } auto* unknown_field_set = reflection->MutableUnknownFields(&message); unknown_field_set->AddLengthDelimited(ProtobufWellKnown::OriginalTypeFieldNumber, - prev_descriptor.full_name()); + prev_descriptor->full_name()); } const void* onField(Protobuf::Message&, const Protobuf::FieldDescriptor& field, const void* ctxt) override { - const Protobuf::Descriptor& prev_descriptor = *static_cast(ctxt); + const Protobuf::Descriptor* prev_descriptor = static_cast(ctxt); + // If there is no previous descriptor for this field, we don't need to annotate anything. + if (prev_descriptor == nullptr) { + return nullptr; + } // TODO(htuch): This is a terrible hack, there should be no per-resource // business logic in this file. The reason this is required is that // endpoints, when captured in configuration such as inlined hosts in @@ -101,13 +109,13 @@ void VersionConverter::annotateWithOriginalType(const Protobuf::Descriptor& prev // In theory, we should be able to just clean up these annotations in // ClusterManagerImpl with type erasure, but protobuf doesn't free up memory // as expected, we probably need some arena level trick to address this. - if (prev_descriptor.full_name() == "envoy.api.v2.Cluster" && + if (prev_descriptor->full_name() == "envoy.api.v2.Cluster" && (field.name() == "hidden_envoy_deprecated_hosts" || field.name() == "load_assignment")) { // This will cause the sub-message visit to abort early. return field.message_type(); } const Protobuf::FieldDescriptor* prev_field = - prev_descriptor.FindFieldByNumber(field.number()); + prev_descriptor->FindFieldByNumber(field.number()); return prev_field != nullptr ? prev_field->message_type() : nullptr; } }; diff --git a/source/common/config/watch_map.cc b/source/common/config/watch_map.cc index 5859313ded60..38040d1bb633 100644 --- a/source/common/config/watch_map.cc +++ b/source/common/config/watch_map.cc @@ -101,7 +101,7 @@ void WatchMap::onConfigUpdate(const Protobuf::RepeatedPtrField absl::flat_hash_map> per_watch_updates; for (const auto& r : resources) { decoded_resources.emplace_back( - new DecodedResourceImpl((*watches_.begin())->resource_decoder_, r, version_info)); + DecodedResourceImpl::fromResource((*watches_.begin())->resource_decoder_, r, version_info)); const absl::flat_hash_set& interested_in_r = watchesInterestedIn(decoded_resources.back()->name()); for (const auto& interested_watch : interested_in_r) { diff --git a/source/common/config/watched_directory.cc b/source/common/config/watched_directory.cc new file mode 100644 index 000000000000..c3843ee3f71c --- /dev/null +++ b/source/common/config/watched_directory.cc @@ -0,0 +1,14 @@ +#include "common/config/watched_directory.h" + +namespace Envoy { +namespace Config { + +WatchedDirectory::WatchedDirectory(const envoy::config::core::v3::WatchedDirectory& config, + Event::Dispatcher& dispatcher) { + watcher_ = dispatcher.createFilesystemWatcher(); + watcher_->addWatch(absl::StrCat(config.path(), "/"), Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { cb_(); }); +} + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/watched_directory.h b/source/common/config/watched_directory.h new file mode 100644 index 000000000000..34e965a0b4df --- /dev/null +++ b/source/common/config/watched_directory.h @@ -0,0 +1,28 @@ +#pragma once + +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/event/dispatcher.h" +#include "envoy/filesystem/watcher.h" + +namespace Envoy { +namespace Config { + +// Implement the common functionality of envoy::config::core::v3::WatchedDirectory. +class WatchedDirectory { +public: + using Callback = std::function; + + WatchedDirectory(const envoy::config::core::v3::WatchedDirectory& config, + Event::Dispatcher& dispatcher); + + void setCallback(Callback cb) { cb_ = cb; } + +private: + std::unique_ptr watcher_; + Callback cb_; +}; + +using WatchedDirectoryPtr = std::unique_ptr; + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/well_known_names.cc b/source/common/config/well_known_names.cc index e2677cb742a4..e8fc767c41a3 100644 --- a/source/common/config/well_known_names.cc +++ b/source/common/config/well_known_names.cc @@ -92,7 +92,7 @@ TagNameValues::TagNameValues() { addRegex(RATELIMIT_PREFIX, R"(^ratelimit\.((.*?)\.)\w+?$)"); // cluster.(.)* - addRegex(CLUSTER_NAME, "^cluster\\.((.*?)\\.)"); + addRe2(CLUSTER_NAME, "^cluster\\.(([^\\.]+)\\.).*"); // listener.[
.]http.(.)* addRegex(HTTP_CONN_MANAGER_PREFIX, R"(^listener(?=\.).*?\.http\.((.*?)\.))", ".http."); @@ -119,7 +119,12 @@ TagNameValues::TagNameValues() { void TagNameValues::addRegex(const std::string& name, const std::string& regex, const std::string& substr) { - descriptor_vec_.emplace_back(Descriptor(name, regex, substr)); + descriptor_vec_.emplace_back(Descriptor{name, regex, substr, Regex::Type::StdRegex}); +} + +void TagNameValues::addRe2(const std::string& name, const std::string& regex, + const std::string& substr) { + descriptor_vec_.emplace_back(Descriptor{name, regex, substr, Regex::Type::Re2}); } } // namespace Config diff --git a/source/common/config/well_known_names.h b/source/common/config/well_known_names.h index 1d3cd09c51d8..97ce58fd7265 100644 --- a/source/common/config/well_known_names.h +++ b/source/common/config/well_known_names.h @@ -6,6 +6,7 @@ #include "envoy/common/exception.h" #include "common/common/assert.h" +#include "common/common/regex.h" #include "common/singleton/const_singleton.h" namespace Envoy { @@ -62,11 +63,10 @@ class TagNameValues { * tags, such as "_rq_(\\d)xx$", will probably stay as regexes. */ struct Descriptor { - Descriptor(const std::string& name, const std::string& regex, const std::string& substr = "") - : name_(name), regex_(regex), substr_(substr) {} const std::string name_; const std::string regex_; const std::string substr_; + const Regex::Type re_type_; }; // Cluster name tag @@ -130,6 +130,7 @@ class TagNameValues { private: void addRegex(const std::string& name, const std::string& regex, const std::string& substr = ""); + void addRe2(const std::string& name, const std::string& regex, const std::string& substr = ""); // Collection of tag descriptors. std::vector descriptor_vec_; diff --git a/source/common/conn_pool/conn_pool_base.h b/source/common/conn_pool/conn_pool_base.h index 85d8f304719d..83265bd23ced 100644 --- a/source/common/conn_pool/conn_pool_base.h +++ b/source/common/conn_pool/conn_pool_base.h @@ -156,7 +156,8 @@ class ConnPoolImplBase : protected Logger::Loggable { virtual ConnectionPool::Cancellable* newPendingStream(AttachContext& context) PURE; - void attachStreamToClient(Envoy::ConnectionPool::ActiveClient& client, AttachContext& context); + virtual void attachStreamToClient(Envoy::ConnectionPool::ActiveClient& client, + AttachContext& context); virtual void onPoolFailure(const Upstream::HostDescriptionConstSharedPtr& host_description, absl::string_view failure_reason, @@ -173,6 +174,7 @@ class ConnPoolImplBase : protected Logger::Loggable { const Network::TransportSocketOptionsSharedPtr& transportSocketOptions() { return transport_socket_options_; } + bool hasPendingStreams() const { return !pending_streams_.empty(); } protected: // Creates up to 3 connections, based on the prefetch ratio. diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index c0beef89afa6..d615e5986f5d 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -58,6 +58,13 @@ DispatcherImpl::DispatcherImpl(const std::string& name, Buffer::WatermarkFactory DispatcherImpl::~DispatcherImpl() { FatalErrorHandler::removeFatalErrorHandler(*this); } +void DispatcherImpl::registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) { + ASSERT(!watchdog_registration_, "Each dispatcher can have at most one registered watchdog."); + watchdog_registration_ = + std::make_unique(watchdog, *scheduler_, min_touch_interval, *this); +} + void DispatcherImpl::initializeStats(Stats::Scope& scope, const absl::optional& prefix) { const std::string effective_prefix = prefix.has_value() ? *prefix : absl::StrCat(name_, "."); @@ -140,17 +147,23 @@ Network::DnsResolverSharedPtr DispatcherImpl::createDnsResolver( "using TCP for DNS lookups is not possible when using Apple APIs for DNS " "resolution. Apple' API only uses UDP for DNS resolution. Use UDP or disable " "the envoy.restart_features.use_apple_api_for_dns_lookups runtime feature."); - return Network::DnsResolverSharedPtr{new Network::AppleDnsResolverImpl(*this)}; + return std::make_shared(*this, api_.randomGenerator(), + api_.rootScope()); } #endif - return Network::DnsResolverSharedPtr{ - new Network::DnsResolverImpl(*this, resolvers, use_tcp_for_dns_lookups)}; + return std::make_shared(*this, resolvers, use_tcp_for_dns_lookups); } FileEventPtr DispatcherImpl::createFileEvent(os_fd_t fd, FileReadyCb cb, FileTriggerType trigger, uint32_t events) { ASSERT(isThreadSafe()); - return FileEventPtr{new FileEventImpl(*this, fd, cb, trigger, events)}; + return FileEventPtr{new FileEventImpl( + *this, fd, + [this, cb](uint32_t events) { + touchWatchdog(); + cb(events); + }, + trigger, events)}; } Filesystem::WatcherPtr DispatcherImpl::createFilesystemWatcher() { @@ -179,11 +192,19 @@ TimerPtr DispatcherImpl::createTimer(TimerCb cb) { Event::SchedulableCallbackPtr DispatcherImpl::createSchedulableCallback(std::function cb) { ASSERT(isThreadSafe()); - return base_scheduler_.createSchedulableCallback(cb); + return base_scheduler_.createSchedulableCallback([this, cb]() { + touchWatchdog(); + cb(); + }); } TimerPtr DispatcherImpl::createTimerInternal(TimerCb cb) { - return scheduler_->createTimer(cb, *this); + return scheduler_->createTimer( + [this, cb]() { + touchWatchdog(); + cb(); + }, + *this); } void DispatcherImpl::deferredDelete(DeferredDeletablePtr&& to_delete) { @@ -257,5 +278,11 @@ void DispatcherImpl::runPostCallbacks() { } } +void DispatcherImpl::touchWatchdog() { + if (watchdog_registration_) { + watchdog_registration_->touchWatchdog(); + } +} + } // namespace Event } // namespace Envoy diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index c01767f40730..62c10920d7fe 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -42,6 +42,8 @@ class DispatcherImpl : Logger::Loggable, // Event::Dispatcher const std::string& name() override { return name_; } + void registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) override; TimeSource& timeSource() override { return api_.timeSource(); } void initializeStats(Stats::Scope& scope, const absl::optional& prefix) override; void clearDeferredDeleteList() override; @@ -93,9 +95,36 @@ class DispatcherImpl : Logger::Loggable, } private: + // Holds a reference to the watchdog registered with this dispatcher and the timer used to ensure + // that the dog is touched periodically. + class WatchdogRegistration { + public: + WatchdogRegistration(const Server::WatchDogSharedPtr& watchdog, Scheduler& scheduler, + std::chrono::milliseconds timer_interval, Dispatcher& dispatcher) + : watchdog_(watchdog), timer_interval_(timer_interval) { + touch_timer_ = scheduler.createTimer( + [this]() -> void { + watchdog_->touch(); + touch_timer_->enableTimer(timer_interval_); + }, + dispatcher); + touch_timer_->enableTimer(timer_interval_); + } + + void touchWatchdog() { watchdog_->touch(); } + + private: + Server::WatchDogSharedPtr watchdog_; + const std::chrono::milliseconds timer_interval_; + TimerPtr touch_timer_; + }; + using WatchdogRegistrationPtr = std::unique_ptr; + TimerPtr createTimerInternal(TimerCb cb); void updateApproximateMonotonicTimeInternal(); void runPostCallbacks(); + // Helper used to touch the watchdog after most schedulable, fd, and timer callbacks. + void touchWatchdog(); // Validate that an operation is thread safe, i.e. it's invoked on the same thread that the // dispatcher run loop is executing on. We allow run_tid_ to be empty for tests where we don't @@ -122,6 +151,7 @@ class DispatcherImpl : Logger::Loggable, const ScopeTrackedObject* current_object_{}; bool deferred_deleting_{}; MonotonicTime approximate_monotonic_time_; + WatchdogRegistrationPtr watchdog_registration_; }; } // namespace Event diff --git a/source/common/event/scaled_range_timer_manager_impl.cc b/source/common/event/scaled_range_timer_manager_impl.cc index eca77f2186b0..3959976846d8 100644 --- a/source/common/event/scaled_range_timer_manager_impl.cc +++ b/source/common/event/scaled_range_timer_manager_impl.cc @@ -116,7 +116,7 @@ class ScaledRangeTimerManagerImpl::RangeTimerImpl final : public Timer { */ void onMinTimerComplete() { ASSERT(manager_.dispatcher_.isThreadSafe()); - ENVOY_LOG_MISC(info, "min timer complete for {}", static_cast(this)); + ENVOY_LOG_MISC(trace, "min timer complete for {}", static_cast(this)); ASSERT(absl::holds_alternative(state_)); const WaitingForMin& waiting = absl::get(state_); diff --git a/source/common/filter/http/filter_config_discovery_impl.cc b/source/common/filter/http/filter_config_discovery_impl.cc index 32b7e3e8b038..aef0519b4a88 100644 --- a/source/common/filter/http/filter_config_discovery_impl.cc +++ b/source/common/filter/http/filter_config_discovery_impl.cc @@ -18,7 +18,7 @@ DynamicFilterConfigProviderImpl::DynamicFilterConfigProviderImpl( const std::set& require_type_urls, Server::Configuration::FactoryContext& factory_context) : subscription_(std::move(subscription)), require_type_urls_(require_type_urls), - tls_(factory_context.threadLocal().allocateSlot()), + tls_(factory_context.threadLocal()), init_target_("DynamicFilterConfigProviderImpl", [this]() { subscription_->start(); // This init target is used to activate the subscription but not wait @@ -27,9 +27,7 @@ DynamicFilterConfigProviderImpl::DynamicFilterConfigProviderImpl( init_target_.ready(); }) { subscription_->filter_config_providers_.insert(this); - tls_->set([](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(); - }); + tls_.set([](Event::Dispatcher&) { return std::make_shared(); }); } DynamicFilterConfigProviderImpl::~DynamicFilterConfigProviderImpl() { @@ -39,7 +37,7 @@ DynamicFilterConfigProviderImpl::~DynamicFilterConfigProviderImpl() { const std::string& DynamicFilterConfigProviderImpl::name() { return subscription_->name(); } absl::optional DynamicFilterConfigProviderImpl::config() { - return tls_->getTyped().config_; + return tls_->config_; } void DynamicFilterConfigProviderImpl::validateConfig( @@ -54,15 +52,12 @@ void DynamicFilterConfigProviderImpl::validateConfig( void DynamicFilterConfigProviderImpl::onConfigUpdate(Envoy::Http::FilterFactoryCb config, const std::string&, Config::ConfigAppliedCb cb) { - tls_->runOnAllThreads( - [config, cb](ThreadLocal::ThreadLocalObjectSharedPtr previous) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - auto prev_config = std::dynamic_pointer_cast(previous); - prev_config->config_ = config; + tls_.runOnAllThreads( + [config, cb](OptRef tls) { + tls->config_ = config; if (cb) { cb(); } - return previous; }, [this, config]() { // This happens after all workers have discarded the previous config so it can be safely diff --git a/source/common/filter/http/filter_config_discovery_impl.h b/source/common/filter/http/filter_config_discovery_impl.h index 43a75542d138..4f79b244fba9 100644 --- a/source/common/filter/http/filter_config_discovery_impl.h +++ b/source/common/filter/http/filter_config_discovery_impl.h @@ -55,7 +55,7 @@ class DynamicFilterConfigProviderImpl : public FilterConfigProvider { // Currently applied configuration to ensure that the main thread deletes the last reference to // it. absl::optional current_config_{absl::nullopt}; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; // Local initialization target to ensure that the subscription starts in // case no warming is requested by any other filter config provider. diff --git a/source/common/grpc/BUILD b/source/common/grpc/BUILD index 3daea1ce4395..79b0eea9b9f3 100644 --- a/source/common/grpc/BUILD +++ b/source/common/grpc/BUILD @@ -171,7 +171,7 @@ envoy_cc_library( "//include/envoy/api:api_interface", "//include/envoy/grpc:google_grpc_creds_interface", "//include/envoy/thread:thread_interface", - "//include/envoy/thread_local:thread_local_interface", + "//include/envoy/thread_local:thread_local_object", "//source/common/common:base64_lib", "//source/common/common:empty_string", "//source/common/common:linked_object", @@ -206,3 +206,18 @@ envoy_cc_library( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) + +envoy_cc_library( + name = "google_async_client_cache", + srcs = ["google_async_client_cache.cc"], + hdrs = ["google_async_client_cache.h"], + external_deps = [ + "grpc", + ], + deps = [ + "//include/envoy/grpc:async_client_manager_interface", + "//include/envoy/server:factory_context_interface", + "//include/envoy/singleton:instance_interface", + "//include/envoy/thread_local:thread_local_interface", + ], +) diff --git a/source/common/grpc/async_client_manager_impl.cc b/source/common/grpc/async_client_manager_impl.cc index dc039473395c..5f809755f89d 100644 --- a/source/common/grpc/async_client_manager_impl.cc +++ b/source/common/grpc/async_client_manager_impl.cc @@ -21,9 +21,9 @@ AsyncClientFactoryImpl::AsyncClientFactoryImpl(Upstream::ClusterManager& cm, } const std::string& cluster_name = config.envoy_grpc().cluster_name(); - auto clusters = cm_.clusters(); - const auto& it = clusters.find(cluster_name); - if (it == clusters.end()) { + auto all_clusters = cm_.clusters(); + const auto& it = all_clusters.active_clusters_.find(cluster_name); + if (it == all_clusters.active_clusters_.end()) { throw EnvoyException(fmt::format("Unknown gRPC client cluster '{}'", cluster_name)); } if (it->second.get().info()->addedViaApi()) { diff --git a/source/common/grpc/google_async_client_cache.cc b/source/common/grpc/google_async_client_cache.cc new file mode 100644 index 000000000000..5150f8182b20 --- /dev/null +++ b/source/common/grpc/google_async_client_cache.cc @@ -0,0 +1,47 @@ +#include "common/grpc/google_async_client_cache.h" + +namespace Envoy { +namespace Grpc { + +void AsyncClientCache::init(const ::envoy::config::core::v3::GrpcService& grpc_proto_config) { + // The cache stores Google gRPC client, so channel is not created for each + // request. + ASSERT(grpc_proto_config.has_google_grpc()); + auto shared_this = shared_from_this(); + tls_slot_.set([shared_this, grpc_proto_config](Event::Dispatcher&) { + return std::make_shared(shared_this->async_client_manager_, + shared_this->scope_, grpc_proto_config); + }); +} + +const Grpc::RawAsyncClientSharedPtr AsyncClientCache::getAsyncClient() { + return tls_slot_->async_client_; +} + +AsyncClientCacheSharedPtr AsyncClientCacheSingleton::getOrCreateAsyncClientCache( + Grpc::AsyncClientManager& async_client_manager, Stats::Scope& scope, + ThreadLocal::SlotAllocator& tls, + const ::envoy::config::core::v3::GrpcService& grpc_proto_config) { + const std::size_t cache_key = MessageUtil::hash(grpc_proto_config.google_grpc()); + const auto it = async_clients_.find(cache_key); + if (it != async_clients_.end()) { + return it->second; + } + AsyncClientCacheSharedPtr async_client = + std::make_shared(async_client_manager, scope, tls); + async_client->init(grpc_proto_config); + async_clients_.emplace(cache_key, async_client); + return async_client; +} + +SINGLETON_MANAGER_REGISTRATION(google_grpc_async_client_cache); + +AsyncClientCacheSingletonSharedPtr +getAsyncClientCacheSingleton(Server::Configuration::ServerFactoryContext& context) { + return context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(google_grpc_async_client_cache), + [] { return std::make_shared(); }); +} + +} // namespace Grpc +} // namespace Envoy diff --git a/source/common/grpc/google_async_client_cache.h b/source/common/grpc/google_async_client_cache.h new file mode 100644 index 000000000000..3d13d8ff8424 --- /dev/null +++ b/source/common/grpc/google_async_client_cache.h @@ -0,0 +1,66 @@ +#include + +#include "envoy/grpc/async_client_manager.h" +#include "envoy/server/factory_context.h" +#include "envoy/singleton/instance.h" +#include "envoy/thread_local/thread_local.h" + +namespace Envoy { +namespace Grpc { + +// The RawAsyncClient client cache for Google grpc so channel is not created +// for each request. +// TODO(fpliu233): Remove when the cleaner and generic solution for gRPC is +// live. Tracking in #2598 and #13417. +class AsyncClientCache : public std::enable_shared_from_this { +public: + AsyncClientCache(AsyncClientManager& async_client_manager, Stats::Scope& scope, + ThreadLocal::SlotAllocator& tls) + : async_client_manager_(async_client_manager), scope_(scope), tls_slot_(tls) {} + void init(const ::envoy::config::core::v3::GrpcService& grpc_proto_config); + const RawAsyncClientSharedPtr getAsyncClient(); + +private: + /** + * Per-thread cache. + */ + struct ThreadLocalCache : public ThreadLocal::ThreadLocalObject { + ThreadLocalCache(AsyncClientManager& async_client_manager, Stats::Scope& scope, + const ::envoy::config::core::v3::GrpcService& grpc_proto_config) { + const AsyncClientFactoryPtr factory = + async_client_manager.factoryForGrpcService(grpc_proto_config, scope, true); + async_client_ = factory->create(); + } + RawAsyncClientSharedPtr async_client_; + }; + + AsyncClientManager& async_client_manager_; + Stats::Scope& scope_; + ThreadLocal::TypedSlot tls_slot_; +}; + +using AsyncClientCacheSharedPtr = std::shared_ptr; + +class AsyncClientCacheSingleton : public Singleton::Instance { +public: + AsyncClientCacheSingleton() = default; + AsyncClientCacheSharedPtr + getOrCreateAsyncClientCache(Grpc::AsyncClientManager& async_client_manager, Stats::Scope& scope, + ThreadLocal::SlotAllocator& tls, + const ::envoy::config::core::v3::GrpcService& grpc_proto_config); + +private: + // The tls slots with client cache stored with key as hash of + // envoy::config::core::v3::GrpcService::GoogleGrpc config. + // TODO(fpliu233): When new configuration is pushed, the old client cache will not get cleaned up. + // Remove when the cleaner and generic solution for gRPC is live. Tracking in #2598 and #13417. + absl::flat_hash_map async_clients_; +}; + +using AsyncClientCacheSingletonSharedPtr = std::shared_ptr; + +AsyncClientCacheSingletonSharedPtr +getAsyncClientCacheSingleton(Server::Configuration::ServerFactoryContext& context); + +} // namespace Grpc +} // namespace Envoy diff --git a/source/common/grpc/google_async_client_impl.cc b/source/common/grpc/google_async_client_impl.cc index 9239fe4d74c5..8f94df661e21 100644 --- a/source/common/grpc/google_async_client_impl.cc +++ b/source/common/grpc/google_async_client_impl.cc @@ -90,6 +90,12 @@ GoogleAsyncClientImpl::GoogleAsyncClientImpl(Event::Dispatcher& dispatcher, // have comparable overhead to what we are doing in Grpc::AsyncClientImpl, i.e. no expensive // new connection implied. std::shared_ptr channel = GoogleGrpcUtils::createChannel(config, api); + // Get state with try_to_connect = true to try connection at channel creation. + // This is for initializing gRPC channel at channel creation. This GetState(true) is used to poke + // the gRPC lb at channel creation, it doesn't have any effect no matter it succeeds or fails. But + // it helps on initialization. Otherwise, the channel establishment still happens at the first + // request, no matter when we create the channel. + channel->GetState(true); stub_ = stub_factory.createStub(channel); scope_->counterFromStatName(stat_names.google_grpc_client_creation_).inc(); // Initialize client stats. diff --git a/source/common/grpc/google_async_client_impl.h b/source/common/grpc/google_async_client_impl.h index a0e27c5e5efd..c3ea7f4c12d4 100644 --- a/source/common/grpc/google_async_client_impl.h +++ b/source/common/grpc/google_async_client_impl.h @@ -10,7 +10,7 @@ #include "envoy/grpc/async_client.h" #include "envoy/stats/scope.h" #include "envoy/thread/thread.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" #include "envoy/tracing/http_tracer.h" #include "common/common/linked_object.h" diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 726c33322614..01798486ffef 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -216,7 +216,7 @@ envoy_cc_library( "//include/envoy/router:rds_interface", "//include/envoy/router:scopes_interface", "//include/envoy/runtime:runtime_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/ssl:connection_interface", "//include/envoy/stats:stats_interface", "//include/envoy/stats:stats_macros", @@ -405,6 +405,7 @@ envoy_cc_library( ], deps = [ ":header_map_lib", + ":status_lib", ":utility_lib", "//include/envoy/common:regex_interface", "//include/envoy/http:header_map_interface", diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 8ec5261e3852..31e43a663601 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -411,7 +411,7 @@ class AsyncStreamImpl : public AsyncClient::Stream, void removeDownstreamWatermarkCallbacks(DownstreamWatermarkCallbacks&) override {} void setDecoderBufferLimit(uint32_t) override {} uint32_t decoderBufferLimit() override { return 0; } - bool recreateStream() override { return false; } + bool recreateStream(const ResponseHeaderMap*) override { return false; } const ScopeTrackedObject& scope() override { return *this; } void addUpstreamSocketOptions(const Network::Socket::OptionsSharedPtr&) override {} Network::Socket::OptionsSharedPtr getUpstreamSocketOptions() const override { return {}; } diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 0da14ae6992a..96c2769ac798 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -138,6 +138,10 @@ void CodecClient::onData(Buffer::Instance& data) { host_->cluster().stats().upstream_cx_protocol_error_.inc(); } } + + // All data should be consumed at this point if the connection remains open. + ASSERT(data.length() == 0 || connection_->state() != Network::Connection::State::Open, + absl::StrCat("extraneous bytes after response complete: ", data.length())); } CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& connection, diff --git a/source/common/http/codec_wrappers.h b/source/common/http/codec_wrappers.h index 6a4503e53451..21390cd5647f 100644 --- a/source/common/http/codec_wrappers.h +++ b/source/common/http/codec_wrappers.h @@ -68,11 +68,12 @@ class ResponseDecoderWrapper : public ResponseDecoder { class RequestEncoderWrapper : public RequestEncoder { public: // RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override { - inner_.encodeHeaders(headers, end_stream); + Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override { + RETURN_IF_ERROR(inner_.encodeHeaders(headers, end_stream)); if (end_stream) { onEncodeComplete(); } + return okStatus(); } void encodeData(Buffer::Instance& data, bool end_stream) override { diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index b67afc95a64c..12a7885273e1 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -58,6 +58,7 @@ namespace Http { COUNTER(downstream_rq_response_before_rq_complete) \ COUNTER(downstream_rq_rx_reset) \ COUNTER(downstream_rq_timeout) \ + COUNTER(downstream_rq_header_timeout) \ COUNTER(downstream_rq_too_large) \ COUNTER(downstream_rq_total) \ COUNTER(downstream_rq_tx_reset) \ @@ -289,6 +290,12 @@ class ConnectionManagerConfig { */ virtual std::chrono::milliseconds requestTimeout() const PURE; + /** + * @return request header timeout for incoming connection manager connections. Zero indicates a + * disabled request header timeout. + */ + virtual std::chrono::milliseconds requestHeadersTimeout() const PURE; + /** * @return delayed close timeout for downstream HTTP connections. Zero indicates a disabled * timeout. See http_connection_manager.proto for a detailed description of this timeout. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index ae40a06b0daa..79235b97fe33 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -227,6 +227,10 @@ void ConnectionManagerImpl::doDeferredStreamDestroy(ActiveStream& stream) { stream.stream_idle_timer_ = nullptr; } stream.filter_manager_.disarmRequestTimeout(); + if (stream.request_header_timer_ != nullptr) { + stream.request_header_timer_->disableTimer(); + stream.request_header_timer_ = nullptr; + } stream.completeRequest(); stream.filter_manager_.onStreamComplete(); @@ -466,13 +470,13 @@ void ConnectionManagerImpl::onIdleTimeout() { } } -// TODO(#13142): Add DurationTimeout response flag for HCM. void ConnectionManagerImpl::onConnectionDurationTimeout() { ENVOY_CONN_LOG(debug, "max connection duration reached", read_callbacks_->connection()); stats_.named_.downstream_cx_max_duration_reached_.inc(); if (!codec_) { // Attempt to write out buffered data one last time and issue a local close if successful. - doConnectionClose(Network::ConnectionCloseType::FlushWrite, absl::nullopt, + doConnectionClose(Network::ConnectionCloseType::FlushWrite, + StreamInfo::ResponseFlag::DurationTimeout, StreamInfo::ResponseCodeDetails::get().DurationTimeout); } else if (drain_state_ == DrainState::NotDraining) { startDrainSequence(); @@ -639,10 +643,19 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect } if (connection_manager_.config_.requestTimeout().count()) { - std::chrono::milliseconds request_timeout_ms_ = connection_manager_.config_.requestTimeout(); + std::chrono::milliseconds request_timeout = connection_manager_.config_.requestTimeout(); request_timer_ = connection_manager.read_callbacks_->connection().dispatcher().createTimer( [this]() -> void { onRequestTimeout(); }); - request_timer_->enableTimer(request_timeout_ms_, this); + request_timer_->enableTimer(request_timeout, this); + } + + if (connection_manager_.config_.requestHeadersTimeout().count()) { + std::chrono::milliseconds request_headers_timeout = + connection_manager_.config_.requestHeadersTimeout(); + request_header_timer_ = + connection_manager.read_callbacks_->connection().dispatcher().createTimer( + [this]() -> void { onRequestHeaderTimeout(); }); + request_header_timer_->enableTimer(request_headers_timeout, this); } const auto max_stream_duration = connection_manager_.config_.maxStreamDuration(); @@ -736,6 +749,14 @@ void ConnectionManagerImpl::ActiveStream::onRequestTimeout() { StreamInfo::ResponseCodeDetails::get().RequestOverallTimeout); } +void ConnectionManagerImpl::ActiveStream::onRequestHeaderTimeout() { + connection_manager_.stats_.named_.downstream_rq_header_timeout_.inc(); + sendLocalReply(request_headers_ != nullptr && + Grpc::Common::isGrpcRequestHeaders(*request_headers_), + Http::Code::RequestTimeout, "request header timeout", nullptr, absl::nullopt, + StreamInfo::ResponseCodeDetails::get().RequestHeaderTimeout); +} + void ConnectionManagerImpl::ActiveStream::onStreamMaxDurationReached() { ENVOY_STREAM_LOG(debug, "Stream max duration time reached", *this); connection_manager_.stats_.named_.downstream_rq_max_duration_reached_.inc(); @@ -771,6 +792,14 @@ void ConnectionManagerImpl::ActiveStream::chargeStats(const ResponseHeaderMap& h } } + // No response is sent back downstream for internal redirects, so don't charge downstream stats. + const absl::optional& response_code_details = + filter_manager_.streamInfo().responseCodeDetails(); + if (response_code_details.has_value() && + response_code_details == Envoy::StreamInfo::ResponseCodeDetails::get().InternalRedirect) { + return; + } + connection_manager_.stats_.named_.downstream_rq_completed_.inc(); connection_manager_.listener_stats_.downstream_rq_completed_.inc(); if (CodeUtility::is1xx(response_code)) { @@ -819,6 +848,10 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he connection_manager_.read_callbacks_->connection().dispatcher()); request_headers_ = std::move(headers); filter_manager_.requestHeadersInitialized(); + if (request_header_timer_ != nullptr) { + request_header_timer_->disableTimer(); + request_header_timer_.reset(); + } Upstream::HostDescriptionConstSharedPtr upstream_host = connection_manager_.read_callbacks_->upstreamHost(); diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 09c66089b7fe..828f8ceb30e4 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -26,7 +26,7 @@ #include "envoy/router/rds.h" #include "envoy/router/scopes.h" #include "envoy/runtime/runtime.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/ssl/connection.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" @@ -158,7 +158,6 @@ class ConnectionManagerImpl : Logger::Loggable, ActiveStream(ConnectionManagerImpl& connection_manager, uint32_t buffer_limit); void completeRequest(); - void chargeStats(const ResponseHeaderMap& headers); const Network::Connection* connection(); void sendLocalReply(bool is_grpc_request, Code code, absl::string_view body, const std::function& modify_headers, @@ -228,6 +227,7 @@ class ConnectionManagerImpl : Logger::Loggable, void setResponseTrailers(Http::ResponseTrailerMapPtr&& response_trailers) override { response_trailers_ = std::move(response_trailers); } + void chargeStats(const ResponseHeaderMap& headers) override; // TODO(snowp): Create shared OptRef/OptConstRef helpers Http::RequestHeaderMapOptRef requestHeaders() override { @@ -314,6 +314,8 @@ class ConnectionManagerImpl : Logger::Loggable, void onIdleTimeout(); // Per-stream request timeout callback. void onRequestTimeout(); + // Per-stream request header timeout callback. + void onRequestHeaderTimeout(); // Per-stream alive duration reached. void onStreamMaxDurationReached(); bool hasCachedRoute() { return cached_route_.has_value() && cached_route_.value(); } @@ -354,11 +356,18 @@ class ConnectionManagerImpl : Logger::Loggable, Tracing::SpanPtr active_span_; ResponseEncoder* response_encoder_{}; Stats::TimespanPtr request_response_timespan_; - // Per-stream idle timeout. + // Per-stream idle timeout. This timer gets reset whenever activity occurs on the stream, and, + // when triggered, will close the stream. Event::TimerPtr stream_idle_timer_; - // Per-stream request timeout. + // Per-stream request timeout. This timer is enabled when the stream is created and disabled + // when the stream ends. If triggered, it will close the stream. Event::TimerPtr request_timer_; - // Per-stream alive duration. + // Per-stream request header timeout. This timer is enabled when the stream is created and + // disabled when the downstream finishes sending headers. If triggered, it will close the + // stream. + Event::TimerPtr request_header_timer_; + // Per-stream alive duration. This timer is enabled once when the stream is created and, if + // triggered, will close the stream. Event::TimerPtr max_stream_duration_timer_; std::chrono::milliseconds idle_timeout_ms_{}; State state_; diff --git a/source/common/http/conn_pool_base.cc b/source/common/http/conn_pool_base.cc index b3f56ff434b8..4fbd6cbd588b 100644 --- a/source/common/http/conn_pool_base.cc +++ b/source/common/http/conn_pool_base.cc @@ -12,35 +12,37 @@ namespace Http { Network::TransportSocketOptionsSharedPtr wrapTransportSocketOptions(Network::TransportSocketOptionsSharedPtr transport_socket_options, - Protocol protocol) { + std::vector protocols) { if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http_default_alpn")) { return transport_socket_options; } - // If configured to do so, we override the ALPN to use for the upstream connection to match the - // selected protocol. - std::string alpn; - switch (protocol) { - case Http::Protocol::Http10: - NOT_REACHED_GCOVR_EXCL_LINE; - case Http::Protocol::Http11: - alpn = Http::Utility::AlpnNames::get().Http11; - break; - case Http::Protocol::Http2: - alpn = Http::Utility::AlpnNames::get().Http2; - break; - case Http::Protocol::Http3: - // TODO(snowp): Add once HTTP/3 upstream support is added. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - break; + std::vector fallbacks; + for (auto protocol : protocols) { + // If configured to do so, we override the ALPN to use for the upstream connection to match the + // selected protocol. + switch (protocol) { + case Http::Protocol::Http10: + NOT_REACHED_GCOVR_EXCL_LINE; + case Http::Protocol::Http11: + fallbacks.push_back(Http::Utility::AlpnNames::get().Http11); + break; + case Http::Protocol::Http2: + fallbacks.push_back(Http::Utility::AlpnNames::get().Http2); + break; + case Http::Protocol::Http3: + // TODO(snowp): Add once HTTP/3 upstream support is added. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + break; + } } if (transport_socket_options) { return std::make_shared( - std::move(alpn), transport_socket_options); + std::move(fallbacks), transport_socket_options); } else { return std::make_shared( - "", std::vector{}, std::vector{}, std::move(alpn)); + "", std::vector{}, std::vector{}, std::move(fallbacks)); } } @@ -48,10 +50,20 @@ HttpConnPoolImplBase::HttpConnPoolImplBase( Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, - Http::Protocol protocol) + Random::RandomGenerator& random_generator, std::vector protocols) : Envoy::ConnectionPool::ConnPoolImplBase( host, priority, dispatcher, options, - wrapTransportSocketOptions(transport_socket_options, protocol)) {} + wrapTransportSocketOptions(transport_socket_options, protocols)), + random_generator_(random_generator) { + ASSERT(!protocols.empty()); + // TODO(alyssawilk) the protocol function should probably be an optional and + // simply not set if there's more than one and ALPN has not been negotiated. + if (!protocols.empty()) { + protocol_ = protocols[0]; + } +} + +HttpConnPoolImplBase::~HttpConnPoolImplBase() { destructAllConnections(); } ConnectionPool::Cancellable* HttpConnPoolImplBase::newStream(Http::ResponseDecoder& response_decoder, diff --git a/source/common/http/conn_pool_base.h b/source/common/http/conn_pool_base.h index f24eea263f06..aad2d57ee3f1 100644 --- a/source/common/http/conn_pool_base.h +++ b/source/common/http/conn_pool_base.h @@ -34,8 +34,15 @@ class HttpPendingStream : public Envoy::ConnectionPool::PendingStream { HttpAttachContext context_; }; -// An implementation of Envoy::ConnectionPool::ConnPoolImplBase for shared code -// between HTTP/1.1 and HTTP/2 +class ActiveClient; + +/* An implementation of Envoy::ConnectionPool::ConnPoolImplBase for shared code + * between HTTP/1.1 and HTTP/2 + * + * NOTE: The connection pool does NOT do DNS resolution. It assumes it is being given a numeric IP + * address. Higher layer code should handle resolving DNS on error and creating a new pool + * bound to a different IP address. + */ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, public Http::ConnectionPool::Instance { public: @@ -43,7 +50,9 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, - Http::Protocol protocol); + Random::RandomGenerator& random_generator, + std::vector protocol); + ~HttpConnPoolImplBase() override; // ConnectionPool::Instance void addDrainedCallback(DrainedCb cb) override { addDrainedCallbackImpl(cb); } @@ -55,6 +64,7 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, return Envoy::ConnectionPool::ConnPoolImplBase::maybePrefetch(ratio); } bool hasActiveConnections() const override; + Http::Protocol protocol() const override { return protocol_; } // Creates a new PendingStream and enqueues it into the queue. ConnectionPool::Cancellable* @@ -69,6 +79,12 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, Envoy::ConnectionPool::AttachContext& context) override; virtual CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) PURE; + Random::RandomGenerator& randomGenerator() { return random_generator_; } + +protected: + friend class ActiveClient; + Random::RandomGenerator& random_generator_; + Http::Protocol protocol_; }; // An implementation of Envoy::ConnectionPool::ActiveClient for HTTP/1.1 and HTTP/2 @@ -78,8 +94,15 @@ class ActiveClient : public Envoy::ConnectionPool::ActiveClient { uint64_t concurrent_stream_limit) : Envoy::ConnectionPool::ActiveClient(parent, lifetime_stream_limit, concurrent_stream_limit) { - Upstream::Host::CreateConnectionData data = parent_.host()->createConnection( - parent_.dispatcher(), parent_.socketOptions(), parent_.transportSocketOptions()); + // The static cast makes sure we call the base class host() and not + // HttpConnPoolImplBase::host which is of a different type. + Upstream::Host::CreateConnectionData data = + static_cast(&parent)->host()->createConnection( + parent.dispatcher(), parent.socketOptions(), parent.transportSocketOptions()); + initialize(data, parent); + } + + void initialize(Upstream::Host::CreateConnectionData& data, HttpConnPoolImplBase& parent) { real_host_description_ = data.host_description_; codec_client_ = parent.createCodecClient(data); codec_client_->addConnectionCallbacks(*this); @@ -90,6 +113,7 @@ class ActiveClient : public Envoy::ConnectionPool::ActiveClient { parent_.host()->cluster().stats().upstream_cx_tx_bytes_buffered_, &parent_.host()->cluster().stats().bind_errors_, nullptr}); } + void close() override { codec_client_->close(); } virtual Http::RequestEncoder& newStreamEncoder(Http::ResponseDecoder& response_decoder) PURE; void onEvent(Network::ConnectionEvent event) override { @@ -97,10 +121,43 @@ class ActiveClient : public Envoy::ConnectionPool::ActiveClient { } size_t numActiveStreams() const override { return codec_client_->numActiveRequests(); } uint64_t id() const override { return codec_client_->id(); } + HttpConnPoolImplBase& parent() { return *static_cast(&parent_); } Http::CodecClientPtr codec_client_; }; +/* An implementation of Envoy::ConnectionPool::ConnPoolImplBase for HTTP/1 and HTTP/2 + */ +class FixedHttpConnPoolImpl : public HttpConnPoolImplBase { +public: + using CreateClientFn = + std::function; + using CreateCodecFn = std::function; + + FixedHttpConnPoolImpl(Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, + Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options, + const Network::TransportSocketOptionsSharedPtr& transport_socket_options, + Random::RandomGenerator& random_generator, CreateClientFn client_fn, + CreateCodecFn codec_fn, std::vector protocol) + : HttpConnPoolImplBase(host, priority, dispatcher, options, transport_socket_options, + random_generator, protocol), + codec_fn_(codec_fn), client_fn_(client_fn) {} + + CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override { + return codec_fn_(data, this); + } + + Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override { + return client_fn_(this); + } + +protected: + const CreateCodecFn codec_fn_; + const CreateClientFn client_fn_; +}; + } // namespace Http } // namespace Envoy diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index e866101e4a26..36aa653a9006 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -1,5 +1,7 @@ #include "common/http/filter_manager.h" +#include "envoy/http/header_map.h" + #include "common/common/enum_to_int.h" #include "common/common/scope_tracker.h" #include "common/http/codes.h" @@ -1253,7 +1255,7 @@ void ActiveStreamDecoderFilter::setDecoderBufferLimit(uint32_t limit) { uint32_t ActiveStreamDecoderFilter::decoderBufferLimit() { return parent_.buffer_limit_; } -bool ActiveStreamDecoderFilter::recreateStream() { +bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) { // 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. @@ -1264,6 +1266,18 @@ bool ActiveStreamDecoderFilter::recreateStream() { parent_.stream_info_.setResponseCodeDetails( StreamInfo::ResponseCodeDetails::get().InternalRedirect); + if (headers != nullptr) { + // The call to setResponseHeaders is needed to ensure that the headers are properly logged in + // access logs before the stream is destroyed. Since the function expects a ResponseHeaderPtr&&, + // ownership of the headers must be passed. This cannot happen earlier in the flow (such as in + // the call to setupRedirect) because at that point it is still possible for the headers to be + // used in a different logical branch. We work around this by creating a copy and passing + // ownership of the copy instead. + ResponseHeaderMapPtr headers_copy = createHeaderMap(*headers); + parent_.filter_manager_callbacks_.setResponseHeaders(std::move(headers_copy)); + parent_.filter_manager_callbacks_.chargeStats(*headers); + } + parent_.filter_manager_callbacks_.recreateStream(parent_.stream_info_.filter_state_); return true; diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 507e3f4a8684..9f1de48ef3a4 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -184,7 +184,7 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, removeDownstreamWatermarkCallbacks(DownstreamWatermarkCallbacks& watermark_callbacks) override; void setDecoderBufferLimit(uint32_t limit) override; uint32_t decoderBufferLimit() override; - bool recreateStream() override; + bool recreateStream(const Http::ResponseHeaderMap* original_response_headers) override; void addUpstreamSocketOptions(const Network::Socket::OptionsSharedPtr& options) override; @@ -331,6 +331,11 @@ class FilterManagerCallbacks { */ virtual void setResponseTrailers(ResponseTrailerMapPtr&& response_trailers) PURE; + /** + * Updates response code stats based on the details in the headers. + */ + virtual void chargeStats(const ResponseHeaderMap& headers) PURE; + // TODO(snowp): We should consider moving filter access to headers/trailers to happen via the // callbacks instead of via the encode/decode callbacks on the filters. diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index 3e030010de4f..c59efda824db 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -276,5 +276,27 @@ bool HeaderUtility::shouldCloseConnection(Http::Protocol protocol, return false; } +Http::Status HeaderUtility::checkRequiredHeaders(const Http::RequestHeaderMap& headers) { + if (!headers.Method()) { + return absl::InvalidArgumentError( + absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Method.get())); + } + bool is_connect = Http::HeaderUtility::isConnect(headers); + if (is_connect) { + if (!headers.Host()) { + // Host header must be present for CONNECT request. + return absl::InvalidArgumentError( + absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Host.get())); + } + } else { + if (!headers.Path()) { + // :path header must be present for non-CONNECT requests. + return absl::InvalidArgumentError( + absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get())); + } + } + return Http::okStatus(); +} + } // namespace Http } // namespace Envoy diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index 0a1e23c46717..864b1d942ad8 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -8,6 +8,7 @@ #include "envoy/http/protocol.h" #include "envoy/type/v3/range.pb.h" +#include "common/http/status.h" #include "common/protobuf/protobuf.h" namespace Envoy { @@ -174,6 +175,14 @@ class HeaderUtility { * @brief Remove the port part from host/authority header if it is equal to provided port */ static void stripPortFromHost(RequestHeaderMap& headers, uint32_t listener_port); + + /* Does a common header check ensuring required headers are present. + * Required request headers include :method header, :path for non-CONNECT requests, and + * host/authority for HTTP/1.1 or CONNECT requests. + * @return Status containing the result. If failed, message includes details on which header was + * missing. + */ + static Http::Status checkRequiredHeaders(const Http::RequestHeaderMap& headers); }; } // namespace Http } // namespace Envoy diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 5a66f8f0ea59..7d89713a3f76 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -59,6 +59,7 @@ class CustomHeaderValues { const LowerCaseString AccessControlExposeHeaders{"access-control-expose-headers"}; const LowerCaseString AccessControlMaxAge{"access-control-max-age"}; const LowerCaseString AccessControlAllowCredentials{"access-control-allow-credentials"}; + const LowerCaseString Authentication{"authentication"}; const LowerCaseString Authorization{"authorization"}; const LowerCaseString CacheControl{"cache-control"}; const LowerCaseString CdnLoop{"cdn-loop"}; diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 42b7f4dfce6a..f8c530cbcf48 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -366,20 +366,16 @@ void ResponseEncoderImpl::encodeHeaders(const ResponseHeaderMap& headers, bool e static const char REQUEST_POSTFIX[] = " HTTP/1.1\r\n"; -void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { +Status RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { + // Required headers must be present. This can only happen by some erroneous processing after the + // downstream codecs decode. + RETURN_IF_ERROR(HeaderUtility::checkRequiredHeaders(headers)); + const HeaderEntry* method = headers.Method(); const HeaderEntry* path = headers.Path(); const HeaderEntry* host = headers.Host(); bool is_connect = HeaderUtility::isConnect(headers); - // TODO(#10878): Include missing host header for CONNECT. - // The RELEASE_ASSERT below does not change the existing behavior of `encodeHeaders`. - // The `encodeHeaders` used to throw on errors. Callers of `encodeHeaders()` do not catch - // exceptions and this would cause abnormal process termination in error cases. This change - // replaces abnormal process termination from unhandled exception with the RELEASE_ASSERT. Further - // work will replace this RELEASE_ASSERT with proper error handling. - RELEASE_ASSERT(method && (path || is_connect), ":method and :path must be specified"); - if (method->value() == Headers::get().MethodValues.Head) { head_request_ = true; } else if (method->value() == Headers::get().MethodValues.Connect) { @@ -400,6 +396,7 @@ void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end connection_.copyToBuffer(REQUEST_POSTFIX, sizeof(REQUEST_POSTFIX) - 1); encodeHeadersBase(headers, absl::nullopt, end_stream); + return okStatus(); } int ConnectionImpl::setAndCheckCallbackStatus(Status&& status) { @@ -555,6 +552,16 @@ Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { [&](Buffer::Instance& data) -> Http::Status { return innerDispatch(data); }, data); } +Http::Status ClientConnectionImpl::dispatch(Buffer::Instance& data) { + Http::Status status = ConnectionImpl::dispatch(data); + if (status.ok() && data.length() > 0) { + // The HTTP/1.1 codec pauses dispatch after a single response is complete. Extraneous data + // after a response is complete indicates an error. + return codecProtocolError("http/1.1 protocol error: extraneous data after response complete"); + } + return status; +} + Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { ENVOY_CONN_LOG(trace, "parsing {} bytes", connection_, data.length()); // Make sure that dispatching_ is set to false after dispatching, even when @@ -1288,6 +1295,9 @@ void ClientConnectionImpl::onMessageComplete() { pending_response_.reset(); headers_or_trailers_.emplace(nullptr); } + + // Pause the parser after a response is complete. Any remaining data indicates an error. + http_parser_pause(&parser_, 1); } void ClientConnectionImpl::onResetStream(StreamResetReason reason) { diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index 2ae00fb03400..f63b777c6a44 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -156,7 +156,7 @@ class RequestEncoderImpl : public StreamEncoderImpl, public RequestEncoder { bool connectRequest() const { return connect_request_; } // Http::RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; + Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; void encodeTrailers(const RequestTrailerMap& trailers) override { encodeTrailersBase(trailers); } private: @@ -580,6 +580,7 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { bool cannotHaveBody(); // ConnectionImpl + Http::Status dispatch(Buffer::Instance& data) override; void onEncodeComplete() override {} Status onMessageBegin() override { return okStatus(); } Status onUrl(const char*, size_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } diff --git a/source/common/http/http1/codec_impl_legacy.cc b/source/common/http/http1/codec_impl_legacy.cc index 332f1241eb7d..d82a6ae15b2f 100644 --- a/source/common/http/http1/codec_impl_legacy.cc +++ b/source/common/http/http1/codec_impl_legacy.cc @@ -365,7 +365,7 @@ void ResponseEncoderImpl::encodeHeaders(const ResponseHeaderMap& headers, bool e static const char REQUEST_POSTFIX[] = " HTTP/1.1\r\n"; -void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { +Status RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { const HeaderEntry* method = headers.Method(); const HeaderEntry* path = headers.Path(); const HeaderEntry* host = headers.Host(); @@ -397,6 +397,7 @@ void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end connection_.copyToBuffer(REQUEST_POSTFIX, sizeof(REQUEST_POSTFIX) - 1); encodeHeadersBase(headers, absl::nullopt, end_stream); + return okStatus(); } http_parser_settings ConnectionImpl::settings_{ @@ -525,6 +526,16 @@ Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { [&](Buffer::Instance& data) -> Http::Status { return innerDispatch(data); }, data); } +Http::Status ClientConnectionImpl::dispatch(Buffer::Instance& data) { + Http::Status status = ConnectionImpl::dispatch(data); + if (status.ok() && data.length() > 0) { + // The HTTP/1.1 codec pauses dispatch after a single response is complete. Extraneous data + // after a response is complete indicates an error. + return codecProtocolError("http/1.1 protocol error: extraneous data after response complete"); + } + return status; +} + Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { ENVOY_CONN_LOG(trace, "parsing {} bytes", connection_, data.length()); ASSERT(buffered_body_.length() == 0); diff --git a/source/common/http/http1/codec_impl_legacy.h b/source/common/http/http1/codec_impl_legacy.h index 8a1b68b0fad4..6510116d7a36 100644 --- a/source/common/http/http1/codec_impl_legacy.h +++ b/source/common/http/http1/codec_impl_legacy.h @@ -160,7 +160,7 @@ class RequestEncoderImpl : public StreamEncoderImpl, public RequestEncoder { bool connectRequest() const { return connect_request_; } // Http::RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; + Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; void encodeTrailers(const RequestTrailerMap& trailers) override { encodeTrailersBase(trailers); } private: @@ -555,6 +555,7 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { bool cannotHaveBody(); // ConnectionImpl + Http::Status dispatch(Buffer::Instance& data) override; void onEncodeComplete() override {} void onMessageBegin() override {} void onUrl(const char*, size_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } diff --git a/source/common/http/http1/conn_pool.cc b/source/common/http/http1/conn_pool.cc index 3115e743e88f..c37e2f8e8635 100644 --- a/source/common/http/http1/conn_pool.cc +++ b/source/common/http/http1/conn_pool.cc @@ -23,71 +23,27 @@ namespace Envoy { namespace Http { namespace Http1 { -ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, - Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, - const Network::ConnectionSocket::OptionsSharedPtr& options, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options) - : HttpConnPoolImplBase(std::move(host), std::move(priority), dispatcher, options, - transport_socket_options, Protocol::Http11), - upstream_ready_cb_(dispatcher_.createSchedulableCallback([this]() { - upstream_ready_enabled_ = false; - onUpstreamReady(); - })), - random_generator_(random_generator) {} - -ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } - -Envoy::ConnectionPool::ActiveClientPtr ConnPoolImpl::instantiateActiveClient() { - return std::make_unique(*this); -} - -void ConnPoolImpl::onDownstreamReset(ActiveClient& client) { - // If we get a downstream reset to an attached client, we just blow it away. - client.codec_client_->close(); -} - -void ConnPoolImpl::onResponseComplete(ActiveClient& client) { - ENVOY_CONN_LOG(debug, "response complete", *client.codec_client_); - - if (!client.stream_wrapper_->encode_complete_) { - ENVOY_CONN_LOG(debug, "response before request complete", *client.codec_client_); - onDownstreamReset(client); - } else if (client.stream_wrapper_->close_connection_ || client.codec_client_->remoteClosed()) { - ENVOY_CONN_LOG(debug, "saw upstream close connection", *client.codec_client_); - onDownstreamReset(client); - } else { - client.stream_wrapper_.reset(); - - if (!pending_streams_.empty() && !upstream_ready_enabled_) { - upstream_ready_enabled_ = true; - upstream_ready_cb_->scheduleCallbackCurrentIteration(); - } - - checkForDrained(); - } -} - -ConnPoolImpl::StreamWrapper::StreamWrapper(ResponseDecoder& response_decoder, ActiveClient& parent) +ActiveClient::StreamWrapper::StreamWrapper(ResponseDecoder& response_decoder, ActiveClient& parent) : RequestEncoderWrapper(parent.codec_client_->newStream(*this)), ResponseDecoderWrapper(response_decoder), parent_(parent) { RequestEncoderWrapper::inner_.getStream().addCallbacks(*this); } -ConnPoolImpl::StreamWrapper::~StreamWrapper() { +ActiveClient::StreamWrapper::~StreamWrapper() { // Upstream connection might be closed right after response is complete. Setting delay=true // here to attach pending requests in next dispatcher loop to handle that case. // https://github.com/envoyproxy/envoy/issues/2715 parent_.parent().onStreamClosed(parent_, true); } -void ConnPoolImpl::StreamWrapper::onEncodeComplete() { encode_complete_ = true; } +void ActiveClient::StreamWrapper::onEncodeComplete() { encode_complete_ = true; } -void ConnPoolImpl::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream) { +void ActiveClient::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream) { if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fixed_connection_close")) { close_connection_ = HeaderUtility::shouldCloseConnection(parent_.codec_client_->protocol(), *headers); if (close_connection_) { - parent_.parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); + parent_.parent().host()->cluster().stats().upstream_cx_close_notify_.inc(); } } else { // If Connection: close OR @@ -100,49 +56,72 @@ void ConnPoolImpl::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, Headers::get().ConnectionValues.KeepAlive)) || (absl::EqualsIgnoreCase(headers->getProxyConnectionValue(), Headers::get().ConnectionValues.Close))) { - parent_.parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); + parent_.parent().host()->cluster().stats().upstream_cx_close_notify_.inc(); close_connection_ = true; } } ResponseDecoderWrapper::decodeHeaders(std::move(headers), end_stream); } -void ConnPoolImpl::StreamWrapper::onDecodeComplete() { +void ActiveClient::StreamWrapper::onDecodeComplete() { + ASSERT(!decode_complete_); decode_complete_ = encode_complete_; - parent_.parent().onResponseComplete(parent_); + + ENVOY_CONN_LOG(debug, "response complete", *parent_.codec_client_); + + if (!parent_.stream_wrapper_->encode_complete_) { + ENVOY_CONN_LOG(debug, "response before request complete", *parent_.codec_client_); + parent_.codec_client_->close(); + } else if (parent_.stream_wrapper_->close_connection_ || parent_.codec_client_->remoteClosed()) { + ENVOY_CONN_LOG(debug, "saw upstream close connection", *parent_.codec_client_); + parent_.codec_client_->close(); + } else { + auto* pool = &parent_.parent(); + pool->dispatcher().post([pool]() -> void { pool->onUpstreamReady(); }); + parent_.stream_wrapper_.reset(); + + pool->checkForDrained(); + } +} + +void ActiveClient::StreamWrapper::onResetStream(StreamResetReason, absl::string_view) { + parent_.codec_client_->close(); } -ConnPoolImpl::ActiveClient::ActiveClient(ConnPoolImpl& parent) +ActiveClient::ActiveClient(HttpConnPoolImplBase& parent) : Envoy::Http::ActiveClient( - parent, parent.host_->cluster().maxRequestsPerConnection(), + parent, parent.host()->cluster().maxRequestsPerConnection(), 1 // HTTP1 always has a concurrent-request-limit of 1 per connection. ) { - parent.host_->cluster().stats().upstream_cx_http1_total_.inc(); + parent.host()->cluster().stats().upstream_cx_http1_total_.inc(); } -bool ConnPoolImpl::ActiveClient::closingWithIncompleteStream() const { +bool ActiveClient::closingWithIncompleteStream() const { return (stream_wrapper_ != nullptr) && (!stream_wrapper_->decode_complete_); } -RequestEncoder& ConnPoolImpl::ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { +RequestEncoder& ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { ASSERT(!stream_wrapper_); stream_wrapper_ = std::make_unique(response_decoder, *this); return *stream_wrapper_; } -CodecClientPtr ProdConnPoolImpl::createCodecClient(Upstream::Host::CreateConnectionData& data) { - CodecClientPtr codec{new CodecClientProd(CodecClient::Type::HTTP1, std::move(data.connection_), - data.host_description_, dispatcher_, random_generator_)}; - return codec; -} - ConnectionPool::InstancePtr allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) { - return std::make_unique( - dispatcher, random_generator, host, priority, options, transport_socket_options); + return std::make_unique( + std::move(host), std::move(priority), dispatcher, options, transport_socket_options, + random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData& data, HttpConnPoolImplBase* pool) { + CodecClientPtr codec{new CodecClientProd( + CodecClient::Type::HTTP1, std::move(data.connection_), data.host_description_, + pool->dispatcher(), pool->randomGenerator())}; + return codec; + }, + std::vector{Protocol::Http11}); } } // namespace Http1 diff --git a/source/common/http/http1/conn_pool.h b/source/common/http/http1/conn_pool.h index 895e6fba6673..5b37d855e52f 100644 --- a/source/common/http/http1/conn_pool.h +++ b/source/common/http/http1/conn_pool.h @@ -12,32 +12,20 @@ namespace Http { namespace Http1 { /** - * A connection pool implementation for HTTP/1.1 connections. - * NOTE: The connection pool does NOT do DNS resolution. It assumes it is being given a numeric IP - * address. Higher layer code should handle resolving DNS on error and creating a new pool - * bound to a different IP address. + * An active client for HTTP/1.1 connections. */ -class ConnPoolImpl : public Http::HttpConnPoolImplBase { +class ActiveClient : public Envoy::Http::ActiveClient { public: - ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, - Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, - const Network::ConnectionSocket::OptionsSharedPtr& options, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options); + ActiveClient(HttpConnPoolImplBase& parent); - ~ConnPoolImpl() override; - - // ConnectionPool::Instance - Http::Protocol protocol() const override { return Http::Protocol::Http11; } - - // ConnPoolImplBase - Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override; - -protected: - class ActiveClient; + // ConnPoolImplBase::ActiveClient + bool closingWithIncompleteStream() const override; + RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; struct StreamWrapper : public RequestEncoderWrapper, public ResponseDecoderWrapper, - public StreamCallbacks { + public StreamCallbacks, + protected Logger::Loggable { StreamWrapper(ResponseDecoder& response_decoder, ActiveClient& parent); ~StreamWrapper() override; @@ -50,9 +38,7 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { void onDecodeComplete() override; // Http::StreamCallbacks - void onResetStream(StreamResetReason, absl::string_view) override { - parent_.parent().onDownstreamReset(parent_); - } + void onResetStream(StreamResetReason, absl::string_view) override; void onAboveWriteBufferHighWatermark() override {} void onBelowWriteBufferLowWatermark() override {} @@ -61,39 +47,9 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { bool close_connection_{}; bool decode_complete_{}; }; - using StreamWrapperPtr = std::unique_ptr; - class ActiveClient : public Envoy::Http::ActiveClient { - public: - ActiveClient(ConnPoolImpl& parent); - - ConnPoolImpl& parent() { return static_cast(parent_); } - - // ConnPoolImplBase::ActiveClient - bool closingWithIncompleteStream() const override; - RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; - - StreamWrapperPtr stream_wrapper_; - }; - - void onDownstreamReset(ActiveClient& client); - void onResponseComplete(ActiveClient& client); - - Event::SchedulableCallbackPtr upstream_ready_cb_; - bool upstream_ready_enabled_{false}; - Random::RandomGenerator& random_generator_; -}; - -/** - * Production implementation of the ConnPoolImpl. - */ -class ProdConnPoolImpl : public ConnPoolImpl { -public: - using ConnPoolImpl::ConnPoolImpl; - - // ConnPoolImpl - CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override; + StreamWrapperPtr stream_wrapper_; }; ConnectionPool::InstancePtr diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index b24fb48cd678..fc5cc2349171 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -183,8 +183,11 @@ void ConnectionImpl::StreamImpl::encodeHeadersBase(const std::vector } } -void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, - bool end_stream) { +Status ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, + bool end_stream) { + // Required headers must be present. This can only happen by some erroneous processing after the + // downstream codecs decode. + RETURN_IF_ERROR(HeaderUtility::checkRequiredHeaders(headers)); // This must exist outside of the scope of isUpgrade as the underlying memory is // needed until encodeHeadersBase has been called. std::vector final_headers; @@ -211,6 +214,7 @@ void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& hea buildHeaders(final_headers, headers); } encodeHeadersBase(final_headers, end_stream); + return okStatus(); } void ConnectionImpl::ServerStreamImpl::encodeHeaders(const ResponseHeaderMap& headers, @@ -1408,6 +1412,7 @@ Status ClientConnectionImpl::onBeginHeaders(const nghttp2_frame* frame) { RELEASE_ASSERT(frame->headers.cat == NGHTTP2_HCAT_RESPONSE || frame->headers.cat == NGHTTP2_HCAT_HEADERS, ""); + RETURN_IF_ERROR(trackInboundFrames(&frame->hd, frame->headers.padlen)); if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { StreamImpl* stream = getStream(frame->hd.stream_id); stream->allocTrailers(); @@ -1479,7 +1484,6 @@ ServerConnectionImpl::ServerConnectionImpl( Status ServerConnectionImpl::onBeginHeaders(const nghttp2_frame* frame) { // For a server connection, we should never get push promise frames. ASSERT(frame->hd.type == NGHTTP2_HEADERS); - RETURN_IF_ERROR(trackInboundFrames(&frame->hd, frame->headers.padlen)); if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 83f5d504e80b..7fed071a8063 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -351,7 +351,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable parent_.checkProtocolConstraintViolation(); } -void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, - bool end_stream) { +Status ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, + bool end_stream) { // This must exist outside of the scope of isUpgrade as the underlying memory is // needed until encodeHeadersBase has been called. std::vector final_headers; @@ -211,6 +211,7 @@ void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& hea buildHeaders(final_headers, headers); } encodeHeadersBase(final_headers, end_stream); + return okStatus(); } void ConnectionImpl::ServerStreamImpl::encodeHeaders(const ResponseHeaderMap& headers, diff --git a/source/common/http/http2/codec_impl_legacy.h b/source/common/http/http2/codec_impl_legacy.h index 238762c45df3..766724a36d53 100644 --- a/source/common/http/http2/codec_impl_legacy.h +++ b/source/common/http/http2/codec_impl_legacy.h @@ -351,7 +351,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable(*this); -} -void ConnPoolImpl::onGoAway(ActiveClient& client, Http::GoAwayErrorCode) { - ENVOY_CONN_LOG(debug, "remote goaway", *client.codec_client_); - host_->cluster().stats().upstream_cx_close_notify_.inc(); - if (client.state_ != ActiveClient::State::DRAINING) { - if (client.codec_client_->numActiveRequests() == 0) { - client.codec_client_->close(); +// All streams are 2^31. Client streams are half that, minus stream 0. Just to be on the safe +// side we do 2^29. +static const uint64_t DEFAULT_MAX_STREAMS = (1 << 29); + +void ActiveClient::onGoAway(Http::GoAwayErrorCode) { + ENVOY_CONN_LOG(debug, "remote goaway", *codec_client_); + parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); + if (state_ != ActiveClient::State::DRAINING) { + if (codec_client_->numActiveRequests() == 0) { + codec_client_->close(); } else { - transitionActiveClientState(client, ActiveClient::State::DRAINING); + parent_.transitionActiveClientState(*this, ActiveClient::State::DRAINING); } } } -void ConnPoolImpl::onStreamDestroy(ActiveClient& client) { - onStreamClosed(client, false); +void ActiveClient::onStreamDestroy() { + parent().onStreamClosed(*this, false); // If we are destroying this stream because of a disconnect, do not check for drain here. We will // wait until the connection has been fully drained of streams and then check in the connection // event callback. - if (!client.closed_with_active_rq_) { - checkForDrained(); + if (!closed_with_active_rq_) { + parent().checkForDrained(); } } -void ConnPoolImpl::onStreamReset(ActiveClient& client, Http::StreamResetReason reason) { +void ActiveClient::onStreamReset(Http::StreamResetReason reason) { if (reason == StreamResetReason::ConnectionTermination || reason == StreamResetReason::ConnectionFailure) { - host_->cluster().stats().upstream_rq_pending_failure_eject_.inc(); - client.closed_with_active_rq_ = true; + parent_.host()->cluster().stats().upstream_rq_pending_failure_eject_.inc(); + closed_with_active_rq_ = true; } else if (reason == StreamResetReason::LocalReset) { - host_->cluster().stats().upstream_rq_tx_reset_.inc(); + parent_.host()->cluster().stats().upstream_rq_tx_reset_.inc(); } else if (reason == StreamResetReason::RemoteReset) { - host_->cluster().stats().upstream_rq_rx_reset_.inc(); + parent_.host()->cluster().stats().upstream_rq_rx_reset_.inc(); } } -uint64_t ConnPoolImpl::maxStreamsPerConnection() { - uint64_t max_streams_config = host_->cluster().maxRequestsPerConnection(); +uint64_t maxStreamsPerConnection(uint64_t max_streams_config) { return (max_streams_config != 0) ? max_streams_config : DEFAULT_MAX_STREAMS; } -ConnPoolImpl::ActiveClient::ActiveClient(ConnPoolImpl& parent) +ActiveClient::ActiveClient(HttpConnPoolImplBase& parent) : Envoy::Http::ActiveClient( - parent, parent.maxStreamsPerConnection(), - parent.host_->cluster().http2Options().max_concurrent_streams().value()) { + parent, maxStreamsPerConnection(parent.host()->cluster().maxRequestsPerConnection()), + parent.host()->cluster().http2Options().max_concurrent_streams().value()) { codec_client_->setCodecClientCallbacks(*this); codec_client_->setCodecConnectionCallbacks(*this); - - parent.host_->cluster().stats().upstream_cx_http2_total_.inc(); + parent.host()->cluster().stats().upstream_cx_http2_total_.inc(); } -bool ConnPoolImpl::ActiveClient::closingWithIncompleteStream() const { - return closed_with_active_rq_; -} +bool ActiveClient::closingWithIncompleteStream() const { return closed_with_active_rq_; } -RequestEncoder& ConnPoolImpl::ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { +RequestEncoder& ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { return codec_client_->newStream(response_decoder); } -CodecClientPtr ProdConnPoolImpl::createCodecClient(Upstream::Host::CreateConnectionData& data) { - CodecClientPtr codec{new CodecClientProd(CodecClient::Type::HTTP2, std::move(data.connection_), - data.host_description_, dispatcher_, random_generator_)}; - return codec; -} - ConnectionPool::InstancePtr allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) { - return std::make_unique( - dispatcher, random_generator, host, priority, options, transport_socket_options); + return std::make_unique( + host, priority, dispatcher, options, transport_socket_options, random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData& data, HttpConnPoolImplBase* pool) { + CodecClientPtr codec{new CodecClientProd( + CodecClient::Type::HTTP2, std::move(data.connection_), data.host_description_, + pool->dispatcher(), pool->randomGenerator())}; + return codec; + }, + std::vector{Protocol::Http2}); } } // namespace Http2 diff --git a/source/common/http/http2/conn_pool.h b/source/common/http/http2/conn_pool.h index 63aabe08e2a5..cc5c335e38be 100644 --- a/source/common/http/http2/conn_pool.h +++ b/source/common/http/http2/conn_pool.h @@ -12,75 +12,27 @@ namespace Http { namespace Http2 { /** - * Implementation of a "connection pool" for HTTP/2. This mainly handles stats as well as - * shifting to a new connection if we reach max streams on the primary. This is a base class - * used for both the prod implementation as well as the testing one. + * Implementation of an active client for HTTP/2 */ -class ConnPoolImpl : public Envoy::Http::HttpConnPoolImplBase { +class ActiveClient : public CodecClientCallbacks, + public Http::ConnectionCallbacks, + public Envoy::Http::ActiveClient { public: - ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, - Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, - const Network::ConnectionSocket::OptionsSharedPtr& options, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options); + ActiveClient(HttpConnPoolImplBase& parent); + ~ActiveClient() override = default; - ~ConnPoolImpl() override; + // ConnPoolImpl::ActiveClient + bool closingWithIncompleteStream() const override; + RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; - // Http::ConnectionPool::Instance - Http::Protocol protocol() const override { return Http::Protocol::Http2; } + // CodecClientCallbacks + void onStreamDestroy() override; + void onStreamReset(Http::StreamResetReason reason) override; - // ConnPoolImplBase - Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override; + // Http::ConnectionCallbacks + void onGoAway(Http::GoAwayErrorCode error_code) override; -protected: - class ActiveClient : public CodecClientCallbacks, - public Http::ConnectionCallbacks, - public Envoy::Http::ActiveClient { - public: - ActiveClient(ConnPoolImpl& parent); - ~ActiveClient() override = default; - - ConnPoolImpl& parent() { return static_cast(parent_); } - - // ConnPoolImpl::ActiveClient - bool closingWithIncompleteStream() const override; - RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; - - // CodecClientCallbacks - void onStreamDestroy() override { parent().onStreamDestroy(*this); } - void onStreamReset(Http::StreamResetReason reason) override { - parent().onStreamReset(*this, reason); - } - - // Http::ConnectionCallbacks - void onGoAway(Http::GoAwayErrorCode error_code) override { - parent().onGoAway(*this, error_code); - } - - bool closed_with_active_rq_{}; - }; - - uint64_t maxStreamsPerConnection(); - void movePrimaryClientToDraining(); - void onGoAway(ActiveClient& client, Http::GoAwayErrorCode error_code); - void onStreamDestroy(ActiveClient& client); - void onStreamReset(ActiveClient& client, Http::StreamResetReason reason); - - // All streams are 2^31. Client streams are half that, minus stream 0. Just to be on the safe - // side we do 2^29. - static const uint64_t DEFAULT_MAX_STREAMS = (1 << 29); - - Random::RandomGenerator& random_generator_; -}; - -/** - * Production implementation of the HTTP/2 connection pool. - */ -class ProdConnPoolImpl : public ConnPoolImpl { -public: - using ConnPoolImpl::ConnPoolImpl; - -private: - CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override; + bool closed_with_active_rq_{}; }; ConnectionPool::InstancePtr diff --git a/source/common/init/manager_impl.cc b/source/common/init/manager_impl.cc index 650203fabbea..1440dd726414 100644 --- a/source/common/init/manager_impl.cc +++ b/source/common/init/manager_impl.cc @@ -71,6 +71,7 @@ void ManagerImpl::dumpUnreadyTargets(envoy::admin::v3::UnreadyTargetsDumps& unre auto& message = *unready_targets_dumps.mutable_unready_targets_dumps()->Add(); message.set_name(name_); for (const auto& [target_name, count] : target_names_count_) { + UNREFERENCED_PARAMETER(count); message.add_target_names(target_name); } } diff --git a/source/common/memory/BUILD b/source/common/memory/BUILD index 45cc04041baa..1fe67e534e49 100644 --- a/source/common/memory/BUILD +++ b/source/common/memory/BUILD @@ -35,7 +35,7 @@ envoy_cc_library( deps = [ ":utils_lib", "//include/envoy/event:dispatcher_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/stats:stats_interface", "//source/common/stats:symbol_table_lib", ], diff --git a/source/common/memory/heap_shrinker.h b/source/common/memory/heap_shrinker.h index 6c4a88bfbbb2..9b1930f2bb06 100644 --- a/source/common/memory/heap_shrinker.h +++ b/source/common/memory/heap_shrinker.h @@ -1,7 +1,7 @@ #pragma once #include "envoy/event/dispatcher.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats.h" diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 969e49f20673..ec11aa935263 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -106,8 +106,10 @@ envoy_cc_library( ":utility_lib", "//include/envoy/event:dispatcher_interface", "//include/envoy/event:file_event_interface", + "//include/envoy/event:timer_interface", "//include/envoy/network:dns_interface", "//source/common/common:assert_lib", + "//source/common/common:backoff_lib", "//source/common/common:linked_object", "//source/common/singleton:threadsafe_singleton", ], diff --git a/source/common/network/addr_family_aware_socket_option_impl.h b/source/common/network/addr_family_aware_socket_option_impl.h index 37d4b93c338c..5f06b13e9cd8 100644 --- a/source/common/network/addr_family_aware_socket_option_impl.h +++ b/source/common/network/addr_family_aware_socket_option_impl.h @@ -42,7 +42,7 @@ class AddrFamilyAwareSocketOptionImpl : public Socket::Option, * platform. * @param optval as per setsockopt(2). * @param optlen as per setsockopt(2). - * @return int as per setsockopt(2). ENOTSUP is returned if the option is not supported on the + * @return int as per setsockopt(2). `ENOTSUP` is returned if the option is not supported on the * platform for fd after the above option level fallback semantics are taken into account or the * socket is non-IP. */ diff --git a/source/common/network/apple_dns_impl.cc b/source/common/network/apple_dns_impl.cc index dd2a75e4c0af..f8994cb48e8c 100644 --- a/source/common/network/apple_dns_impl.cc +++ b/source/common/network/apple_dns_impl.cc @@ -44,15 +44,26 @@ DNSServiceErrorType DnsService::dnsServiceGetAddrInfo(DNSServiceRef* sdRef, DNSS return DNSServiceGetAddrInfo(sdRef, flags, interfaceIndex, protocol, hostname, callBack, context); } -AppleDnsResolverImpl::AppleDnsResolverImpl(Event::Dispatcher& dispatcher) - : dispatcher_(dispatcher) { +// Parameters of the jittered backoff strategy. +static constexpr std::chrono::milliseconds RetryInitialDelayMilliseconds(30); +static constexpr std::chrono::milliseconds RetryMaxDelayMilliseconds(30000); + +AppleDnsResolverImpl::AppleDnsResolverImpl(Event::Dispatcher& dispatcher, + Random::RandomGenerator& random, + Stats::Scope& root_scope) + : dispatcher_(dispatcher), initialize_failure_timer_(dispatcher.createTimer( + [this]() -> void { initializeMainSdRef(); })), + backoff_strategy_(std::make_unique( + RetryInitialDelayMilliseconds.count(), RetryMaxDelayMilliseconds.count(), random)), + scope_(root_scope.createScope("dns.apple.")), stats_(generateAppleDnsResolverStats(*scope_)) { ENVOY_LOG(debug, "Constructing DNS resolver"); initializeMainSdRef(); } -AppleDnsResolverImpl::~AppleDnsResolverImpl() { - ENVOY_LOG(debug, "Destructing DNS resolver"); - deallocateMainSdRef(); +AppleDnsResolverImpl::~AppleDnsResolverImpl() { deallocateMainSdRef(); } + +AppleDnsResolverStats AppleDnsResolverImpl::generateAppleDnsResolverStats(Stats::Scope& scope) { + return {ALL_APPLE_DNS_RESOLVER_STATS(POOL_COUNTER(scope))}; } void AppleDnsResolverImpl::deallocateMainSdRef() { @@ -78,13 +89,28 @@ void AppleDnsResolverImpl::initializeMainSdRef() { // However, using a shared connection brings some complexities detailed in the inline comments // for kDNSServiceFlagsShareConnection in dns_sd.h, and copied (and edited) in this implementation // where relevant. + // + // When error occurs while the main_sd_ref_ is initialized, the initialize_failure_timer_ will be + // enabled to retry initialization. Retries can also be triggered via query submission, @see + // AppleDnsResolverImpl::resolve(...) for details. auto error = DnsServiceSingleton::get().dnsServiceCreateConnection(&main_sd_ref_); - RELEASE_ASSERT(error == kDNSServiceErr_NoError, - fmt::format("error={} in DNSServiceCreateConnection", error)); + if (error != kDNSServiceErr_NoError) { + stats_.connection_failure_.inc(); + initialize_failure_timer_->enableTimer( + std::chrono::milliseconds(backoff_strategy_->nextBackOffMs())); + return; + } auto fd = DnsServiceSingleton::get().dnsServiceRefSockFD(main_sd_ref_); - RELEASE_ASSERT(fd != -1, "error in DNSServiceRefSockFD"); - ENVOY_LOG(debug, "DNS resolver has fd={}", fd); + // According to dns_sd.h: DnsServiceRefSockFD returns "The DNSServiceRef's underlying socket + // descriptor, or -1 on error.". Although it gives no detailed description on when/why this call + // would fail. + if (fd == -1) { + stats_.socket_failure_.inc(); + initialize_failure_timer_->enableTimer( + std::chrono::milliseconds(backoff_strategy_->nextBackOffMs())); + return; + } sd_ref_event_ = dispatcher_.createFileEvent( fd, @@ -93,6 +119,12 @@ void AppleDnsResolverImpl::initializeMainSdRef() { [this](uint32_t events) { onEventCallback(events); }, Event::FileTriggerType::Level, Event::FileReadyType::Read); sd_ref_event_->setEnabled(Event::FileReadyType::Read); + + // Disable the failure timer and reset the backoff strategy because the main_sd_ref_ was + // successfully initialized. Note that these actions will be no-ops if the timer was not armed to + // begin with. + initialize_failure_timer_->disableTimer(); + backoff_strategy_->reset(); } void AppleDnsResolverImpl::onEventCallback(uint32_t events) { @@ -102,6 +134,7 @@ void AppleDnsResolverImpl::onEventCallback(uint32_t events) { DNSServiceErrorType error = DnsServiceSingleton::get().dnsServiceProcessResult(main_sd_ref_); if (error != kDNSServiceErr_NoError) { ENVOY_LOG(warn, "DNS resolver error ({}) in DNSServiceProcessResult", error); + stats_.processing_failure_.inc(); // Similar to receiving an error in onDNSServiceGetAddrInfoReply, an error while processing fd // events indicates that the sd_ref state is broken. // Therefore, flush queries with_error == true. @@ -125,7 +158,28 @@ ActiveDnsQuery* AppleDnsResolverImpl::resolve(const std::string& dns_name, dns_name, address->asString()); } catch (const EnvoyException& e) { // Resolution via Apple APIs - ENVOY_LOG(debug, "DNS resolver local resolution failed with: {}", e.what()); + ENVOY_LOG(trace, "DNS resolver local resolution failed with: {}", e.what()); + + // First check that the main_sd_ref is alive by checking if the resolver is currently trying to + // initialize its main_sd_ref. + if (initialize_failure_timer_->enabled()) { + // No queries should be accumulating while the main_sd_ref_ is not alive. Either they were + // flushed when the error that deallocated occurred, or they have all failed in this branch of + // the code synchronously due to continuous inability to initialize the main_sd_ref_. + ASSERT(queries_with_pending_cb_.empty()); + + // Short-circuit the pending retry to initialize the main_sd_ref_ and try now. + initializeMainSdRef(); + + // If the timer is still enabled, that means the initialization failed. Synchronously fail the + // resolution, the callback target should retry. + if (initialize_failure_timer_->enabled()) { + callback(DnsResolver::ResolutionStatus::Failure, {}); + return nullptr; + } + } + + // Proceed with resolution after establishing that the resolver has a live main_sd_ref_. std::unique_ptr pending_resolution( new PendingResolution(*this, callback, dispatcher_, main_sd_ref_, dns_name)); @@ -204,6 +258,7 @@ AppleDnsResolverImpl::PendingResolution::~PendingResolution() { // thus the DNSServiceRef is null. // Therefore, only deallocate if the ref is not null. if (individual_sd_ref_) { + ENVOY_LOG(debug, "DNSServiceRefDeallocate individual sd ref"); DnsServiceSingleton::get().dnsServiceRefDeallocate(individual_sd_ref_); } } @@ -321,7 +376,7 @@ AppleDnsResolverImpl::PendingResolution::dnsServiceGetAddrInfo(DnsLookupFamily d // TODO: explore caching: there are caching flags in the dns_sd.h flags, allow expired answers // from the cache? - // TODO: explore validation via DNSSEC? + // TODO: explore validation via `DNSSEC`? return DnsServiceSingleton::get().dnsServiceGetAddrInfo( &individual_sd_ref_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, protocol, dns_name_.c_str(), diff --git a/source/common/network/apple_dns_impl.h b/source/common/network/apple_dns_impl.h index d3fcda152c25..0ca2cd2517bc 100644 --- a/source/common/network/apple_dns_impl.h +++ b/source/common/network/apple_dns_impl.h @@ -8,8 +8,10 @@ #include "envoy/common/platform.h" #include "envoy/event/dispatcher.h" #include "envoy/event/file_event.h" +#include "envoy/event/timer.h" #include "envoy/network/dns.h" +#include "common/common/backoff_strategy.h" #include "common/common/linked_object.h" #include "common/common/logger.h" #include "common/common/utility.h" @@ -37,14 +39,31 @@ class DnsService { using DnsServiceSingleton = ThreadSafeSingleton; +/** + * All DNS resolver stats. @see stats_macros.h + */ +#define ALL_APPLE_DNS_RESOLVER_STATS(COUNTER) \ + COUNTER(connection_failure) \ + COUNTER(socket_failure) \ + COUNTER(processing_failure) + +/** + * Struct definition for all DNS resolver stats. @see stats_macros.h + */ +struct AppleDnsResolverStats { + ALL_APPLE_DNS_RESOLVER_STATS(GENERATE_COUNTER_STRUCT) +}; + /** * Implementation of DnsResolver that uses Apple dns_sd.h APIs. All calls and callbacks are assumed * to happen on the thread that owns the creating dispatcher. */ class AppleDnsResolverImpl : public DnsResolver, protected Logger::Loggable { public: - AppleDnsResolverImpl(Event::Dispatcher& dispatcher); + AppleDnsResolverImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random, + Stats::Scope& root_scope); ~AppleDnsResolverImpl() override; + static AppleDnsResolverStats generateAppleDnsResolverStats(Stats::Scope& scope); // Network::DnsResolver ActiveDnsQuery* resolve(const std::string& dns_name, DnsLookupFamily dns_lookup_family, @@ -111,8 +130,12 @@ class AppleDnsResolverImpl : public DnsResolver, protected Logger::Loggable(state()), read_buffer_.length()); // When we disable reads, we still allow for early close notifications (the equivalent of - // EPOLLRDHUP for an epoll backend). For backends that support it, this allows us to apply + // `EPOLLRDHUP` for an epoll backend). For backends that support it, this allows us to apply // back pressure at the kernel layer, but still get timely notification of a FIN. Note that // we are not guaranteed to get notified, so even if the remote has closed, we may not know // until we try to write. Further note that currently we optionally don't correctly handle half @@ -818,7 +818,7 @@ void ClientConnectionImpl::connect() { ASSERT(SOCKET_FAILURE(result.rc_)); #ifdef WIN32 // winsock2 connect returns EWOULDBLOCK if the socket is non-blocking and the connection - // cannot be completed immediately. We do not check for EINPROGRESS as that error is for + // cannot be completed immediately. We do not check for `EINPROGRESS` as that error is for // blocking operations. if (result.errno_ == SOCKET_ERROR_AGAIN) { #else diff --git a/source/common/network/raw_buffer_socket.cc b/source/common/network/raw_buffer_socket.cc index 69f9d7095313..c539add2f71a 100644 --- a/source/common/network/raw_buffer_socket.cc +++ b/source/common/network/raw_buffer_socket.cc @@ -93,7 +93,5 @@ RawBufferSocketFactory::createTransportSocket(TransportSocketOptionsSharedPtr) c } bool RawBufferSocketFactory::implementsSecureTransport() const { return false; } - -bool RawBufferSocketFactory::isReady() const { return true; } } // namespace Network } // namespace Envoy diff --git a/source/common/network/raw_buffer_socket.h b/source/common/network/raw_buffer_socket.h index 9fb37b31613d..9a17fe35516b 100644 --- a/source/common/network/raw_buffer_socket.h +++ b/source/common/network/raw_buffer_socket.h @@ -32,7 +32,7 @@ class RawBufferSocketFactory : public TransportSocketFactory { // Network::TransportSocketFactory TransportSocketPtr createTransportSocket(TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; + bool usesProxyProtocolOptions() const override { return false; } }; } // namespace Network diff --git a/source/common/network/transport_socket_options_impl.cc b/source/common/network/transport_socket_options_impl.cc index 62358ce48710..9f158f416bf0 100644 --- a/source/common/network/transport_socket_options_impl.cc +++ b/source/common/network/transport_socket_options_impl.cc @@ -16,7 +16,8 @@ namespace Envoy { namespace Network { namespace { -void commonHashKey(const TransportSocketOptions& options, std::vector& key) { +void commonHashKey(const TransportSocketOptions& options, std::vector& key, + const Network::TransportSocketFactory& factory) { const auto& server_name_overide = options.serverNameOverride(); if (server_name_overide.has_value()) { pushScalarToByteVector(StringUtil::CaseInsensitiveHash()(server_name_overide.value()), key); @@ -35,19 +36,32 @@ void commonHashKey(const TransportSocketOptions& options, std::vector& key) const { - commonHashKey(*this, key); +void AlpnDecoratingTransportSocketOptions::hashKey( + std::vector& key, const Network::TransportSocketFactory& factory) const { + commonHashKey(*this, key, factory); } -void TransportSocketOptionsImpl::hashKey(std::vector& key) const { - commonHashKey(*this, key); +void TransportSocketOptionsImpl::hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const { + commonHashKey(*this, key, factory); } TransportSocketOptionsSharedPtr @@ -55,6 +69,7 @@ TransportSocketOptionsUtility::fromFilterState(const StreamInfo::FilterState& fi absl::string_view server_name; std::vector application_protocols; std::vector subject_alt_names; + std::vector alpn_fallback; absl::optional proxy_protocol_options; bool needs_transport_socket_options = false; @@ -88,8 +103,8 @@ TransportSocketOptionsUtility::fromFilterState(const StreamInfo::FilterState& fi if (needs_transport_socket_options) { return std::make_shared( - server_name, std::move(subject_alt_names), std::move(application_protocols), absl::nullopt, - proxy_protocol_options); + server_name, std::move(subject_alt_names), std::move(application_protocols), + std::move(alpn_fallback), proxy_protocol_options); } else { return nullptr; } diff --git a/source/common/network/transport_socket_options_impl.h b/source/common/network/transport_socket_options_impl.h index 3611f117c8e5..8a961e4fa33d 100644 --- a/source/common/network/transport_socket_options_impl.h +++ b/source/common/network/transport_socket_options_impl.h @@ -10,7 +10,7 @@ namespace Network { // A wrapper around another TransportSocketOptions that overrides the ALPN fallback. class AlpnDecoratingTransportSocketOptions : public TransportSocketOptions { public: - AlpnDecoratingTransportSocketOptions(std::string&& alpn, + AlpnDecoratingTransportSocketOptions(std::vector&& alpn, TransportSocketOptionsSharedPtr inner_options) : alpn_fallback_(std::move(alpn)), inner_options_(std::move(inner_options)) {} // Network::TransportSocketOptions @@ -23,16 +23,17 @@ class AlpnDecoratingTransportSocketOptions : public TransportSocketOptions { const std::vector& applicationProtocolListOverride() const override { return inner_options_->applicationProtocolListOverride(); } - const absl::optional& applicationProtocolFallback() const override { + const std::vector& applicationProtocolFallback() const override { return alpn_fallback_; } absl::optional proxyProtocolOptions() const override { return inner_options_->proxyProtocolOptions(); } - void hashKey(std::vector& key) const override; + void hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const override; private: - const absl::optional alpn_fallback_; + const std::vector alpn_fallback_; const TransportSocketOptionsSharedPtr inner_options_; }; @@ -41,8 +42,7 @@ class TransportSocketOptionsImpl : public TransportSocketOptions { TransportSocketOptionsImpl( absl::string_view override_server_name = "", std::vector&& override_verify_san_list = {}, - std::vector&& override_alpn = {}, - absl::optional&& fallback_alpn = {}, + std::vector&& override_alpn = {}, std::vector&& fallback_alpn = {}, absl::optional proxy_proto_options = absl::nullopt) : override_server_name_(override_server_name.empty() ? absl::nullopt @@ -61,19 +61,20 @@ class TransportSocketOptionsImpl : public TransportSocketOptions { const std::vector& applicationProtocolListOverride() const override { return override_alpn_list_; } - const absl::optional& applicationProtocolFallback() const override { + const std::vector& applicationProtocolFallback() const override { return alpn_fallback_; } absl::optional proxyProtocolOptions() const override { return proxy_protocol_options_; } - void hashKey(std::vector& key) const override; + void hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const override; private: const absl::optional override_server_name_; const std::vector override_verify_san_list_; const std::vector override_alpn_list_; - const absl::optional alpn_fallback_; + const std::vector alpn_fallback_; const absl::optional proxy_protocol_options_; }; diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index 015177dcc186..12d2e4e424f9 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -73,6 +73,7 @@ envoy_cc_library( "//source/common/config:api_type_oracle_lib", "//source/common/config:version_converter_lib", "//source/common/protobuf:visitor_lib", + "//source/common/runtime:runtime_features_lib", "@com_github_cncf_udpa//udpa/annotations:pkg_cc_proto", "@envoy_api//envoy/annotations:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index f945a458d5de..e7842b35706e 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -16,6 +16,7 @@ #include "common/protobuf/protobuf.h" #include "common/protobuf/visitor.h" #include "common/protobuf/well_known.h" +#include "common/runtime/runtime_features.h" #include "absl/strings/match.h" #include "udpa/annotations/sensitive.pb.h" @@ -164,7 +165,7 @@ void tryWithApiBoosting(MessageXformFn f, Protobuf::Message& message) { try { f(message, MessageVersion::LatestVersionValidate); } catch (EnvoyException& e) { - MessageUtil::onVersionUpgradeWarn(e.what()); + MessageUtil::onVersionUpgradeDeprecation(e.what()); } // Now we do the real work of upgrading. Config::VersionConverter::upgrade(*earlier_message, message); @@ -275,8 +276,7 @@ void ProtoExceptionUtil::throwProtoValidationException(const std::string& valida throw ProtoValidationException(validation_error, message); } -// TODO(htuch): this is where we will also reject v2 configs by default. -void MessageUtil::onVersionUpgradeWarn(absl::string_view desc) { +void MessageUtil::onVersionUpgradeDeprecation(absl::string_view desc, bool reject) { const std::string& warning_str = fmt::format("Configuration does not parse cleanly as v3. v2 configuration is " "deprecated and will be removed from Envoy at the start of Q1 2021: {}", @@ -300,6 +300,15 @@ void MessageUtil::onVersionUpgradeWarn(absl::string_view desc) { if (loader != nullptr) { loader->countDeprecatedFeatureUse(); } + if (reject && + !Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_deprecated_v2_api")) { + throw DeprecatedMajorVersionException(fmt::format( + "The v2 xDS major version is deprecated and disabled by default. Support for v2 will be " + "removed from Envoy at the start of Q1 2021. You may make use of v2 in Q3 2020 by setting " + "'--bootstrap-version 2' on the CLI for a v2 bootstrap file and also enabling the runtime " + "envoy.reloadable_features.enable_deprecated_v2_api flag. ({})", + desc)); + } } size_t MessageUtil::hash(const Protobuf::Message& message) { @@ -647,7 +656,7 @@ void MessageUtil::unpackTo(const ProtobufWkt::Any& any_message, Protobuf::Messag any_message_with_fixup.DebugString())); } Config::VersionConverter::annotateWithOriginalType(*earlier_version_desc, message); - MessageUtil::onVersionUpgradeWarn(any_full_name); + MessageUtil::onVersionUpgradeDeprecation(any_full_name); return; } } diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 24c3e639e45e..a23fe7d216f3 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -131,6 +131,14 @@ uint64_t fractionalPercentDenominatorToInt( namespace Envoy { +/** + * Exception class for rejecting a deprecated major version. + */ +class DeprecatedMajorVersionException : public EnvoyException { +public: + DeprecatedMajorVersionException(const std::string& message) : EnvoyException(message) {} +}; + class MissingFieldException : public EnvoyException { public: MissingFieldException(const std::string& field_name, const Protobuf::Message& message); @@ -367,8 +375,9 @@ class MessageUtil { * Invoke when a version upgrade (e.g. v2 -> v3) is detected. This may warn or throw * depending on where we are in the major version deprecation cycle. * @param desc description of upgrade to include in warning or exception. + * @param reject should a DeprecatedMajorVersionException be thrown on failure? */ - static void onVersionUpgradeWarn(absl::string_view desc); + static void onVersionUpgradeDeprecation(absl::string_view desc, bool reject = true); /** * Obtain a string field from a protobuf message dynamically. diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index cf48e3551b62..3886e68e01f6 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -228,7 +228,7 @@ RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( : subscription_(std::move(subscription)), config_update_info_(subscription_->routeConfigUpdate()), factory_context_(factory_context), validator_(factory_context.messageValidationContext().dynamicValidationVisitor()), - tls_(factory_context.threadLocal().allocateSlot()) { + tls_(factory_context.threadLocal()) { ConfigConstSharedPtr initial_config; if (config_update_info_->configInfo().has_value()) { initial_config = std::make_shared(config_update_info_->routeConfiguration(), @@ -236,7 +236,7 @@ RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( } else { initial_config = std::make_shared(); } - tls_->set([initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.set([initial_config](Event::Dispatcher&) { return std::make_shared(initial_config); }); // It should be 1:1 mapping due to shared rds config. @@ -250,19 +250,12 @@ RdsRouteConfigProviderImpl::~RdsRouteConfigProviderImpl() { ASSERT(subscription_->routeConfigProviders().empty()); } -Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { - return tls_->getTyped().config_; -} +Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { return tls_->config_; } void RdsRouteConfigProviderImpl::onConfigUpdate() { ConfigConstSharedPtr new_config(new ConfigImpl(config_update_info_->routeConfiguration(), factory_context_, validator_, false)); - tls_->runOnAllThreads([new_config](ThreadLocal::ThreadLocalObjectSharedPtr previous) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - auto prev_config = std::dynamic_pointer_cast(previous); - prev_config->config_ = new_config; - return previous; - }); + tls_.runOnAllThreads([new_config](OptRef tls) { tls->config_ = new_config; }); const auto aliases = config_update_info_->resourceIdsInLastVhdsUpdate(); // Regular (non-VHDS) RDS updates don't populate aliases fields in resources. diff --git a/source/common/router/rds_impl.h b/source/common/router/rds_impl.h index ca9e3c562741..10717c8bce3c 100644 --- a/source/common/router/rds_impl.h +++ b/source/common/router/rds_impl.h @@ -224,7 +224,7 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, RouteConfigUpdatePtr& config_update_info_; Server::Configuration::ServerFactoryContext& factory_context_; ProtobufMessage::ValidationVisitor& validator_; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; std::list config_update_callbacks_; // A flag used to determine if this instance of RdsRouteConfigProviderImpl hasn't been // deallocated. Please also see a comment in requestVirtualHostsUpdate() method implementation. diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 34d13e9648f8..62634c95aee4 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -329,11 +329,6 @@ void Filter::chargeUpstreamCode(Http::Code code, } Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { - // Do a common header check. We make sure that all outgoing requests have all HTTP/2 headers. - // These get stripped by HTTP/1 codec where applicable. - ASSERT(headers.Method()); - ASSERT(headers.Host()); - downstream_headers_ = &headers; // Extract debug configuration from filter state. This is used further along to determine whether @@ -1426,7 +1421,7 @@ bool Filter::setupRedirect(const Http::ResponseHeaderMap& headers, !callbacks_->decodingBuffer() && // Redirects with body not yet supported. location != nullptr && convertRequestHeadersForInternalRedirect(*downstream_headers_, *location) && - callbacks_->recreateStream()) { + callbacks_->recreateStream(&headers)) { cluster_->stats().upstream_internal_redirect_succeeded_total_.inc(); return true; } diff --git a/source/common/router/scoped_rds.cc b/source/common/router/scoped_rds.cc index d9ca4781e7b5..17613814efea 100644 --- a/source/common/router/scoped_rds.cc +++ b/source/common/router/scoped_rds.cc @@ -427,6 +427,7 @@ ScopedRdsConfigSubscription::detectUpdateConflictAndCleanupRemoved( absl::flat_hash_map scope_name_by_hash = scope_name_by_hash_; absl::erase_if(scope_name_by_hash, [&updated_or_removed_scopes](const auto& key_name) { auto const& [key, name] = key_name; + UNREFERENCED_PARAMETER(key); return updated_or_removed_scopes.contains(name); }); absl::flat_hash_map diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index b906df34fdc9..57bcb5cc4fee 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -416,10 +416,22 @@ void UpstreamRequest::onPoolReady( } } - upstream_->encodeHeaders(*parent_.downstreamHeaders(), shouldSendEndStream()); - + const Http::Status status = + upstream_->encodeHeaders(*parent_.downstreamHeaders(), shouldSendEndStream()); calling_encode_headers_ = false; + if (!status.ok()) { + // It is possible that encodeHeaders() fails. This can happen if filters or other extensions + // erroneously remove required headers. + stream_info_.setResponseFlag(StreamInfo::ResponseFlag::DownstreamProtocolError); + const std::string details = + absl::StrCat(StreamInfo::ResponseCodeDetails::get().FilterRemovedRequiredHeaders, "{", + status.message(), "}"); + parent_.callbacks()->sendLocalReply(Http::Code::ServiceUnavailable, status.message(), nullptr, + absl::nullopt, details); + return; + } + if (!paused_for_connect_) { encodeBodyAndTrailers(); } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 992399f0ed81..c12fe99c223b 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -88,6 +88,7 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.stop_faking_paths", "envoy.reloadable_features.strict_1xx_and_204_response_headers", "envoy.reloadable_features.tls_use_io_handle_bio", + "envoy.reloadable_features.vhds_heartbeats", "envoy.reloadable_features.unify_grpc_handling", "envoy.restart_features.use_apple_api_for_dns_lookups", }; @@ -101,6 +102,8 @@ constexpr const char* runtime_features[] = { // When features are added here, there should be a tracking bug assigned to the // code owner to flip the default after sufficient testing. constexpr const char* disabled_runtime_features[] = { + // v2 is fatal-by-default. + "envoy.reloadable_features.enable_deprecated_v2_api", // Allow Envoy to upgrade or downgrade version of type url, should be removed when support for // v2 url is removed from codebase. "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", @@ -112,8 +115,7 @@ constexpr const char* disabled_runtime_features[] = { "envoy.reloadable_features.test_feature_false", // gRPC Timeout header is missing (#13580) "envoy.reloadable_features.ext_authz_measure_timeout_on_check_created", - // The cluster which can't extract secret entity by SDS to be warming and never activate. - "envoy.reloadable_features.cluster_keep_warming_no_secret_entity", + }; RuntimeFeatures::RuntimeFeatures() { diff --git a/source/common/secret/BUILD b/source/common/secret/BUILD index f486c2f7ce8e..84defb7ef88b 100644 --- a/source/common/secret/BUILD +++ b/source/common/secret/BUILD @@ -58,6 +58,7 @@ envoy_cc_library( "//source/common/config:api_version_lib", "//source/common/config:subscription_base_interface", "//source/common/config:utility_lib", + "//source/common/config:watched_directory_lib", "//source/common/init:target_lib", "//source/common/protobuf:utility_lib", "//source/common/ssl:certificate_validation_context_config_impl_lib", diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc index 163b91a13a31..3f319f03e638 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -12,6 +12,10 @@ namespace Envoy { namespace Secret { +SdsApiStats SdsApi::generateStats(Stats::Scope& scope) { + return {ALL_SDS_API_STATS(POOL_COUNTER(scope))}; +} + SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_view sds_config_name, Config::SubscriptionFactory& subscription_factory, TimeSource& time_source, ProtobufMessage::ValidationVisitor& validation_visitor, Stats::Store& stats, @@ -19,16 +23,17 @@ SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_vi : Envoy::Config::SubscriptionBase( sds_config.resource_api_version(), validation_visitor, "name"), init_target_(fmt::format("SdsApi {}", sds_config_name), [this] { initialize(); }), - stats_(stats), sds_config_(std::move(sds_config)), sds_config_name_(sds_config_name), - secret_hash_(0), clean_up_(std::move(destructor_cb)), + dispatcher_(dispatcher), api_(api), + scope_(stats.createScope(absl::StrCat("sds.", sds_config_name, "."))), + sds_api_stats_(generateStats(*scope_)), sds_config_(std::move(sds_config)), + sds_config_name_(sds_config_name), secret_hash_(0), clean_up_(std::move(destructor_cb)), subscription_factory_(subscription_factory), time_source_(time_source), secret_data_{sds_config_name_, "uninitialized", - time_source_.systemTime()}, - dispatcher_(dispatcher), api_(api) { + time_source_.systemTime()} { const auto resource_name = getResourceName(); // This has to happen here (rather than in initialize()) as it can throw exceptions. subscription_ = subscription_factory_.subscriptionFromConfigSource( - sds_config_, Grpc::Common::typeUrl(resource_name), stats_, *this, resource_decoder_); + sds_config_, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_); // TODO(JimmyCYJ): Implement chained_init_manager, so that multiple init_manager // can be chained together to behave as one init_manager. In that way, we let @@ -36,6 +41,48 @@ SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_vi // each init manager has a chance to initialize its targets. } +void SdsApi::resolveDataSource(const FileContentMap& files, + envoy::config::core::v3::DataSource& data_source) { + if (data_source.specifier_case() == + envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { + const std::string& content = files.at(data_source.filename()); + data_source.set_inline_bytes(content); + } +} + +void SdsApi::onWatchUpdate() { + // Filesystem reads and update callbacks can fail if the key material is missing or bad. We're not + // under an onConfigUpdate() context, so we need to catch these cases explicitly here. + try { + // Obtain a stable set of files. If a rotation happens while we're reading, + // then we need to try again. + uint64_t prev_hash = 0; + FileContentMap files = loadFiles(); + uint64_t next_hash = getHashForFiles(files); + const uint64_t MaxBoundedRetries = 5; + for (uint64_t bounded_retries = MaxBoundedRetries; + next_hash != prev_hash && bounded_retries > 0; --bounded_retries) { + files = loadFiles(); + prev_hash = next_hash; + next_hash = getHashForFiles(files); + } + if (next_hash != prev_hash) { + ENVOY_LOG_MISC( + warn, "Unable to atomically refresh secrets due to > {} non-atomic rotations observed", + MaxBoundedRetries); + } + const uint64_t new_hash = next_hash; + if (new_hash != files_hash_) { + resolveSecret(files); + update_callback_manager_.runCallbacks(); + files_hash_ = new_hash; + } + } catch (const EnvoyException& e) { + ENVOY_LOG_MISC(warn, fmt::format("Failed to reload certificates: {}", e.what())); + sds_api_stats_.key_rotation_failed_.inc(); + } +} + void SdsApi::onConfigUpdate(const std::vector& resources, const std::string& version_info) { validateUpdateSize(resources.size()); @@ -47,35 +94,40 @@ void SdsApi::onConfigUpdate(const std::vector& resou fmt::format("Unexpected SDS secret (expecting {}): {}", sds_config_name_, secret.name())); } - uint64_t new_hash = MessageUtil::hash(secret); + const uint64_t new_hash = MessageUtil::hash(secret); if (new_hash != secret_hash_) { validateConfig(secret); secret_hash_ = new_hash; setSecret(secret); + const auto files = loadFiles(); + files_hash_ = getHashForFiles(files); + resolveSecret(files); update_callback_manager_.runCallbacks(); - // List DataSources that refer to files - auto files = getDataSourceFilenames(); - if (!files.empty()) { - // Create new watch, also destroys the old watch if any. - watcher_ = dispatcher_.createFilesystemWatcher(); - files_hash_ = getHashForFiles(); - for (auto const& filename : files) { - // Watch for directory instead of file. This allows users to do atomic renames - // on directory level (e.g. Kubernetes secret update). - const auto result = api_.fileSystem().splitPathFromFilename(filename); - watcher_->addWatch(absl::StrCat(result.directory_, "/"), - Filesystem::Watcher::Events::MovedTo, [this](uint32_t) { - uint64_t new_hash = getHashForFiles(); - if (new_hash != files_hash_) { - update_callback_manager_.runCallbacks(); - files_hash_ = new_hash; - } - }); - } + auto* watched_directory = getWatchedDirectory(); + // Either we have a watched path and can defer the watch monitoring to a + // WatchedDirectory object, or we need to implement per-file watches in the else + // clause. + if (watched_directory != nullptr) { + watched_directory->setCallback([this]() { onWatchUpdate(); }); } else { - watcher_.reset(); // Destroy the old watch if any + // List DataSources that refer to files + auto files = getDataSourceFilenames(); + if (!files.empty()) { + // Create new watch, also destroys the old watch if any. + watcher_ = dispatcher_.createFilesystemWatcher(); + for (auto const& filename : files) { + // Watch for directory instead of file. This allows users to do atomic renames + // on directory level (e.g. Kubernetes secret update). + const auto result = api_.fileSystem().splitPathFromFilename(filename); + watcher_->addWatch(absl::StrCat(result.directory_, "/"), + Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { onWatchUpdate(); }); + } + } else { + watcher_.reset(); // Destroy the old watch if any + } } } secret_data_.last_updated_ = time_source_.systemTime(); @@ -114,36 +166,44 @@ void SdsApi::initialize() { SdsApi::SecretData SdsApi::secretData() { return secret_data_; } -uint64_t SdsApi::getHashForFiles() { - uint64_t hash = 0; +SdsApi::FileContentMap SdsApi::loadFiles() { + FileContentMap files; for (auto const& filename : getDataSourceFilenames()) { - hash = HashUtil::xxHash64(api_.fileSystem().fileReadToEnd(filename), hash); + files[filename] = api_.fileSystem().fileReadToEnd(filename); + } + return files; +} + +uint64_t SdsApi::getHashForFiles(const FileContentMap& files) { + uint64_t hash = 0; + for (const auto& it : files) { + hash = HashUtil::xxHash64(it.second, hash); } return hash; } std::vector TlsCertificateSdsApi::getDataSourceFilenames() { std::vector files; - if (tls_certificate_secrets_ && tls_certificate_secrets_->has_certificate_chain() && - tls_certificate_secrets_->certificate_chain().specifier_case() == + if (sds_tls_certificate_secrets_ && sds_tls_certificate_secrets_->has_certificate_chain() && + sds_tls_certificate_secrets_->certificate_chain().specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { - files.push_back(tls_certificate_secrets_->certificate_chain().filename()); + files.push_back(sds_tls_certificate_secrets_->certificate_chain().filename()); } - if (tls_certificate_secrets_ && tls_certificate_secrets_->has_private_key() && - tls_certificate_secrets_->private_key().specifier_case() == + if (sds_tls_certificate_secrets_ && sds_tls_certificate_secrets_->has_private_key() && + sds_tls_certificate_secrets_->private_key().specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { - files.push_back(tls_certificate_secrets_->private_key().filename()); + files.push_back(sds_tls_certificate_secrets_->private_key().filename()); } return files; } std::vector CertificateValidationContextSdsApi::getDataSourceFilenames() { std::vector files; - if (certificate_validation_context_secrets_ && - certificate_validation_context_secrets_->has_trusted_ca() && - certificate_validation_context_secrets_->trusted_ca().specifier_case() == + if (sds_certificate_validation_context_secrets_ && + sds_certificate_validation_context_secrets_->has_trusted_ca() && + sds_certificate_validation_context_secrets_->trusted_ca().specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { - files.push_back(certificate_validation_context_secrets_->trusted_ca().filename()); + files.push_back(sds_certificate_validation_context_secrets_->trusted_ca().filename()); } return files; } diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index 52b78a37a49b..1c58feb6c5bc 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -23,6 +23,7 @@ #include "common/common/cleanup.h" #include "common/config/subscription_base.h" #include "common/config/utility.h" +#include "common/config/watched_directory.h" #include "common/init/target_impl.h" #include "common/ssl/certificate_validation_context_config_impl.h" #include "common/ssl/tls_certificate_config_impl.h" @@ -30,6 +31,18 @@ namespace Envoy { namespace Secret { +/** + * All SDS API. @see stats_macros.h + */ +#define ALL_SDS_API_STATS(COUNTER) COUNTER(key_rotation_failed) + +/** + * Struct definition for all SDS API stats. @see stats_macros.h + */ +struct SdsApiStats { + ALL_SDS_API_STATS(GENERATE_COUNTER_STRUCT) +}; + /** * SDS API implementation that fetches secrets from SDS server via Subscription. */ @@ -60,8 +73,13 @@ class SdsApi : public Envoy::Config::SubscriptionBase< } protected: + // Ordered for hash stability. + using FileContentMap = std::map; + // Creates new secrets. virtual void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret&) PURE; + // Refresh secrets, e.g. re-resolve symlinks in secret paths. + virtual void resolveSecret(const FileContentMap& /*files*/){}; virtual void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret&) PURE; Common::CallbackManager<> update_callback_manager_; @@ -74,15 +92,26 @@ class SdsApi : public Envoy::Config::SubscriptionBase< void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; virtual std::vector getDataSourceFilenames() PURE; + virtual Config::WatchedDirectory* getWatchedDirectory() PURE; + + void resolveDataSource(const FileContentMap& files, + envoy::config::core::v3::DataSource& data_source); Init::TargetImpl init_target_; + Event::Dispatcher& dispatcher_; + Api::Api& api_; private: void validateUpdateSize(int num_resources); void initialize(); - uint64_t getHashForFiles(); + FileContentMap loadFiles(); + uint64_t getHashForFiles(const FileContentMap& files); + // Invoked for filesystem watches on update. + void onWatchUpdate(); + SdsApiStats generateStats(Stats::Scope& scope); - Stats::Store& stats_; + Stats::ScopePtr scope_; + SdsApiStats sds_api_stats_; const envoy::config::core::v3::ConfigSource sds_config_; Config::SubscriptionPtr subscription_; @@ -94,8 +123,6 @@ class SdsApi : public Envoy::Config::SubscriptionBase< Config::SubscriptionFactory& subscription_factory_; TimeSource& time_source_; SecretData secret_data_; - Event::Dispatcher& dispatcher_; - Api::Api& api_; std::unique_ptr watcher_; bool registered_init_target_{false}; }; @@ -142,7 +169,7 @@ class TlsCertificateSdsApi : public SdsApi, public TlsCertificateConfigProvider // SecretProvider const envoy::extensions::transport_sockets::tls::v3::TlsCertificate* secret() const override { - return tls_certificate_secrets_.get(); + return resolved_tls_certificate_secrets_.get(); } Common::CallbackHandle* addValidationCallback( std::function) @@ -158,15 +185,37 @@ class TlsCertificateSdsApi : public SdsApi, public TlsCertificateConfigProvider protected: void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { - tls_certificate_secrets_ = + sds_tls_certificate_secrets_ = std::make_unique( secret.tls_certificate()); + resolved_tls_certificate_secrets_ = nullptr; + if (secret.tls_certificate().has_watched_directory()) { + watched_directory_ = std::make_unique( + secret.tls_certificate().watched_directory(), dispatcher_); + } else { + watched_directory_.reset(); + } + } + void resolveSecret(const FileContentMap& files) override { + resolved_tls_certificate_secrets_ = + std::make_unique( + *sds_tls_certificate_secrets_); + // We replace path based secrets with inlined secrets on update. + resolveDataSource(files, *resolved_tls_certificate_secrets_->mutable_certificate_chain()); + resolveDataSource(files, *resolved_tls_certificate_secrets_->mutable_private_key()); } void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return watched_directory_.get(); } private: - TlsCertificatePtr tls_certificate_secrets_; + // Path to watch for rotation. + Config::WatchedDirectoryPtr watched_directory_; + // TlsCertificate according to SDS source. + TlsCertificatePtr sds_tls_certificate_secrets_; + // TlsCertificate after reloading. Path based certificates are inlined for + // future read consistency. + TlsCertificatePtr resolved_tls_certificate_secrets_; }; /** @@ -205,7 +254,7 @@ class CertificateValidationContextSdsApi : public SdsApi, // SecretProvider const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext* secret() const override { - return certificate_validation_context_secrets_.get(); + return resolved_certificate_validation_context_secrets_.get(); } Common::CallbackHandle* addUpdateCallback(std::function callback) override { if (secret()) { @@ -223,9 +272,26 @@ class CertificateValidationContextSdsApi : public SdsApi, protected: void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { - certificate_validation_context_secrets_ = std::make_unique< + sds_certificate_validation_context_secrets_ = std::make_unique< envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext>( secret.validation_context()); + resolved_certificate_validation_context_secrets_ = nullptr; + if (secret.validation_context().has_watched_directory()) { + watched_directory_ = std::make_unique( + secret.validation_context().watched_directory(), dispatcher_); + } else { + watched_directory_.reset(); + } + } + + void resolveSecret(const FileContentMap& files) override { + // Copy existing CertificateValidationContext. + resolved_certificate_validation_context_secrets_ = std::make_unique< + envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext>( + *sds_certificate_validation_context_secrets_); + // We replace path based secrets with inlined secrets on update. + resolveDataSource(files, + *resolved_certificate_validation_context_secrets_->mutable_trusted_ca()); } void @@ -233,9 +299,16 @@ class CertificateValidationContextSdsApi : public SdsApi, validation_callback_manager_.runCallbacks(secret.validation_context()); } std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return watched_directory_.get(); } private: - CertificateValidationContextPtr certificate_validation_context_secrets_; + // Directory to watch for rotation. + Config::WatchedDirectoryPtr watched_directory_; + // CertificateValidationContext according to SDS source; + CertificateValidationContextPtr sds_certificate_validation_context_secrets_; + // CertificateValidationContext after resolving paths via watched_directory_. + CertificateValidationContextPtr resolved_certificate_validation_context_secrets_; + // Path based certificates are inlined for future read consistency. Common::CallbackManager< const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext&> validation_callback_manager_; @@ -306,6 +379,7 @@ class TlsSessionTicketKeysSdsApi : public SdsApi, public TlsSessionTicketKeysCon validation_callback_manager_.runCallbacks(secret.session_ticket_keys()); } std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return nullptr; } private: Secret::TlsSessionTicketKeysPtr tls_session_ticket_keys_; @@ -346,7 +420,7 @@ class GenericSecretSdsApi : public SdsApi, public GenericSecretConfigProvider { // SecretProvider const envoy::extensions::transport_sockets::tls::v3::GenericSecret* secret() const override { - return generic_secret.get(); + return generic_secret_.get(); } Common::CallbackHandle* addUpdateCallback(std::function callback) override { return update_callback_manager_.add(callback); @@ -359,17 +433,19 @@ class GenericSecretSdsApi : public SdsApi, public GenericSecretConfigProvider { protected: void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { - generic_secret = std::make_unique( - secret.generic_secret()); + generic_secret_ = + std::make_unique( + secret.generic_secret()); } void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { validation_callback_manager_.runCallbacks(secret.generic_secret()); } std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return nullptr; } private: - GenericSecretPtr generic_secret; + GenericSecretPtr generic_secret_; Common::CallbackManager validation_callback_manager_; }; diff --git a/source/common/stats/BUILD b/source/common/stats/BUILD index fb8528063934..edc68f4774e7 100644 --- a/source/common/stats/BUILD +++ b/source/common/stats/BUILD @@ -195,6 +195,7 @@ envoy_cc_library( hdrs = ["tag_extractor_impl.h"], deps = [ "//include/envoy/stats:stats_interface", + "//source/common/common:assert_lib", "//source/common/common:perf_annotation_lib", "//source/common/common:regex_lib", ], diff --git a/source/common/stats/tag_extractor_impl.cc b/source/common/stats/tag_extractor_impl.cc index 3c666a13e105..6aefbdf6cd25 100644 --- a/source/common/stats/tag_extractor_impl.cc +++ b/source/common/stats/tag_extractor_impl.cc @@ -5,6 +5,7 @@ #include "envoy/common/exception.h" +#include "common/common/assert.h" #include "common/common/fmt.h" #include "common/common/perf_annotation.h" #include "common/common/regex.h" @@ -23,12 +24,11 @@ bool regexStartsWithDot(absl::string_view regex) { } // namespace -TagExtractorImpl::TagExtractorImpl(const std::string& name, const std::string& regex, - const std::string& substr) - : name_(name), prefix_(std::string(extractRegexPrefix(regex))), substr_(substr), - regex_(Regex::Utility::parseStdRegex(regex)) {} +TagExtractorImplBase::TagExtractorImplBase(absl::string_view name, absl::string_view regex, + absl::string_view substr) + : name_(name), prefix_(std::string(extractRegexPrefix(regex))), substr_(substr) {} -std::string TagExtractorImpl::extractRegexPrefix(absl::string_view regex) { +std::string TagExtractorImplBase::extractRegexPrefix(absl::string_view regex) { std::string prefix; if (absl::StartsWith(regex, "^")) { for (absl::string_view::size_type i = 1; i < regex.size(); ++i) { @@ -47,10 +47,10 @@ std::string TagExtractorImpl::extractRegexPrefix(absl::string_view regex) { return prefix; } -TagExtractorPtr TagExtractorImpl::createTagExtractor(const std::string& name, - const std::string& regex, - const std::string& substr) { - +TagExtractorPtr TagExtractorImplBase::createTagExtractor(absl::string_view name, + absl::string_view regex, + absl::string_view substr, + Regex::Type re_type) { if (name.empty()) { throw EnvoyException("tag_name cannot be empty"); } @@ -59,19 +59,37 @@ TagExtractorPtr TagExtractorImpl::createTagExtractor(const std::string& name, throw EnvoyException(fmt::format( "No regex specified for tag specifier and no default regex for name: '{}'", name)); } - return TagExtractorPtr{new TagExtractorImpl(name, regex, substr)}; + switch (re_type) { + case Regex::Type::Re2: + return std::make_unique(name, regex, substr); + case Regex::Type::StdRegex: + return std::make_unique(name, regex, substr); + } + NOT_REACHED_GCOVR_EXCL_LINE; } -bool TagExtractorImpl::substrMismatch(absl::string_view stat_name) const { +bool TagExtractorImplBase::substrMismatch(absl::string_view stat_name) const { return !substr_.empty() && stat_name.find(substr_) == absl::string_view::npos; } -bool TagExtractorImpl::extractTag(absl::string_view stat_name, TagVector& tags, - IntervalSet& remove_characters) const { +TagExtractorStdRegexImpl::TagExtractorStdRegexImpl(absl::string_view name, absl::string_view regex, + absl::string_view substr) + : TagExtractorImplBase(name, regex, substr), + regex_(Regex::Utility::parseStdRegex(std::string(regex))) {} + +std::string& TagExtractorImplBase::addTag(std::vector& tags) const { + tags.emplace_back(); + Tag& tag = tags.back(); + tag.name_ = name_; + return tag.value_; +} + +bool TagExtractorStdRegexImpl::extractTag(absl::string_view stat_name, std::vector& tags, + IntervalSet& remove_characters) const { PERF_OPERATION(perf); if (substrMismatch(stat_name)) { - PERF_RECORD(perf, "re-skip-substr", name_); + PERF_RECORD(perf, "re-skip", name_); return false; } @@ -88,11 +106,7 @@ bool TagExtractorImpl::extractTag(absl::string_view stat_name, TagVector& tags, // from the string but also not necessary in the tag value ("." for example). If there is no // second submatch, then the value_subexpr is the same as the remove_subexpr. const auto& value_subexpr = match.size() > 2 ? match[2] : remove_subexpr; - - tags.emplace_back(); - Tag& tag = tags.back(); - tag.name_ = name_; - tag.value_ = value_subexpr.str(); + addTag(tags) = value_subexpr.str(); // Determines which characters to remove from stat_name to elide remove_subexpr. std::string::size_type start = remove_subexpr.first - stat_name.begin(); @@ -105,5 +119,47 @@ bool TagExtractorImpl::extractTag(absl::string_view stat_name, TagVector& tags, return false; } +TagExtractorRe2Impl::TagExtractorRe2Impl(absl::string_view name, absl::string_view regex, + absl::string_view substr) + : TagExtractorImplBase(name, regex, substr), regex_(regex) {} + +bool TagExtractorRe2Impl::extractTag(absl::string_view stat_name, std::vector& tags, + IntervalSet& remove_characters) const { + PERF_OPERATION(perf); + + if (substrMismatch(stat_name)) { + PERF_RECORD(perf, "re2-skip", name_); + return false; + } + + // remove_subexpr is the first submatch. It represents the portion of the string to be removed. + re2::StringPiece remove_subexpr, value_subexpr; + + // The regex must match and contain one or more subexpressions (all after the first are ignored). + if (re2::RE2::FullMatch(re2::StringPiece(stat_name.data(), stat_name.size()), regex_, + &remove_subexpr, &value_subexpr) && + !remove_subexpr.empty()) { + + // value_subexpr is the optional second submatch. It is usually inside the first submatch + // (remove_subexpr) to allow the expression to strip off extra characters that should be removed + // from the string but also not necessary in the tag value ("." for example). If there is no + // second submatch, then the value_subexpr is the same as the remove_subexpr. + if (value_subexpr.empty()) { + value_subexpr = remove_subexpr; + } + addTag(tags) = std::string(value_subexpr); + + // Determines which characters to remove from stat_name to elide remove_subexpr. + std::string::size_type start = remove_subexpr.data() - stat_name.data(); + std::string::size_type end = remove_subexpr.data() + remove_subexpr.size() - stat_name.data(); + remove_characters.insert(start, end); + + PERF_RECORD(perf, "re2-match", name_); + return true; + } + PERF_RECORD(perf, "re2-miss", name_); + return false; +} + } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/tag_extractor_impl.h b/source/common/stats/tag_extractor_impl.h index a63c7e1e4626..f909868eb236 100644 --- a/source/common/stats/tag_extractor_impl.h +++ b/source/common/stats/tag_extractor_impl.h @@ -6,12 +6,15 @@ #include "envoy/stats/tag_extractor.h" +#include "common/common/regex.h" + #include "absl/strings/string_view.h" +#include "re2/re2.h" namespace Envoy { namespace Stats { -class TagExtractorImpl : public TagExtractor { +class TagExtractorImplBase : public TagExtractor { public: /** * Creates a tag extractor from the regex provided. name and regex must be non-empty. @@ -20,16 +23,16 @@ class TagExtractorImpl : public TagExtractor { * @param substr a substring that -- if provided -- must be present in a stat name * in order to match the regex. This is an optional performance tweak * to avoid large numbers of failed regex lookups. + * @param re_type the regular expression syntax used (Regex::Type::StdRegex or Regex::Type::Re2). * @return TagExtractorPtr newly constructed TagExtractor. */ - static TagExtractorPtr createTagExtractor(const std::string& name, const std::string& regex, - const std::string& substr = ""); + static TagExtractorPtr createTagExtractor(absl::string_view name, absl::string_view regex, + absl::string_view substr = "", + Regex::Type re_type = Regex::Type::StdRegex); - TagExtractorImpl(const std::string& name, const std::string& regex, - const std::string& substr = ""); + TagExtractorImplBase(absl::string_view name, absl::string_view regex, + absl::string_view substr = ""); std::string name() const override { return name_; } - bool extractTag(absl::string_view tag_extracted_name, TagVector& tags, - IntervalSet& remove_characters) const override; absl::string_view prefixToken() const override { return prefix_; } /** @@ -39,7 +42,7 @@ class TagExtractorImpl : public TagExtractor { */ bool substrMismatch(absl::string_view stat_name) const; -private: +protected: /** * Examines a regex string, looking for the pattern: ^alphanumerics_with_underscores\. * Returns "alphanumerics_with_underscores" if that pattern is found, empty-string otherwise. @@ -47,11 +50,43 @@ class TagExtractorImpl : public TagExtractor { * @return std::string the prefix, or "" if no prefix found. */ static std::string extractRegexPrefix(absl::string_view regex); + + /** + * Adds a new tag for the current name, returning a reference to the tag value. + * + * @param tags the list of tags + * @return a reference to the value of the tag that was added. + */ + std::string& addTag(std::vector& tags) const; + const std::string name_; const std::string prefix_; const std::string substr_; +}; + +class TagExtractorStdRegexImpl : public TagExtractorImplBase { +public: + TagExtractorStdRegexImpl(absl::string_view name, absl::string_view regex, + absl::string_view substr = ""); + + bool extractTag(absl::string_view tag_extracted_name, std::vector& tags, + IntervalSet& remove_characters) const override; + +private: const std::regex regex_; }; +class TagExtractorRe2Impl : public TagExtractorImplBase { +public: + TagExtractorRe2Impl(absl::string_view name, absl::string_view regex, + absl::string_view substr = ""); + + bool extractTag(absl::string_view tag_extracted_name, std::vector& tags, + IntervalSet& remove_characters) const override; + +private: + const re2::RE2 regex_; +}; + } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/tag_producer_impl.cc b/source/common/stats/tag_producer_impl.cc index 255dfcaeed39..bfc35b52e40a 100644 --- a/source/common/stats/tag_producer_impl.cc +++ b/source/common/stats/tag_producer_impl.cc @@ -34,11 +34,11 @@ TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& "No regex specified for tag specifier and no default regex for name: '{}'", name)); } } else { - addExtractor(Stats::TagExtractorImpl::createTagExtractor(name, tag_specifier.regex())); + addExtractor(TagExtractorImplBase::createTagExtractor(name, tag_specifier.regex())); } } else if (tag_specifier.tag_value_case() == envoy::config::metrics::v3::TagSpecifier::TagValueCase::kFixedValue) { - default_tags_.emplace_back(Stats::Tag{name, tag_specifier.fixed_value()}); + default_tags_.emplace_back(Tag{name, tag_specifier.fixed_value()}); } } } @@ -47,8 +47,8 @@ int TagProducerImpl::addExtractorsMatching(absl::string_view name) { int num_found = 0; for (const auto& desc : Config::TagNames::get().descriptorVec()) { if (desc.name_ == name) { - addExtractor( - Stats::TagExtractorImpl::createTagExtractor(desc.name_, desc.regex_, desc.substr_)); + addExtractor(TagExtractorImplBase::createTagExtractor(desc.name_, desc.regex_, desc.substr_, + desc.re_type_)); ++num_found; } } @@ -103,8 +103,8 @@ TagProducerImpl::addDefaultExtractors(const envoy::config::metrics::v3::StatsCon if (!config.has_use_all_default_tags() || config.use_all_default_tags().value()) { for (const auto& desc : Config::TagNames::get().descriptorVec()) { names.emplace(desc.name_); - addExtractor( - Stats::TagExtractorImpl::createTagExtractor(desc.name_, desc.regex_, desc.substr_)); + addExtractor(TagExtractorImplBase::createTagExtractor(desc.name_, desc.regex_, desc.substr_, + desc.re_type_)); } } return names; diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index b0704ff97c19..11b977440837 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -205,8 +205,8 @@ void ThreadLocalStoreImpl::mergeHistograms(PostMergeCb merge_complete_cb) { ASSERT(!merge_in_progress_); merge_in_progress_ = true; tls_cache_->runOnAllThreads( - [](TlsCache& tls_cache) { - for (const auto& id_hist : tls_cache.tls_histogram_cache_) { + [](OptRef tls_cache) { + for (const auto& id_hist : tls_cache->tls_histogram_cache_) { const TlsHistogramSharedPtr& tls_hist = id_hist.second; tls_hist->beginMerge(); } @@ -303,7 +303,7 @@ void ThreadLocalStoreImpl::clearScopeFromCaches(uint64_t scope_id, if (!shutting_down_) { // Perform a cache flush on all threads. tls_cache_->runOnAllThreads( - [scope_id](TlsCache& tls_cache) { tls_cache.eraseScope(scope_id); }, + [scope_id](OptRef tls_cache) { tls_cache->eraseScope(scope_id); }, [central_cache]() { /* Holds onto central_cache until all tls caches are clear */ }); } } @@ -320,7 +320,7 @@ void ThreadLocalStoreImpl::clearHistogramFromCaches(uint64_t histogram_id) { // contains a patch that will implement batching together to clear multiple // histograms. tls_cache_->runOnAllThreads( - [histogram_id](TlsCache& tls_cache) { tls_cache.eraseHistogram(histogram_id); }); + [histogram_id](OptRef tls_cache) { tls_cache->eraseHistogram(histogram_id); }); } } @@ -489,7 +489,7 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counterFromStatNameWithTags( StatRefMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().insertScope(this->scope_id_); + TlsCacheEntry& entry = parent_.tlsCache().insertScope(this->scope_id_); tls_cache = &entry.counters_; tls_rejected_stats = &entry.rejected_stats_; } @@ -541,7 +541,7 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugeFromStatNameWithTags( StatRefMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().scope_cache_[this->scope_id_]; + TlsCacheEntry& entry = parent_.tlsCache().scope_cache_[this->scope_id_]; tls_cache = &entry.gauges_; tls_rejected_stats = &entry.rejected_stats_; } @@ -579,7 +579,7 @@ Histogram& ThreadLocalStoreImpl::ScopeImpl::histogramFromStatNameWithTags( StatNameHashMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().scope_cache_[this->scope_id_]; + TlsCacheEntry& entry = parent_.tlsCache().scope_cache_[this->scope_id_]; tls_cache = &entry.parent_histograms_; auto iter = tls_cache->find(final_stat_name); if (iter != tls_cache->end()) { @@ -657,7 +657,7 @@ TextReadout& ThreadLocalStoreImpl::ScopeImpl::textReadoutFromStatNameWithTags( StatRefMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().insertScope(this->scope_id_); + TlsCacheEntry& entry = parent_.tlsCache().insertScope(this->scope_id_); tls_cache = &entry.text_readouts_; tls_rejected_stats = &entry.rejected_stats_; } @@ -703,7 +703,7 @@ Histogram& ThreadLocalStoreImpl::tlsHistogram(ParentHistogramImpl& parent, uint6 TlsHistogramSharedPtr* tls_histogram = nullptr; if (!shutting_down_ && tls_cache_) { - tls_histogram = &tls_cache_->get().tls_histogram_cache_[id]; + tls_histogram = &(tlsCache().tls_histogram_cache_[id]); if (*tls_histogram != nullptr) { return **tls_histogram; } diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 410254afeb33..22707accacbd 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -477,6 +477,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo void removeRejectedStats(StatMapClass& map, StatListClass& list); bool checkAndRememberRejection(StatName name, StatNameStorageSet& central_rejected_stats, StatNameHashSet* tls_rejected_stats); + TlsCache& tlsCache() { return **tls_cache_; } Allocator& alloc_; Event::Dispatcher* main_thread_dispatcher_{}; diff --git a/source/common/tcp/conn_pool.cc b/source/common/tcp/conn_pool.cc index 7fc370570052..5abc86fc959c 100644 --- a/source/common/tcp/conn_pool.cc +++ b/source/common/tcp/conn_pool.cc @@ -12,7 +12,8 @@ namespace Envoy { namespace Tcp { -ActiveTcpClient::ActiveTcpClient(ConnPoolImpl& parent, const Upstream::HostConstSharedPtr& host, +ActiveTcpClient::ActiveTcpClient(Envoy::ConnectionPool::ConnPoolImplBase& parent, + const Upstream::HostConstSharedPtr& host, uint64_t concurrent_stream_limit) : Envoy::ConnectionPool::ActiveClient(parent, host->cluster().maxRequestsPerConnection(), concurrent_stream_limit), @@ -24,6 +25,12 @@ ActiveTcpClient::ActiveTcpClient(ConnPoolImpl& parent, const Upstream::HostConst connection_->addConnectionCallbacks(*this); connection_->detectEarlyCloseWhenReadDisabled(false); connection_->addReadFilter(std::make_shared(*this)); + connection_->setConnectionStats({host->cluster().stats().upstream_cx_rx_bytes_total_, + host->cluster().stats().upstream_cx_rx_bytes_buffered_, + host->cluster().stats().upstream_cx_tx_bytes_total_, + host->cluster().stats().upstream_cx_tx_bytes_buffered_, + &host->cluster().stats().bind_errors_, nullptr}); + connection_->connect(); } @@ -37,13 +44,12 @@ ActiveTcpClient::~ActiveTcpClient() { parent_.onStreamClosed(*this, true); parent_.checkForDrained(); } - parent_.onConnDestroyed(); } void ActiveTcpClient::clearCallbacks() { - if (state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY || - state_ == Envoy::ConnectionPool::ActiveClient::State::DRAINING) { - parent_.onConnReleased(*this); + if (state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY && parent_.hasPendingStreams()) { + auto* pool = &parent_; + pool->dispatcher().post([pool]() -> void { pool->onUpstreamReady(); }); } callbacks_ = nullptr; tcp_connection_data_ = nullptr; diff --git a/source/common/tcp/conn_pool.h b/source/common/tcp/conn_pool.h index c75b28f59156..a3637b8a43cd 100644 --- a/source/common/tcp/conn_pool.h +++ b/source/common/tcp/conn_pool.h @@ -84,8 +84,8 @@ class ActiveTcpClient : public Envoy::ConnectionPool::ActiveClient { Network::ClientConnection& connection_; }; - ActiveTcpClient(ConnPoolImpl& parent, const Upstream::HostConstSharedPtr& host, - uint64_t concurrent_stream_limit); + ActiveTcpClient(Envoy::ConnectionPool::ConnPoolImplBase& parent, + const Upstream::HostConstSharedPtr& host, uint64_t concurrent_stream_limit); ~ActiveTcpClient() override; // Override the default's of Envoy::ConnectionPool::ActiveClient for class-specific functions. @@ -106,9 +106,9 @@ class ActiveTcpClient : public Envoy::ConnectionPool::ActiveClient { close(); } } - void clearCallbacks(); + virtual void clearCallbacks(); - ConnPoolImpl& parent_; + Envoy::ConnectionPool::ConnPoolImplBase& parent_; ConnectionPool::UpstreamCallbacks* callbacks_{}; Network::ClientConnectionPtr connection_; ConnectionPool::ConnectionStatePtr connection_state_; @@ -123,11 +123,7 @@ class ConnPoolImpl : public Envoy::ConnectionPool::ConnPoolImplBase, const Network::ConnectionSocket::OptionsSharedPtr& options, Network::TransportSocketOptionsSharedPtr transport_socket_options) : Envoy::ConnectionPool::ConnPoolImplBase(host, priority, dispatcher, options, - transport_socket_options), - upstream_ready_cb_(dispatcher.createSchedulableCallback([this]() { - upstream_ready_enabled_ = false; - onUpstreamReady(); - })) {} + transport_socket_options) {} ~ConnPoolImpl() override { destructAllConnections(); } void addDrainedCallback(DrainedCb cb) override { addDrainedCallbackImpl(cb); } @@ -196,18 +192,8 @@ class ConnPoolImpl : public Envoy::ConnectionPool::ConnPoolImplBase, } // These two functions exist for testing parity between old and new Tcp Connection Pools. - virtual void onConnReleased(Envoy::ConnectionPool::ActiveClient& client) { - if (client.state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY) { - if (!pending_streams_.empty() && !upstream_ready_enabled_) { - upstream_ready_cb_->scheduleCallbackCurrentIteration(); - } - } - } + virtual void onConnReleased(Envoy::ConnectionPool::ActiveClient&) {} virtual void onConnDestroyed() {} - -protected: - Event::SchedulableCallbackPtr upstream_ready_cb_; - bool upstream_ready_enabled_{}; }; } // namespace Tcp diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD index f75138d14134..401b8c3e8794 100644 --- a/source/common/tcp_proxy/BUILD +++ b/source/common/tcp_proxy/BUILD @@ -8,17 +8,35 @@ licenses(["notice"]) # Apache 2 envoy_package() +envoy_cc_library( + name = "upstream_lib", + srcs = [ + "upstream.cc", + ], + hdrs = [ + "upstream.h", + ], + deps = [ + "//include/envoy/tcp:conn_pool_interface", + "//include/envoy/tcp:upstream_interface", + "//include/envoy/upstream:cluster_manager_interface", + "//include/envoy/upstream:load_balancer_interface", + "//source/common/http:header_map_lib", + "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + ], +) + envoy_cc_library( name = "tcp_proxy", srcs = [ "tcp_proxy.cc", - "upstream.cc", ], hdrs = [ "tcp_proxy.h", - "upstream.h", ], deps = [ + ":upstream_lib", "//include/envoy/access_log:access_log_interface", "//include/envoy/buffer:buffer_interface", "//include/envoy/common:time_interface", @@ -32,6 +50,7 @@ envoy_cc_library( "//include/envoy/stats:timespan_interface", "//include/envoy/stream_info:filter_state_interface", "//include/envoy/tcp:conn_pool_interface", + "//include/envoy/tcp:upstream_interface", "//include/envoy/upstream:cluster_manager_interface", "//include/envoy/upstream:upstream_interface", "//source/common/access_log:access_log_lib", @@ -39,7 +58,6 @@ envoy_cc_library( "//source/common/common:empty_string", "//source/common/common:macros", "//source/common/common:minimal_logger_lib", - "//source/common/http:headers_lib", "//source/common/network:application_protocol_lib", "//source/common/network:cidr_range_lib", "//source/common/network:filter_lib", @@ -51,6 +69,7 @@ envoy_cc_library( "//source/common/router:metadatamatchcriteria_lib", "//source/common/stream_info:stream_info_lib", "//source/common/upstream:load_balancer_lib", + "//source/extensions/upstreams/tcp/generic:config", "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 3688634f5305..736435a7b803 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -20,6 +20,7 @@ #include "common/common/fmt.h" #include "common/common/macros.h" #include "common/common/utility.h" +#include "common/config/utility.h" #include "common/config/well_known_names.h" #include "common/network/application_protocol.h" #include "common/network/proxy_protocol_filter_state.h" @@ -431,7 +432,7 @@ Network::FilterStatus Filter::initializeUpstreamConnection() { downstreamConnection()->streamInfo().filterState()); } - if (!maybeTunnel(cluster_name)) { + if (!maybeTunnel(*thread_local_cluster, cluster_name)) { // Either cluster is unknown or there are no healthy hosts. tcpConnPoolForCluster() increments // cluster->stats().upstream_cx_none_healthy in the latter case. getStreamInfo().setResponseFlag(StreamInfo::ResponseFlag::NoHealthyUpstream); @@ -440,47 +441,32 @@ Network::FilterStatus Filter::initializeUpstreamConnection() { return Network::FilterStatus::StopIteration; } -bool Filter::maybeTunnel(const std::string& cluster_name) { - if (!config_->tunnelingConfig()) { - generic_conn_pool_ = - std::make_unique(cluster_name, cluster_manager_, this, *upstream_callbacks_); - if (generic_conn_pool_->valid()) { - connecting_ = true; - connect_attempts_++; - generic_conn_pool_->newStream(this); - // Because we never return open connections to the pool, this either has a handle waiting on - // connection completion, or onPoolFailure has been invoked. Either way, stop iteration. - return true; - } else { - generic_conn_pool_.reset(); - } +bool Filter::maybeTunnel(Upstream::ThreadLocalCluster& cluster, const std::string& cluster_name) { + GenericConnPoolFactory* factory = nullptr; + if (cluster.info()->upstreamConfig().has_value()) { + factory = Envoy::Config::Utility::getFactory( + cluster.info()->upstreamConfig().value()); } else { - auto* cluster = cluster_manager_.get(cluster_name); - if (!cluster) { - return false; - } - // TODO(snowp): Ideally we should prevent this from being configured, but that's tricky to get - // right since whether a cluster is invalid depends on both the tcp_proxy config + cluster - // config. - if ((cluster->info()->features() & Upstream::ClusterInfo::Features::HTTP2) == 0) { - ENVOY_LOG(error, "Attempted to tunnel over HTTP/1.1, this is not supported. Set " - "http2_protocol_options on the cluster."); - return false; - } - - generic_conn_pool_ = std::make_unique(cluster_name, cluster_manager_, this, - config_->tunnelingConfig()->hostname(), - *upstream_callbacks_); - if (generic_conn_pool_->valid()) { - generic_conn_pool_->newStream(this); - return true; - } else { - generic_conn_pool_.reset(); - } + factory = Envoy::Config::Utility::getFactoryByName( + "envoy.filters.connection_pools.tcp.generic"); + } + if (!factory) { + return false; } + generic_conn_pool_ = factory->createGenericConnPool( + cluster_name, cluster_manager_, config_->tunnelingConfig(), this, *upstream_callbacks_); + if (generic_conn_pool_) { + connecting_ = true; + connect_attempts_++; + generic_conn_pool_->newStream(*this); + // Because we never return open connections to the pool, this either has a handle waiting on + // connection completion, or onPoolFailure has been invoked. Either way, stop iteration. + return true; + } return false; } + void Filter::onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, Upstream::HostDescriptionConstSharedPtr host) { generic_conn_pool_.reset(); diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 12b482e01690..de61eb665139 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -345,7 +345,7 @@ class Filter : public Network::ReadFilter, void initialize(Network::ReadFilterCallbacks& callbacks, bool set_connection_stats); Network::FilterStatus initializeUpstreamConnection(); - bool maybeTunnel(const std::string& cluster_name); + bool maybeTunnel(Upstream::ThreadLocalCluster& cluster, const std::string& cluster_name); void onConnectTimeout(); void onDownstreamEvent(Network::ConnectionEvent event); void onUpstreamData(Buffer::Instance& data, bool end_stream); diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc index 1da6eb915797..3f3d73c6add4 100644 --- a/source/common/tcp_proxy/upstream.cc +++ b/source/common/tcp_proxy/upstream.cc @@ -123,7 +123,9 @@ void HttpUpstream::setRequestEncoder(Http::RequestEncoder& request_encoder, bool {Http::Headers::get().Scheme, scheme}, {Http::Headers::get().Path, "/"}, {Http::Headers::get().Host, hostname_}}); - request_encoder_->encodeHeaders(*headers, false); + const auto status = request_encoder_->encodeHeaders(*headers, false); + // Encoding can only fail on missing required request headers. + ASSERT(status.ok()); } void HttpUpstream::resetEncoder(Network::ConnectionEvent event, bool inform_downstream) { @@ -168,10 +170,8 @@ TcpConnPool::~TcpConnPool() { } } -bool TcpConnPool::valid() const { return conn_pool_ != nullptr; } - -void TcpConnPool::newStream(GenericConnectionPoolCallbacks* callbacks) { - callbacks_ = callbacks; +void TcpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { + callbacks_ = &callbacks; // Given this function is reentrant, make sure we only reset the upstream_handle_ if given a // valid connection handle. If newConnection fails inline it may result in attempting to // select a new host, and a recursive call to initializeUpstreamConnection. In this case the @@ -203,9 +203,9 @@ void TcpConnPool::onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn_data HttpConnPool::HttpConnPool(const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, - Upstream::LoadBalancerContext* context, std::string hostname, + Upstream::LoadBalancerContext* context, const TunnelingConfig& config, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) - : hostname_(hostname), upstream_callbacks_(upstream_callbacks) { + : hostname_(config.hostname()), upstream_callbacks_(upstream_callbacks) { conn_pool_ = cluster_manager.httpConnPoolForCluster( cluster_name, Upstream::ResourcePriority::Default, absl::nullopt, context); } @@ -218,10 +218,8 @@ HttpConnPool::~HttpConnPool() { } } -bool HttpConnPool::valid() const { return conn_pool_ != nullptr; } - -void HttpConnPool::newStream(GenericConnectionPoolCallbacks* callbacks) { - callbacks_ = callbacks; +void HttpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { + callbacks_ = &callbacks; upstream_ = std::make_unique(upstream_callbacks_, hostname_); Tcp::ConnectionPool::Cancellable* handle = conn_pool_->newStream(upstream_->responseDecoder(), *this); diff --git a/source/common/tcp_proxy/upstream.h b/source/common/tcp_proxy/upstream.h index 33943e70b982..e132b667e2ca 100644 --- a/source/common/tcp_proxy/upstream.h +++ b/source/common/tcp_proxy/upstream.h @@ -3,28 +3,13 @@ #include "envoy/http/conn_pool.h" #include "envoy/network/connection.h" #include "envoy/tcp/conn_pool.h" +#include "envoy/tcp/upstream.h" #include "envoy/upstream/load_balancer.h" #include "envoy/upstream/upstream.h" namespace Envoy { namespace TcpProxy { -class GenericConnectionPoolCallbacks; -class GenericUpstream; - -// An API for wrapping either an HTTP or a TCP connection pool. -class GenericConnPool : public Logger::Loggable { -public: - virtual ~GenericConnPool() = default; - - // Called to create a new HTTP stream or TCP connection. The implementation - // is then responsible for calling either onPoolReady or onPoolFailure on the - // supplied GenericConnectionPoolCallbacks. - virtual void newStream(GenericConnectionPoolCallbacks* callbacks) PURE; - // Returns true if there was a valid connection pool, false otherwise. - virtual bool valid() const PURE; -}; - class TcpConnPool : public GenericConnPool, public Tcp::ConnectionPool::Callbacks { public: TcpConnPool(const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, @@ -32,9 +17,10 @@ class TcpConnPool : public GenericConnPool, public Tcp::ConnectionPool::Callback Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks); ~TcpConnPool() override; + bool valid() const { return conn_pool_ != nullptr; } + // GenericConnPool - bool valid() const override; - void newStream(GenericConnectionPoolCallbacks* callbacks) override; + void newStream(GenericConnectionPoolCallbacks& callbacks) override; // Tcp::ConnectionPool::Callbacks void onPoolFailure(ConnectionPool::PoolFailureReason reason, @@ -53,14 +39,18 @@ class HttpUpstream; class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callbacks { public: + using TunnelingConfig = + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; + HttpConnPool(const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, - Upstream::LoadBalancerContext* context, std::string hostname, + Upstream::LoadBalancerContext* context, const TunnelingConfig& config, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks); ~HttpConnPool() override; + bool valid() const { return conn_pool_ != nullptr; } + // GenericConnPool - bool valid() const override; - void newStream(GenericConnectionPoolCallbacks* callbacks) override; + void newStream(GenericConnectionPoolCallbacks& callbacks) override; // Http::ConnectionPool::Callbacks, void onPoolFailure(ConnectionPool::PoolFailureReason reason, @@ -79,39 +69,6 @@ class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callba std::unique_ptr upstream_; }; -// An API for the UpstreamRequest to get callbacks from either an HTTP or TCP -// connection pool. -class GenericConnectionPoolCallbacks { -public: - virtual ~GenericConnectionPoolCallbacks() = default; - - virtual void onGenericPoolReady(StreamInfo::StreamInfo* info, - std::unique_ptr&& upstream, - Upstream::HostDescriptionConstSharedPtr& host, - const Network::Address::InstanceConstSharedPtr& local_address, - Ssl::ConnectionInfoConstSharedPtr ssl_info) PURE; - virtual void onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, - Upstream::HostDescriptionConstSharedPtr host) PURE; -}; - -// Interface for a generic Upstream, which can communicate with a TCP or HTTP -// upstream. -class GenericUpstream { -public: - virtual ~GenericUpstream() = default; - // Calls readDisable on the upstream connection. Returns false if readDisable could not be - // performed (e.g. if the connection is closed) - virtual bool readDisable(bool disable) PURE; - // Encodes data upstream. - virtual void encodeData(Buffer::Instance& data, bool end_stream) PURE; - // Adds a callback to be called when the data is sent to the kernel. - virtual void addBytesSentCallback(Network::Connection::BytesSentCb cb) PURE; - // Called when a Network::ConnectionEvent is received on the downstream connection, to allow the - // upstream to do any cleanup. - virtual Tcp::ConnectionPool::ConnectionData* - onDownstreamEvent(Network::ConnectionEvent event) PURE; -}; - class TcpUpstream : public GenericUpstream { public: TcpUpstream(Tcp::ConnectionPool::ConnectionDataPtr&& data, @@ -149,7 +106,7 @@ class HttpUpstream : public GenericUpstream, Http::StreamCallbacks { void onAboveWriteBufferHighWatermark() override; void onBelowWriteBufferLowWatermark() override; - void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); + virtual void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); Http::ResponseDecoder& responseDecoder() { return response_decoder_; } diff --git a/source/common/thread_local/thread_local_impl.cc b/source/common/thread_local/thread_local_impl.cc index a6c924ba9f9f..0815236a3195 100644 --- a/source/common/thread_local/thread_local_impl.cc +++ b/source/common/thread_local/thread_local_impl.cc @@ -41,7 +41,7 @@ SlotPtr InstanceImpl::allocateSlot() { InstanceImpl::SlotImpl::SlotImpl(InstanceImpl& parent, uint32_t index) : parent_(parent), index_(index), still_alive_guard_(std::make_shared(true)) {} -Event::PostCb InstanceImpl::SlotImpl::wrapCallback(Event::PostCb&& cb) { +Event::PostCb InstanceImpl::SlotImpl::wrapCallback(const Event::PostCb& cb) { // See the header file comments for still_alive_guard_ for the purpose of this capture and the // expired check below. // @@ -76,23 +76,13 @@ Event::PostCb InstanceImpl::SlotImpl::dataCallback(const UpdateCb& cb) { // works, but incurs another indirection of lambda at runtime. As the // duplicated logic is only an if-statement and a bool function, it doesn't // seem worth factoring that out to a helper function. - if (still_alive_guard.expired()) { - return; + if (!still_alive_guard.expired()) { + cb(getWorker(index)); } - auto obj = getWorker(index); - auto new_obj = cb(obj); - // The API definition for runOnAllThreads allows for replacing the object - // via the callback return value. However, this never occurs in the codebase - // as of Oct 2020, and we plan to remove this API. To avoid PR races, we - // will add an assert to ensure such a dependency does not emerge. - // - // TODO(jmarantz): remove this once we phase out use of the untyped slot - // API, rename it, and change all call-sites to use TypedSlot. - ASSERT(obj.get() == new_obj.get()); }; } -void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) { +void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) { parent_.runOnAllThreads(dataCallback(cb), complete_cb); } diff --git a/source/common/thread_local/thread_local_impl.h b/source/common/thread_local/thread_local_impl.h index 888657ad5e81..7abed0499166 100644 --- a/source/common/thread_local/thread_local_impl.h +++ b/source/common/thread_local/thread_local_impl.h @@ -36,7 +36,7 @@ class InstanceImpl : Logger::Loggable, public NonCopyable, pub struct SlotImpl : public Slot { SlotImpl(InstanceImpl& parent, uint32_t index); ~SlotImpl() override { parent_.removeSlot(index_); } - Event::PostCb wrapCallback(Event::PostCb&& cb); + Event::PostCb wrapCallback(const Event::PostCb& cb); Event::PostCb dataCallback(const UpdateCb& cb); static bool currentThreadRegisteredWorker(uint32_t index); static ThreadLocalObjectSharedPtr getWorker(uint32_t index); @@ -44,7 +44,7 @@ class InstanceImpl : Logger::Loggable, public NonCopyable, pub // ThreadLocal::Slot ThreadLocalObjectSharedPtr get() override; void runOnAllThreads(const UpdateCb& cb) override; - void runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) override; + void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) override; bool currentThreadRegistered() override; void set(InitializeCb cb) override; diff --git a/source/common/upstream/cds_api_impl.cc b/source/common/upstream/cds_api_impl.cc index 0ca7d5763d5a..4568bf84b8f3 100644 --- a/source/common/upstream/cds_api_impl.cc +++ b/source/common/upstream/cds_api_impl.cc @@ -38,15 +38,22 @@ CdsApiImpl::CdsApiImpl(const envoy::config::core::v3::ConfigSource& cds_config, void CdsApiImpl::onConfigUpdate(const std::vector& resources, const std::string& version_info) { - ClusterManager::ClusterInfoMap clusters_to_remove = cm_.clusters(); - std::vector clusters; + auto all_existing_clusters = cm_.clusters(); + // Exclude the clusters which CDS wants to add. for (const auto& resource : resources) { - clusters_to_remove.erase(resource.get().name()); + all_existing_clusters.active_clusters_.erase(resource.get().name()); + all_existing_clusters.warming_clusters_.erase(resource.get().name()); } Protobuf::RepeatedPtrField to_remove_repeated; - for (const auto& [cluster_name, _] : clusters_to_remove) { + for (const auto& [cluster_name, _] : all_existing_clusters.active_clusters_) { *to_remove_repeated.Add() = cluster_name; } + for (const auto& [cluster_name, _] : all_existing_clusters.warming_clusters_) { + // Do not add the cluster twice when the cluster is both active and warming. + if (all_existing_clusters.active_clusters_.count(cluster_name) == 0) { + *to_remove_repeated.Add() = cluster_name; + } + } onConfigUpdate(resources, to_remove_repeated, version_info); } @@ -64,7 +71,7 @@ void CdsApiImpl::onConfigUpdate(const std::vector& a removed_resources.size()); std::vector exception_msgs; - absl::node_hash_set cluster_names; + absl::flat_hash_set cluster_names(added_resources.size()); bool any_applied = false; for (const auto& resource : added_resources) { envoy::config::cluster::v3::Cluster cluster; diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 924c0a6feb07..3e816fc6e160 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -56,18 +56,29 @@ void addOptionsIfNotNull(Network::Socket::OptionsSharedPtr& options, } // namespace -void ClusterManagerInitHelper::addCluster(Cluster& cluster) { +void ClusterManagerInitHelper::addCluster(ClusterManagerCluster& cm_cluster) { // See comments in ClusterManagerImpl::addOrUpdateCluster() for why this is only called during // server initialization. ASSERT(state_ != State::AllClustersInitialized); - const auto initialize_cb = [&cluster, this] { onClusterInit(cluster); }; + const auto initialize_cb = [&cm_cluster, this] { onClusterInit(cm_cluster); }; + Cluster& cluster = cm_cluster.cluster(); if (cluster.initializePhase() == Cluster::InitializePhase::Primary) { - primary_init_clusters_.push_back(&cluster); + // Remove the previous cluster before the cluster object is destroyed. + primary_init_clusters_.remove_if( + [name_to_remove = cluster.info()->name()](ClusterManagerCluster* cluster_iter) { + return cluster_iter->cluster().info()->name() == name_to_remove; + }); + primary_init_clusters_.push_back(&cm_cluster); cluster.initialize(initialize_cb); } else { ASSERT(cluster.initializePhase() == Cluster::InitializePhase::Secondary); - secondary_init_clusters_.push_back(&cluster); + // Remove the previous cluster before the cluster object is destroyed. + secondary_init_clusters_.remove_if( + [name_to_remove = cluster.info()->name()](ClusterManagerCluster* cluster_iter) { + return cluster_iter->cluster().info()->name() == name_to_remove; + }); + secondary_init_clusters_.push_back(&cm_cluster); if (started_secondary_initialize_) { // This can happen if we get a second CDS update that adds new clusters after we have // already started secondary init. In this case, just immediately initialize. @@ -79,24 +90,24 @@ void ClusterManagerInitHelper::addCluster(Cluster& cluster) { primary_init_clusters_.size(), secondary_init_clusters_.size()); } -void ClusterManagerInitHelper::onClusterInit(Cluster& cluster) { +void ClusterManagerInitHelper::onClusterInit(ClusterManagerCluster& cluster) { ASSERT(state_ != State::AllClustersInitialized); per_cluster_init_callback_(cluster); removeCluster(cluster); } -void ClusterManagerInitHelper::removeCluster(Cluster& cluster) { +void ClusterManagerInitHelper::removeCluster(ClusterManagerCluster& cluster) { if (state_ == State::AllClustersInitialized) { return; } // There is a remote edge case where we can remove a cluster via CDS that has not yet been // initialized. When called via the remove cluster API this code catches that case. - std::list* cluster_list; - if (cluster.initializePhase() == Cluster::InitializePhase::Primary) { + std::list* cluster_list; + if (cluster.cluster().initializePhase() == Cluster::InitializePhase::Primary) { cluster_list = &primary_init_clusters_; } else { - ASSERT(cluster.initializePhase() == Cluster::InitializePhase::Secondary); + ASSERT(cluster.cluster().initializePhase() == Cluster::InitializePhase::Secondary); cluster_list = &secondary_init_clusters_; } @@ -104,7 +115,8 @@ void ClusterManagerInitHelper::removeCluster(Cluster& cluster) { // present in the initializer list. If so, this is fine. cluster_list->remove(&cluster); ENVOY_LOG(debug, "cm init: init complete: cluster={} primary={} secondary={}", - cluster.info()->name(), primary_init_clusters_.size(), secondary_init_clusters_.size()); + cluster.cluster().info()->name(), primary_init_clusters_.size(), + secondary_init_clusters_.size()); maybeFinishInitialize(); } @@ -114,10 +126,10 @@ void ClusterManagerInitHelper::initializeSecondaryClusters() { // the item currently being initialized, so we eschew range-based-for and do this complicated // dance to increment the iterator before calling initialize. for (auto iter = secondary_init_clusters_.begin(); iter != secondary_init_clusters_.end();) { - Cluster* cluster = *iter; + ClusterManagerCluster* cluster = *iter; ++iter; - ENVOY_LOG(debug, "initializing secondary cluster {}", cluster->info()->name()); - cluster->initialize([cluster, this] { onClusterInit(*cluster); }); + ENVOY_LOG(debug, "initializing secondary cluster {}", cluster->cluster().info()->name()); + cluster->cluster().initialize([cluster, this] { onClusterInit(*cluster); }); } } @@ -238,11 +250,11 @@ ClusterManagerImpl::ClusterManagerImpl( Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, ProtobufMessage::ValidationContext& validation_context, Api::Api& api, Http::Context& http_context, Grpc::Context& grpc_context) - : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls.allocateSlot()), + : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls), random_(api.randomGenerator()), bind_config_(bootstrap.cluster_manager().upstream_bind_config()), local_info_(local_info), cm_stats_(generateStats(stats)), - init_helper_(*this, [this](Cluster& cluster) { onClusterInit(cluster); }), + init_helper_(*this, [this](ClusterManagerCluster& cluster) { onClusterInit(cluster); }), config_tracker_entry_( admin.getConfigTracker().add("clusters", [this] { return dumpClusterConfigs(); })), time_source_(main_thread_dispatcher.timeSource()), dispatcher_(main_thread_dispatcher), @@ -362,11 +374,29 @@ ClusterManagerImpl::ClusterManagerImpl( // Once the initial set of static bootstrap clusters are created (including the local cluster), // we can instantiate the thread local cluster manager. - tls_->set([this, local_cluster_name = local_cluster_name_]( - Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(*this, dispatcher, local_cluster_name); + tls_.set([this](Event::Dispatcher& dispatcher) { + return std::make_shared(*this, dispatcher); }); + // For active clusters that exist in bootstrap, post an empty thread local cluster update to + // populate them. + // TODO(mattklein123): It would be nice if we did not do this and instead all thread local cluster + // creation happened as part of the cluster init flow, however there are certain cases that depend + // on this behavior including route checking. It may be possible to fix static route checking to + // not depend on this behavior, but for now this is consistent with the way we have always done + // this so in the interest of minimal change it is not being done now. + for (auto& cluster : active_clusters_) { + // Skip posting the thread local cluster which is created as part of the thread local cluster + // manager constructor. See the TODO in that code for eventually cleaning this up. + if (local_cluster_name_ && local_cluster_name_.value() == cluster.first) { + continue; + } + + // Avoid virtual call in the constructor. This only impacts tests. Remove this when fixing + // the above TODO. + postThreadLocalClusterUpdateNonVirtual(*cluster.second, ThreadLocalClusterUpdateParams()); + } + // We can now potentially create the CDS API once the backing cluster exists. if (dyn_resources.has_cds_config()) { cds_api_ = factory_.createCds(dyn_resources.cds_config(), *this); @@ -379,7 +409,7 @@ ClusterManagerImpl::ClusterManagerImpl( // initialize any primary clusters. Post-init processing further initializes any thread // aware load balancer and sets up the per-worker host set updates. for (auto& cluster : active_clusters_) { - init_helper_.addCluster(*cluster.second->cluster_); + init_helper_.addCluster(*cluster.second); } // Potentially move to secondary initialization on the static bootstrap clusters if all primary @@ -412,31 +442,17 @@ ClusterManagerStats ClusterManagerImpl::generateStats(Stats::Scope& scope) { POOL_GAUGE_PREFIX(scope, final_prefix))}; } -void ClusterManagerImpl::onClusterInit(Cluster& cluster) { +void ClusterManagerImpl::onClusterInit(ClusterManagerCluster& cm_cluster) { // This routine is called when a cluster has finished initializing. The cluster has not yet // been setup for cross-thread updates to avoid needless updates during initialization. The order // of operations here is important. We start by initializing the thread aware load balancer if // needed. This must happen first so cluster updates are heard first by the load balancer. // Also, it assures that all of clusters which this function is called should be always active. + auto& cluster = cm_cluster.cluster(); auto cluster_data = warming_clusters_.find(cluster.info()->name()); // We have a situation that clusters will be immediately active, such as static and primary // cluster. So we must have this prevention logic here. if (cluster_data != warming_clusters_.end()) { - Network::TransportSocketFactory& factory = - cluster.info()->transportSocketMatcher().resolve(&cluster.info()->metadata()).factory_; - // If there is no secret entity, currently supports only TLS Certificate and Validation - // Context, when it failed to extract them via SDS, it will fail to change cluster status from - // warming to active. In current implementation, there is no strategy to activate clusters - // which failed to initialize at once. - // TODO(shikugawa): To implement to be available by keeping warming after no-available secret - // entity behavior occurred. And remove - // `envoy.reloadable_features.cluster_keep_warming_no_secret_entity` runtime feature flag. - const bool keep_warming_enabled = Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.cluster_keep_warming_no_secret_entity"); - if (!factory.isReady() && keep_warming_enabled) { - ENVOY_LOG(warn, "Failed to activate {}", cluster.info()->name()); - return; - } clusterWarmingToActive(cluster.info()->name()); updateClusterCounts(); } @@ -467,9 +483,9 @@ void ClusterManagerImpl::onClusterInit(Cluster& cluster) { } }); - cluster.prioritySet().addPriorityUpdateCb([&cluster, this](uint32_t priority, - const HostVector& hosts_added, - const HostVector& hosts_removed) { + cluster.prioritySet().addPriorityUpdateCb([&cm_cluster, this](uint32_t priority, + const HostVector& hosts_added, + const HostVector& hosts_removed) { // This fires when a cluster is about to have an updated member set. We need to send this // out to all of the thread local configurations. @@ -486,38 +502,49 @@ void ClusterManagerImpl::onClusterInit(Cluster& cluster) { // // See https://github.com/envoyproxy/envoy/pull/3941 for more context. bool scheduled = false; - const auto merge_timeout = - PROTOBUF_GET_MS_OR_DEFAULT(cluster.info()->lbConfig(), update_merge_window, 1000); + const auto merge_timeout = PROTOBUF_GET_MS_OR_DEFAULT(cm_cluster.cluster().info()->lbConfig(), + update_merge_window, 1000); // Remember: we only merge updates with no adds/removes — just hc/weight/metadata changes. const bool is_mergeable = hosts_added.empty() && hosts_removed.empty(); if (merge_timeout > 0) { // If this is not mergeable, we should cancel any scheduled updates since // we'll deliver it immediately. - scheduled = scheduleUpdate(cluster, priority, is_mergeable, merge_timeout); + scheduled = scheduleUpdate(cm_cluster, priority, is_mergeable, merge_timeout); } // If an update was not scheduled for later, deliver it immediately. if (!scheduled) { cm_stats_.cluster_updated_.inc(); - postThreadLocalClusterUpdate(cluster, priority, hosts_added, hosts_removed); + postThreadLocalClusterUpdate( + cm_cluster, ThreadLocalClusterUpdateParams(priority, hosts_added, hosts_removed)); } }); - // Finally, if the cluster has any hosts, post updates cross-thread so the per-thread load - // balancers are ready. + // Finally, post updates cross-thread so the per-thread load balancers are ready. First we + // populate any update information that may be available after cluster init. + ThreadLocalClusterUpdateParams params; for (auto& host_set : cluster.prioritySet().hostSetsPerPriority()) { if (host_set->hosts().empty()) { continue; } - postThreadLocalClusterUpdate(cluster, host_set->priority(), host_set->hosts(), HostVector{}); + params.per_priority_update_params_.emplace_back(host_set->priority(), host_set->hosts(), + HostVector{}); + } + // At this point the update is posted if either there are actual updates or the cluster has + // not been added yet. The latter can only happen with dynamic cluster as static clusters are + // added immediately. + // TODO(mattklein123): Per related TODOs we will see if we can centralize all logic so that + // clusters only get added in this path and all of the special casing can be removed. + if (!params.per_priority_update_params_.empty() || !cm_cluster.addedOrUpdated()) { + postThreadLocalClusterUpdate(cm_cluster, std::move(params)); } } -bool ClusterManagerImpl::scheduleUpdate(const Cluster& cluster, uint32_t priority, bool mergeable, - const uint64_t timeout) { +bool ClusterManagerImpl::scheduleUpdate(ClusterManagerCluster& cluster, uint32_t priority, + bool mergeable, const uint64_t timeout) { // Find pending updates for this cluster. - auto& updates_by_prio = updates_map_[cluster.info()->name()]; + auto& updates_by_prio = updates_map_[cluster.cluster().info()->name()]; if (!updates_by_prio) { updates_by_prio = std::make_unique(); } @@ -572,7 +599,7 @@ bool ClusterManagerImpl::scheduleUpdate(const Cluster& cluster, uint32_t priorit return true; } -void ClusterManagerImpl::applyUpdates(const Cluster& cluster, uint32_t priority, +void ClusterManagerImpl::applyUpdates(ClusterManagerCluster& cluster, uint32_t priority, PendingUpdates& updates) { // Deliver pending updates. @@ -582,7 +609,8 @@ void ClusterManagerImpl::applyUpdates(const Cluster& cluster, uint32_t priority, static const HostVector hosts_added; static const HostVector hosts_removed; - postThreadLocalClusterUpdate(cluster, priority, hosts_added, hosts_removed); + postThreadLocalClusterUpdate( + cluster, ThreadLocalClusterUpdateParams(priority, hosts_added, hosts_removed)); cm_stats_.cluster_updated_via_merge_.inc(); updates.last_updated_ = time_source_.monotonicTime(); @@ -610,7 +638,7 @@ bool ClusterManagerImpl::addOrUpdateCluster(const envoy::config::cluster::v3::Cl if (existing_active_cluster != active_clusters_.end()) { // The following init manager remove call is a NOP in the case we are already initialized. // It's just kept here to avoid additional logic. - init_helper_.removeCluster(*existing_active_cluster->second->cluster_); + init_helper_.removeCluster(*existing_active_cluster->second); } cm_stats_.cluster_modified_.inc(); } else { @@ -629,19 +657,19 @@ bool ClusterManagerImpl::addOrUpdateCluster(const envoy::config::cluster::v3::Cl // and easy to understand. const bool all_clusters_initialized = init_helper_.state() == ClusterManagerInitHelper::State::AllClustersInitialized; - loadCluster(cluster, version_info, true, warming_clusters_); + // Preserve the previous cluster data to avoid early destroy. The same cluster should be added + // before destroy to avoid early initialization complete. + const auto previous_cluster = loadCluster(cluster, version_info, true, warming_clusters_); auto& cluster_entry = warming_clusters_.at(cluster_name); if (!all_clusters_initialized) { ENVOY_LOG(debug, "add/update cluster {} during init", cluster_name); - createOrUpdateThreadLocalCluster(*cluster_entry); - init_helper_.addCluster(*cluster_entry->cluster_); + init_helper_.addCluster(*cluster_entry); } else { ENVOY_LOG(debug, "add/update cluster {} starting warming", cluster_name); cluster_entry->cluster_->initialize([this, cluster_name] { ENVOY_LOG(debug, "warming cluster {} complete", cluster_name); auto state_changed_cluster_entry = warming_clusters_.find(cluster_name); - createOrUpdateThreadLocalCluster(*state_changed_cluster_entry->second); - onClusterInit(*state_changed_cluster_entry->second->cluster_); + onClusterInit(*state_changed_cluster_entry->second); }); } @@ -660,53 +688,23 @@ void ClusterManagerImpl::clusterWarmingToActive(const std::string& cluster_name) warming_clusters_.erase(warming_it); } -void ClusterManagerImpl::createOrUpdateThreadLocalCluster(ClusterData& cluster) { - tls_->runOnAllThreads([new_cluster = cluster.cluster_->info(), - thread_aware_lb_factory = cluster.loadBalancerFactory()]( - ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - ThreadLocalClusterManagerImpl& cluster_manager = - object->asType(); - - if (cluster_manager.thread_local_clusters_.count(new_cluster->name()) > 0) { - ENVOY_LOG(debug, "updating TLS cluster {}", new_cluster->name()); - } else { - ENVOY_LOG(debug, "adding TLS cluster {}", new_cluster->name()); - } - - auto thread_local_cluster = new ThreadLocalClusterManagerImpl::ClusterEntry( - cluster_manager, new_cluster, thread_aware_lb_factory); - cluster_manager.thread_local_clusters_[new_cluster->name()].reset(thread_local_cluster); - for (auto& cb : cluster_manager.update_callbacks_) { - cb->onClusterAddOrUpdate(*thread_local_cluster); - } - - return object; - }); -} - bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { bool removed = false; auto existing_active_cluster = active_clusters_.find(cluster_name); if (existing_active_cluster != active_clusters_.end() && existing_active_cluster->second->added_via_api_) { removed = true; - init_helper_.removeCluster(*existing_active_cluster->second->cluster_); + init_helper_.removeCluster(*existing_active_cluster->second); active_clusters_.erase(existing_active_cluster); ENVOY_LOG(info, "removing cluster {}", cluster_name); - tls_->runOnAllThreads([cluster_name](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - ThreadLocalClusterManagerImpl& cluster_manager = - object->asType(); - - ASSERT(cluster_manager.thread_local_clusters_.count(cluster_name) == 1); + tls_.runOnAllThreads([cluster_name](OptRef cluster_manager) { + ASSERT(cluster_manager->thread_local_clusters_.count(cluster_name) == 1); ENVOY_LOG(debug, "removing TLS cluster {}", cluster_name); - for (auto& cb : cluster_manager.update_callbacks_) { + for (auto& cb : cluster_manager->update_callbacks_) { cb->onClusterRemoval(cluster_name); } - cluster_manager.thread_local_clusters_.erase(cluster_name); - return object; + cluster_manager->thread_local_clusters_.erase(cluster_name); }); } @@ -714,7 +712,7 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { if (existing_warming_cluster != warming_clusters_.end() && existing_warming_cluster->second->added_via_api_) { removed = true; - init_helper_.removeCluster(*existing_warming_cluster->second->cluster_); + init_helper_.removeCluster(*existing_warming_cluster->second); warming_clusters_.erase(existing_warming_cluster); ENVOY_LOG(info, "removing warming cluster {}", cluster_name); } @@ -729,9 +727,10 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { return removed; } -void ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& cluster, - const std::string& version_info, bool added_via_api, - ClusterMap& cluster_map) { +ClusterManagerImpl::ClusterDataPtr +ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& cluster, + const std::string& version_info, bool added_via_api, + ClusterMap& cluster_map) { std::pair new_cluster_pair = factory_.clusterFromProto(cluster, *this, outlier_event_logger_, added_via_api); auto& new_cluster = new_cluster_pair.first; @@ -776,11 +775,20 @@ void ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& } }); } - - cluster_map[cluster_reference.info()->name()] = std::make_unique( - cluster, version_info, added_via_api, std::move(new_cluster), time_source_); - const auto cluster_entry_it = cluster_map.find(cluster_reference.info()->name()); - + ClusterDataPtr result; + auto cluster_entry_it = cluster_map.find(cluster_reference.info()->name()); + if (cluster_entry_it != cluster_map.end()) { + result = std::exchange(cluster_entry_it->second, + std::make_unique(cluster, version_info, added_via_api, + std::move(new_cluster), time_source_)); + } else { + bool inserted = false; + std::tie(cluster_entry_it, inserted) = + cluster_map.emplace(cluster_reference.info()->name(), + std::make_unique(cluster, version_info, added_via_api, + std::move(new_cluster), time_source_)); + ASSERT(inserted); + } // If an LB is thread aware, create it here. The LB is not initialized until cluster pre-init // finishes. For RingHash/Maglev don't create the LB here if subset balancing is enabled, // because the thread_aware_lb_ field takes precedence over the subset lb). @@ -803,6 +811,7 @@ void ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& } updateClusterCounts(); + return result; } void ClusterManagerImpl::updateClusterCounts() { @@ -834,7 +843,7 @@ void ClusterManagerImpl::updateClusterCounts() { } ThreadLocalCluster* ClusterManagerImpl::get(absl::string_view cluster) { - auto& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry != cluster_manager.thread_local_clusters_.end()) { @@ -873,7 +882,7 @@ Http::ConnectionPool::Instance* ClusterManagerImpl::httpConnPoolForCluster(const std::string& cluster, ResourcePriority priority, absl::optional protocol, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -899,7 +908,7 @@ ClusterManagerImpl::httpConnPoolForCluster(const std::string& cluster, ResourceP Tcp::ConnectionPool::Instance* ClusterManagerImpl::tcpConnPoolForCluster(const std::string& cluster, ResourcePriority priority, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -924,43 +933,80 @@ ClusterManagerImpl::tcpConnPoolForCluster(const std::string& cluster, ResourcePr void ClusterManagerImpl::postThreadLocalDrainConnections(const Cluster& cluster, const HostVector& hosts_removed) { - tls_->runOnAllThreads( - [name = cluster.info()->name(), hosts_removed](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().removeHosts(name, hosts_removed); - return object; - }); + tls_.runOnAllThreads([name = cluster.info()->name(), + hosts_removed](OptRef cluster_manager) { + cluster_manager->removeHosts(name, hosts_removed); + }); } -void ClusterManagerImpl::postThreadLocalClusterUpdate(const Cluster& cluster, uint32_t priority, - const HostVector& hosts_added, - const HostVector& hosts_removed) { - const auto& host_set = cluster.prioritySet().hostSetsPerPriority()[priority]; - - tls_->runOnAllThreads([name = cluster.info()->name(), priority, - update_params = HostSetImpl::updateHostsParams(*host_set), - locality_weights = host_set->localityWeights(), hosts_added, hosts_removed, - overprovisioning_factor = host_set->overprovisioningFactor()]( - ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().updateClusterMembership( - name, priority, update_params, locality_weights, hosts_added, hosts_removed, - overprovisioning_factor); - return object; - }); +void ClusterManagerImpl::postThreadLocalClusterUpdateNonVirtual( + ClusterManagerCluster& cm_cluster, ThreadLocalClusterUpdateParams&& params) { + const bool is_local_cluster = local_cluster_name_.has_value() && + local_cluster_name_.value() == cm_cluster.cluster().info()->name(); + bool add_or_update_cluster = false; + if (!cm_cluster.addedOrUpdated()) { + add_or_update_cluster = true; + cm_cluster.setAddedOrUpdated(); + } + if (is_local_cluster) { + // TODO(mattklein123): This is needed because of the special case of how local cluster is + // initialized in the thread local cluster manager constructor. This will all be cleaned up + // in a follow up. + add_or_update_cluster = false; + } + + LoadBalancerFactorySharedPtr load_balancer_factory; + if (add_or_update_cluster) { + load_balancer_factory = cm_cluster.loadBalancerFactory(); + } + + for (auto& per_priority : params.per_priority_update_params_) { + const auto& host_set = + cm_cluster.cluster().prioritySet().hostSetsPerPriority()[per_priority.priority_]; + per_priority.update_hosts_params_ = HostSetImpl::updateHostsParams(*host_set); + per_priority.locality_weights_ = host_set->localityWeights(); + per_priority.overprovisioning_factor_ = host_set->overprovisioningFactor(); + } + + tls_.runOnAllThreads( + [info = cm_cluster.cluster().info(), params = std::move(params), add_or_update_cluster, + load_balancer_factory](OptRef cluster_manager) { + if (add_or_update_cluster) { + if (cluster_manager->thread_local_clusters_.count(info->name()) > 0) { + ENVOY_LOG(debug, "updating TLS cluster {}", info->name()); + } else { + ENVOY_LOG(debug, "adding TLS cluster {}", info->name()); + } + + auto thread_local_cluster = new ThreadLocalClusterManagerImpl::ClusterEntry( + *cluster_manager, info, load_balancer_factory); + cluster_manager->thread_local_clusters_[info->name()].reset(thread_local_cluster); + // TODO(mattklein123): It would be better if update callbacks were done after the initial + // cluster member is seeded, assuming it is. In the interest of minimal change this is + // deferred for a future change. + for (auto& cb : cluster_manager->update_callbacks_) { + cb->onClusterAddOrUpdate(*thread_local_cluster); + } + } + + for (const auto& per_priority : params.per_priority_update_params_) { + cluster_manager->updateClusterMembership( + info->name(), per_priority.priority_, per_priority.update_hosts_params_, + per_priority.locality_weights_, per_priority.hosts_added_, + per_priority.hosts_removed_, per_priority.overprovisioning_factor_); + } + }); } void ClusterManagerImpl::postThreadLocalHealthFailure(const HostSharedPtr& host) { - tls_->runOnAllThreads([host](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().onHostHealthFailure(host); - return object; + tls_.runOnAllThreads([host](OptRef cluster_manager) { + cluster_manager->onHostHealthFailure(host); }); } Host::CreateConnectionData ClusterManagerImpl::tcpConnForCluster(const std::string& cluster, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -988,7 +1034,7 @@ Host::CreateConnectionData ClusterManagerImpl::tcpConnForCluster(const std::stri } Http::AsyncClient& ClusterManagerImpl::httpAsyncClientForCluster(const std::string& cluster) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry != cluster_manager.thread_local_clusters_.end()) { return entry->second->http_async_client_; @@ -999,7 +1045,7 @@ Http::AsyncClient& ClusterManagerImpl::httpAsyncClientForCluster(const std::stri ClusterUpdateCallbacksHandlePtr ClusterManagerImpl::addThreadLocalClusterUpdateCallbacks(ClusterUpdateCallbacks& cb) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; return std::make_unique(cb, cluster_manager.update_callbacks_); } @@ -1035,32 +1081,22 @@ ProtobufTypes::MessagePtr ClusterManagerImpl::dumpClusterConfigs() { } ClusterManagerImpl::ThreadLocalClusterManagerImpl::ThreadLocalClusterManagerImpl( - ClusterManagerImpl& parent, Event::Dispatcher& dispatcher, - const absl::optional& local_cluster_name) + ClusterManagerImpl& parent, Event::Dispatcher& dispatcher) : parent_(parent), thread_local_dispatcher_(dispatcher) { // If local cluster is defined then we need to initialize it first. - if (local_cluster_name) { - ENVOY_LOG(debug, "adding TLS local cluster {}", local_cluster_name.value()); - auto& local_cluster = parent.active_clusters_.at(local_cluster_name.value()); - thread_local_clusters_[local_cluster_name.value()] = std::make_unique( + // TODO(mattklein123): Technically accessing active_clusters_ here is a race condition. This has + // been this way "forever" but should be fixed in a follow up. + if (parent.localClusterName()) { + ENVOY_LOG(debug, "adding TLS local cluster {}", parent.localClusterName().value()); + auto& local_cluster = parent.active_clusters_.at(parent.localClusterName().value()); + thread_local_clusters_[parent.localClusterName().value()] = std::make_unique( *this, local_cluster->cluster_->info(), local_cluster->loadBalancerFactory()); } - local_priority_set_ = local_cluster_name - ? &thread_local_clusters_[local_cluster_name.value()]->priority_set_ - : nullptr; - - for (auto& cluster : parent.active_clusters_) { - // If local cluster name is set then we already initialized this cluster. - if (local_cluster_name && local_cluster_name.value() == cluster.first) { - continue; - } - - ENVOY_LOG(debug, "adding TLS initial cluster {}", cluster.first); - ASSERT(thread_local_clusters_.count(cluster.first) == 0); - thread_local_clusters_[cluster.first] = std::make_unique( - *this, cluster.second->cluster_->info(), cluster.second->loadBalancerFactory()); - } + local_priority_set_ = + parent.localClusterName() + ? &thread_local_clusters_[parent.localClusterName().value()]->priority_set_ + : nullptr; } ClusterManagerImpl::ThreadLocalClusterManagerImpl::~ThreadLocalClusterManagerImpl() { @@ -1226,7 +1262,7 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::onHostHealthFailure( const HostSharedPtr& host) { // Drain all HTTP connection pool connections in the case of a host health failure. If outlier/ - // health is due to ECMP flow hashing issues for example, a new set of connections might do + // health is due to `ECMP` flow hashing issues for example, a new set of connections might do // better. // TODO(mattklein123): This function is currently very specific, but in the future when we do // more granular host set changes, we should be able to capture single host changes and make them @@ -1395,7 +1431,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::connPool( bool have_transport_socket_options = false; if (context && context->upstreamTransportSocketOptions()) { - context->upstreamTransportSocketOptions()->hashKey(hash_key); + context->upstreamTransportSocketOptions()->hashKey(hash_key, host->transportSocketFactory()); have_transport_socket_options = true; } @@ -1455,7 +1491,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::tcpConnPool( bool have_transport_socket_options = false; if (context != nullptr && context->upstreamTransportSocketOptions() != nullptr) { have_transport_socket_options = true; - context->upstreamTransportSocketOptions()->hashKey(hash_key); + context->upstreamTransportSocketOptions()->hashKey(hash_key, host->transportSocketFactory()); } TcpConnPoolsContainer& container = parent_.host_tcp_conn_pool_map_[host]; diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 1aa14c4be78c..837afe731a4e 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -97,6 +97,27 @@ class ProdClusterManagerFactory : public ClusterManagerFactory { // For friend declaration in ClusterManagerInitHelper. class ClusterManagerImpl; +/** + * Wrapper for a cluster owned by the cluster manager. Used by both the cluster manager and the + * cluster manager init helper which needs to pass clusters back to the cluster manager. + */ +class ClusterManagerCluster { +public: + virtual ~ClusterManagerCluster() = default; + + // Return the underlying cluster. + virtual Cluster& cluster() PURE; + + // Return a new load balancer factory if the cluster has one. + virtual LoadBalancerFactorySharedPtr loadBalancerFactory() PURE; + + // Return true if a cluster has already been added or updated. + virtual bool addedOrUpdated() PURE; + + // Set when a cluster has been added or updated. This is only called a single time for a cluster. + virtual void setAddedOrUpdated() PURE; +}; + /** * This is a helper class used during cluster management initialization. Dealing with primary * clusters, secondary clusters, and CDS, is quite complicated, so this makes it easier to test. @@ -107,8 +128,9 @@ class ClusterManagerInitHelper : Logger::Loggable { * @param per_cluster_init_callback supplies the callback to call when a cluster has itself * initialized. The cluster manager can use this for post-init processing. */ - ClusterManagerInitHelper(ClusterManager& cm, - const std::function& per_cluster_init_callback) + ClusterManagerInitHelper( + ClusterManager& cm, + const std::function& per_cluster_init_callback) : cm_(cm), per_cluster_init_callback_(per_cluster_init_callback) {} enum class State { @@ -135,9 +157,9 @@ class ClusterManagerInitHelper : Logger::Loggable { AllClustersInitialized }; - void addCluster(Cluster& cluster); + void addCluster(ClusterManagerCluster& cluster); void onStaticLoadComplete(); - void removeCluster(Cluster& cluster); + void removeCluster(ClusterManagerCluster& cluster); void setCds(CdsApi* cds); void setPrimaryClustersInitializedCb(ClusterManager::PrimaryClustersReadyCallback callback); void setInitializedCb(ClusterManager::InitializationCompleteCallback callback); @@ -151,15 +173,15 @@ class ClusterManagerInitHelper : Logger::Loggable { void initializeSecondaryClusters(); void maybeFinishInitialize(); - void onClusterInit(Cluster& cluster); + void onClusterInit(ClusterManagerCluster& cluster); ClusterManager& cm_; - std::function per_cluster_init_callback_; + std::function per_cluster_init_callback_; CdsApi* cds_{}; ClusterManager::PrimaryClustersReadyCallback primary_clusters_initialized_callback_; ClusterManager::InitializationCompleteCallback initialized_callback_; - std::list primary_init_clusters_; - std::list secondary_init_clusters_; + std::list primary_init_clusters_; + std::list secondary_init_clusters_; State state_{State::Loading}; bool started_secondary_initialize_{}; }; @@ -214,15 +236,17 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggablecluster_); + clusters_maps.active_clusters_.emplace(cluster.first, *cluster.second->cluster_); } - - return clusters_map; + for (auto& cluster : warming_clusters_) { + clusters_maps.warming_clusters_.emplace(cluster.first, *cluster.second->cluster_); + } + return clusters_maps; } + const ClusterSet& primaryClusters() override { return primary_clusters_; } ThreadLocalCluster* get(absl::string_view cluster) override; @@ -273,9 +297,33 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable per_priority_update_params_; + }; + + virtual void postThreadLocalClusterUpdate(ClusterManagerCluster& cm_cluster, + ThreadLocalClusterUpdateParams&& params) { + return postThreadLocalClusterUpdateNonVirtual(cm_cluster, std::move(params)); + } private: /** @@ -361,8 +409,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable; - ThreadLocalClusterManagerImpl(ClusterManagerImpl& parent, Event::Dispatcher& dispatcher, - const absl::optional& local_cluster_name); + ThreadLocalClusterManagerImpl(ClusterManagerImpl& parent, Event::Dispatcher& dispatcher); ~ThreadLocalClusterManagerImpl() override; void drainConnPools(const HostVector& hosts); void drainConnPools(HostSharedPtr old_host, ConnPoolsContainer& container); @@ -395,7 +442,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggablefactory(); } else { return nullptr; } } + bool addedOrUpdated() override { return added_or_updated_; } + void setAddedOrUpdated() override { + ASSERT(!added_or_updated_); + added_or_updated_ = true; + } const envoy::config::cluster::v3::Cluster cluster_config_; const uint64_t config_hash_; @@ -421,6 +475,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable; using ClusterUpdatesMap = absl::node_hash_map; - void applyUpdates(const Cluster& cluster, uint32_t priority, PendingUpdates& updates); - bool scheduleUpdate(const Cluster& cluster, uint32_t priority, bool mergeable, + void applyUpdates(ClusterManagerCluster& cluster, uint32_t priority, PendingUpdates& updates); + bool scheduleUpdate(ClusterManagerCluster& cluster, uint32_t priority, bool mergeable, const uint64_t timeout); - void createOrUpdateThreadLocalCluster(ClusterData& cluster); ProtobufTypes::MessagePtr dumpClusterConfigs(); static ClusterManagerStats generateStats(Stats::Scope& scope); - void loadCluster(const envoy::config::cluster::v3::Cluster& cluster, - const std::string& version_info, bool added_via_api, ClusterMap& cluster_map); - void onClusterInit(Cluster& cluster); + + /** + * @return ClusterDataPtr contains the previous cluster in the cluster_map, or + * nullptr if cluster_map did not contain the same cluster. + */ + ClusterDataPtr loadCluster(const envoy::config::cluster::v3::Cluster& cluster, + const std::string& version_info, bool added_via_api, + ClusterMap& cluster_map); + void onClusterInit(ClusterManagerCluster& cluster); void postThreadLocalHealthFailure(const HostSharedPtr& host); + void postThreadLocalClusterUpdateNonVirtual(ClusterManagerCluster& cm_cluster, + ThreadLocalClusterUpdateParams&& params); void updateClusterCounts(); void clusterWarmingToActive(const std::string& cluster_name); void maybePrefetch(ThreadLocalClusterManagerImpl::ClusterEntryPtr& cluster_entry, @@ -489,7 +551,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable tls_; Random::RandomGenerator& random_; protected: diff --git a/source/common/upstream/eds.cc b/source/common/upstream/eds.cc index d8dc0d21c242..124ea3c5489d 100644 --- a/source/common/upstream/eds.cc +++ b/source/common/upstream/eds.cc @@ -173,12 +173,14 @@ void EdsClusterImpl::onAssignmentTimeout() { // TODO(vishalpowar) This is not going to work for incremental updates, and we // need to instead change the health status to indicate the assignments are // stale. + // TODO(snowp): This should probably just use xDS TTLs? envoy::config::endpoint::v3::ClusterLoadAssignment resource; resource.set_cluster_name(cluster_name_); ProtobufWkt::Any any_resource; any_resource.PackFrom(resource); - Config::DecodedResourceImpl decoded_resource(resource_decoder_, any_resource, ""); - std::vector resource_refs = {decoded_resource}; + auto decoded_resource = + Config::DecodedResourceImpl::fromResource(resource_decoder_, any_resource, ""); + std::vector resource_refs = {*decoded_resource}; onConfigUpdate(resource_refs, ""); // Stat to track how often we end up with stale assignments. info_->stats().assignment_stale_.inc(); diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index ebe3c1d40e50..0906bef6a32c 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -277,7 +277,9 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() { stream_info.setDownstreamRemoteAddress(local_address_); stream_info.onUpstreamHostSelected(host_); parent_.request_headers_parser_->evaluateHeaders(*request_headers, stream_info); - request_encoder->encodeHeaders(*request_headers, true); + auto status = request_encoder->encodeHeaders(*request_headers, true); + // Encoding will only fail if required request headers are missing. + ASSERT(status.ok()); } void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onResetStream(Http::StreamResetReason, @@ -658,12 +660,12 @@ void GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::decodeData(Buffer::Ins "gRPC protocol violation: unexpected stream end", true); return; } - // We should end up with only one frame here. std::vector decoded_frames; if (!decoder_.decode(data, decoded_frames)) { onRpcComplete(Grpc::Status::WellKnownGrpcStatus::Internal, "gRPC wire protocol decode error", false); + return; } for (auto& frame : decoded_frames) { if (frame.length_ > 0) { @@ -733,7 +735,9 @@ void GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::onInterval() { Router::FilterUtility::setUpstreamScheme( headers_message->headers(), host_->transportSocketFactory().implementsSecureTransport()); - request_encoder_->encodeHeaders(headers_message->headers(), false); + auto status = request_encoder_->encodeHeaders(headers_message->headers(), false); + // Encoding will only fail if required headers are missing. + ASSERT(status.ok()); grpc::health::v1::HealthCheckRequest request; if (parent_.service_name_.has_value()) { diff --git a/source/common/upstream/load_stats_reporter.cc b/source/common/upstream/load_stats_reporter.cc index fa5697e86fbd..b7ff8e94b7c3 100644 --- a/source/common/upstream/load_stats_reporter.cc +++ b/source/common/upstream/load_stats_reporter.cc @@ -62,11 +62,11 @@ void LoadStatsReporter::sendLoadStatsRequest() { // added to the cluster manager. When we get the notification, we record the current time in // clusters_ as the start time for the load reporting window for that cluster. request_.mutable_cluster_stats()->Clear(); + auto all_clusters = cm_.clusters(); for (const auto& cluster_name_and_timestamp : clusters_) { const std::string& cluster_name = cluster_name_and_timestamp.first; - auto cluster_info_map = cm_.clusters(); - auto it = cluster_info_map.find(cluster_name); - if (it == cluster_info_map.end()) { + auto it = all_clusters.active_clusters_.find(cluster_name); + if (it == all_clusters.active_clusters_.end()) { ENVOY_LOG(debug, "Cluster {} does not exist", cluster_name); continue; } @@ -154,7 +154,8 @@ void LoadStatsReporter::startLoadReportPeriod() { // converge. absl::node_hash_map existing_clusters; if (message_->send_all_clusters()) { - for (const auto& p : cm_.clusters()) { + auto cluster_info_map = cm_.clusters(); + for (const auto& p : cluster_info_map.active_clusters_) { const std::string& cluster_name = p.first; if (clusters_.count(cluster_name) > 0) { existing_clusters.emplace(cluster_name, clusters_[cluster_name]); @@ -173,9 +174,10 @@ void LoadStatsReporter::startLoadReportPeriod() { clusters_.emplace(cluster_name, existing_clusters.count(cluster_name) > 0 ? existing_clusters[cluster_name] : time_source_.monotonicTime().time_since_epoch()); + // TODO(lambdai): Move the clusters() call out of this lambda. auto cluster_info_map = cm_.clusters(); - auto it = cluster_info_map.find(cluster_name); - if (it == cluster_info_map.end()) { + auto it = cluster_info_map.active_clusters_.find(cluster_name); + if (it == cluster_info_map.active_clusters_.end()) { return; } // Don't reset stats for existing tracked clusters. @@ -193,7 +195,8 @@ void LoadStatsReporter::startLoadReportPeriod() { cluster.info()->loadReportStats().upstream_rq_dropped_.latch(); }; if (message_->send_all_clusters()) { - for (const auto& p : cm_.clusters()) { + auto cluster_info_map = cm_.clusters(); + for (const auto& p : cluster_info_map.active_clusters_) { const std::string& cluster_name = p.first; handle_cluster_func(cluster_name); } diff --git a/source/extensions/access_loggers/wasm/config.cc b/source/extensions/access_loggers/wasm/config.cc index 718adb0fad93..8ca765442e9c 100644 --- a/source/extensions/access_loggers/wasm/config.cc +++ b/source/extensions/access_loggers/wasm/config.cc @@ -23,8 +23,6 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con const auto& config = MessageUtil::downcastAndValidate< const envoy::extensions::access_loggers::wasm::v3::WasmAccessLog&>( proto_config, context.messageValidationVisitor()); - auto access_log = - std::make_shared(config.config().root_id(), nullptr, std::move(filter)); // Create a base WASM to verify that the code loads before setting/cloning the for the // individual threads. @@ -35,25 +33,15 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con envoy::config::core::v3::TrafficDirection::UNSPECIFIED, context.localInfo(), nullptr /* listener_metadata */); - auto callback = [access_log, &context, plugin](Common::Wasm::WasmHandleSharedPtr base_wasm) { - auto tls_slot = context.threadLocal().allocateSlot(); + auto access_log = std::make_shared(plugin, nullptr, std::move(filter)); + auto callback = [access_log, &context, plugin](Common::Wasm::WasmHandleSharedPtr 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) -> std::shared_ptr { - if (!base_wasm) { - // There is no way to prevent the connection at this point. The user could choose to use - // an HTTP Wasm plugin and only handle onLog() which would correctly close the - // connection in onRequestHeaders(). - if (!plugin->fail_open_) { - ENVOY_LOG(critical, "Plugin configured to fail closed failed to load"); - } - return nullptr; - } - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); - }); + auto tls_slot = + ThreadLocal::TypedSlot::makeUnique(context.threadLocal()); + tls_slot->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); + }); access_log->setTlsSlot(std::move(tls_slot)); }; diff --git a/source/extensions/access_loggers/wasm/wasm_access_log_impl.h b/source/extensions/access_loggers/wasm/wasm_access_log_impl.h index 5a7654b97bee..94910ef56712 100644 --- a/source/extensions/access_loggers/wasm/wasm_access_log_impl.h +++ b/source/extensions/access_loggers/wasm/wasm_access_log_impl.h @@ -12,13 +12,15 @@ namespace Extensions { namespace AccessLoggers { namespace Wasm { -using Envoy::Extensions::Common::Wasm::WasmHandle; +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; class WasmAccessLog : public AccessLog::Instance { public: - WasmAccessLog(absl::string_view root_id, ThreadLocal::SlotPtr tls_slot, + WasmAccessLog(const PluginSharedPtr& plugin, ThreadLocal::TypedSlotPtr&& tls_slot, AccessLog::FilterPtr filter) - : root_id_(root_id), tls_slot_(std::move(tls_slot)), filter_(std::move(filter)) {} + : plugin_(plugin), tls_slot_(std::move(tls_slot)), filter_(std::move(filter)) {} + void log(const Http::RequestHeaderMap* request_headers, const Http::ResponseHeaderMap* response_headers, const Http::ResponseTrailerMap* response_trailers, @@ -30,20 +32,21 @@ class WasmAccessLog : public AccessLog::Instance { } } - if (tls_slot_->get()) { - tls_slot_->getTyped().wasm()->log(root_id_, request_headers, response_headers, - response_trailers, stream_info); + auto handle = tls_slot_->get(); + if (handle.has_value()) { + handle->wasm()->log(plugin_, request_headers, response_headers, response_trailers, + stream_info); } } - void setTlsSlot(ThreadLocal::SlotPtr tls_slot) { + void setTlsSlot(ThreadLocal::TypedSlotPtr&& tls_slot) { ASSERT(tls_slot_ == nullptr); tls_slot_ = std::move(tls_slot); } private: - std::string root_id_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + ThreadLocal::TypedSlotPtr tls_slot_; AccessLog::FilterPtr filter_; }; diff --git a/source/extensions/bootstrap/wasm/config.cc b/source/extensions/bootstrap/wasm/config.cc index 3cc0068b9a16..0e8f4caa99ac 100644 --- a/source/extensions/bootstrap/wasm/config.cc +++ b/source/extensions/bootstrap/wasm/config.cc @@ -37,18 +37,18 @@ void WasmFactory::createWasm(const envoy::extensions::wasm::v3::WasmService& con } if (singleton) { // Return a Wasm VM which will be stored as a singleton by the Server. - cb(std::make_unique( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, context.dispatcher()))); + cb(std::make_unique(plugin, Common::Wasm::getOrCreateThreadLocalPlugin( + base_wasm, plugin, context.dispatcher()))); return; } // Per-thread WASM VM. // NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call. - auto tls_slot = context.threadLocal().allocateSlot(); + auto tls_slot = + ThreadLocal::TypedSlot::makeUnique(context.threadLocal()); tls_slot->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); }); - cb(std::make_unique(std::move(tls_slot))); + cb(std::make_unique(plugin, std::move(tls_slot))); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/bootstrap/wasm/config.h b/source/extensions/bootstrap/wasm/config.h index e70306746389..b8f3850ef621 100644 --- a/source/extensions/bootstrap/wasm/config.h +++ b/source/extensions/bootstrap/wasm/config.h @@ -16,14 +16,21 @@ namespace Extensions { namespace Bootstrap { namespace Wasm { +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginHandleSharedPtr; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; + class WasmService { public: - WasmService(Common::Wasm::WasmHandleSharedPtr singleton) : singleton_(std::move(singleton)) {} - WasmService(ThreadLocal::SlotPtr tls_slot) : tls_slot_(std::move(tls_slot)) {} + WasmService(PluginSharedPtr plugin, PluginHandleSharedPtr singleton) + : plugin_(plugin), singleton_(std::move(singleton)) {} + WasmService(PluginSharedPtr plugin, ThreadLocal::TypedSlotPtr&& tls_slot) + : plugin_(plugin), tls_slot_(std::move(tls_slot)) {} private: - Common::Wasm::WasmHandleSharedPtr singleton_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + PluginHandleSharedPtr singleton_; + ThreadLocal::TypedSlotPtr tls_slot_; }; using WasmServicePtr = std::unique_ptr; diff --git a/source/extensions/clusters/aggregate/cluster.cc b/source/extensions/clusters/aggregate/cluster.cc index 958c678d0202..c630a580a49a 100644 --- a/source/extensions/clusters/aggregate/cluster.cc +++ b/source/extensions/clusters/aggregate/cluster.cc @@ -19,8 +19,8 @@ Cluster::Cluster(const envoy::config::cluster::v3::Cluster& cluster, Stats::ScopePtr&& stats_scope, ThreadLocal::SlotAllocator& tls, bool added_via_api) : Upstream::ClusterImplBase(cluster, runtime, factory_context, std::move(stats_scope), added_via_api), - cluster_manager_(cluster_manager), runtime_(runtime), random_(random), - tls_(tls.allocateSlot()), clusters_(config.clusters().begin(), config.clusters().end()) {} + cluster_manager_(cluster_manager), runtime_(runtime), random_(random), tls_(tls), + clusters_(config.clusters().begin(), config.clusters().end()) {} PriorityContextPtr Cluster::linearizePrioritySet(const std::function& skip_predicate) { @@ -91,15 +91,13 @@ void Cluster::startPreInit() { void Cluster::refresh(const std::function& skip_predicate) { // Post the priority set to worker threads. // TODO(mattklein123): Remove "this" capture. - tls_->runOnAllThreads([this, skip_predicate, cluster_name = this->info()->name()]( - ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.runOnAllThreads([this, skip_predicate, cluster_name = this->info()->name()]( + OptRef) { PriorityContextPtr priority_context = linearizePrioritySet(skip_predicate); Upstream::ThreadLocalCluster* cluster = cluster_manager_.get(cluster_name); ASSERT(cluster != nullptr); dynamic_cast(cluster->loadBalancer()) .refresh(std::move(priority_context)); - return object; }); } diff --git a/source/extensions/clusters/aggregate/cluster.h b/source/extensions/clusters/aggregate/cluster.h index 92adfe68f187..e5beeb46ef5a 100644 --- a/source/extensions/clusters/aggregate/cluster.h +++ b/source/extensions/clusters/aggregate/cluster.h @@ -54,7 +54,7 @@ class Cluster : public Upstream::ClusterImplBase, Upstream::ClusterUpdateCallbac Upstream::ClusterManager& cluster_manager_; Runtime::Loader& runtime_; Random::RandomGenerator& random_; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot<> tls_; const std::vector clusters_; private: diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index d378534b7cfc..ac09ab220f72 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -21,8 +21,7 @@ DnsCacheImpl::DnsCacheImpl( : main_thread_dispatcher_(main_thread_dispatcher), dns_lookup_family_(Upstream::getDnsLookupFamilyFromEnum(config.dns_lookup_family())), resolver_(main_thread_dispatcher.createDnsResolver({}, config.use_tcp_for_dns_lookups())), - tls_slot_(tls.allocateSlot()), - scope_(root_scope.createScope(fmt::format("dns_cache.{}.", config.name()))), + tls_slot_(tls), scope_(root_scope.createScope(fmt::format("dns_cache.{}.", config.name()))), stats_(generateDnsCacheStats(*scope_)), resource_manager_(*scope_, loader, config.name(), config.dns_cache_circuit_breaker()), refresh_interval_(PROTOBUF_GET_MS_OR_DEFAULT(config, dns_refresh_rate, 60000)), @@ -32,7 +31,7 @@ DnsCacheImpl::DnsCacheImpl( config, refresh_interval_.count(), random)), host_ttl_(PROTOBUF_GET_MS_OR_DEFAULT(config, host_ttl, 300000)), max_hosts_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_hosts, 1024)) { - tls_slot_->set([](Event::Dispatcher&) { return std::make_shared(); }); + tls_slot_.set([](Event::Dispatcher&) { return std::make_shared(); }); updateTlsHostsMap(); } @@ -56,7 +55,7 @@ DnsCacheImpl::LoadDnsCacheEntryResult DnsCacheImpl::loadDnsCacheEntry(absl::string_view host, uint16_t default_port, LoadDnsCacheEntryCallbacks& callbacks) { ENVOY_LOG(debug, "thread local lookup for host '{}'", host); - auto& tls_host_info = tls_slot_->getTyped(); + ThreadLocalHostInfo& tls_host_info = *tls_slot_; auto tls_host = tls_host_info.host_map_->find(host); if (tls_host != tls_host_info.host_map_->end()) { ENVOY_LOG(debug, "thread local hit for host '{}'", host); @@ -276,10 +275,8 @@ void DnsCacheImpl::updateTlsHostsMap() { } } - tls_slot_->runOnAllThreads([new_host_map](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().updateHostMap(new_host_map); - return object; + tls_slot_.runOnAllThreads([new_host_map](OptRef local_host_info) { + local_host_info->updateHostMap(new_host_map); }); } diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h index b0f3444fdac6..bf5106374702 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h @@ -140,7 +140,7 @@ class DnsCacheImpl : public DnsCache, Logger::Loggable tls_slot_; Stats::ScopePtr scope_; DnsCacheStats stats_; std::list update_callbacks_; diff --git a/source/extensions/common/redis/cluster_refresh_manager_impl.cc b/source/extensions/common/redis/cluster_refresh_manager_impl.cc index 8da52d96665c..c3caa96d9eec 100644 --- a/source/extensions/common/redis/cluster_refresh_manager_impl.cc +++ b/source/extensions/common/redis/cluster_refresh_manager_impl.cc @@ -133,9 +133,9 @@ bool ClusterRefreshManagerImpl::onEvent(const std::string& cluster_name, EventTy if (post_callback) { main_thread_dispatcher_.post([this, cluster_name, info]() { // Ensure that cluster is still active before calling callback. - auto map = cm_.clusters(); - auto it = map.find(cluster_name); - if (it != map.end()) { + auto maps = cm_.clusters(); + auto it = maps.active_clusters_.find(cluster_name); + if (it != maps.active_clusters_.end()) { info->cb_(); } }); diff --git a/source/extensions/common/tap/admin.cc b/source/extensions/common/tap/admin.cc index 79de7a296895..dbc0d3f50207 100644 --- a/source/extensions/common/tap/admin.cc +++ b/source/extensions/common/tap/admin.cc @@ -31,6 +31,10 @@ AdminHandler::AdminHandler(Server::Admin& admin, Event::Dispatcher& main_thread_ const bool rc = admin_.addHandler("/tap", "tap filter control", MAKE_ADMIN_HANDLER(handler), true, true); RELEASE_ASSERT(rc, "/tap admin endpoint is taken"); + if (admin_.socket().addressType() == Network::Address::Type::Pipe) { + ENVOY_LOG(warn, "Admin tapping (via /tap) is unreliable when the admin endpoint is a pipe and " + "the connection is HTTP/1. Either use an IP address or connect using HTTP/2."); + } } AdminHandler::~AdminHandler() { diff --git a/source/extensions/common/tap/extension_config_base.cc b/source/extensions/common/tap/extension_config_base.cc index 93d257c94b1c..04a6fb73d20b 100644 --- a/source/extensions/common/tap/extension_config_base.cc +++ b/source/extensions/common/tap/extension_config_base.cc @@ -13,11 +13,8 @@ ExtensionConfigBase::ExtensionConfigBase( TapConfigFactoryPtr&& config_factory, Server::Admin& admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher) - : proto_config_(proto_config), config_factory_(std::move(config_factory)), - tls_slot_(tls.allocateSlot()) { - tls_slot_->set([](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(); - }); + : proto_config_(proto_config), config_factory_(std::move(config_factory)), tls_slot_(tls) { + tls_slot_.set([](Event::Dispatcher&) { return std::make_shared(); }); switch (proto_config_.config_type_case()) { case envoy::extensions::common::tap::v3::CommonExtensionConfig::ConfigTypeCase::kAdminConfig: { @@ -60,21 +57,16 @@ const absl::string_view ExtensionConfigBase::adminId() { } void ExtensionConfigBase::clearTapConfig() { - tls_slot_->runOnAllThreads([](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().config_ = nullptr; - return object; - }); + tls_slot_.runOnAllThreads( + [](OptRef tls_filter_config) { tls_filter_config->config_ = nullptr; }); } void ExtensionConfigBase::installNewTap(const envoy::config::tap::v3::TapConfig& proto_config, Sink* admin_streamer) { TapConfigSharedPtr new_config = config_factory_->createConfigFromProto(proto_config, admin_streamer); - tls_slot_->runOnAllThreads([new_config](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().config_ = new_config; - return object; + tls_slot_.runOnAllThreads([new_config](OptRef tls_filter_config) { + tls_filter_config->config_ = new_config; }); } diff --git a/source/extensions/common/tap/extension_config_base.h b/source/extensions/common/tap/extension_config_base.h index 88cea02fae8f..7e0e00625dd9 100644 --- a/source/extensions/common/tap/extension_config_base.h +++ b/source/extensions/common/tap/extension_config_base.h @@ -34,7 +34,7 @@ class ExtensionConfigBase : public ExtensionConfig, Logger::Loggable std::shared_ptr currentConfigHelper() const { - return std::dynamic_pointer_cast(tls_slot_->getTyped().config_); + return std::dynamic_pointer_cast(tls_slot_->config_); } private: @@ -48,7 +48,7 @@ class ExtensionConfigBase : public ExtensionConfig, Logger::Loggable tls_slot_; AdminHandlerSharedPtr admin_handler_; }; diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 02eb727951c1..0f351b56b5bd 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -16,6 +16,16 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "wasm_runtime_factory_interface", + hdrs = [ + "wasm_runtime_factory.h", + ], + deps = [ + "@proxy_wasm_cpp_host//:include", + ], +) + # NB: Used to break the circular dependency between wasm_lib and null_plugin_lib. envoy_cc_library( name = "wasm_hdr", @@ -32,6 +42,7 @@ envoy_cc_library( "//include/envoy/http:codes_interface", "//include/envoy/http:filter_interface", "//include/envoy/server:lifecycle_notifier_interface", + "//include/envoy/thread_local:thread_local_object", "//include/envoy/upstream:cluster_manager_interface", "//source/common/config:datasource_lib", "//source/common/singleton:const_singleton", @@ -61,6 +72,7 @@ envoy_cc_library( "//source/common/singleton:const_singleton", "@com_github_google_flatbuffers//:flatbuffers", "@com_google_cel_cpp//eval/public:cel_value", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@com_google_cel_cpp//tools:flatbuffers_backed_impl", ], ) @@ -84,6 +96,7 @@ envoy_cc_library( deps = [ ":wasm_hdr", ":wasm_interoperation_lib", + ":wasm_runtime_factory_interface", "//external:abseil_base", "//external:abseil_node_hash_map", "//include/envoy/server:lifecycle_notifier_interface", @@ -96,15 +109,17 @@ envoy_cc_library( "//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/eval:field_access", - "@com_google_cel_cpp//eval/eval:field_backed_list_impl", - "@com_google_cel_cpp//eval/eval:field_backed_map_impl", + "@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", "@envoy_api//envoy/extensions/wasm/v3:pkg_cc_proto", - "@proxy_wasm_cpp_host//:lib", + "@proxy_wasm_cpp_host//:common_lib", + "@proxy_wasm_cpp_host//:null_lib", ] + select( { "//bazel:windows_x86_64": [], diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index e6e4f8ae0f05..006e7648c0e6 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -34,10 +34,11 @@ #include "absl/container/node_hash_map.h" #include "absl/strings/str_cat.h" #include "absl/synchronization/mutex.h" -#include "eval/eval/field_access.h" -#include "eval/eval/field_backed_list_impl.h" -#include "eval/eval/field_backed_map_impl.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" #include "openssl/bytestring.h" #include "openssl/hmac.h" #include "openssl/sha.h" @@ -421,6 +422,7 @@ static absl::flat_hash_map property_tokens = {PROPER absl::optional Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) const { + using google::api::expr::runtime::CelProtoWrapper; using google::api::expr::runtime::CelValue; const StreamInfo::StreamInfo* info = getConstRequestStreamInfo(); @@ -448,7 +450,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co switch (part_token->second) { case PropertyToken::METADATA: if (info) { - return CelValue::CreateMessage(&info->dynamicMetadata(), arena); + return CelProtoWrapper::CreateMessage(&info->dynamicMetadata(), arena); } break; case PropertyToken::REQUEST: @@ -485,9 +487,9 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::NODE: if (root_local_info_) { - return CelValue::CreateMessage(&root_local_info_->node(), arena); + return CelProtoWrapper::CreateMessage(&root_local_info_->node(), arena); } else if (plugin_) { - return CelValue::CreateMessage(&plugin()->local_info_.node(), arena); + return CelProtoWrapper::CreateMessage(&plugin()->local_info_.node(), arena); } break; case PropertyToken::SOURCE: @@ -509,7 +511,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::LISTENER_METADATA: if (plugin_) { - return CelValue::CreateMessage(plugin()->listener_metadata_, arena); + return CelProtoWrapper::CreateMessage(plugin()->listener_metadata_, arena); } break; case PropertyToken::CLUSTER_NAME: @@ -524,15 +526,16 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::CLUSTER_METADATA: if (info && info->upstreamHost()) { - return CelValue::CreateMessage(&info->upstreamHost()->cluster().metadata(), arena); + return CelProtoWrapper::CreateMessage(&info->upstreamHost()->cluster().metadata(), arena); } else if (info && info->upstreamClusterInfo().has_value() && info->upstreamClusterInfo().value()) { - return CelValue::CreateMessage(&info->upstreamClusterInfo().value()->metadata(), arena); + return CelProtoWrapper::CreateMessage(&info->upstreamClusterInfo().value()->metadata(), + arena); } break; case PropertyToken::UPSTREAM_HOST_METADATA: if (info && info->upstreamHost() && info->upstreamHost()->metadata()) { - return CelValue::CreateMessage(info->upstreamHost()->metadata().get(), arena); + return CelProtoWrapper::CreateMessage(info->upstreamHost()->metadata().get(), arena); } break; case PropertyToken::ROUTE_NAME: @@ -542,7 +545,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::ROUTE_METADATA: if (info && info->routeEntry()) { - return CelValue::CreateMessage(&info->routeEntry()->metadata(), arena); + return CelProtoWrapper::CreateMessage(&info->routeEntry()->metadata(), arena); } break; case PropertyToken::PLUGIN_NAME: @@ -810,8 +813,8 @@ BufferInterface* Context::getBuffer(WasmBufferType type) { case WasmBufferType::VmConfiguration: return buffer_.set(wasm()->vm_configuration()); case WasmBufferType::PluginConfiguration: - if (plugin_) { - return buffer_.set(plugin_->plugin_configuration_); + if (temp_plugin_) { + return buffer_.set(temp_plugin_->plugin_configuration_); } return nullptr; case WasmBufferType::HttpRequestBody: @@ -1182,18 +1185,18 @@ bool Context::validateConfiguration(absl::string_view configuration, if (!wasm()->validate_configuration_) { return true; } - plugin_ = plugin_base; + temp_plugin_ = plugin_base; auto result = wasm() ->validate_configuration_(this, id_, static_cast(configuration.size())) .u64_ != 0; - plugin_.reset(); + temp_plugin_.reset(); return result; } absl::string_view Context::getConfiguration() { - if (plugin_) { - return plugin_->plugin_configuration_; + if (temp_plugin_) { + return temp_plugin_->plugin_configuration_; } else { return wasm()->vm_configuration(); } @@ -1490,12 +1493,14 @@ WasmResult Context::continueStream(WasmStreamType stream_type) { switch (stream_type) { case WasmStreamType::Request: if (decoder_callbacks_) { - decoder_callbacks_->continueDecoding(); + // We are in a reentrant call, so defer. + wasm()->addAfterVmCallAction([this] { decoder_callbacks_->continueDecoding(); }); } break; case WasmStreamType::Response: if (encoder_callbacks_) { - encoder_callbacks_->continueEncoding(); + // We are in a reentrant call, so defer. + wasm()->addAfterVmCallAction([this] { encoder_callbacks_->continueEncoding(); }); } break; default: diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index e288c1e50602..657a0331addd 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -31,6 +31,7 @@ using proxy_wasm::ContextBase; using proxy_wasm::Pairs; using proxy_wasm::PairsWithStringValues; using proxy_wasm::PluginBase; +using proxy_wasm::PluginHandleBase; using proxy_wasm::SharedQueueDequeueToken; using proxy_wasm::SharedQueueEnqueueToken; using proxy_wasm::WasmBase; @@ -45,6 +46,7 @@ using GrpcService = envoy::config::core::v3::GrpcService; class Wasm; +using PluginHandleBaseSharedPtr = std::shared_ptr; using WasmHandleBaseSharedPtr = std::shared_ptr; // Opaque context object. diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index ab2a45f0aaf7..1b0e8e513be2 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -243,16 +243,16 @@ ContextBase* Wasm::createRootContext(const std::shared_ptr& plugin) ContextBase* Wasm::createVmContext() { return new Context(this); } -void Wasm::log(absl::string_view root_id, const Http::RequestHeaderMap* request_headers, +void Wasm::log(const PluginSharedPtr& plugin, const Http::RequestHeaderMap* request_headers, const Http::ResponseHeaderMap* response_headers, const Http::ResponseTrailerMap* response_trailers, const StreamInfo::StreamInfo& stream_info) { - auto context = getRootContext(root_id); + auto context = getRootContext(plugin, true); context->log(request_headers, response_headers, response_trailers, stream_info); } -void Wasm::onStatsUpdate(absl::string_view root_id, Envoy::Stats::MetricSnapshot& snapshot) { - auto context = getRootContext(root_id); +void Wasm::onStatsUpdate(const PluginSharedPtr& plugin, Envoy::Stats::MetricSnapshot& snapshot) { + auto context = getRootContext(plugin, true); context->onStatsUpdate(snapshot); } @@ -281,6 +281,14 @@ getCloneFactory(WasmExtension* wasm_extension, Event::Dispatcher& dispatcher, }; } +static proxy_wasm::PluginHandleFactory getPluginFactory(WasmExtension* wasm_extension) { + auto wasm_plugin_factory = wasm_extension->pluginFactory(); + return [wasm_plugin_factory](WasmHandleBaseSharedPtr base_wasm, + absl::string_view plugin_key) -> std::shared_ptr { + return wasm_plugin_factory(std::static_pointer_cast(base_wasm), plugin_key); + }; +} + WasmEvent toWasmEvent(const std::shared_ptr& wasm) { if (!wasm) { return WasmEvent::UnableToCreateVM; @@ -474,13 +482,21 @@ bool createWasm(const VmConfig& vm_config, const PluginSharedPtr& plugin, create_root_context_for_testing); } -WasmHandleSharedPtr getOrCreateThreadLocalWasm(const WasmHandleSharedPtr& base_wasm, - const PluginSharedPtr& plugin, - Event::Dispatcher& dispatcher, - CreateContextFn create_root_context_for_testing) { - return std::static_pointer_cast(proxy_wasm::getOrCreateThreadLocalWasm( +PluginHandleSharedPtr +getOrCreateThreadLocalPlugin(const WasmHandleSharedPtr& base_wasm, const PluginSharedPtr& plugin, + Event::Dispatcher& dispatcher, + CreateContextFn create_root_context_for_testing) { + if (!base_wasm) { + if (!plugin->fail_open_) { + ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::wasm), critical, + "Plugin configured to fail closed failed to load"); + } + return nullptr; + } + return std::static_pointer_cast(proxy_wasm::getOrCreateThreadLocalPlugin( std::static_pointer_cast(base_wasm), plugin, - getCloneFactory(getWasmExtension(), dispatcher, create_root_context_for_testing))); + getCloneFactory(getWasmExtension(), dispatcher, create_root_context_for_testing), + getPluginFactory(getWasmExtension()))); } } // namespace Wasm diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index a812d1a1a522..8ade0d66e63d 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -11,7 +11,7 @@ #include "envoy/server/lifecycle_notifier.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" #include "envoy/upstream/cluster_manager.h" #include "common/common/assert.h" @@ -54,8 +54,8 @@ class Wasm : public WasmBase, Logger::Loggable { Upstream::ClusterManager& clusterManager() const { return cluster_manager_; } Event::Dispatcher& dispatcher() { return dispatcher_; } - Context* getRootContext(absl::string_view root_id) { - return static_cast(WasmBase::getRootContext(root_id)); + Context* getRootContext(const std::shared_ptr& plugin, bool allow_closed) { + return static_cast(WasmBase::getRootContext(plugin, allow_closed)); } void setTimerPeriod(uint32_t root_context_id, std::chrono::milliseconds period) override; virtual void tickHandler(uint32_t root_context_id); @@ -72,12 +72,13 @@ class Wasm : public WasmBase, Logger::Loggable { void getFunctions() override; // AccessLog::Instance - void log(absl::string_view root_id, const Http::RequestHeaderMap* request_headers, + void log(const PluginSharedPtr& plugin, const Http::RequestHeaderMap* request_headers, const Http::ResponseHeaderMap* response_headers, const Http::ResponseTrailerMap* response_trailers, const StreamInfo::StreamInfo& stream_info); - void onStatsUpdate(absl::string_view root_id, Envoy::Stats::MetricSnapshot& snapshot); + void onStatsUpdate(const PluginSharedPtr& plugin, Envoy::Stats::MetricSnapshot& snapshot); + virtual std::string buildVersion() { return BUILD_VERSION_NUMBER; } void initializeLifecycle(Server::ServerLifecycleNotifier& lifecycle_notifier); @@ -136,6 +137,23 @@ class WasmHandle : public WasmHandleBase, public ThreadLocal::ThreadLocalObject WasmSharedPtr wasm_; }; +using WasmHandleSharedPtr = std::shared_ptr; + +class PluginHandle : public PluginHandleBase, public ThreadLocal::ThreadLocalObject { +public: + explicit PluginHandle(const WasmHandleSharedPtr& wasm_handle, absl::string_view plugin_key) + : PluginHandleBase(std::static_pointer_cast(wasm_handle), plugin_key), + wasm_handle_(wasm_handle) {} + + WasmSharedPtr& wasm() { return wasm_handle_->wasm(); } + WasmHandleSharedPtr& wasmHandleForTest() { return wasm_handle_; } + +private: + WasmHandleSharedPtr wasm_handle_; +}; + +using PluginHandleSharedPtr = std::shared_ptr; + using CreateWasmCallback = std::function; // Returns false if createWasm failed synchronously. This is necessary because xDS *MUST* report @@ -150,10 +168,10 @@ bool createWasm(const VmConfig& vm_config, const PluginSharedPtr& plugin, CreateWasmCallback&& callback, CreateContextFn create_root_context_for_testing = nullptr); -WasmHandleSharedPtr -getOrCreateThreadLocalWasm(const WasmHandleSharedPtr& base_wasm, const PluginSharedPtr& plugin, - Event::Dispatcher& dispatcher, - CreateContextFn create_root_context_for_testing = nullptr); +PluginHandleSharedPtr +getOrCreateThreadLocalPlugin(const WasmHandleSharedPtr& base_wasm, const PluginSharedPtr& plugin, + Event::Dispatcher& dispatcher, + CreateContextFn create_root_context_for_testing = nullptr); void clearCodeCacheForTesting(); std::string anyToBytes(const ProtobufWkt::Any& any); diff --git a/source/extensions/common/wasm/wasm_extension.cc b/source/extensions/common/wasm/wasm_extension.cc index c75168f1761c..1917fa792a82 100644 --- a/source/extensions/common/wasm/wasm_extension.cc +++ b/source/extensions/common/wasm/wasm_extension.cc @@ -46,6 +46,14 @@ EnvoyWasm::createEnvoyWasmVmIntegration(const Stats::ScopeSharedPtr& scope, return std::make_unique(scope, runtime, short_runtime); } +PluginHandleExtensionFactory EnvoyWasm::pluginFactory() { + return [](const WasmHandleSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::static_pointer_cast( + std::make_shared(base_wasm, plugin_key)); + }; +} + WasmHandleExtensionFactory EnvoyWasm::wasmFactory() { return [](const VmConfig vm_config, const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher, diff --git a/source/extensions/common/wasm/wasm_extension.h b/source/extensions/common/wasm/wasm_extension.h index 5d41a58bb337..22ae373162f2 100644 --- a/source/extensions/common/wasm/wasm_extension.h +++ b/source/extensions/common/wasm/wasm_extension.h @@ -31,6 +31,8 @@ class EnvoyWasmVmIntegration; using WasmHandleSharedPtr = std::shared_ptr; using CreateContextFn = std::function& plugin)>; +using PluginHandleExtensionFactory = std::function; using WasmHandleExtensionFactory = std::function { virtual std::unique_ptr createEnvoyWasmVmIntegration(const Stats::ScopeSharedPtr& scope, absl::string_view runtime, absl::string_view short_runtime) = 0; + virtual PluginHandleExtensionFactory pluginFactory() = 0; virtual WasmHandleExtensionFactory wasmFactory() = 0; virtual WasmHandleExtensionCloneFactory wasmCloneFactory() = 0; enum class WasmEvent : int { @@ -100,6 +103,7 @@ class EnvoyWasm : public WasmExtension { std::unique_ptr createEnvoyWasmVmIntegration(const Stats::ScopeSharedPtr& scope, absl::string_view runtime, absl::string_view short_runtime) override; + PluginHandleExtensionFactory pluginFactory() override; WasmHandleExtensionFactory wasmFactory() override; WasmHandleExtensionCloneFactory wasmCloneFactory() override; void onEvent(WasmEvent event, const PluginSharedPtr& plugin) override; diff --git a/source/extensions/common/wasm/wasm_runtime_factory.h b/source/extensions/common/wasm/wasm_runtime_factory.h new file mode 100644 index 000000000000..d0d589705f91 --- /dev/null +++ b/source/extensions/common/wasm/wasm_runtime_factory.h @@ -0,0 +1,29 @@ +#pragma once + +#include "envoy/common/pure.h" + +#include "absl/strings/string_view.h" +#include "include/proxy-wasm/wasm_vm.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +using WasmVmPtr = std::unique_ptr; + +class WasmRuntimeFactory { +public: + virtual ~WasmRuntimeFactory() = default; + virtual WasmVmPtr createWasmVm() PURE; + + virtual absl::string_view name() PURE; + virtual absl::string_view shortName() PURE; + + std::string category() { return "envoy.wasm.runtime"; } +}; + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/wasm_state.cc b/source/extensions/common/wasm/wasm_state.cc index 573523f1d83e..7062afa0ab9d 100644 --- a/source/extensions/common/wasm/wasm_state.cc +++ b/source/extensions/common/wasm/wasm_state.cc @@ -1,5 +1,6 @@ #include "extensions/common/wasm/wasm_state.h" +#include "eval/public/structs/cel_proto_wrapper.h" #include "flatbuffers/reflection.h" #include "tools/flatbuffers_backed_impl.h" @@ -23,7 +24,7 @@ CelValue WasmState::exprValue(Protobuf::Arena* arena, bool last) const { } // Note that this is very expensive since it incurs a de-serialization const auto any = serializeAsProto(); - return CelValue::CreateMessage(any.get(), arena); + return google::api::expr::runtime::CelProtoWrapper::CreateMessage(any.get(), arena); } case WasmType::FlatBuffers: if (last) { diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index 9f888b18f8f5..b7b8ec0c6734 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -6,18 +6,11 @@ #include "extensions/common/wasm/context.h" #include "extensions/common/wasm/ext/envoy_null_vm_wasm_api.h" #include "extensions/common/wasm/wasm_extension.h" +#include "extensions/common/wasm/wasm_runtime_factory.h" #include "extensions/common/wasm/well_known_names.h" -#include "include/proxy-wasm/null.h" #include "include/proxy-wasm/null_plugin.h" -#if defined(ENVOY_WASM_V8) -#include "include/proxy-wasm/v8.h" -#endif -#if defined(ENVOY_WASM_WAVM) -#include "include/proxy-wasm/wavm.h" -#endif - using ContextBase = proxy_wasm::ContextBase; using Word = proxy_wasm::Word; @@ -67,29 +60,21 @@ WasmVmPtr createWasmVm(absl::string_view runtime, const Stats::ScopeSharedPtr& s ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::wasm), warn, "Failed to create Wasm VM with unspecified runtime"); return nullptr; - } else if (runtime == WasmRuntimeNames::get().Null) { - auto wasm = proxy_wasm::createNullVm(); - wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "null"); - return wasm; -#if defined(ENVOY_WASM_V8) - } else if (runtime == WasmRuntimeNames::get().V8) { - auto wasm = proxy_wasm::createV8Vm(); - wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "v8"); - return wasm; -#endif -#if defined(ENVOY_WASM_WAVM) - } else if (runtime == WasmRuntimeNames::get().Wavm) { - auto wasm = proxy_wasm::createWavmVm(); - wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "wavm"); - return wasm; -#endif - } else { + } + + auto runtime_factory = Registry::FactoryRegistry::getFactory(runtime); + if (runtime_factory == nullptr) { ENVOY_LOG_TO_LOGGER( Envoy::Logger::Registry::getLog(Envoy::Logger::Id::wasm), warn, "Failed to create Wasm VM using {} runtime. Envoy was compiled without support for it", runtime); return nullptr; } + + auto wasm = runtime_factory->createWasmVm(); + wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration( + scope, runtime_factory->name(), runtime_factory->shortName()); + return wasm; } } // namespace Wasm diff --git a/source/extensions/common/wasm/well_known_names.h b/source/extensions/common/wasm/well_known_names.h index 5fb8602bf831..3904868bba88 100644 --- a/source/extensions/common/wasm/well_known_names.h +++ b/source/extensions/common/wasm/well_known_names.h @@ -15,6 +15,8 @@ namespace Wasm { */ class WasmRuntimeValues { public: + // Wasmtime (https://github.com/bytecodealliance/wasmtime). + const std::string Wasmtime = "envoy.wasm.runtime.wasmtime"; // WAVM (https://github.com/WAVM/WAVM) Wasm VM. const std::string Wavm = "envoy.wasm.runtime.wavm"; // Null sandbox: modules must be compiled into envoy and registered name is given in the diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index e3ec724d9339..8a48fb678264 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -166,6 +166,7 @@ EXTENSIONS = { "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", # # Transport sockets @@ -213,8 +214,17 @@ EXTENSIONS = { # # Watchdog actions # + "envoy.watchdog.profile_action": "//source/extensions/watchdog/profile_action:config", + # + # WebAssembly runtimes + # + + "envoy.wasm.runtime.null": "//source/extensions/wasm_runtime/null:config", + "envoy.wasm.runtime.v8": "//source/extensions/wasm_runtime/v8:config", + "envoy.wasm.runtime.wavm": "//source/extensions/wasm_runtime/wavm:config", + "envoy.wasm.runtime.wasmtime": "//source/extensions/wasm_runtime/wasmtime:config", } # These can be changed to ["//visibility:public"], for downstream builds which diff --git a/source/extensions/filters/common/expr/BUILD b/source/extensions/filters/common/expr/BUILD index fbbcd725ba43..2e51de38daaa 100644 --- a/source/extensions/filters/common/expr/BUILD +++ b/source/extensions/filters/common/expr/BUILD @@ -34,6 +34,7 @@ envoy_cc_library( "//source/common/stream_info:utility_lib", "@com_google_cel_cpp//eval/public:cel_value", "@com_google_cel_cpp//eval/public:cel_value_producer", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc index 9313a550695e..a0298a5095b4 100644 --- a/source/extensions/filters/common/expr/context.cc +++ b/source/extensions/filters/common/expr/context.cc @@ -190,6 +190,11 @@ absl::optional ConnectionWrapper::operator[](CelValue key) const { return CelValue::CreateUint64(id.value()); } return {}; + } else if (value == ConnectionTerminationDetails) { + if (info_.connectionTerminationDetails().has_value()) { + return CelValue::CreateString(&info_.connectionTerminationDetails().value()); + } + return {}; } auto ssl_info = info_.downstreamSslConnection(); diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index fd4b386a9a32..ad6fcb972160 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -9,6 +9,7 @@ #include "eval/public/cel_value.h" #include "eval/public/cel_value_producer.h" +#include "eval/public/structs/cel_proto_wrapper.h" namespace Envoy { namespace Extensions { @@ -17,6 +18,7 @@ namespace Common { namespace Expr { using CelValue = google::api::expr::runtime::CelValue; +using CelProtoWrapper = google::api::expr::runtime::CelProtoWrapper; // Symbols for traversing the request properties constexpr absl::string_view Request = "request"; @@ -54,6 +56,7 @@ constexpr absl::string_view Connection = "connection"; constexpr absl::string_view MTLS = "mtls"; constexpr absl::string_view RequestedServerName = "requested_server_name"; constexpr absl::string_view TLSVersion = "tls_version"; +constexpr absl::string_view ConnectionTerminationDetails = "termination_details"; constexpr absl::string_view SubjectLocalCertificate = "subject_local_certificate"; constexpr absl::string_view SubjectPeerCertificate = "subject_peer_certificate"; constexpr absl::string_view URISanLocalCertificate = "uri_san_local_certificate"; @@ -187,7 +190,7 @@ class MetadataProducer : public google::api::expr::runtime::CelValueProducer { public: MetadataProducer(const envoy::config::core::v3::Metadata& metadata) : metadata_(metadata) {} CelValue Produce(ProtobufWkt::Arena* arena) override { - return CelValue::CreateMessage(&metadata_, arena); + return CelProtoWrapper::CreateMessage(&metadata_, arena); } private: diff --git a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc index cfb417d6c927..5340af7d3eb3 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc @@ -142,23 +142,6 @@ void GrpcClientImpl::toAuthzResponseHeader( } } -const Grpc::RawAsyncClientSharedPtr AsyncClientCache::getOrCreateAsyncClient( - const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& proto_config) { - // The cache stores Google gRPC client, so channel is not created for each request. - ASSERT(proto_config.has_grpc_service() && proto_config.grpc_service().has_google_grpc()); - auto& cache = tls_slot_->getTyped(); - const std::size_t cache_key = MessageUtil::hash(proto_config.grpc_service().google_grpc()); - const auto it = cache.async_clients_.find(cache_key); - if (it != cache.async_clients_.end()) { - return it->second; - } - const Grpc::AsyncClientFactoryPtr factory = - async_client_manager_.factoryForGrpcService(proto_config.grpc_service(), scope_, true); - const Grpc::RawAsyncClientSharedPtr async_client = factory->create(); - cache.async_clients_.emplace(cache_key, async_client); - return async_client; -} - } // namespace ExtAuthz } // namespace Common } // namespace Filters diff --git a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h index 792f7bb748cc..9db0831a1274 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h +++ b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h @@ -81,39 +81,6 @@ class GrpcClientImpl : public Client, using GrpcClientImplPtr = std::unique_ptr; -// The client cache for RawAsyncClient for Google grpc so channel is not created for each request. -// TODO(fpliu233): The cache will cause resource leak that a new channel is created every time a new -// config is pushed. Improve gRPC channel cache with better solution. -class AsyncClientCache : public Singleton::Instance { -public: - AsyncClientCache(Grpc::AsyncClientManager& async_client_manager, Stats::Scope& scope, - ThreadLocal::SlotAllocator& tls) - : async_client_manager_(async_client_manager), scope_(scope), tls_slot_(tls.allocateSlot()) { - tls_slot_->set([](Event::Dispatcher&) { return std::make_shared(); }); - } - - const Grpc::RawAsyncClientSharedPtr getOrCreateAsyncClient( - const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& proto_config); - -private: - /** - * Per-thread cache. - */ - struct ThreadLocalCache : public ThreadLocal::ThreadLocalObject { - ThreadLocalCache() = default; - // The client cache stored with key as hash of - // envoy::config::core::v3::GrpcService::GoogleGrpc config. - // TODO(fpliu233): Remove when the cleaner and generic solution for gRPC is live. - absl::flat_hash_map async_clients_; - }; - - Grpc::AsyncClientManager& async_client_manager_; - Stats::Scope& scope_; - ThreadLocal::SlotPtr tls_slot_; -}; - -using AsyncClientCacheSharedPtr = std::shared_ptr; - } // namespace ExtAuthz } // namespace Common } // namespace Filters diff --git a/source/extensions/filters/common/lua/lua.cc b/source/extensions/filters/common/lua/lua.cc index b5be8771cdac..f23f968c9e7f 100644 --- a/source/extensions/filters/common/lua/lua.cc +++ b/source/extensions/filters/common/lua/lua.cc @@ -49,7 +49,7 @@ void Coroutine::resume(int num_args, const std::function& yield_callback } ThreadLocalState::ThreadLocalState(const std::string& code, ThreadLocal::SlotAllocator& tls) - : tls_slot_(tls.allocateSlot()) { + : tls_slot_(ThreadLocal::TypedSlot::makeUnique(tls)) { // First verify that the supplied code can be parsed. CSmartPtr state(lua_open()); @@ -61,36 +61,32 @@ ThreadLocalState::ThreadLocalState(const std::string& code, ThreadLocal::SlotAll } // Now initialize on all threads. - tls_slot_->set([code](Event::Dispatcher&) { - return ThreadLocal::ThreadLocalObjectSharedPtr{new LuaThreadLocal(code)}; - }); + tls_slot_->set([code](Event::Dispatcher&) { return std::make_shared(code); }); } int ThreadLocalState::getGlobalRef(uint64_t slot) { - LuaThreadLocal& tls = tls_slot_->getTyped(); + LuaThreadLocal& tls = **tls_slot_; ASSERT(tls.global_slots_.size() > slot); return tls.global_slots_[slot]; } uint64_t ThreadLocalState::registerGlobal(const std::string& global) { - tls_slot_->runOnAllThreads([global](ThreadLocal::ThreadLocalObjectSharedPtr previous) { - LuaThreadLocal& tls = *std::dynamic_pointer_cast(previous); - lua_getglobal(tls.state_.get(), global.c_str()); - if (lua_isfunction(tls.state_.get(), -1)) { - tls.global_slots_.push_back(luaL_ref(tls.state_.get(), LUA_REGISTRYINDEX)); + tls_slot_->runOnAllThreads([global](OptRef tls) { + lua_getglobal(tls->state_.get(), global.c_str()); + if (lua_isfunction(tls->state_.get(), -1)) { + tls->global_slots_.push_back(luaL_ref(tls->state_.get(), LUA_REGISTRYINDEX)); } else { ENVOY_LOG(debug, "definition for '{}' not found in script", global); - lua_pop(tls.state_.get(), 1); - tls.global_slots_.push_back(LUA_REFNIL); + lua_pop(tls->state_.get(), 1); + tls->global_slots_.push_back(LUA_REFNIL); } - return previous; }); return current_global_slot_++; } CoroutinePtr ThreadLocalState::createCoroutine() { - lua_State* state = tls_slot_->getTyped().state_.get(); + lua_State* state = tlsState().get(); return std::make_unique(std::make_pair(lua_newthread(state), state)); } diff --git a/source/extensions/filters/common/lua/lua.h b/source/extensions/filters/common/lua/lua.h index 726b6c149e16..6112df91b0de 100644 --- a/source/extensions/filters/common/lua/lua.h +++ b/source/extensions/filters/common/lua/lua.h @@ -386,27 +386,23 @@ class ThreadLocalState : Logger::Loggable { * all threaded workers. */ template void registerType() { - tls_slot_->runOnAllThreads([](ThreadLocal::ThreadLocalObjectSharedPtr previous) { - LuaThreadLocal& tls = *std::dynamic_pointer_cast(previous); - T::registerType(tls.state_.get()); - return previous; - }); + tls_slot_->runOnAllThreads( + [](OptRef tls) { T::registerType(tls->state_.get()); }); } /** * Return the number of bytes used by the runtime. */ uint64_t runtimeBytesUsed() { - uint64_t bytes_used = - lua_gc(tls_slot_->getTyped().state_.get(), LUA_GCCOUNT, 0) * 1024; - bytes_used += lua_gc(tls_slot_->getTyped().state_.get(), LUA_GCCOUNTB, 0); + uint64_t bytes_used = lua_gc(tlsState().get(), LUA_GCCOUNT, 0) * 1024; + bytes_used += lua_gc(tlsState().get(), LUA_GCCOUNTB, 0); return bytes_used; } /** * Force a full runtime GC. */ - void runtimeGC() { lua_gc(tls_slot_->getTyped().state_.get(), LUA_GCCOLLECT, 0); } + void runtimeGC() { lua_gc(tlsState().get(), LUA_GCCOLLECT, 0); } private: struct LuaThreadLocal : public ThreadLocal::ThreadLocalObject { @@ -416,7 +412,9 @@ class ThreadLocalState : Logger::Loggable { std::vector global_slots_; }; - ThreadLocal::SlotPtr tls_slot_; + CSmartPtr& tlsState() { return (*tls_slot_)->state_; } + + ThreadLocal::TypedSlotPtr tls_slot_; uint64_t current_global_slot_{}; }; diff --git a/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc b/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc index d8063ec6723f..654e0aa252a9 100644 --- a/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc +++ b/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc @@ -132,7 +132,7 @@ std::chrono::milliseconds GradientController::applyJitter(std::chrono::milliseco return interval; } - const uint32_t jitter_range_ms = interval.count() * jitter_pct; + const uint32_t jitter_range_ms = std::ceil(interval.count() * jitter_pct); return std::chrono::milliseconds(interval.count() + (random_.random() % jitter_range_ms)); } diff --git a/source/extensions/filters/http/admission_control/BUILD b/source/extensions/filters/http/admission_control/BUILD index 07acbda5fe58..9dab0fc8f6bc 100644 --- a/source/extensions/filters/http/admission_control/BUILD +++ b/source/extensions/filters/http/admission_control/BUILD @@ -25,6 +25,7 @@ envoy_cc_extension( deps = [ "//include/envoy/http:filter_interface", "//include/envoy/runtime:runtime_interface", + "//include/envoy/thread_local:thread_local_object", "//source/common/common:cleanup_lib", "//source/common/http:codes_lib", "//source/common/runtime:runtime_lib", diff --git a/source/extensions/filters/http/admission_control/admission_control.cc b/source/extensions/filters/http/admission_control/admission_control.cc index 0875429bd2df..9329e10ab351 100644 --- a/source/extensions/filters/http/admission_control/admission_control.cc +++ b/source/extensions/filters/http/admission_control/admission_control.cc @@ -34,7 +34,8 @@ static constexpr double defaultSuccessRateThreshold = 95.0; AdmissionControlFilterConfig::AdmissionControlFilterConfig( const AdmissionControlProto& proto_config, Runtime::Loader& runtime, - Random::RandomGenerator& random, Stats::Scope& scope, ThreadLocal::SlotPtr&& tls, + Random::RandomGenerator& random, Stats::Scope& scope, + ThreadLocal::TypedSlotPtr&& tls, std::shared_ptr response_evaluator) : random_(random), scope_(scope), tls_(std::move(tls)), admission_control_feature_(proto_config.enabled(), runtime), diff --git a/source/extensions/filters/http/admission_control/admission_control.h b/source/extensions/filters/http/admission_control/admission_control.h index 99554d5e5e76..7b4e83de80c7 100644 --- a/source/extensions/filters/http/admission_control/admission_control.h +++ b/source/extensions/filters/http/admission_control/admission_control.h @@ -55,13 +55,11 @@ class AdmissionControlFilterConfig { public: AdmissionControlFilterConfig(const AdmissionControlProto& proto_config, Runtime::Loader& runtime, Random::RandomGenerator& random, Stats::Scope& scope, - ThreadLocal::SlotPtr&& tls, + ThreadLocal::TypedSlotPtr&& tls, std::shared_ptr response_evaluator); virtual ~AdmissionControlFilterConfig() = default; - virtual ThreadLocalController& getController() const { - return tls_->getTyped(); - } + virtual ThreadLocalController& getController() const { return **tls_; } Random::RandomGenerator& random() const { return random_; } bool filterEnabled() const { return admission_control_feature_.enabled(); } @@ -73,7 +71,7 @@ class AdmissionControlFilterConfig { private: Random::RandomGenerator& random_; Stats::Scope& scope_; - const ThreadLocal::SlotPtr tls_; + const ThreadLocal::TypedSlotPtr tls_; Runtime::FeatureFlag admission_control_feature_; std::unique_ptr aggression_; std::unique_ptr sr_threshold_; diff --git a/source/extensions/filters/http/admission_control/config.cc b/source/extensions/filters/http/admission_control/config.cc index 01aef0125bbf..fcd8c42fcc9b 100644 --- a/source/extensions/filters/http/admission_control/config.cc +++ b/source/extensions/filters/http/admission_control/config.cc @@ -29,14 +29,13 @@ Http::FilterFactoryCb AdmissionControlFilterFactory::createFilterFactoryFromProt const std::string prefix = stats_prefix + "admission_control."; // Create the thread-local controller. - auto tls = context.threadLocal().allocateSlot(); + auto tls = ThreadLocal::TypedSlot::makeUnique(context.threadLocal()); auto sampling_window = std::chrono::seconds( PROTOBUF_GET_MS_OR_DEFAULT(config, sampling_window, 1000 * defaultSamplingWindow.count()) / 1000); - tls->set( - [sampling_window, &context](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(context.timeSource(), sampling_window); - }); + tls->set([sampling_window, &context](Event::Dispatcher&) { + return std::make_shared(context.timeSource(), sampling_window); + }); std::unique_ptr response_evaluator; switch (config.evaluation_criteria_case()) { diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.h b/source/extensions/filters/http/admission_control/thread_local_controller.h index 11f938758177..fde56131ecd8 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.h +++ b/source/extensions/filters/http/admission_control/thread_local_controller.h @@ -3,7 +3,7 @@ #include "envoy/common/pure.h" #include "envoy/common/time.h" #include "envoy/http/codes.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" namespace Envoy { namespace Extensions { diff --git a/source/extensions/filters/http/cdn_loop/filter.cc b/source/extensions/filters/http/cdn_loop/filter.cc index 9cc81ca0a928..38158011502e 100644 --- a/source/extensions/filters/http/cdn_loop/filter.cc +++ b/source/extensions/filters/http/cdn_loop/filter.cc @@ -33,7 +33,7 @@ Http::FilterHeadersStatus CdnLoopFilter::decodeHeaders(Http::RequestHeaderMap& h header_entry != nullptr) { if (StatusOr count = countCdnLoopOccurrences(header_entry->value().getStringView(), cdn_id_); - !count) { + !count.ok()) { decoder_callbacks_->sendLocalReply(Http::Code::BadRequest, ParseErrorMessage, nullptr, absl::nullopt, ParseErrorDetails); return Http::FilterHeadersStatus::StopIteration; diff --git a/source/extensions/filters/http/cdn_loop/parser.cc b/source/extensions/filters/http/cdn_loop/parser.cc index e686406c2fd3..6f37ac5eb3fa 100644 --- a/source/extensions/filters/http/cdn_loop/parser.cc +++ b/source/extensions/filters/http/cdn_loop/parser.cc @@ -118,7 +118,7 @@ StatusOr parseQuotedString(const ParseContext& input) { continue; } else if (context.peek() == '\\') { if (StatusOr quoted_pair_context = parseQuotedPair(context); - !quoted_pair_context) { + !quoted_pair_context.ok()) { return quoted_pair_context.status(); } else { context.setNext(*quoted_pair_context); @@ -222,13 +222,13 @@ StatusOr parseCdnId(const ParseContext& input) { // Optimization: dispatch on the next character to avoid the StrFormat in the // error path of an IPv6 parser when the value has a token (and vice versa). if (context.peek() == '[') { - if (StatusOr ipv6 = parsePlausibleIpV6(context); !ipv6) { + if (StatusOr ipv6 = parsePlausibleIpV6(context); !ipv6.ok()) { return ipv6.status(); } else { context.setNext(*ipv6); } } else { - if (StatusOr token = parseToken(context); !token) { + if (StatusOr token = parseToken(context); !token.ok()) { return token.status(); } else { context.setNext(*token); @@ -260,7 +260,7 @@ StatusOr parseCdnId(const ParseContext& input) { StatusOr parseParameter(const ParseContext& input) { ParseContext context = input; - if (StatusOr parsed_token = parseToken(context); !parsed_token) { + if (StatusOr parsed_token = parseToken(context); !parsed_token.ok()) { return parsed_token.status(); } else { context.setNext(*parsed_token); @@ -286,13 +286,13 @@ StatusOr parseParameter(const ParseContext& input) { // error path of an quoted string parser when the next item is a token (and // vice versa). if (context.peek() == '"') { - if (StatusOr value_quote = parseQuotedString(context); !value_quote) { + if (StatusOr value_quote = parseQuotedString(context); !value_quote.ok()) { return value_quote.status(); } else { return *value_quote; } } else { - if (StatusOr value_token = parseToken(context); !value_token) { + if (StatusOr value_token = parseToken(context); !value_token.ok()) { return value_token.status(); } else { return *value_token; @@ -303,7 +303,7 @@ StatusOr parseParameter(const ParseContext& input) { StatusOr parseCdnInfo(const ParseContext& input) { absl::string_view cdn_id; ParseContext context = input; - if (StatusOr parsed_id = parseCdnId(input); !parsed_id) { + if (StatusOr parsed_id = parseCdnId(input); !parsed_id.ok()) { return parsed_id.status(); } else { context.setNext(parsed_id->context()); @@ -320,7 +320,7 @@ StatusOr parseCdnInfo(const ParseContext& input) { context.setNext(skipOptionalWhitespace(context)); - if (StatusOr parameter = parseParameter(context); !parameter) { + if (StatusOr parameter = parseParameter(context); !parameter.ok()) { return parameter.status(); } else { context.setNext(*parameter); @@ -348,7 +348,7 @@ StatusOr parseCdnInfoList(const ParseContext& input) { continue; } - if (StatusOr parsed_cdn_info = parseCdnInfo(context); !parsed_cdn_info) { + if (StatusOr parsed_cdn_info = parseCdnInfo(context); !parsed_cdn_info.ok()) { return parsed_cdn_info.status(); } else { cdn_infos.push_back(parsed_cdn_info->cdnId()); diff --git a/source/extensions/filters/http/cdn_loop/utils.cc b/source/extensions/filters/http/cdn_loop/utils.cc index 2ea1a6a1945d..e7c478509910 100644 --- a/source/extensions/filters/http/cdn_loop/utils.cc +++ b/source/extensions/filters/http/cdn_loop/utils.cc @@ -19,7 +19,8 @@ StatusOr countCdnLoopOccurrences(absl::string_view header, absl::string_vie return absl::InvalidArgumentError("cdn_id cannot be empty"); } - if (absl::StatusOr parsed = Parser::parseCdnInfoList(header); parsed) { + if (absl::StatusOr parsed = Parser::parseCdnInfoList(header); + parsed.ok()) { return std::count(parsed->cdnIds().begin(), parsed->cdnIds().end(), cdn_id); } else { return parsed.status(); diff --git a/source/extensions/filters/http/ext_authz/BUILD b/source/extensions/filters/http/ext_authz/BUILD index 0d789c30c048..c7583679783b 100644 --- a/source/extensions/filters/http/ext_authz/BUILD +++ b/source/extensions/filters/http/ext_authz/BUILD @@ -45,6 +45,7 @@ envoy_cc_extension( ":ext_authz", "//include/envoy/registry", "//include/envoy/stats:stats_macros", + "//source/common/grpc:google_async_client_cache", "//source/common/protobuf:utility_lib", "//source/extensions/filters/common/ext_authz:ext_authz_http_lib", "//source/extensions/filters/http:well_known_names", diff --git a/source/extensions/filters/http/ext_authz/config.cc b/source/extensions/filters/http/ext_authz/config.cc index 788740278af1..01c77e4705d7 100644 --- a/source/extensions/filters/http/ext_authz/config.cc +++ b/source/extensions/filters/http/ext_authz/config.cc @@ -8,6 +8,7 @@ #include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.validate.h" #include "envoy/registry/registry.h" +#include "common/grpc/google_async_client_cache.h" #include "common/protobuf/utility.h" #include "common/runtime/runtime_features.h" @@ -52,13 +53,18 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoTyped( const uint32_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config.grpc_service(), timeout, DefaultTimeout); - auto async_client_cache = getAsyncClientCacheSingleton(context); + Grpc::AsyncClientCacheSingletonSharedPtr async_client_cache_singleton = + Grpc::getAsyncClientCacheSingleton(context.getServerFactoryContext()); + Grpc::AsyncClientCacheSharedPtr async_client_cache = + async_client_cache_singleton->getOrCreateAsyncClientCache( + context.clusterManager().grpcAsyncClientManager(), context.scope(), + context.threadLocal(), proto_config.grpc_service()); callback = [async_client_cache, filter_config, timeout_ms, proto_config, transport_api_version = proto_config.transport_api_version()]( Http::FilterChainFactoryCallbacks& callbacks) { auto client = std::make_unique( - async_client_cache->getOrCreateAsyncClient(proto_config), - std::chrono::milliseconds(timeout_ms), transport_api_version); + async_client_cache->getAsyncClient(), std::chrono::milliseconds(timeout_ms), + transport_api_version); callbacks.addStreamDecoderFilter(Http::StreamDecoderFilterSharedPtr{ std::make_shared(filter_config, std::move(client))}); }; @@ -103,18 +109,6 @@ ExtAuthzFilterConfig::createRouteSpecificFilterConfigTyped( REGISTER_FACTORY(ExtAuthzFilterConfig, Server::Configuration::NamedHttpFilterConfigFactory){"envoy.ext_authz"}; -SINGLETON_MANAGER_REGISTRATION(google_grpc_async_client_cache); - -Filters::Common::ExtAuthz::AsyncClientCacheSharedPtr -getAsyncClientCacheSingleton(Server::Configuration::FactoryContext& context) { - return context.singletonManager().getTyped( - SINGLETON_MANAGER_REGISTERED_NAME(google_grpc_async_client_cache), [&context] { - return std::make_shared( - context.clusterManager().grpcAsyncClientManager(), context.scope(), - context.threadLocal()); - }); -} - } // namespace ExtAuthz } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/ext_authz/config.h b/source/extensions/filters/http/ext_authz/config.h index 5ca66d423805..600c572f0507 100644 --- a/source/extensions/filters/http/ext_authz/config.h +++ b/source/extensions/filters/http/ext_authz/config.h @@ -34,9 +34,6 @@ class ExtAuthzFilterConfig ProtobufMessage::ValidationVisitor& validator) override; }; -Filters::Common::ExtAuthz::AsyncClientCacheSharedPtr -getAsyncClientCacheSingleton(Server::Configuration::FactoryContext& context); - } // namespace ExtAuthz } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc index e2998a3f5866..d770b9feb51a 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc +++ b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc @@ -188,6 +188,24 @@ JsonTranscoderConfig::JsonTranscoderConfig( } } + switch (proto_config.url_unescape_spec()) { + case envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder:: + ALL_CHARACTERS_EXCEPT_RESERVED: + pmb.SetUrlUnescapeSpec( + google::grpc::transcoding::UrlUnescapeSpec::kAllCharactersExceptReserved); + break; + case envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder:: + ALL_CHARACTERS_EXCEPT_SLASH: + pmb.SetUrlUnescapeSpec(google::grpc::transcoding::UrlUnescapeSpec::kAllCharactersExceptSlash); + break; + case envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder:: + ALL_CHARACTERS: + pmb.SetUrlUnescapeSpec(google::grpc::transcoding::UrlUnescapeSpec::kAllCharacters); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + path_matcher_ = pmb.Build(); const auto& print_config = proto_config.print_options(); diff --git a/source/extensions/filters/http/grpc_web/grpc_web_filter.cc b/source/extensions/filters/http/grpc_web/grpc_web_filter.cc index 6a7fd9b74c32..62d7eda354f9 100644 --- a/source/extensions/filters/http/grpc_web/grpc_web_filter.cc +++ b/source/extensions/filters/http/grpc_web/grpc_web_filter.cc @@ -203,7 +203,7 @@ Http::FilterTrailersStatus GrpcWebFilter::encodeTrailers(Http::ResponseTrailerMa } // Trailers are expected to come all in once, and will be encoded into one single trailers frame. - // Trailers in the trailers frame are separated by CRLFs. + // Trailers in the trailers frame are separated by `CRLFs`. Buffer::OwnedImpl temp; trailers.iterate([&temp](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { temp.add(header.key().getStringView().data(), header.key().size()); diff --git a/source/extensions/filters/http/jwt_authn/authenticator.cc b/source/extensions/filters/http/jwt_authn/authenticator.cc index 1b73eeaf08b2..cf447f68bdaa 100644 --- a/source/extensions/filters/http/jwt_authn/authenticator.cc +++ b/source/extensions/filters/http/jwt_authn/authenticator.cc @@ -141,7 +141,7 @@ void AuthenticatorImpl::startVerify() { jwt_ = std::make_unique<::google::jwt_verify::Jwt>(); ENVOY_LOG(debug, "{}: Parse Jwt {}", name(), curr_token_->token()); - const Status status = jwt_->parseFromString(curr_token_->token()); + Status status = jwt_->parseFromString(curr_token_->token()); if (status != Status::Ok) { doneWithStatus(status); return; @@ -163,33 +163,23 @@ void AuthenticatorImpl::startVerify() { } } - // TODO(qiwzhang): Cross-platform-wise the below unix_timestamp code is wrong as the - // epoch is not guaranteed to be defined as the unix epoch. We should use - // the abseil time functionality instead or use the jwt_verify_lib to check - // the validity of a JWT. - // Check "exp" claim. - const uint64_t unix_timestamp = - std::chrono::duration_cast(timeSource().systemTime().time_since_epoch()) - .count(); - // If the nbf claim does *not* appear in the JWT, then the nbf field is defaulted - // to 0. - if (jwt_->nbf_ > unix_timestamp) { - doneWithStatus(Status::JwtNotYetValid); - return; - } - // If the exp claim does *not* appear in the JWT then the exp field is defaulted - // to 0. - if (jwt_->exp_ > 0 && jwt_->exp_ < unix_timestamp) { - doneWithStatus(Status::JwtExpired); - return; - } - // Check the issuer is configured or not. jwks_data_ = provider_ ? jwks_cache_.findByProvider(provider_.value()) : jwks_cache_.findByIssuer(jwt_->iss_); // isIssuerSpecified() check already make sure the issuer is in the cache. ASSERT(jwks_data_ != nullptr); + // Default is 60 seconds + uint64_t clock_skew_seconds = ::google::jwt_verify::kClockSkewInSecond; + if (jwks_data_->getJwtProvider().clock_skew_seconds() > 0) { + clock_skew_seconds = jwks_data_->getJwtProvider().clock_skew_seconds(); + } + status = jwt_->verifyTimeConstraint(absl::ToUnixSeconds(absl::Now()), clock_skew_seconds); + if (status != Status::Ok) { + doneWithStatus(status); + return; + } + // Check if audience is allowed bool is_allowed = check_audience_ ? check_audience_->areAudiencesAllowed(jwt_->audiences_) : jwks_data_->areAudiencesAllowed(jwt_->audiences_); @@ -247,7 +237,8 @@ void AuthenticatorImpl::onDestroy() { // Verify with a specific public key. void AuthenticatorImpl::verifyKey() { - const Status status = ::google::jwt_verify::verifyJwt(*jwt_, *jwks_data_->getJwksObj()); + const Status status = + ::google::jwt_verify::verifyJwtWithoutTimeChecking(*jwt_, *jwks_data_->getJwksObj()); if (status != Status::Ok) { doneWithStatus(status); return; diff --git a/source/extensions/filters/http/jwt_authn/filter.cc b/source/extensions/filters/http/jwt_authn/filter.cc index 7dfa78a4257b..343ebc1cbeff 100644 --- a/source/extensions/filters/http/jwt_authn/filter.cc +++ b/source/extensions/filters/http/jwt_authn/filter.cc @@ -5,6 +5,7 @@ #include "extensions/filters/http/well_known_names.h" +#include "absl/strings/str_split.h" #include "jwt_verify_lib/status.h" using ::google::jwt_verify::Status; @@ -29,6 +30,14 @@ bool isCorsPreflightRequest(const Http::RequestHeaderMap& headers) { // The prefix used in the response code detail sent from jwt authn filter. constexpr absl::string_view kRcDetailJwtAuthnPrefix = "jwt_authn_access_denied"; + +std::string generateRcDetails(absl::string_view error_msg) { + // Replace space with underscore since RCDetails may be written to access log. + // Some log processors assume each log segment is separated by whitespace. + return absl::StrCat(kRcDetailJwtAuthnPrefix, "{", + absl::StrJoin(absl::StrSplit(error_msg, ' '), "_"), "}"); +} + } // namespace Filter::Filter(FilterConfigSharedPtr config) @@ -56,12 +65,29 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, return Http::FilterHeadersStatus::Continue; } - // Verify the JWT token, onComplete() will be called when completed. - const auto* verifier = - config_->findVerifier(headers, *decoder_callbacks_->streamInfo().filterState()); - if (!verifier) { + const Verifier* verifier = nullptr; + const auto* per_route_config = + Http::Utility::resolveMostSpecificPerFilterConfig( + HttpFilterNames::get().JwtAuthn, decoder_callbacks_->route()); + if (per_route_config != nullptr) { + std::string error_msg; + std::tie(verifier, error_msg) = config_->findPerRouteVerifier(*per_route_config); + if (!error_msg.empty()) { + stats_.denied_.inc(); + state_ = Responded; + decoder_callbacks_->sendLocalReply(Http::Code::Forbidden, + absl::StrCat("Failed JWT authentication: ", error_msg), + nullptr, absl::nullopt, generateRcDetails(error_msg)); + return Http::FilterHeadersStatus::StopIteration; + } + } else { + verifier = config_->findVerifier(headers, *decoder_callbacks_->streamInfo().filterState()); + } + + if (verifier == nullptr) { onComplete(Status::Ok); } else { + // Verify the JWT token, onComplete() will be called when completed. context_ = Verifier::createContext(headers, decoder_callbacks_->activeSpan(), this); verifier->verify(context_); } @@ -94,8 +120,7 @@ void Filter::onComplete(const Status& status) { // return failure reason as message body decoder_callbacks_->sendLocalReply( code, ::google::jwt_verify::getStatusString(status), nullptr, absl::nullopt, - absl::StrCat(kRcDetailJwtAuthnPrefix, "{", ::google::jwt_verify::getStatusString(status), - "}")); + generateRcDetails(::google::jwt_verify::getStatusString(status))); return; } stats_.allowed_.inc(); diff --git a/source/extensions/filters/http/jwt_authn/filter_config.cc b/source/extensions/filters/http/jwt_authn/filter_config.cc index 860236284692..82d0fdd3ae42 100644 --- a/source/extensions/filters/http/jwt_authn/filter_config.cc +++ b/source/extensions/filters/http/jwt_authn/filter_config.cc @@ -1,5 +1,11 @@ #include "extensions/filters/http/jwt_authn/filter_config.h" +#include // std::sort + +#include "common/common/empty_string.h" + +using envoy::extensions::filters::http::jwt_authn::v3::RequirementRule; + namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -17,9 +23,38 @@ void FilterConfigImpl::init() { shared_this->api_); }); + std::vector names; + for (const auto& it : proto_config_.requirement_map()) { + names.push_back(it.first); + name_verifiers_.emplace(it.first, + Verifier::create(it.second, proto_config_.providers(), *this)); + } + // sort is just for unit-test since protobuf map order is not deterministic. + std::sort(names.begin(), names.end()); + all_requirement_names_ = absl::StrJoin(names, ","); + for (const auto& rule : proto_config_.rules()) { - rule_pairs_.emplace_back(Matcher::create(rule), - Verifier::create(rule.requires(), proto_config_.providers(), *this)); + switch (rule.requirement_type_case()) { + case RequirementRule::RequirementTypeCase::kRequires: + rule_pairs_.emplace_back(Matcher::create(rule), + Verifier::create(rule.requires(), proto_config_.providers(), *this)); + break; + case RequirementRule::RequirementTypeCase::kRequirementName: { + // Use requirement_name to lookup requirement_map. + auto map_it = proto_config_.requirement_map().find(rule.requirement_name()); + if (map_it == proto_config_.requirement_map().end()) { + throw EnvoyException(fmt::format("Wrong requirement_name: {}. It should be one of [{}]", + rule.requirement_name(), all_requirement_names_)); + } + rule_pairs_.emplace_back(Matcher::create(rule), + Verifier::create(map_it->second, proto_config_.providers(), *this)); + } break; + case RequirementRule::RequirementTypeCase::REQUIREMENT_TYPE_NOT_SET: + rule_pairs_.emplace_back(Matcher::create(rule), nullptr); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } } if (proto_config_.has_filter_state_rules()) { @@ -31,6 +66,22 @@ void FilterConfigImpl::init() { } } +std::pair +FilterConfigImpl::findPerRouteVerifier(const PerRouteFilterConfig& per_route) const { + if (per_route.config().disabled()) { + return std::make_pair(nullptr, EMPTY_STRING); + } + + const auto& it = name_verifiers_.find(per_route.config().requirement_name()); + if (it != name_verifiers_.end()) { + return std::make_pair(it->second.get(), EMPTY_STRING); + } + + return std::make_pair( + nullptr, absl::StrCat("Wrong requirement_name: ", per_route.config().requirement_name(), + ". It should be one of [", all_requirement_names_, "]")); +} + } // namespace JwtAuthn } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/jwt_authn/filter_config.h b/source/extensions/filters/http/jwt_authn/filter_config.h index 53c7cc965be3..9b6e64b7e6d7 100644 --- a/source/extensions/filters/http/jwt_authn/filter_config.h +++ b/source/extensions/filters/http/jwt_authn/filter_config.h @@ -54,6 +54,23 @@ struct JwtAuthnFilterStats { ALL_JWT_AUTHN_FILTER_STATS(GENERATE_COUNTER_STRUCT) }; +/** + * The per-route filter config + */ +class PerRouteFilterConfig : public Envoy::Router::RouteSpecificFilterConfig { +public: + PerRouteFilterConfig( + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& config) + : config_(config) {} + + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& config() const { + return config_; + } + +private: + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig config_; +}; + /** * The filter config interface. It is an interface so that we can mock it in tests. */ @@ -68,6 +85,10 @@ class FilterConfig { // Finds the matcher that matched the header virtual const Verifier* findVerifier(const Http::RequestHeaderMap& headers, const StreamInfo::FilterState& filter_state) const PURE; + + // Finds the verifier based on per-route config. If fail, pair.second has the error message. + virtual std::pair + findPerRouteVerifier(const PerRouteFilterConfig& per_route) const PURE; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -123,6 +144,9 @@ class FilterConfigImpl : public Logger::Loggable, return nullptr; } + std::pair + findPerRouteVerifier(const PerRouteFilterConfig& per_route) const override; + // methods for AuthFactory interface. Factory method to help create authenticators. AuthenticatorPtr create(const ::google::jwt_verify::CheckAudience* check_audience, const absl::optional& provider, bool allow_failed, @@ -168,6 +192,10 @@ class FilterConfigImpl : public Logger::Loggable, std::string filter_state_name_; // The filter state verifier map from filter_state_rules. absl::flat_hash_map filter_state_verifiers_; + // The requirement_name to verifier map. + absl::flat_hash_map name_verifiers_; + // all requirement_names for debug + std::string all_requirement_names_; TimeSource& time_source_; Api::Api& api_; }; diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.cc b/source/extensions/filters/http/jwt_authn/filter_factory.cc index 60d938a76932..08b2669d65a2 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.cc +++ b/source/extensions/filters/http/jwt_authn/filter_factory.cc @@ -51,6 +51,14 @@ FilterFactory::createFilterFactoryFromProtoTyped(const JwtAuthentication& proto_ }; } +Envoy::Router::RouteSpecificFilterConfigConstSharedPtr +FilterFactory::createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& per_route, + Envoy::Server::Configuration::ServerFactoryContext&, + Envoy::ProtobufMessage::ValidationVisitor&) { + return std::make_shared(per_route); +} + /** * Static registration for this jwt_authn filter. @see RegisterFactory. */ diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.h b/source/extensions/filters/http/jwt_authn/filter_factory.h index b085300edd1d..c0398c71e421 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.h +++ b/source/extensions/filters/http/jwt_authn/filter_factory.h @@ -15,8 +15,9 @@ namespace JwtAuthn { /** * Config registration for jwt_authn filter. */ -class FilterFactory : public Common::FactoryBase< - envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication> { +class FilterFactory + : public Common::FactoryBase { public: FilterFactory() : FactoryBase(HttpFilterNames::get().JwtAuthn) {} @@ -24,6 +25,11 @@ class FilterFactory : public Common::FactoryBase< Http::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; + + Envoy::Router::RouteSpecificFilterConfigConstSharedPtr createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& per_route, + Envoy::Server::Configuration::ServerFactoryContext&, + Envoy::ProtobufMessage::ValidationVisitor&) override; }; } // namespace JwtAuthn diff --git a/source/extensions/filters/http/jwt_authn/verifier.cc b/source/extensions/filters/http/jwt_authn/verifier.cc index e8b613911e8d..cb920c1a5477 100644 --- a/source/extensions/filters/http/jwt_authn/verifier.cc +++ b/source/extensions/filters/http/jwt_authn/verifier.cc @@ -26,7 +26,7 @@ struct CompletionState { // number of completed inner verifier for an any/all verifier. std::size_t number_completed_children_{0}; // A valid error for a RequireAny - Status any_valid_error_{Status::Ok}; + Status status_; }; class ContextImpl : public Verifier::Context { @@ -82,6 +82,8 @@ class BaseVerifierImpl : public Logger::Loggable, public Verifi void completeWithStatus(Status status, ContextImpl& context) const { if (parent_ != nullptr) { + auto& completion_state = context.getCompletionState(this); + completion_state.status_ = status; return parent_->onComplete(status, context); } @@ -113,7 +115,7 @@ class ProviderVerifierImpl : public BaseVerifierImpl { ProviderVerifierImpl(const std::string& provider_name, const AuthFactory& factory, const JwtProvider& provider, const BaseVerifierImpl* parent) : BaseVerifierImpl(parent), auth_factory_(factory), extractor_(Extractor::create(provider)), - provider_name_(absl::make_optional(provider_name)) {} + provider_name_(provider_name) {} void verify(ContextSharedPtr context) const override { auto& ctximpl = static_cast(*context); @@ -140,7 +142,7 @@ class ProviderVerifierImpl : public BaseVerifierImpl { private: const AuthFactory& auth_factory_; const ExtractorConstPtr extractor_; - const absl::optional provider_name_; + const std::string provider_name_; }; class ProviderAndAudienceVerifierImpl : public ProviderVerifierImpl { @@ -227,9 +229,7 @@ class AllowMissingVerifierImpl : public BaseVerifierImpl { VerifierConstPtr innerCreate(const JwtRequirement& requirement, const Protobuf::Map& providers, - const AuthFactory& factory, - const std::vector parent_provider_names, - const BaseVerifierImpl* parent); + const AuthFactory& factory, const BaseVerifierImpl* parent); // Base verifier for requires all or any. class BaseGroupVerifierImpl : public BaseVerifierImpl { @@ -258,39 +258,30 @@ class AnyVerifierImpl : public BaseGroupVerifierImpl { const Protobuf::Map& providers, const BaseVerifierImpl* parent) : BaseGroupVerifierImpl(parent) { - const JwtRequirement* by_pass_type_requirement = nullptr; - std::vector used_providers; + for (const auto& it : or_list.requirements()) { - bool is_regular_requirement = true; switch (it.requires_type_case()) { - case JwtRequirement::RequiresTypeCase::kProviderName: - used_providers.emplace_back(it.provider_name()); - break; - case JwtRequirement::RequiresTypeCase::kProviderAndAudiences: - used_providers.emplace_back(it.provider_and_audiences().provider_name()); - break; case JwtRequirement::RequiresTypeCase::kAllowMissingOrFailed: + is_allow_missing_or_failed_ = true; + break; case JwtRequirement::RequiresTypeCase::kAllowMissing: - is_regular_requirement = false; - if (by_pass_type_requirement == nullptr || - by_pass_type_requirement->requires_type_case() == - JwtRequirement::RequiresTypeCase::kAllowMissing) { - // We need to keep only one by_pass_type_requirement. If both - // kAllowMissing and kAllowMissingOrFailed are set, use - // kAllowMissingOrFailed. - by_pass_type_requirement = ⁢ - } + is_allow_missing_ = true; + break; default: + verifiers_.emplace_back(innerCreate(it, providers, factory, this)); break; } - if (is_regular_requirement) { - verifiers_.emplace_back( - innerCreate(it, providers, factory, std::vector{}, this)); - } } - if (by_pass_type_requirement) { - verifiers_.emplace_back( - innerCreate(*by_pass_type_requirement, providers, factory, used_providers, this)); + + // RequiresAny only has one missing or failed requirement. + if (verifiers_.empty() && (is_allow_missing_or_failed_ || is_allow_missing_)) { + JwtRequirement requirement; + if (is_allow_missing_or_failed_) { + requirement.mutable_allow_missing_or_failed(); + } else { + requirement.mutable_allow_missing(); + } + verifiers_.emplace_back(innerCreate(requirement, providers, factory, this)); } } @@ -299,22 +290,45 @@ class AnyVerifierImpl : public BaseGroupVerifierImpl { if (completion_state.is_completed_) { return; } - // For RequireAny: usually it returns the error from the last provider. - // But if a Jwt is not for a provider, its auth returns JwtMissed or JwtUnknownIssuer. - // Such error should not be used as the final error if there are other valid errors. - if (status != Status::Ok && status != Status::JwtMissed && status != Status::JwtUnknownIssuer) { - completion_state.any_valid_error_ = status; - } - if (++completion_state.number_completed_children_ == verifiers_.size() || - Status::Ok == status) { + + // If any of children is OK, return OK + if (Status::Ok == status) { completion_state.is_completed_ = true; - Status final_status = status; - if (status != Status::Ok && completion_state.any_valid_error_ != Status::Ok) { - final_status = completion_state.any_valid_error_; + completeWithStatus(status, context); + return; + } + + // Then wait for all children to be done. + if (++completion_state.number_completed_children_ == verifiers_.size()) { + // Aggregate all children status into a final status. + // JwtMissing should be treated differently than other failure status + // since it simply means there is not Jwt token for the required provider. + // If there is a failure status other than JwtMissing in the children, + // it should be used as the final status. + Status final_status = Status::JwtMissed; + for (const auto& it : verifiers_) { + // If a Jwt is extracted from a location not specified by the required provider, + // the authenticator returns JwtUnknownIssuer. It should be treated the same as + // JwtMissed. + Status child_status = context.getCompletionState(it.get()).status_; + if (child_status != Status::JwtMissed && child_status != Status::JwtUnknownIssuer) { + final_status = child_status; + } } + + if (is_allow_missing_or_failed_) { + final_status = Status::Ok; + } else if (is_allow_missing_ && final_status == Status::JwtMissed) { + final_status = Status::Ok; + } + completion_state.is_completed_ = true; completeWithStatus(final_status, context); } } + +private: + bool is_allow_missing_or_failed_{false}; + bool is_allow_missing_{false}; }; // Requires all verifier @@ -326,8 +340,7 @@ class AllVerifierImpl : public BaseGroupVerifierImpl { const BaseVerifierImpl* parent) : BaseGroupVerifierImpl(parent) { for (const auto& it : and_list.requirements()) { - verifiers_.emplace_back( - innerCreate(it, providers, factory, std::vector{}, this)); + verifiers_.emplace_back(innerCreate(it, providers, factory, this)); } } @@ -354,21 +367,19 @@ class AllowAllVerifierImpl : public BaseVerifierImpl { } }; +JwtProviderList getAllProvidersAsList(const Protobuf::Map& providers) { + JwtProviderList list; + for (const auto& it : providers) { + list.emplace_back(&it.second); + } + return list; +} + VerifierConstPtr innerCreate(const JwtRequirement& requirement, const Protobuf::Map& providers, - const AuthFactory& factory, - const std::vector parent_provider_names, - const BaseVerifierImpl* parent) { + const AuthFactory& factory, const BaseVerifierImpl* parent) { std::string provider_name; std::vector audiences; - JwtProviderList parent_providers; - for (const auto& name : parent_provider_names) { - const auto& it = providers.find(name); - if (it == providers.end()) { - throw EnvoyException(fmt::format("Required provider ['{}'] is not configured.", name)); - } - parent_providers.emplace_back(&it->second); - } switch (requirement.requires_type_case()) { case JwtRequirement::RequiresTypeCase::kProviderName: provider_name = requirement.provider_name(); @@ -386,9 +397,11 @@ VerifierConstPtr innerCreate(const JwtRequirement& requirement, return std::make_unique(requirement.requires_all(), factory, providers, parent); case JwtRequirement::RequiresTypeCase::kAllowMissingOrFailed: - return std::make_unique(factory, parent_providers, parent); + return std::make_unique(factory, getAllProvidersAsList(providers), + parent); case JwtRequirement::RequiresTypeCase::kAllowMissing: - return std::make_unique(factory, parent_providers, parent); + return std::make_unique(factory, getAllProvidersAsList(providers), + parent); case JwtRequirement::RequiresTypeCase::REQUIRES_TYPE_NOT_SET: return std::make_unique(parent); default: @@ -416,7 +429,7 @@ ContextSharedPtr Verifier::createContext(Http::RequestHeaderMap& headers, VerifierConstPtr Verifier::create(const JwtRequirement& requirement, const Protobuf::Map& providers, const AuthFactory& factory) { - return innerCreate(requirement, providers, factory, std::vector{}, nullptr); + return innerCreate(requirement, providers, factory, nullptr); } } // namespace JwtAuthn diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index 1603b9b417e5..cfd341828b29 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -230,15 +230,18 @@ Http::FilterHeadersStatus OAuth2Filter::decodeHeaders(Http::RequestHeaderMap& he return Http::FilterHeadersStatus::Continue; } + // Save the request headers for later modification if needed. + if (config_->forwardBearerToken()) { + request_headers_ = &headers; + } + // If a bearer token is supplied as a header or param, we ingest it here and kick off the // user resolution immediately. Note this comes after HMAC validation, so technically this // header is sanitized in a way, as the validation check forces the correct Bearer Cookie value. access_token_ = extractAccessToken(headers); if (!access_token_.empty()) { found_bearer_token_ = true; - request_headers_ = &headers; finishFlow(); - return Http::FilterHeadersStatus::Continue; } @@ -327,7 +330,9 @@ bool OAuth2Filter::canSkipOAuth(Http::RequestHeaderMap& headers) const { validator_->setParams(headers, config_->tokenSecret()); if (validator_->isValid()) { config_->stats().oauth_success_.inc(); - setBearerToken(headers, validator_->token()); + if (config_->forwardBearerToken() && !validator_->token().empty()) { + setBearerToken(headers, validator_->token()); + } return true; } @@ -373,7 +378,9 @@ void OAuth2Filter::finishFlow() { // We have fully completed the entire OAuth flow, whether through Authorization header or from // user redirection to the auth server. if (found_bearer_token_) { - setBearerToken(*request_headers_, access_token_); + if (config_->forwardBearerToken()) { + setBearerToken(*request_headers_, access_token_); + } config_->stats().oauth_success_.inc(); decoder_callbacks_->continueDecoding(); return; diff --git a/source/extensions/filters/http/oauth2/oauth_client.cc b/source/extensions/filters/http/oauth2/oauth_client.cc index a25672c81083..71db1ce74020 100644 --- a/source/extensions/filters/http/oauth2/oauth_client.cc +++ b/source/extensions/filters/http/oauth2/oauth_client.cc @@ -39,6 +39,7 @@ void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, Http::RequestMessagePtr request = createPostRequest(); const std::string body = fmt::format(GetAccessTokenBodyFormatString, auth_code, encoded_client_id, encoded_secret, encoded_cb_url); + request->body().add(body); ENVOY_LOG(debug, "Dispatching OAuth request for access token."); dispatchRequest(std::move(request)); 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 ef75595fb7ff..2e392846f3b0 100644 --- a/source/extensions/filters/http/on_demand/on_demand_update.cc +++ b/source/extensions/filters/http/on_demand/on_demand_update.cc @@ -61,7 +61,7 @@ void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool route_exists) { if (route_exists && // route can be resolved after an on-demand // VHDS update !callbacks_->decodingBuffer() && // Redirects with body not yet supported. - callbacks_->recreateStream()) { + callbacks_->recreateStream(/*headers=*/nullptr)) { return; } diff --git a/source/extensions/filters/http/wasm/wasm_filter.cc b/source/extensions/filters/http/wasm/wasm_filter.cc index c62b06c4102d..90713ba01989 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.cc +++ b/source/extensions/filters/http/wasm/wasm_filter.cc @@ -1,14 +1,5 @@ #include "extensions/filters/http/wasm/wasm_filter.h" -#include "envoy/http/codes.h" - -#include "common/buffer/buffer_impl.h" -#include "common/common/assert.h" -#include "common/common/enum_to_int.h" -#include "common/http/header_map_impl.h" -#include "common/http/message_impl.h" -#include "common/http/utility.h" - namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -16,7 +7,8 @@ namespace Wasm { FilterConfig::FilterConfig(const envoy::extensions::filters::http::wasm::v3::Wasm& config, Server::Configuration::FactoryContext& context) - : tls_slot_(context.threadLocal().allocateSlot()) { + : tls_slot_( + ThreadLocal::TypedSlot::makeUnique(context.threadLocal())) { plugin_ = std::make_shared( config.config().name(), config.config().root_id(), config.config().vm_config().vm_id(), config.config().vm_config().runtime(), @@ -26,15 +18,9 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::wasm::v3::Was auto plugin = plugin_; auto callback = [plugin, this](const Common::Wasm::WasmHandleSharedPtr& 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) -> std::shared_ptr { - if (!base_wasm) { - return nullptr; - } - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); - }); + tls_slot_->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); + }); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index 36bfd1503b77..956862e5f09b 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -16,8 +16,9 @@ namespace HttpFilters { namespace Wasm { using Envoy::Extensions::Common::Wasm::Context; +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; using Envoy::Extensions::Common::Wasm::Wasm; -using Envoy::Extensions::Common::Wasm::WasmHandle; class FilterConfig : Logger::Loggable { public: @@ -26,22 +27,23 @@ class FilterConfig : Logger::Loggable { std::shared_ptr createFilter() { Wasm* wasm = nullptr; - if (tls_slot_->get()) { - wasm = tls_slot_->getTyped().wasm().get(); + auto handle = tls_slot_->get(); + if (handle.has_value()) { + wasm = handle->wasm().get(); } if (plugin_->fail_open_ && (!wasm || wasm->isFailed())) { return nullptr; } if (wasm && !root_context_id_) { - root_context_id_ = wasm->getRootContext(plugin_->root_id_)->id(); + root_context_id_ = wasm->getRootContext(plugin_, false)->id(); } return std::make_shared(wasm, root_context_id_, plugin_); } private: uint32_t root_context_id_{0}; - Envoy::Extensions::Common::Wasm::PluginSharedPtr plugin_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + ThreadLocal::TypedSlotPtr tls_slot_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; }; diff --git a/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc b/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc index c06ca93dd2c4..777bf3854968 100644 --- a/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc +++ b/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc @@ -210,6 +210,9 @@ long HessianUtils::peekLong(Buffer::Instance& buffer, size_t* size, uint64_t off case 0xe8: case 0xe9: case 0xea: + case 0xeb: + case 0xec: + case 0xed: case 0xee: case 0xef: @@ -229,6 +232,8 @@ long HessianUtils::peekLong(Buffer::Instance& buffer, size_t* size, uint64_t off case 0xf9: case 0xfa: case 0xfb: + case 0xfc: + case 0xfd: case 0xfe: case 0xff: @@ -340,6 +345,7 @@ int HessianUtils::peekInt(Buffer::Instance& buffer, size_t* size, uint64_t offse case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 93fff20c3ca9..2222f052e590 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -223,6 +223,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( stream_idle_timeout_( PROTOBUF_GET_MS_OR_DEFAULT(config, stream_idle_timeout, StreamIdleTimeoutMs)), request_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, request_timeout, RequestTimeoutMs)), + request_headers_timeout_( + PROTOBUF_GET_MS_OR_DEFAULT(config, request_headers_timeout, RequestHeaderTimeoutMs)), drain_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, drain_timeout, 5000)), generate_request_id_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, generate_request_id, true)), preserve_external_request_id_(config.preserve_external_request_id()), diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 47cc707bdb89..180e67cec294 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -125,6 +125,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } + std::chrono::milliseconds requestHeadersTimeout() const override { + return request_headers_timeout_; + } absl::optional maxStreamDuration() const override { return max_stream_duration_; } @@ -233,6 +236,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, absl::optional max_stream_duration_; std::chrono::milliseconds stream_idle_timeout_; std::chrono::milliseconds request_timeout_; + std::chrono::milliseconds request_headers_timeout_; Router::RouteConfigProviderSharedPtr route_config_provider_; Config::ConfigProviderPtr scoped_routes_config_provider_; std::chrono::milliseconds drain_timeout_; @@ -255,6 +259,8 @@ class HttpConnectionManagerConfig : Logger::Loggable, static const uint64_t StreamIdleTimeoutMs = 5 * 60 * 1000; // request timeout is disabled by default static const uint64_t RequestTimeoutMs = 0; + // request header timeout is disabled by default + static const uint64_t RequestHeaderTimeoutMs = 0; }; /** diff --git a/source/extensions/filters/network/wasm/wasm_filter.cc b/source/extensions/filters/network/wasm/wasm_filter.cc index 9d253b675abd..ccceeb9dc478 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.cc +++ b/source/extensions/filters/network/wasm/wasm_filter.cc @@ -1,9 +1,5 @@ #include "extensions/filters/network/wasm/wasm_filter.h" -#include "common/buffer/buffer_impl.h" -#include "common/common/assert.h" -#include "common/common/enum_to_int.h" - namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -11,7 +7,8 @@ namespace Wasm { FilterConfig::FilterConfig(const envoy::extensions::filters::network::wasm::v3::Wasm& config, Server::Configuration::FactoryContext& context) - : tls_slot_(context.threadLocal().allocateSlot()) { + : tls_slot_( + ThreadLocal::TypedSlot::makeUnique(context.threadLocal())) { plugin_ = std::make_shared( config.config().name(), config.config().root_id(), config.config().vm_config().vm_id(), config.config().vm_config().runtime(), @@ -21,15 +18,9 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::network::wasm::v3:: auto plugin = plugin_; auto callback = [plugin, this](Common::Wasm::WasmHandleSharedPtr 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) -> std::shared_ptr { - if (!base_wasm) { - return nullptr; - } - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); - }); + tls_slot_->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); + }); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index 51adbcd7ac0c..6a6fe2584b2c 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -16,8 +16,9 @@ namespace NetworkFilters { namespace Wasm { using Envoy::Extensions::Common::Wasm::Context; +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; using Envoy::Extensions::Common::Wasm::Wasm; -using Envoy::Extensions::Common::Wasm::WasmHandle; class FilterConfig : Logger::Loggable { public: @@ -26,25 +27,25 @@ class FilterConfig : Logger::Loggable { std::shared_ptr createFilter() { Wasm* wasm = nullptr; - if (tls_slot_->get()) { - wasm = tls_slot_->getTyped().wasm().get(); + auto handle = tls_slot_->get(); + if (handle.has_value()) { + wasm = handle->wasm().get(); } if (plugin_->fail_open_ && (!wasm || wasm->isFailed())) { return nullptr; } if (wasm && !root_context_id_) { - root_context_id_ = wasm->getRootContext(plugin_->root_id_)->id(); + root_context_id_ = wasm->getRootContext(plugin_, false)->id(); } return std::make_shared(wasm, root_context_id_, plugin_); } - Envoy::Extensions::Common::Wasm::Wasm* wasm() { - return tls_slot_->getTyped().wasm().get(); - } + + Wasm* wasmForTest() { return tls_slot_->get()->wasm().get(); } private: uint32_t root_context_id_{0}; - Envoy::Extensions::Common::Wasm::PluginSharedPtr plugin_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + ThreadLocal::TypedSlotPtr tls_slot_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; }; diff --git a/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc b/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc index 5d2cbb3cb4b6..d6de809ef35d 100644 --- a/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc +++ b/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc @@ -80,7 +80,8 @@ void DnsFilterResolver::resolveExternalQuery(DnsQueryContextPtr context, // timer ctx.timeout_timer->disableTimer(); - ENVOY_LOG(trace, "async query status returned. Entries {}", response.size()); + ENVOY_LOG(trace, "async query status returned for query [{}]. Entries {}", + ctx.query_context->id_, response.size()); ASSERT(ctx.resolver_status == DnsFilterResolverStatus::Pending); ctx.query_context->in_callback_ = true; diff --git a/source/extensions/filters/udp/dns_filter/dns_parser.cc b/source/extensions/filters/udp/dns_filter/dns_parser.cc index 763424a63324..b779120c942b 100644 --- a/source/extensions/filters/udp/dns_filter/dns_parser.cc +++ b/source/extensions/filters/udp/dns_filter/dns_parser.cc @@ -150,7 +150,7 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, bool done = false; DnsQueryParseState state{DnsQueryParseState::Init}; - header_ = {}; + context->header_ = {}; do { // Ensure that we have enough data remaining in the buffer to parse the query if (available_bytes < field_size) { @@ -167,53 +167,54 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, switch (state) { case DnsQueryParseState::Init: - header_.id = data; + context->header_.id = data; state = DnsQueryParseState::Flags; break; case DnsQueryParseState::Flags: - ::memcpy(static_cast(&header_.flags), &data, field_size); + ::memcpy(static_cast(&context->header_.flags), &data, field_size); state = DnsQueryParseState::Questions; break; case DnsQueryParseState::Questions: - header_.questions = data; + context->header_.questions = data; state = DnsQueryParseState::Answers; break; case DnsQueryParseState::Answers: - header_.answers = data; + context->header_.answers = data; state = DnsQueryParseState::Authority; break; case DnsQueryParseState::Authority: - header_.authority_rrs = data; + context->header_.authority_rrs = data; state = DnsQueryParseState::Authority2; break; case DnsQueryParseState::Authority2: - header_.additional_rrs = data; + context->header_.additional_rrs = data; done = true; break; } } while (!done); - if (!header_.flags.qr && header_.answers) { + if (!context->header_.flags.qr && context->header_.answers) { ENVOY_LOG(debug, "Answer records present in query"); return false; } - if (header_.questions != 1) { + if (context->header_.questions != 1) { context->response_code_ = DNS_RESPONSE_CODE_FORMAT_ERROR; - ENVOY_LOG(debug, "Unexpected number [{}] of questions in DNS query", header_.questions); + ENVOY_LOG(debug, "Unexpected number [{}] of questions in DNS query", + context->header_.questions); return false; } - context->id_ = static_cast(header_.id); + context->id_ = static_cast(context->header_.id); if (context->id_ == 0) { ENVOY_LOG(debug, "No ID in DNS query"); return false; } // Almost always, we will have only one query here. Per the RFC, QDCOUNT is usually 1 - context->queries_.reserve(header_.questions); - for (auto index = 0; index < header_.questions; index++) { - ENVOY_LOG(trace, "Parsing [{}/{}] questions", index, header_.questions); + context->queries_.reserve(context->header_.questions); + for (auto index = 0; index < context->header_.questions; index++) { + ENVOY_LOG(trace, "Parsing [{}/{}] questions", index, context->header_.questions); auto rec = parseDnsQueryRecord(buffer, offset); if (rec == nullptr) { context->counters_.query_parsing_failure.inc(); @@ -232,11 +233,12 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, // Parse Answer Records and Additional Resource Records. This is primarily used for tests // to validate the response generated by the filter - if (header_.answers && !parseAnswerRecords(context->answers_, header_.answers, buffer, offset)) { + if (context->header_.answers && + !parseAnswerRecords(context->answers_, context->header_.answers, buffer, offset)) { return false; } - if (header_.authority_rrs) { + if (context->header_.authority_rrs) { // We are not generating these in the filter and don't have a use for them at the moment. // If they exist, we will not parse them and return an error to the client since they appear // between the answers and additional resource records in the buffer. We return true so that @@ -245,11 +247,11 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, return true; } - if (header_.additional_rrs) { + if (context->header_.additional_rrs) { // We may encounter additional resource records that we do not support. Since the filter // operates on queries, we can skip any additional records that we cannot parse since // they will not affect responses. - parseAnswerRecords(context->additional_, header_.additional_rrs, buffer, offset); + parseAnswerRecords(context->additional_, context->header_.additional_rrs, buffer, offset); } return true; @@ -503,36 +505,36 @@ void DnsMessageParser::setDnsResponseFlags(DnsQueryContextPtr& query_context, const uint16_t authority_rrs, const uint16_t additional_rrs) { // Copy the transaction ID - response_header_.id = header_.id; + query_context->response_header_.id = query_context->header_.id; // Signify that this is a response to a query - response_header_.flags.qr = 1; + query_context->response_header_.flags.qr = 1; - response_header_.flags.opcode = header_.flags.opcode; - response_header_.flags.aa = 0; - response_header_.flags.tc = 0; + query_context->response_header_.flags.opcode = query_context->header_.flags.opcode; + query_context->response_header_.flags.aa = 0; + query_context->response_header_.flags.tc = 0; // Copy Recursion flags - response_header_.flags.rd = header_.flags.rd; + query_context->response_header_.flags.rd = query_context->header_.flags.rd; // Set the recursion flag based on whether Envoy is configured to forward queries - response_header_.flags.ra = recursion_available_; + query_context->response_header_.flags.ra = recursion_available_; // reserved flag is not set - response_header_.flags.z = 0; + query_context->response_header_.flags.z = 0; // Set the authenticated flags to zero - response_header_.flags.ad = 0; + query_context->response_header_.flags.ad = 0; - response_header_.flags.cd = 0; - response_header_.answers = answers; - response_header_.flags.rcode = query_context->response_code_; + query_context->response_header_.flags.cd = 0; + query_context->response_header_.answers = answers; + query_context->response_header_.flags.rcode = query_context->response_code_; // Set the number of questions from the incoming query - response_header_.questions = questions; + query_context->response_header_.questions = questions; - response_header_.authority_rrs = authority_rrs; - response_header_.additional_rrs = additional_rrs; + query_context->response_header_.authority_rrs = authority_rrs; + query_context->response_header_.additional_rrs = additional_rrs; } bool DnsMessageParser::createAndStoreDnsAnswerRecord( @@ -738,16 +740,16 @@ void DnsMessageParser::buildResponseBuffer(DnsQueryContextPtr& query_context, serialized_authority_rrs, serialized_additional_rrs); // Build the response buffer for transmission to the client - buffer.writeBEInt(response_header_.id); + buffer.writeBEInt(query_context->response_header_.id); uint16_t flags; - ::memcpy(&flags, static_cast(&response_header_.flags), sizeof(uint16_t)); + ::memcpy(&flags, static_cast(&query_context->response_header_.flags), sizeof(uint16_t)); buffer.writeBEInt(flags); - buffer.writeBEInt(response_header_.questions); - buffer.writeBEInt(response_header_.answers); - buffer.writeBEInt(response_header_.authority_rrs); - buffer.writeBEInt(response_header_.additional_rrs); + buffer.writeBEInt(query_context->response_header_.questions); + buffer.writeBEInt(query_context->response_header_.answers); + buffer.writeBEInt(query_context->response_header_.authority_rrs); + buffer.writeBEInt(query_context->response_header_.additional_rrs); // write the queries and answers buffer.move(query_buffer); diff --git a/source/extensions/filters/udp/dns_filter/dns_parser.h b/source/extensions/filters/udp/dns_filter/dns_parser.h index 8dbd07afc02f..d604f0784ba7 100644 --- a/source/extensions/filters/udp/dns_filter/dns_parser.h +++ b/source/extensions/filters/udp/dns_filter/dns_parser.h @@ -133,6 +133,35 @@ struct DnsParserCounters { query_parsing_failure(query_parsing) {} }; +// The flags have been verified with dig and this structure should not be modified. The flag +// order here does not match the RFC, but takes byte ordering into account so that serialization +// does not bitwise operations. +PACKED_STRUCT(struct DnsHeaderFlags { + unsigned rcode : 4; // return code + unsigned cd : 1; // checking disabled + unsigned ad : 1; // authenticated data + unsigned z : 1; // z - bit (must be zero in queries per RFC1035) + unsigned ra : 1; // recursion available + unsigned rd : 1; // recursion desired + unsigned tc : 1; // truncated response + unsigned aa : 1; // authoritative answer + unsigned opcode : 4; // operation code + unsigned qr : 1; // query or response +}); + +/** + * Structure representing the DNS header as it appears in a packet + * See https://www.ietf.org/rfc/rfc1035.txt for more details + */ +PACKED_STRUCT(struct DnsHeader { + uint16_t id; + struct DnsHeaderFlags flags; + uint16_t questions; + uint16_t answers; + uint16_t authority_rrs; + uint16_t additional_rrs; +}); + /** * DnsQueryContext contains all the data necessary for responding to a query from a given client. */ @@ -152,10 +181,18 @@ class DnsQueryContext { uint64_t retry_; uint16_t id_; Network::DnsResolver::ResolutionStatus resolution_status_; + DnsHeader header_; + DnsHeader response_header_; DnsQueryPtrVec queries_; DnsAnswerMap answers_; DnsAnswerMap additional_; bool in_callback_; + + /** + * @param context the query context for which we are querying the response code + * @return uint16_t the response code flag value from a parsed dns object + */ + uint16_t getQueryResponseCode() { return static_cast(header_.flags.rcode); } }; using DnsQueryContextPtr = std::unique_ptr; @@ -167,44 +204,6 @@ using DnsFilterResolverCallback = std::function { public: - enum class DnsQueryParseState { - Init, - Flags, // 2 bytes - Questions, // 2 bytes - Answers, // 2 bytes - Authority, // 2 bytes - Authority2 // 2 bytes - }; - - // The flags have been verified with dig and this structure should not be modified. The flag - // order here does not match the RFC, but takes byte ordering into account so that serialization - // does not bitwise operations. - PACKED_STRUCT(struct DnsHeaderFlags { - unsigned rcode : 4; // return code - unsigned cd : 1; // checking disabled - unsigned ad : 1; // authenticated data - unsigned z : 1; // z - bit (must be zero in queries per RFC1035) - unsigned ra : 1; // recursion available - unsigned rd : 1; // recursion desired - unsigned tc : 1; // truncated response - unsigned aa : 1; // authoritative answer - unsigned opcode : 4; // operation code - unsigned qr : 1; // query or response - }); - - /** - * Structure representing the DNS header as it appears in a packet - * See https://www.ietf.org/rfc/rfc1035.txt for more details - */ - PACKED_STRUCT(struct DnsHeader { - uint16_t id; - struct DnsHeaderFlags flags; - uint16_t questions; - uint16_t answers; - uint16_t authority_rrs; - uint16_t additional_rrs; - }); - DnsMessageParser(bool recurse, TimeSource& timesource, uint64_t retry_count, Random::RandomGenerator& random, Stats::Histogram& latency_histogram) : recursion_available_(recurse), timesource_(timesource), retry_count_(retry_count), @@ -351,11 +350,6 @@ class DnsMessageParser : public Logger::Loggable { const std::chrono::seconds ttl, Network::Address::InstanceConstSharedPtr ipaddr); - /** - * @return uint16_t the response code flag value from a parsed dns object - */ - uint16_t getQueryResponseCode() { return static_cast(header_.flags.rcode); } - /** * @brief Parse the incoming query and create a context object for the filter * @@ -371,6 +365,15 @@ class DnsMessageParser : public Logger::Loggable { bool parseDnsObject(DnsQueryContextPtr& context, const Buffer::InstancePtr& buffer); private: + enum class DnsQueryParseState { + Init, + Flags, // 2 bytes + Questions, // 2 bytes + Answers, // 2 bytes + Authority, // 2 bytes + Authority2 // 2 bytes + }; + /** * @brief Adds a new DNS SRV Answer record for a given service to the service map * @@ -420,11 +423,8 @@ class DnsMessageParser : public Logger::Loggable { TimeSource& timesource_; uint64_t retry_count_; Stats::Histogram& query_latency_histogram_; - DnsHeader header_; - DnsHeader response_header_; Random::RandomGenerator& rng_; }; - } // namespace DnsFilter } // namespace UdpFilters } // namespace Extensions diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 29eb78d155e1..a90cfde6ddc6 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -212,6 +212,7 @@ envoy_cc_library( "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/http:header_map_lib", + "//source/common/http:header_utility_lib", "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", "@com_googlesource_quiche//:quic_core_http_client_lib", ], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index f4808adc52b0..86912292ae88 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -55,7 +55,7 @@ ActiveQuicListener::ActiveQuicListener( quic::QuicRandom* const random = quic::QuicRandom::GetInstance(); random->RandBytes(random_seed_, sizeof(random_seed_)); crypto_config_ = std::make_unique( - quiche::QuicheStringPiece(reinterpret_cast(random_seed_), sizeof(random_seed_)), + absl::string_view(reinterpret_cast(random_seed_), sizeof(random_seed_)), quic::QuicRandom::GetInstance(), std::make_unique(listen_socket_, listener_config.filterChainManager(), stats_), diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc index b92a77147e91..40e980441915 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -43,7 +43,7 @@ EnvoyQuicClientConnection::EnvoyQuicClientConnection( const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) : EnvoyQuicConnection( - server_connection_id, + server_connection_id, quic::QuicSocketAddress(), envoyIpAddressToQuicSocketAddress(connection_socket->remoteAddress()->ip()), helper, alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, supported_versions, std::move(connection_socket)), diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 866e35416b0b..79e442ea8628 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -20,6 +20,7 @@ #include "common/buffer/buffer_impl.h" #include "common/http/header_map_impl.h" +#include "common/http/header_utility.h" #include "common/common/assert.h" namespace Envoy { @@ -46,7 +47,12 @@ EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, 16 * 1024, [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} -void EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) { +Http::Status EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers, + bool end_stream) { + // Required headers must be present. This can only happen by some erroneous processing after the + // downstream codecs decode. + RETURN_IF_ERROR(Http::HeaderUtility::checkRequiredHeaders(headers)); + ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); quic::QuicStream* writing_stream = quic::VersionUsesHttp3(transport_version()) @@ -60,11 +66,16 @@ void EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers, // IETF QUIC sends HEADER frame on current stream. After writing headers, the // buffer may increase. maybeCheckWatermark(bytes_to_send_old, bytes_to_send_new, *filterManagerConnection()); + return Http::okStatus(); } void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, data.length()); + if (data.length() == 0 && !end_stream) { + return; + } + ASSERT(!local_end_stream_); local_end_stream_ = end_stream; // This is counting not serialized bytes in the send buffer. const uint64_t bytes_to_send_old = BufferedDataBytes(); @@ -105,8 +116,6 @@ void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*meta } void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { - // Higher layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); Reset(envoyResetReasonToQuicRstError(reason)); } @@ -123,13 +132,15 @@ void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { - quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); if (rst_sent()) { return; } - ASSERT(headers_decompressed()); + quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); + ASSERT(headers_decompressed() && !header_list.empty()); + response_decoder_->decodeHeaders( - quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + quicHeadersToEnvoyHeaders(header_list), + /*end_stream=*/fin); if (fin) { end_stream_decoded_ = true; } @@ -158,18 +169,17 @@ void EnvoyQuicClientStream::OnBodyAvailable() { buffer->commit(&slice, 1); MarkConsumed(bytes_read); } + ASSERT(buffer->length() == 0 || !end_stream_decoded_); - // True if no trailer and FIN read. - bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && fin_received(); + bool fin_read_and_no_trailers = IsDoneReading(); // If this call is triggered by an empty frame with FIN which is not from peer // but synthesized by stream itself upon receiving HEADERS with FIN or // TRAILERS, do not deliver end of stream here. Because either decodeHeaders // already delivered it or decodeTrailers will be called. - bool skip_decoding = empty_payload_with_fin && (end_stream_decoded_ || !finished_reading); + bool skip_decoding = (buffer->length() == 0 && !fin_read_and_no_trailers) || end_stream_decoded_; if (!skip_decoding) { - response_decoder_->decodeData(*buffer, finished_reading); - if (finished_reading) { + response_decoder_->decodeData(*buffer, fin_read_and_no_trailers); + if (fin_read_and_no_trailers) { end_stream_decoded_ = true; } } @@ -184,14 +194,10 @@ void EnvoyQuicClientStream::OnBodyAvailable() { return; } - if (!quic::VersionUsesHttp3(transport_version()) && !FinishedReadingTrailers()) { - // For Google QUIC implementation, trailers may arrived earlier and wait to - // be consumed after reading all the body. Consume it here. - // IETF QUIC shouldn't reach here because trailers are sent on same stream. - response_decoder_->decodeTrailers( - spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } + // Trailers may arrived earlier and wait to be consumed after reading all the body. Consume it + // here. + maybeDecodeTrailers(); + OnFinRead(); in_decode_data_callstack_ = false; } @@ -200,20 +206,31 @@ void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len const quic::QuicHeaderList& header_list) { quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); ASSERT(trailers_decompressed()); - if (session()->connection()->connected() && - (quic::VersionUsesHttp3(transport_version()) || sequencer()->IsClosed()) && - !FinishedReadingTrailers()) { - // Before QPack, trailers can arrive before body. Only decode trailers after finishing decoding - // body. + if (session()->connection()->connected() && !rst_sent()) { + maybeDecodeTrailers(); + } +} + +void EnvoyQuicClientStream::maybeDecodeTrailers() { + if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { + ASSERT(!received_trailers().empty()); + // Only decode trailers after finishing decoding body. response_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); + end_stream_decoded_ = true; MarkTrailersConsumed(); } } void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { quic::QuicSpdyClientStream::OnStreamReset(frame); - runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); + runResetCallbacks(quicRstErrorToEnvoyRemoteResetReason(frame.error_code)); +} + +void EnvoyQuicClientStream::Reset(quic::QuicRstStreamErrorCode error) { + // Upper layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(quicRstErrorToEnvoyLocalResetReason(error)); + quic::QuicSpdyClientStream::Reset(error); } void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h index 79003e4621f4..2702b5f8fe79 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -37,7 +37,7 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, } // Http::RequestEncoder - void encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) override; + Http::Status encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) override; void encodeTrailers(const Http::RequestTrailerMap& trailers) override; // Http::Stream @@ -46,6 +46,7 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void Reset(quic::QuicRstStreamErrorCode error) override; void OnClose() override; void OnCanWrite() override; // quic::Stream @@ -67,6 +68,9 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, private: QuicFilterManagerConnectionImpl* filterManagerConnection(); + // Deliver awaiting trailers if body has been delivered. + void maybeDecodeTrailers(); + Http::ResponseDecoder* response_decoder_{nullptr}; }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index dcc311a6eaac..d813dfe4badb 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -6,6 +6,7 @@ namespace Envoy { namespace Quic { EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, @@ -13,8 +14,9 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co quic::Perspective perspective, const quic::ParsedQuicVersionVector& supported_versions, Network::ConnectionSocketPtr&& connection_socket) - : quic::QuicConnection(server_connection_id, initial_peer_address, &helper, &alarm_factory, - writer, owns_writer, perspective, supported_versions), + : quic::QuicConnection(server_connection_id, initial_self_address, initial_peer_address, + &helper, &alarm_factory, writer, owns_writer, perspective, + supported_versions), connection_socket_(std::move(connection_socket)) {} EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_connection.h index f4c8589d7118..f8543bc938d7 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.h @@ -26,6 +26,7 @@ class EnvoyQuicConnection : public quic::QuicConnection, protected Logger::Loggable { public: EnvoyQuicConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index ba8f7f3a8239..e6351f643653 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -48,11 +48,11 @@ void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_i } std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( - quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& /*self_address*/, - const quic::QuicSocketAddress& peer_address, quiche::QuicheStringPiece /*alpn*/, + quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, + const quic::QuicSocketAddress& peer_address, absl::string_view /*alpn*/, const quic::ParsedQuicVersion& version) { auto quic_connection = std::make_unique( - server_connection_id, peer_address, *helper(), *alarm_factory(), writer(), + server_connection_id, self_address, peer_address, *helper(), *alarm_factory(), writer(), /*owns_writer=*/false, quic::ParsedQuicVersionVector{version}, listen_socket_); auto quic_session = std::make_unique( config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h index 589ff5327706..d59307f415ec 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h @@ -62,7 +62,7 @@ class EnvoyQuicDispatcher : public quic::QuicDispatcher { std::unique_ptr CreateQuicSession(quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, - const quic::QuicSocketAddress& peer_address, quiche::QuicheStringPiece alpn, + const quic::QuicSocketAddress& peer_address, absl::string_view alpn, const quic::ParsedQuicVersion& version) override; private: diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index 1f65e4e7e6a0..967765829a28 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -36,7 +36,7 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address void EnvoyQuicProofSource::signPayload( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, - const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, + const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) { CertConfigWithFilterChain res = getTlsCertConfigAndFilterChain(server_address, client_address, hostname); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index 6e1c74c9234c..e22bf3465f49 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -28,7 +28,7 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { // quic::ProofSource void signPayload(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, - uint16_t signature_algorithm, quiche::QuicheStringPiece in, + uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) override; private: diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc index 2c82c04d901d..9ad3cb07f428 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -21,7 +21,7 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad const std::string& hostname, const std::string& server_config, quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece chlo_hash, + absl::string_view chlo_hash, std::unique_ptr callback) { quic::QuicReferenceCountedPointer chain = GetCertChain(server_address, client_address, hostname); @@ -68,13 +68,12 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad auto signature_callback = std::make_unique(std::move(callback), chain); signPayload(server_address, client_address, hostname, sign_alg, - quiche::QuicheStringPiece(payload.get(), payload_size), - std::move(signature_callback)); + absl::string_view(payload.get(), payload_size), std::move(signature_callback)); } void EnvoyQuicProofSourceBase::ComputeTlsSignature( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, - const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, + const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) { signPayload(server_address, client_address, hostname, signature_algorithm, in, std::move(callback)); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index b7d76981e519..a9e7e8c3f094 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -57,7 +57,7 @@ class EnvoyQuicProofSourceBase : public quic::ProofSource, void GetProof(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, const std::string& server_config, quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece chlo_hash, + absl::string_view chlo_hash, std::unique_ptr callback) override; TicketCrypter* GetTicketCrypter() override { return nullptr; } @@ -65,14 +65,14 @@ class EnvoyQuicProofSourceBase : public quic::ProofSource, void ComputeTlsSignature(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, - quiche::QuicheStringPiece in, + absl::string_view in, std::unique_ptr callback) override; protected: virtual void signPayload(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, - quiche::QuicheStringPiece in, + absl::string_view in, std::unique_ptr callback) PURE; private: diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc index 229b3ab36628..e375905295a3 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc @@ -58,8 +58,8 @@ bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_confi *error_details = "QuicPacketWriter error."; return false; } - bool valid = cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), - signature, sign_alg); + bool valid = cert_view->VerifySignature(absl::string_view(payload.get(), payload_size), signature, + sign_alg); if (!valid) { *error_details = "Signature is not valid."; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc index b8fa94221f05..974c6c8ebc8c 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc @@ -11,11 +11,13 @@ namespace Quic { EnvoyQuicServerConnection::EnvoyQuicServerConnection( const quic::QuicConnectionId& server_connection_id, - quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, + quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, + quic::QuicPacketWriter* writer, bool owns_writer, const quic::ParsedQuicVersionVector& supported_versions, Network::Socket& listen_socket) - : EnvoyQuicConnection(server_connection_id, initial_peer_address, helper, alarm_factory, writer, - owns_writer, quic::Perspective::IS_SERVER, supported_versions, + : EnvoyQuicConnection(server_connection_id, initial_self_address, initial_peer_address, helper, + alarm_factory, writer, owns_writer, quic::Perspective::IS_SERVER, + supported_versions, std::make_unique( // Wraps the real IoHandle instance so that if the connection socket // gets closed, the real IoHandle won't be affected. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h index 7b7fac05e925..7625fad02d0b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h @@ -10,6 +10,7 @@ namespace Quic { class EnvoyQuicServerConnection : public EnvoyQuicConnection { public: EnvoyQuicServerConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index d5e5726bf369..c7d25b8d47da 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -84,6 +84,10 @@ void EnvoyQuicServerStream::encodeHeaders(const Http::ResponseHeaderMap& headers void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) { ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, data.length()); + if (data.length() == 0 && !end_stream) { + return; + } + ASSERT(!local_end_stream_); local_end_stream_ = end_stream; // This is counting not serialized bytes in the send buffer. const uint64_t bytes_to_send_old = BufferedDataBytes(); @@ -121,8 +125,6 @@ void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*meta } void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { - // Upper layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); if (local_end_stream_ && !reading_stopped()) { // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead // of propagating original reset reason. In QUICHE if a stream stops reading @@ -146,10 +148,17 @@ void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) { void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { + // TODO(danzh) Fix in QUICHE. If the stream has been reset in the call stack, + // OnInitialHeadersComplete() shouldn't be called. + if (rst_sent()) { + return; + } quic::QuicSpdyServerStreamBase::OnInitialHeadersComplete(fin, frame_len, header_list); - ASSERT(headers_decompressed()); + ASSERT(headers_decompressed() && !header_list.empty()); + request_decoder_->decodeHeaders( - quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + quicHeadersToEnvoyHeaders(header_list), + /*end_stream=*/fin); if (fin) { end_stream_decoded_ = true; } @@ -179,17 +188,15 @@ void EnvoyQuicServerStream::OnBodyAvailable() { MarkConsumed(bytes_read); } - // True if no trailer and FIN read. - bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && fin_received(); + bool fin_read_and_no_trailers = IsDoneReading(); // If this call is triggered by an empty frame with FIN which is not from peer // but synthesized by stream itself upon receiving HEADERS with FIN or // TRAILERS, do not deliver end of stream here. Because either decodeHeaders // already delivered it or decodeTrailers will be called. - bool skip_decoding = empty_payload_with_fin && (end_stream_decoded_ || !finished_reading); + bool skip_decoding = (buffer->length() == 0 && !fin_read_and_no_trailers) || end_stream_decoded_; if (!skip_decoding) { - request_decoder_->decodeData(*buffer, finished_reading); - if (finished_reading) { + request_decoder_->decodeData(*buffer, fin_read_and_no_trailers); + if (fin_read_and_no_trailers) { end_stream_decoded_ = true; } } @@ -204,14 +211,10 @@ void EnvoyQuicServerStream::OnBodyAvailable() { return; } - if (!quic::VersionUsesHttp3(transport_version()) && !FinishedReadingTrailers()) { - // For Google QUIC implementation, trailers may arrived earlier and wait to - // be consumed after reading all the body. Consume it here. - // IETF QUIC shouldn't reach here because trailers are sent on same stream. - request_decoder_->decodeTrailers( - spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } + // Trailers may arrived earlier and wait to be consumed after reading all the body. Consume it + // here. + maybeDecodeTrailers(); + OnFinRead(); in_decode_data_callstack_ = false; } @@ -219,20 +222,32 @@ void EnvoyQuicServerStream::OnBodyAvailable() { void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { quic::QuicSpdyServerStreamBase::OnTrailingHeadersComplete(fin, frame_len, header_list); - if (session()->connection()->connected() && - (quic::VersionUsesHttp3(transport_version()) || sequencer()->IsClosed()) && - !FinishedReadingTrailers()) { - // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding - // body. + ASSERT(trailers_decompressed()); + if (session()->connection()->connected() && !rst_sent()) { + maybeDecodeTrailers(); + } +} + +void EnvoyQuicServerStream::maybeDecodeTrailers() { + if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { + ASSERT(!received_trailers().empty()); + // Only decode trailers after finishing decoding body. request_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); + end_stream_decoded_ = true; MarkTrailersConsumed(); } } void EnvoyQuicServerStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { quic::QuicSpdyServerStreamBase::OnStreamReset(frame); - runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); + runResetCallbacks(quicRstErrorToEnvoyRemoteResetReason(frame.error_code)); +} + +void EnvoyQuicServerStream::Reset(quic::QuicRstStreamErrorCode error) { + // Upper layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(quicRstErrorToEnvoyLocalResetReason(error)); + quic::QuicSpdyServerStreamBase::Reset(error); } void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index b05a707751ff..acd4138db398 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -49,9 +49,10 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void Reset(quic::QuicRstStreamErrorCode error) override; void OnClose() override; void OnCanWrite() override; - // quic::QuicServerSessionBase + // quic::QuicSpdyServerStreamBase void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; protected: @@ -69,6 +70,9 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, private: QuicFilterManagerConnectionImpl* filterManagerConnection(); + // Deliver awaiting trailers if body has been delivered. + void maybeDecodeTrailers(); + Http::RequestDecoder* request_decoder_{nullptr}; }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index c7a32fbf317d..bd0a8a657ccd 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -65,20 +65,32 @@ quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetRea case Http::StreamResetReason::LocalRefusedStreamReset: return quic::QUIC_REFUSED_STREAM; case Http::StreamResetReason::ConnectionFailure: + case Http::StreamResetReason::ConnectionTermination: return quic::QUIC_STREAM_CONNECTION_ERROR; case Http::StreamResetReason::LocalReset: return quic::QUIC_STREAM_CANCELLED; - case Http::StreamResetReason::ConnectionTermination: - return quic::QUIC_STREAM_NO_ERROR; default: return quic::QUIC_BAD_APPLICATION_PAYLOAD; } } -Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err) { +Http::StreamResetReason quicRstErrorToEnvoyLocalResetReason(quic::QuicRstStreamErrorCode rst_err) { + switch (rst_err) { + case quic::QUIC_REFUSED_STREAM: + return Http::StreamResetReason::LocalRefusedStreamReset; + case quic::QUIC_STREAM_CONNECTION_ERROR: + return Http::StreamResetReason::ConnectionFailure; + default: + return Http::StreamResetReason::LocalReset; + } +} + +Http::StreamResetReason quicRstErrorToEnvoyRemoteResetReason(quic::QuicRstStreamErrorCode rst_err) { switch (rst_err) { case quic::QUIC_REFUSED_STREAM: return Http::StreamResetReason::RemoteRefusedStreamReset; + case quic::QUIC_STREAM_CONNECTION_ERROR: + return Http::StreamResetReason::ConnectError; default: return Http::StreamResetReason::RemoteReset; } @@ -170,9 +182,9 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin ASSERT(rsa_public_key != nullptr); const unsigned rsa_key_length = RSA_size(rsa_public_key); #ifdef BORINGSSL_FIPS - if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8) { - *error_details = "Invalid leaf cert, only RSA certificates with 2048-bit or 3072-bit keys " - "are supported in FIPS mode"; + if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8 && rsa_key_length != 4096 / 8) { + *error_details = "Invalid leaf cert, only RSA certificates with 2048-bit, 3072-bit or " + "4096-bit keys are supported in FIPS mode"; break; } #else diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index 563e0960cbd9..3c29b28ef21f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -66,7 +66,10 @@ spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& heade quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason); // Called when a RST_STREAM frame is received. -Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err); +Http::StreamResetReason quicRstErrorToEnvoyLocalResetReason(quic::QuicRstStreamErrorCode rst_err); + +// Called when a QUIC stack reset the stream. +Http::StreamResetReason quicRstErrorToEnvoyRemoteResetReason(quic::QuicRstStreamErrorCode rst_err); // Called when underlying QUIC connection is closed either locally or by peer. Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error); diff --git a/source/extensions/quic_listeners/quiche/platform/BUILD b/source/extensions/quic_listeners/quiche/platform/BUILD index f53e07b58a33..839664d52f97 100644 --- a/source/extensions/quic_listeners/quiche/platform/BUILD +++ b/source/extensions/quic_listeners/quiche/platform/BUILD @@ -36,15 +36,16 @@ envoy_extension_package() envoy_cc_library( name = "flags_impl_lib", srcs = ["flags_impl.cc"], - hdrs = [ - "flags_impl.h", - "flags_list.h", - ], + hdrs = ["flags_impl.h"], external_deps = [ "abseil_base", "abseil_synchronization", ], visibility = ["//visibility:public"], + deps = [ + "@com_googlesource_quiche//:quic_core_flags_list_lib", + "@com_googlesource_quiche//:quic_core_protocol_flags_list_lib", + ], ) envoy_cc_library( @@ -62,7 +63,6 @@ envoy_cc_library( envoy_cc_library( name = "http2_platform_impl_lib", hdrs = [ - "http2_arraysize_impl.h", "http2_bug_tracker_impl.h", "http2_containers_impl.h", "http2_estimate_memory_usage_impl.h", @@ -74,7 +74,6 @@ envoy_cc_library( ], external_deps = [ "abseil_base", - "abseil_optional", "abseil_str_format", ], visibility = ["//visibility:public"], @@ -114,16 +113,13 @@ envoy_cc_library( "quic_mem_slice_impl.cc", ], hdrs = [ - "quic_aligned_impl.h", "quic_client_stats_impl.h", "quic_containers_impl.h", "quic_error_code_wrappers_impl.h", "quic_estimate_memory_usage_impl.h", - "quic_fallthrough_impl.h", "quic_flag_utils_impl.h", "quic_flags_impl.h", "quic_iovec_impl.h", - "quic_macros_impl.h", "quic_map_util_impl.h", "quic_mem_slice_impl.h", "quic_prefetch_impl.h", @@ -132,6 +128,7 @@ envoy_cc_library( "quic_server_stats_impl.h", "quic_stack_trace_impl.h", "quic_stream_buffer_allocator_impl.h", + "quic_testvalue_impl.h", "quic_uint128_impl.h", ], external_deps = [ @@ -141,7 +138,6 @@ envoy_cc_library( "abseil_memory", "abseil_node_hash_map", "abseil_node_hash_set", - "abseil_optional", ], tags = ["nofips"], visibility = ["//visibility:public"], @@ -236,6 +232,7 @@ envoy_cc_library( }), repository = "@envoy", tags = ["nofips"], + visibility = ["//visibility:public"], ) envoy_cc_library( @@ -250,23 +247,12 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "quiche_common_platform_optional_impl_lib", - hdrs = ["quiche_optional_impl.h"], - external_deps = [ - "abseil_node_hash_map", - ], - visibility = ["//visibility:public"], -) - envoy_cc_library( name = "quiche_common_platform_impl_lib", srcs = ["quiche_time_utils_impl.cc"], hdrs = [ - "quiche_arraysize_impl.h", "quiche_logging_impl.h", "quiche_map_util_impl.h", - "quiche_ptr_util_impl.h", "quiche_str_cat_impl.h", "quiche_string_piece_impl.h", "quiche_text_utils_impl.h", @@ -281,17 +267,14 @@ envoy_cc_library( deps = [ ":quic_platform_logging_impl_lib", ":string_utils_lib", - "@com_googlesource_quiche//:quiche_common_platform_optional", ], ) envoy_cc_library( name = "spdy_platform_impl_lib", hdrs = [ - "spdy_arraysize_impl.h", "spdy_bug_tracker_impl.h", "spdy_containers_impl.h", - "spdy_endianness_util_impl.h", "spdy_estimate_memory_usage_impl.h", "spdy_flags_impl.h", "spdy_logging_impl.h", @@ -331,14 +314,3 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], ) - -envoy_cc_library( - name = "quiche_common_platform_endian_impl_lib", - hdrs = ["quiche_endian_impl.h"], - tags = ["nofips"], - visibility = ["//visibility:public"], - deps = [ - "quiche_common_platform_export_impl_lib", - "//source/common/common:byte_order_lib", - ], -) diff --git a/source/extensions/quic_listeners/quiche/platform/flags_impl.cc b/source/extensions/quic_listeners/quiche/platform/flags_impl.cc index 70fb182d673d..9d4ea89ce3a6 100644 --- a/source/extensions/quic_listeners/quiche/platform/flags_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/flags_impl.cc @@ -15,12 +15,24 @@ namespace quiche { namespace { -absl::flat_hash_map MakeFlagMap() { +absl::flat_hash_map makeFlagMap() { absl::flat_hash_map flags; -#define QUICHE_FLAG(type, flag, value, help) flags.emplace(FLAGS_##flag->name(), FLAGS_##flag); -#include "extensions/quic_listeners/quiche/platform/flags_list.h" -#undef QUICHE_FLAG +#define QUIC_FLAG(flag, ...) flags.emplace(flag->name(), flag); +#include "quiche/quic/core/quic_flags_list.h" + QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_true, true) + QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_true, true) + QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_true, true) + QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_true, true) +#undef QUIC_FLAG + +#define QUIC_PROTOCOL_FLAG(type, flag, ...) flags.emplace(FLAGS_##flag->name(), FLAGS_##flag); +#include "quiche/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG return flags; } @@ -28,75 +40,123 @@ absl::flat_hash_map MakeFlagMap() { } // namespace // static -FlagRegistry& FlagRegistry::GetInstance() { +FlagRegistry& FlagRegistry::getInstance() { static auto* instance = new FlagRegistry(); return *instance; } -FlagRegistry::FlagRegistry() : flags_(MakeFlagMap()) {} +FlagRegistry::FlagRegistry() : flags_(makeFlagMap()) {} -void FlagRegistry::ResetFlags() const { +void FlagRegistry::resetFlags() const { for (auto& kv : flags_) { - kv.second->ResetValue(); + kv.second->resetValue(); } } -Flag* FlagRegistry::FindFlag(const std::string& name) const { +Flag* FlagRegistry::findFlag(const std::string& name) const { auto it = flags_.find(name); return (it != flags_.end()) ? it->second : nullptr; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { static const auto* kTrueValues = new std::set({"1", "t", "true", "y", "yes"}); static const auto* kFalseValues = new std::set({"0", "f", "false", "n", "no"}); auto lower = absl::AsciiStrToLower(value_str); if (kTrueValues->find(lower) != kTrueValues->end()) { - SetValue(true); + setValue(true); return true; } if (kFalseValues->find(lower) != kFalseValues->end()) { - SetValue(false); + setValue(false); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { int32_t value; if (absl::SimpleAtoi(value_str, &value)) { - SetValue(value); + setValue(value); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { int64_t value; if (absl::SimpleAtoi(value_str, &value)) { - SetValue(value); + setValue(value); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { double value; if (absl::SimpleAtod(value_str, &value)) { - SetValue(value); + setValue(value); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { - SetValue(value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { + setValue(value_str); return true; } +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { + unsigned long value; + if (absl::SimpleAtoi(value_str, &value)) { + setValue(value); + return true; + } + return false; +} + +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { + unsigned long long value; + if (absl::SimpleAtoi(value_str, &value)) { + setValue(value); + return true; + } + return false; +} + // Flag definitions -#define QUICHE_FLAG(type, flag, value, help) \ - TypedFlag* FLAGS_##flag = new TypedFlag(#flag, value, help); -#include "extensions/quic_listeners/quiche/platform/flags_list.h" -#undef QUICHE_FLAG +#define QUIC_FLAG(flag, value) TypedFlag* flag = new TypedFlag(#flag, value, ""); +#include "quiche/quic/core/quic_flags_list.h" +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_true, true) + +#undef QUIC_FLAG + +#define STRINGIFY(X) #X + +#define DEFINE_QUIC_PROTOCOL_FLAG_IMPL(type, flag, value, help) \ + TypedFlag* FLAGS_##flag = new TypedFlag(STRINGIFY(FLAGS_##flag), value, help); + +#define DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE(type, flag, value, doc) \ + DEFINE_QUIC_PROTOCOL_FLAG_IMPL(type, flag, value, doc) + +#define DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES(type, flag, internal_value, external_value, doc) \ + DEFINE_QUIC_PROTOCOL_FLAG_IMPL(type, flag, external_value, doc) + +// Select the right macro based on the number of arguments. +#define GET_6TH_ARG(arg1, arg2, arg3, arg4, arg5, arg6, ...) arg6 + +#define QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(...) \ + GET_6TH_ARG(__VA_ARGS__, DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES, \ + DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE) + +#define QUIC_PROTOCOL_FLAG(...) QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) +#include "quiche/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG } // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/flags_impl.h b/source/extensions/quic_listeners/quiche/platform/flags_impl.h index 5db939925510..83ed8430c07d 100644 --- a/source/extensions/quic_listeners/quiche/platform/flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/flags_impl.h @@ -26,13 +26,13 @@ class FlagRegistry { ~FlagRegistry() = default; // Return singleton instance. - static FlagRegistry& GetInstance(); + static FlagRegistry& getInstance(); // Reset all registered flags to their default values. - void ResetFlags() const; + void resetFlags() const; // Look up a flag by name. - Flag* FindFlag(const std::string& name) const; + Flag* findFlag(const std::string& name) const; private: FlagRegistry(); @@ -48,10 +48,10 @@ class Flag { virtual ~Flag() = default; // Set flag value from given string, returning true iff successful. - virtual bool SetValueFromString(const std::string& value_str) = 0; + virtual bool setValueFromString(const std::string& value_str) = 0; // Reset flag to default value. - virtual void ResetValue() = 0; + virtual void resetValue() = 0; // Return flag name. std::string name() const { return name_; } @@ -70,15 +70,15 @@ template class TypedFlag : public Flag { TypedFlag(const char* name, T default_value, const char* help) : Flag(name, help), value_(default_value), default_value_(default_value) {} - bool SetValueFromString(const std::string& value_str) override; + bool setValueFromString(const std::string& value_str) override; - void ResetValue() override { + void resetValue() override { absl::MutexLock lock(&mutex_); value_ = default_value_; } // Set flag value. - void SetValue(T value) { + void setValue(T value) { absl::MutexLock lock(&mutex_); value_ = value; } @@ -96,15 +96,29 @@ template class TypedFlag : public Flag { }; // SetValueFromString specializations -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); // Flag declarations -#define QUICHE_FLAG(type, flag, value, help) extern TypedFlag* FLAGS_##flag; -#include "extensions/quic_listeners/quiche/platform/flags_list.h" -#undef QUICHE_FLAG +#define QUIC_FLAG(flag, ...) extern TypedFlag* flag; +#include "quiche/quic/core/quic_flags_list.h" +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_true, true) +#undef QUIC_FLAG + +#define QUIC_PROTOCOL_FLAG(type, flag, ...) extern TypedFlag* FLAGS_##flag; +#include "quiche/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG } // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/flags_list.h b/source/extensions/quic_listeners/quiche/platform/flags_list.h deleted file mode 100644 index 7e9e20a7c192..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/flags_list.h +++ /dev/null @@ -1,502 +0,0 @@ -// This file intentionally does not have header guards. It is intended to be -// included multiple times, each time with a different definition of -// QUICHE_FLAG. - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -// This file is generated by //third_party/quic/tools:quic_flags_list in -// Google3. - -#if defined(QUICHE_FLAG) - -QUICHE_FLAG( - bool, http2_reloadable_flag_http2_backend_alpn_failure_error_code, false, - "If true, the GFE will return a new ResponseCodeDetails error when ALPN to the backend fails.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_ip_based_cwnd_exp, true, - "If true, enable IP address based CWND bootstrapping experiment with different " - "bandwidth models and priorities in HTTP2.") - -QUICHE_FLAG( - bool, http2_reloadable_flag_http2_load_based_goaway_warning, false, - "If true, load-based connection closures will send a warning GOAWAY before the actual GOAWAY.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_security_requirement_for_client3, false, - "If true, check whether client meets security requirements during SSL handshake. If " - "flag is true and client does not meet security requirements, do not negotiate HTTP/2 " - "with client or terminate the session with SPDY_INADEQUATE_SECURITY if HTTP/2 is " - "already negotiated. The spec contains both cipher and TLS version requirements.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_websocket_detection, false, - "If true, uses a HTTP/2-specific method of detecting websocket upgrade requests.") - -QUICHE_FLAG(bool, http2_reloadable_flag_permissive_http2_switch, false, - "If true, the GFE allows both HTTP/1.0 and HTTP/1.1 versions in HTTP/2 upgrade " - "requests/responses.") - -QUICHE_FLAG(bool, quic_reloadable_flag_advertise_quic_for_https_for_debugips, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_advertise_quic_for_https_for_external_users, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_gclb_quic_allow_alia, true, - "If gfe2_reloadable_flag_gclb_use_alia is also true, use Alia for GCLB QUIC " - "handshakes. To be used as a big red button if there's a problem with Alia/QUIC.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_abort_qpack_on_stream_close, false, - "If true, abort async QPACK header decompression in QuicSpdyStream::OnClose().") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_ack_delay_alarm_granularity, false, - "When true, ensure the ACK delay is never less than the alarm granularity when ACK " - "decimation is enabled.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_add_missing_connected_checks, false, - "If true, add missing connected checks.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_add_silent_idle_timeout, true, - "If true, when server is silently closing connections due to idle timeout, serialize " - "the connection close packets which will be added to time wait list.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, false, - "If true, include stream information in idle timeout connection close detail.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_allow_backend_set_stream_ttl, false, - "If true, check backend response header for X-Response-Ttl. If it is provided, the " - "stream TTL is set. A QUIC stream will be immediately canceled when tries to write " - "data if this TTL expired.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, true, - "If true, allow client to enable BBRv2 on server via connection option 'B2ON'.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_alpn_dispatch, false, - "Support different QUIC sessions, as indicated by ALPN. Used for QBONE.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false, - "If true, QUIC BBRv2's PROBE_BW mode will not reduce cwnd below BDP+ack_height.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr2_fewer_startup_round_trips, false, - "When true, the 1RTT and 2RTT connection options decrease the number of round trips in " - "BBRv2 STARTUP without a 25% bandwidth increase to 1 or 2 round trips respectively.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_bbr2_limit_inflight_hi, false, - "When true, the B2HI connection option limits reduction of inflight_hi to (1-Beta)*CWND.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr2_use_post_inflight_to_detect_queuing, false, - "If true, QUIC BBRv2 will use inflight byte after congestion event to detect queuing " - "during PROBE_UP.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr_no_bytes_acked_in_startup_recovery, false, - "When in STARTUP and recovery, do not add bytes_acked to QUIC BBR's CWND in " - "CalculateCongestionWindow()") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bootstrap_cwnd_by_spdy_priority, true, - "If true, bootstrap initial QUIC cwnd by SPDY priorities.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_cap_large_client_initial_rtt, true, - "If true, cap client suggested initial RTT to 1s if it is longer than 1s.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_clean_up_spdy_session_destructor, false, - "If true, QuicSpdySession's destructor won't need to do cleanup.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_close_connection_in_on_can_write_with_blocked_writer, - false, - "If true, close connection if writer is still blocked while OnCanWrite is called.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_close_connection_on_serialization_failure, false, - "If true, close connection on packet serialization failures.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_bursts, false, - "If true, set burst token to 2 in cwnd bootstrapping experiment.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false, - "If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_copy_bbr_cwnd_to_bbr2, false, - "If true, when switching from BBR to BBRv2, BBRv2 will use BBR's cwnd as its initial cwnd.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_enable_5rto_blackhole_detection2, true, - "If true, default-enable 5RTO blachole detection.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_on_pto, false, - "If true, default on PTO which unifies TLP + RTO loss recovery.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_to_bbr, true, - "When true, defaults to BBR congestion control instead of Cubic.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_to_bbr_v2, false, - "If true, use BBRv2 as the default congestion controller. Takes precedence over " - "--quic_default_to_bbr.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_server_blackhole_detection, false, - "If true, disable blackhole detection on server side.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_draft_27, false, - "If true, disable QUIC version h3-27.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_draft_29, false, - "If true, disable QUIC version h3-29.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_q043, false, - "If true, disable QUIC version Q043.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_q046, false, - "If true, disable QUIC version Q046.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_q050, false, - "If true, disable QUIC version Q050.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_t050, false, - "If true, disable QUIC version h3-T050.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_t051, false, - "If true, disable QUIC version h3-T051.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_discard_initial_packet_with_key_dropped, false, - "If true, discard INITIAL packet if the key has been dropped.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_do_not_accept_stop_waiting, false, - "In v44 and above, where STOP_WAITING is never sent, close the connection if it's received.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_donot_reset_ideal_next_packet_send_time, false, - "If true, stop resetting ideal_next_packet_send_time_ in pacing sender.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_loss_detection_experiment_at_gfe, false, - "If ture, enable GFE-picked loss detection experiment.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_loss_detection_tuner, false, - "If true, allow QUIC loss detection tuning to be enabled by connection option ELDT.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false, - "If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enabled, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_arm_pto_for_application_data, false, - "If true, do not arm PTO for application data until handshake confirmed.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_bytes_left_for_batch_write, false, - "If true, convert bytes_left_for_batch_write_ to unsigned int.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_fix_http3_goaway_stream_id, false, - "If true, send the lowest stream ID that can be retried by the client in a GOAWAY frame. If " - "false, send the highest received stream ID, which actually should not be retried.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_out_of_order_sending, false, - "If true, fix a potential out of order sending caused by handshake gets confirmed " - "while the coalescer is not empty.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_fix_pto_pending_timer_count, false, - "If true, make sure there is pending timer credit when trying to PTO retransmit any packets.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_undecryptable_packets2, false, - "If true, remove processed undecryptable packets.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_get_stream_information_from_stream_map, true, - "If true, gQUIC will only consult stream_map in QuicSession::GetNumActiveStreams().") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_give_sent_packet_to_debug_visitor_after_sent, false, - "If true, QUIC connection will pass sent packet information to the debug visitor after " - "a packet is recorded as sent in sent packet manager.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_http3_new_default_urgency_value, false, - "If true, QuicStream::kDefaultUrgency is 3, otherwise 1.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_ip_based_cwnd_exp, true, - "If true, enable IP address based CWND bootstrapping experiment with different " - "bandwidth models and priorities. ") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_listener_never_fake_epollout, false, - "If true, QuicListener::OnSocketIsWritable will always return false, which means there " - "will never be a fake EPOLLOUT event in the next epoll iteration.") - -QUICHE_FLAG(bool, - quic_reloadable_flag_quic_neuter_initial_packet_in_coalescer_with_initial_key_discarded, - false, "If true, neuter initial packet in the coalescer when discarding initial keys.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_dup_experiment_id_2, false, - "If true, transport connection stats doesn't report duplicated experiments for same " - "connection.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_silent_close_for_idle_timeout, true, - "If true, always send connection close for idle timeout if NSLC is received.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_only_set_uaid_in_tcs_visitor, false, - "If true, QuicTransportConnectionStatsVisitor::PopulateTransportConnectionStats will " - "be the only place where TCS's uaid field is set.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_only_truncate_long_cids, true, - "In IETF QUIC, only truncate long CIDs from the client's Initial, don't modify them.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_preferred_altsvc_version, false, - "When true, we will send a preferred QUIC version at the start of our Alt-Svc list.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_proxy_write_packed_strings, false, - "If true, QuicProxyDispatcher will write packed_client_address and packed_server_vip " - "in TcpProxyHeaderProto.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_record_frontend_service_vip_mapping, true, - "If true, for L1 GFE, as requests come in, record frontend service to VIP mapping " - "which is used to announce VIP in SHLO for proxied sessions. ") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_record_received_min_ack_delay, false, - "If true, record the received min_ack_delay in transport parameters to QUIC config.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_reject_all_traffic, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_remove_zombie_streams, true, - "If true, QuicSession doesn't keep a separate zombie_streams. Instead, all streams are " - "stored in stream_map_.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_require_handshake_confirmation, false, - "If true, require handshake confirmation for QUIC connections, functionally disabling " - "0-rtt handshakes.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_send_key_update_not_yet_supported, false, - "When true, QUIC+TLS versions will send the key_update_not_yet_supported transport parameter.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_send_path_response, false, - "If true, send PATH_RESPONSE upon receiving PATH_CHALLENGE regardless of perspective. " - "--gfe2_reloadable_flag_quic_start_peer_migration_earlier has to be true before turn " - "on this flag.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_send_timestamps, false, - "When the STMP connection option is sent by the client, timestamps in the QUIC ACK " - "frame are sent and processed.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_server_push, false, - "If true, enable server push feature on QUIC.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_set_resumed_ssl_session_early, false, - "If true, set resumed_ssl_session if this is a 0-RTT connection.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_start_peer_migration_earlier, false, - "If true, while reading an IETF quic packet, start peer migration immediately when " - "detecting the existence of any non-probing frame instead of at the end of the packet.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_stop_sending_uses_ietf_error_code, false, - "If true, use IETF QUIC application error codes in STOP_SENDING frames. If false, use " - "QuicRstStreamErrorCodes.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_testonly_default_false, false, - "A testonly reloadable flag that will always default to false.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_testonly_default_true, true, - "A testonly reloadable flag that will always default to true.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_unified_iw_options, false, - "When true, set the initial congestion control window from connection options in " - "QuicSentPacketManager rather than TcpCubicSenderBytes.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_header_stage_idle_list2, false, - "If true, use header stage idle list for QUIC connections in GFE.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_leto_key_exchange, false, - "If true, QUIC will attempt to use the Leto key exchange service and only fall back to " - "local key exchange if that fails.") - -QUICHE_FLAG(bool, quic_reloadable_flag_send_quic_fallback_server_config_on_leto_error, false, - "If true and using Leto for QUIC shared-key calculations, GFE will react to a failure " - "to contact Leto by sending a REJ containing a fallback ServerConfig, allowing the " - "client to continue the handshake.") - -QUICHE_FLAG( - bool, quic_restart_flag_dont_fetch_quic_private_keys_from_leto, false, - "If true, GFE will not request private keys when fetching QUIC ServerConfigs from Leto.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_adjust_initial_cwnd_by_gws, true, - "If true, GFE informs backend that a client request is the first one on the connection " - "via frontline header \"first_request=1\". Also, adjust initial cwnd based on " - "X-Google-Gws-Initial-Cwnd-Mode sent by GWS.") - -QUICHE_FLAG( - bool, quic_restart_flag_quic_allow_loas_multipacket_chlo, false, - "If true, inspects QUIC CHLOs for kLOAS and early creates sessions to allow multi-packet CHLOs") - -QUICHE_FLAG( - bool, quic_restart_flag_quic_disable_gws_cwnd_experiment, false, - "If true, X-Google-Gws-Initial-Cwnd-Mode related header sent by GWS becomes no-op for QUIC.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_enable_tls_resumption_v4, true, - "If true, enables support for TLS resumption in QUIC.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_enable_zero_rtt_for_tls_v2, true, - "If true, support for IETF QUIC 0-rtt is enabled.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_offload_pacing_to_usps2, false, - "If true, QUIC offload pacing when using USPS as egress method.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_rx_ring_use_tpacket_v3, false, - "If true, use TPACKET_V3 for QuicRxRing instead of TPACKET_V2.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_should_accept_new_connection, false, - "If true, reject QUIC CHLO packets when dispatcher is asked to do so.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_support_release_time_for_gso, false, - "If true, QuicGsoBatchWriter will support release time if it is available and the " - "process has the permission to do so.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_testonly_default_false, false, - "A testonly restart flag that will always default to false.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_testonly_default_true, true, - "A testonly restart flag that will always default to true.") - -QUICHE_FLAG( - bool, quic_restart_flag_quic_use_leto_for_quic_configs, false, - "If true, use Leto to fetch QUIC server configs instead of using the seeds from Memento.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_use_pigeon_socket_to_backend, false, - "If true, create a shared pigeon socket for all quic to backend connections and switch " - "to use it after successful handshake.") - -QUICHE_FLAG(bool, spdy_reloadable_flag_quic_bootstrap_cwnd_by_spdy_priority, true, - "If true, bootstrap initial QUIC cwnd by SPDY priorities.") - -QUICHE_FLAG(bool, spdy_reloadable_flag_quic_clean_up_spdy_session_destructor, false, - "If true, QuicSpdySession's destructor won't need to do cleanup.") - -QUICHE_FLAG( - bool, spdy_reloadable_flag_spdy_discard_response_body_if_disallowed, false, - "If true, SPDY will discard all response body bytes when response code indicates no response " - "body should exist. Previously, we only discard partial bytes on the first response processing " - "and the rest of the response bytes would still be delivered even though the response code " - "said there should not be any body associated with the response code.") - -QUICHE_FLAG(bool, quic_allow_chlo_buffering, true, - "If true, allows packets to be buffered in anticipation of a " - "future CHLO, and allow CHLO packets to be buffered until next " - "iteration of the event loop.") - -QUICHE_FLAG(bool, quic_disable_pacing_for_perf_tests, false, "If true, disable pacing in QUIC") - -QUICHE_FLAG(bool, quic_enforce_single_packet_chlo, true, - "If true, enforce that QUIC CHLOs fit in one packet") - -QUICHE_FLAG(int64_t, quic_time_wait_list_max_connections, 600000, - "Maximum number of connections on the time-wait list. " - "A negative value implies no configured limit.") - -QUICHE_FLAG(int64_t, quic_time_wait_list_seconds, 200, - "Time period for which a given connection_id should live in " - "the time-wait state.") - -QUICHE_FLAG(double, quic_bbr_cwnd_gain, 2.0f, - "Congestion window gain for QUIC BBR during PROBE_BW phase.") - -QUICHE_FLAG(int32_t, quic_buffered_data_threshold, 8 * 1024, - "If buffered data in QUIC stream is less than this " - "threshold, buffers all provided data or asks upper layer for more data") - -QUICHE_FLAG(int32_t, quic_send_buffer_max_data_slice_size, 4 * 1024, - "Max size of data slice in bytes for QUIC stream send buffer.") - -QUICHE_FLAG(int32_t, quic_lumpy_pacing_size, 2, - "Number of packets that the pacing sender allows in bursts during " - "pacing. This flag is ignored if a flow's estimated bandwidth is " - "lower than 1200 kbps.") - -QUICHE_FLAG(double, quic_lumpy_pacing_cwnd_fraction, 0.25f, - "Congestion window fraction that the pacing sender allows in bursts " - "during pacing.") - -QUICHE_FLAG(int32_t, quic_max_pace_time_into_future_ms, 10, - "Max time that QUIC can pace packets into the future in ms.") - -QUICHE_FLAG(double, quic_pace_time_into_future_srtt_fraction, 0.125f, - "Smoothed RTT fraction that a connection can pace packets into the future.") - -QUICHE_FLAG(bool, quic_export_server_num_packets_per_write_histogram, false, - "If true, export number of packets written per write operation histogram.") - -QUICHE_FLAG(bool, quic_disable_version_negotiation_grease_randomness, false, - "If true, use predictable version negotiation versions.") - -QUICHE_FLAG(bool, quic_enable_http3_grease_randomness, true, - "If true, use random greased settings and frames.") - -QUICHE_FLAG(int64_t, quic_max_tracked_packet_count, 10000, "Maximum number of tracked packets.") - -QUICHE_FLAG(bool, quic_prober_uses_length_prefixed_connection_ids, false, - "If true, QuicFramer::WriteClientVersionNegotiationProbePacket uses " - "length-prefixed connection IDs.") - -QUICHE_FLAG(bool, quic_client_convert_http_header_name_to_lowercase, true, - "If true, HTTP request header names sent from QuicSpdyClientBase(and " - "descendents) will be automatically converted to lower case.") - -QUICHE_FLAG(bool, quic_enable_http3_server_push, false, - "If true, server push will be allowed in QUIC versions that use HTTP/3.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_bw_base_duration_ms, 2000, - "The default minimum duration for BBRv2-native probes, in milliseconds.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_bw_max_rand_duration_ms, 1000, - "The default upper bound of the random amount of BBRv2-native " - "probes, in milliseconds.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_rtt_period_ms, 10000, - "The default period for entering PROBE_RTT, in milliseconds.") - -QUICHE_FLAG(double, quic_bbr2_default_loss_threshold, 0.02, - "The default loss threshold for QUIC BBRv2, should be a value " - "between 0 and 1.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_startup_full_loss_count, 8, - "The default minimum number of loss marking events to exit STARTUP.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_bw_full_loss_count, 2, - "The default minimum number of loss marking events to exit PROBE_UP phase.") - -QUICHE_FLAG(double, quic_bbr2_default_inflight_hi_headroom, 0.01, - "The default fraction of unutilized headroom to try to leave in path " - "upon high loss.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_initial_ack_height_filter_window, 10, - "The default initial value of the max ack height filter's window length.") - -QUICHE_FLAG(double, quic_ack_aggregation_bandwidth_threshold, 1.0, - "If the bandwidth during ack aggregation is smaller than (estimated " - "bandwidth * this flag), consider the current aggregation completed " - "and starts a new one.") - -QUICHE_FLAG(int32_t, quic_anti_amplification_factor, 5, - "Anti-amplification factor. Before address validation, server will " - "send no more than factor times bytes received.") - -QUICHE_FLAG(int32_t, quic_max_buffered_crypto_bytes, 16 * 1024, - "The maximum amount of CRYPTO frame data that can be buffered.") - -QUICHE_FLAG(int32_t, quic_max_aggressive_retransmittable_on_wire_ping_count, 0, - "If set to non-zero, the maximum number of consecutive pings that " - "can be sent with aggressive initial retransmittable on wire timeout " - "if there is no new data received. After which, the timeout will be " - "exponentially back off until exceeds the default ping timeout.") - -QUICHE_FLAG(int32_t, quic_max_congestion_window, 2000, "The maximum congestion window in packets.") - -QUICHE_FLAG(int32_t, quic_max_streams_window_divisor, 2, - "The divisor that controls how often MAX_STREAMS frame is sent.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_testonly_default_false, false, - "A testonly reloadable flag that will always default to false.") - -QUICHE_FLAG(bool, http2_restart_flag_http2_testonly_default_false, false, - "A testonly restart flag that will always default to false.") - -QUICHE_FLAG(bool, spdy_reloadable_flag_spdy_testonly_default_false, false, - "A testonly reloadable flag that will always default to false.") - -QUICHE_FLAG(bool, spdy_restart_flag_spdy_testonly_default_false, false, - "A testonly restart flag that will always default to false.") - -#endif diff --git a/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h b/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h index 7d2561469780..dc6fe5429bde 100644 --- a/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h @@ -8,10 +8,10 @@ #include "extensions/quic_listeners/quiche/platform/flags_impl.h" -#define GetHttp2ReloadableFlagImpl(flag) quiche::FLAGS_http2_reloadable_flag_##flag->value() +#define GetHttp2ReloadableFlagImpl(flag) quiche::FLAGS_quic_reloadable_flag_##flag->value() #define SetHttp2ReloadableFlagImpl(flag, value) \ - quiche::FLAGS_http2_reloadable_flag_##flag->SetValue(value) + quiche::FLAGS_quic_reloadable_flag_##flag->setValue(value) #define HTTP2_CODE_COUNT_N_IMPL(flag, instance, total) \ do { \ diff --git a/source/extensions/quic_listeners/quiche/platform/quic_aligned_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_aligned_impl.h deleted file mode 100644 index 3f595380b720..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quic_aligned_impl.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "absl/base/optimization.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#define QUIC_ALIGN_OF_IMPL alignof -#ifdef _MSC_VER -#define QUIC_ALIGNED_IMPL(X) __declspec(align(X)) -#else -#define QUIC_ALIGNED_IMPL(X) __attribute__((aligned(X))) -#endif -#define QUIC_CACHELINE_ALIGNED_IMPL ABSL_CACHELINE_ALIGNED -#define QUIC_CACHELINE_SIZE_IMPL ABSL_CACHELINE_SIZE diff --git a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc index 2a886a12caa1..27b977908d95 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc @@ -10,25 +10,7 @@ namespace quic { -// static -bool QuicCertUtilsImpl::ExtractSubjectNameFromDERCert(quiche::QuicheStringPiece cert, - quiche::QuicheStringPiece* subject_out) { - CBS tbs_certificate; - if (!SeekToSubject(cert, &tbs_certificate)) { - return false; - } - - CBS subject; - if (!CBS_get_asn1_element(&tbs_certificate, &subject, CBS_ASN1_SEQUENCE)) { - return false; - } - *subject_out = - absl::string_view(reinterpret_cast(CBS_data(&subject)), CBS_len(&subject)); - return true; -} - -// static -bool QuicCertUtilsImpl::SeekToSubject(quiche::QuicheStringPiece cert, CBS* tbs_certificate) { +bool seekToSubject(absl::string_view cert, CBS* tbs_certificate) { CBS der; CBS_init(&der, reinterpret_cast(cert.data()), cert.size()); CBS certificate; @@ -65,4 +47,22 @@ bool QuicCertUtilsImpl::SeekToSubject(quiche::QuicheStringPiece cert, CBS* tbs_c return true; } +// static +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicCertUtilsImpl::ExtractSubjectNameFromDERCert(absl::string_view cert, + absl::string_view* subject_out) { + CBS tbs_certificate; + if (!seekToSubject(cert, &tbs_certificate)) { + return false; + } + + CBS subject; + if (!CBS_get_asn1_element(&tbs_certificate, &subject, CBS_ASN1_SEQUENCE)) { + return false; + } + *subject_out = + absl::string_view(reinterpret_cast(CBS_data(&subject)), CBS_len(&subject)); + return true; +} + } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h index 0c41b9dbcf21..29b882b7d75d 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h @@ -6,18 +6,15 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. +#include "absl/strings/string_view.h" #include "openssl/base.h" -#include "quiche/common/platform/api/quiche_string_piece.h" namespace quic { class QuicCertUtilsImpl { public: - static bool ExtractSubjectNameFromDERCert(quiche::QuicheStringPiece cert, - quiche::QuicheStringPiece* subject_out); - -private: - static bool SeekToSubject(quiche::QuicheStringPiece cert, CBS* tbs_certificate); + // NOLINTNEXTLINE(readability-identifier-naming) + static bool ExtractSubjectNameFromDERCert(absl::string_view cert, absl::string_view* subject_out); }; } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_fallthrough_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_fallthrough_impl.h deleted file mode 100644 index aa9d6bc36a18..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quic_fallthrough_impl.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#include "absl/base/macros.h" - -#define QUIC_FALLTHROUGH_INTENDED_IMPL ABSL_FALLTHROUGH_INTENDED diff --git a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc index 91d52c44abbc..b2e396fab4be 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc @@ -36,6 +36,7 @@ void depthFirstTraverseDirectory(const std::string& dirname, std::vector ReadFileContentsImpl(const std::string& dirname) { std::vector files; depthFirstTraverseDirectory(dirname, files); @@ -43,7 +44,8 @@ std::vector ReadFileContentsImpl(const std::string& dirname) { } // Reads the contents of |filename| as a string into |contents|. -void ReadFileContentsImpl(quiche::QuicheStringPiece filename, std::string* contents) { +// NOLINTNEXTLINE(readability-identifier-naming) +void ReadFileContentsImpl(absl::string_view filename, std::string* contents) { #ifdef WIN32 Envoy::Filesystem::InstanceImplWin32 fs; #else diff --git a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h index 654c1ad1826b..25c31e9deca2 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h @@ -8,7 +8,7 @@ #include -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" namespace quic { @@ -16,6 +16,7 @@ namespace quic { * Traverses the directory |dirname| and returns all of the files it contains. * @param dirname full path without trailing '/'. */ +// NOLINTNEXTLINE(readability-identifier-naming)` std::vector ReadFileContentsImpl(const std::string& dirname); /** @@ -23,6 +24,7 @@ std::vector ReadFileContentsImpl(const std::string& dirname); * @param filename the full path to the file. * @param contents output location of the file content. */ -void ReadFileContentsImpl(quiche::QuicheStringPiece filename, std::string* contents); +// NOLINTNEXTLINE(readability-identifier-naming) +void ReadFileContentsImpl(absl::string_view filename, std::string* contents); } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h index 872495f2db8e..d562bb1a4813 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h @@ -15,16 +15,16 @@ #define GetQuicFlagImpl(flag) (quiche::flag)->value() // |flag| is the global flag variable, which is a pointer to TypedFlag. -#define SetQuicFlagImpl(flag, value) (quiche::flag)->SetValue(value) +#define SetQuicFlagImpl(flag, value) (quiche::flag)->setValue(value) #define GetQuicReloadableFlagImpl(flag) quiche::FLAGS_quic_reloadable_flag_##flag->value() #define SetQuicReloadableFlagImpl(flag, value) \ - quiche::FLAGS_quic_reloadable_flag_##flag->SetValue(value) + quiche::FLAGS_quic_reloadable_flag_##flag->setValue(value) #define GetQuicRestartFlagImpl(flag) quiche::FLAGS_quic_restart_flag_##flag->value() -#define SetQuicRestartFlagImpl(flag, value) quiche::FLAGS_quic_restart_flag_##flag->SetValue(value) +#define SetQuicRestartFlagImpl(flag, value) quiche::FLAGS_quic_restart_flag_##flag->setValue(value) // Not wired into command-line parsing. #define DEFINE_QUIC_COMMAND_LINE_FLAG_IMPL(type, flag, value, help) \ diff --git a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc index bcbafb56639e..75849611d6a2 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc @@ -19,7 +19,8 @@ namespace quic { // static -bool QuicHostnameUtilsImpl::IsValidSNI(quiche::QuicheStringPiece sni) { +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicHostnameUtilsImpl::IsValidSNI(absl::string_view sni) { // TODO(wub): Implement it on top of GoogleUrl, once it is available. return sni.find_last_of('.') != std::string::npos && @@ -27,7 +28,8 @@ bool QuicHostnameUtilsImpl::IsValidSNI(quiche::QuicheStringPiece sni) { } // static -std::string QuicHostnameUtilsImpl::NormalizeHostname(quiche::QuicheStringPiece hostname) { +// NOLINTNEXTLINE(readability-identifier-naming) +std::string QuicHostnameUtilsImpl::NormalizeHostname(absl::string_view hostname) { // TODO(wub): Implement it on top of GoogleUrl, once it is available. std::string host = absl::AsciiStrToLower(hostname); diff --git a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h index 2b7ed43571b4..67cd787d03c2 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h @@ -6,7 +6,7 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" #include "quiche/quic/platform/api/quic_export.h" namespace quic { @@ -18,7 +18,8 @@ class QUIC_EXPORT_PRIVATE QuicHostnameUtilsImpl { // (2) check that the hostname contains valid characters only; and // (3) contains at least one dot. // NOTE(wub): Only (3) is implemented for now. - static bool IsValidSNI(quiche::QuicheStringPiece sni); + // NOLINTNEXTLINE(readability-identifier-naming) + static bool IsValidSNI(absl::string_view sni); // Normalize a hostname: // (1) Canonicalize it, similar to what Chromium does in @@ -27,7 +28,8 @@ class QUIC_EXPORT_PRIVATE QuicHostnameUtilsImpl { // (3) Remove the trailing '.'. // WARNING: May mutate |hostname| in place. // NOTE(wub): Only (2) and (3) are implemented for now. - static std::string NormalizeHostname(quiche::QuicheStringPiece hostname); + // NOLINTNEXTLINE(readability-identifier-naming) + static std::string NormalizeHostname(absl::string_view hostname); private: QuicHostnameUtilsImpl() = delete; diff --git a/source/extensions/quic_listeners/quiche/platform/quic_macros_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_macros_impl.h deleted file mode 100644 index b8b70a0426b4..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quic_macros_impl.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#include "absl/base/attributes.h" - -#define QUIC_MUST_USE_RESULT_IMPL ABSL_MUST_USE_RESULT -#define QUIC_UNUSED_IMPL ABSL_ATTRIBUTE_UNUSED -#define QUIC_CONST_INIT_IMPL ABSL_CONST_INIT diff --git a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc index c2eb527d6584..9e46c37df344 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc @@ -10,7 +10,8 @@ namespace quic { -quiche::QuicheStringPiece QuicMemSliceSpanImpl::GetData(size_t index) { +// NOLINTNEXTLINE(readability-identifier-naming) +absl::string_view QuicMemSliceSpanImpl::GetData(size_t index) { Envoy::Buffer::RawSliceVector slices = buffer_->getRawSlices(/*max_slices=*/index + 1); ASSERT(slices.size() > index); return {reinterpret_cast(slices[index].mem_), slices[index].len_}; diff --git a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h index 1824fb8d1fa5..ef40e6387057 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h @@ -9,7 +9,7 @@ #include "envoy/buffer/buffer.h" #include "absl/container/fixed_array.h" -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" #include "quiche/quic/core/quic_types.h" #include "quiche/quic/platform/api/quic_mem_slice.h" @@ -43,9 +43,13 @@ class QuicMemSliceSpanImpl { } // QuicMemSliceSpan - quiche::QuicheStringPiece GetData(size_t index); + // NOLINTNEXTLINE(readability-identifier-naming) + absl::string_view GetData(size_t index); + // NOLINTNEXTLINE(readability-identifier-naming) QuicByteCount total_length() { return buffer_->length(); }; + // NOLINTNEXTLINE(readability-identifier-naming) size_t NumSlices() { return buffer_->getRawSlices().size(); } + // NOLINTNEXTLINE(readability-identifier-naming) template QuicByteCount ConsumeAll(ConsumeFunction consume); bool empty() const { return buffer_->length() == 0; } @@ -54,6 +58,7 @@ class QuicMemSliceSpanImpl { }; template +// NOLINTNEXTLINE(readability-identifier-naming) QuicByteCount QuicMemSliceSpanImpl::ConsumeAll(ConsumeFunction consume) { size_t saved_length = 0; for (auto& slice : buffer_->getRawSlices()) { diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_ptr_util_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_testvalue_impl.h similarity index 52% rename from source/extensions/quic_listeners/quiche/platform/quiche_ptr_util_impl.h rename to source/extensions/quic_listeners/quiche/platform/quic_testvalue_impl.h index aaebe5d5c352..4b0201c35af6 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_ptr_util_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_testvalue_impl.h @@ -6,12 +6,11 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. -#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" -namespace quiche { +namespace quic { -template std::unique_ptr QuicheWrapUniqueImpl(T* ptr) { - return absl::WrapUnique(ptr); -} +// NOLINTNEXTLINE(readability-identifier-naming) +template void AdjustTestValueImpl(absl::string_view /*label*/, T* /*var*/) {} -} // namespace quiche +} // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h index 248cfc193e02..1e88abe466cc 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h @@ -19,4 +19,7 @@ inline bool GetGooglePacketHeadersFromControlMessageImpl(struct ::cmsghdr* /*cms return false; } +// NOLINTNEXTLINE(readability-identifier-naming) +inline void SetGoogleSocketOptionsImpl(int /*fd*/) {} + } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_arraysize_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_arraysize_impl.h deleted file mode 100644 index 7a23b53da85d..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quiche_arraysize_impl.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "absl/base/macros.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#define QUICHE_ARRAYSIZE_IMPL(array) ABSL_ARRAYSIZE(array) diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_optional_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_optional_impl.h deleted file mode 100644 index f8b2b6c0800d..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quiche_optional_impl.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "absl/types/optional.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -namespace quiche { - -template using QuicheOptionalImpl = absl::optional; - -#define QUICHE_NULLOPT_IMPL absl::nullopt - -} // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h index 3a6d1a393a8b..7b87c1cd61e8 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h @@ -2,7 +2,6 @@ #include "common/common/base64.h" -#include "extensions/quic_listeners/quiche/platform/quiche_optional_impl.h" #include "extensions/quic_listeners/quiche/platform/quiche_string_piece_impl.h" #include "extensions/quic_listeners/quiche/platform/string_utils.h" @@ -13,6 +12,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" +#include "absl/types/optional.h" // NOLINT(namespace-envoy) @@ -25,58 +25,16 @@ namespace quiche { class QuicheTextUtilsImpl { public: // NOLINTNEXTLINE(readability-identifier-naming) - static bool StartsWith(QuicheStringPieceImpl data, QuicheStringPieceImpl prefix) { - return absl::StartsWith(data, prefix); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool EndsWith(QuicheStringPieceImpl data, QuicheStringPieceImpl suffix) { - return absl::EndsWith(data, suffix); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool EndsWithIgnoreCase(QuicheStringPieceImpl data, QuicheStringPieceImpl suffix) { - return absl::EndsWithIgnoreCase(data, suffix); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string ToLower(QuicheStringPieceImpl data) { return absl::AsciiStrToLower(data); } + static std::string ToLower(absl::string_view data) { return absl::AsciiStrToLower(data); } // NOLINTNEXTLINE(readability-identifier-naming) - static void RemoveLeadingAndTrailingWhitespace(QuicheStringPieceImpl* data) { + static void RemoveLeadingAndTrailingWhitespace(absl::string_view* data) { *data = absl::StripAsciiWhitespace(*data); } - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToUint64(QuicheStringPieceImpl in, uint64_t* out) { - return absl::SimpleAtoi(in, out); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToInt(QuicheStringPieceImpl in, int* out) { return absl::SimpleAtoi(in, out); } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToUint32(QuicheStringPieceImpl in, uint32_t* out) { - return absl::SimpleAtoi(in, out); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToSizeT(QuicheStringPieceImpl in, size_t* out) { - return absl::SimpleAtoi(in, out); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string Uint64ToString(uint64_t in) { return absl::StrCat(in); } - - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string HexEncode(QuicheStringPieceImpl data) { return absl::BytesToHexString(data); } - // NOLINTNEXTLINE(readability-identifier-naming) static std::string Hex(uint32_t v) { return absl::StrCat(absl::Hex(v)); } - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string HexDecode(QuicheStringPieceImpl data) { return absl::HexStringToBytes(data); } - // NOLINTNEXTLINE(readability-identifier-naming) static void Base64Encode(const uint8_t* data, size_t data_len, std::string* output) { *output = @@ -84,27 +42,28 @@ class QuicheTextUtilsImpl { } // NOLINTNEXTLINE(readability-identifier-naming) - static QuicheOptionalImpl Base64Decode(QuicheStringPieceImpl input) { + static absl::optional Base64Decode(absl::string_view input) { return Envoy::Base64::decodeWithoutPadding(input); } // NOLINTNEXTLINE(readability-identifier-naming) - static std::string HexDump(QuicheStringPieceImpl binary_data) { - return quiche::HexDump(binary_data); - } + static std::string Uint64ToString(uint64_t in) { return absl::StrCat(in); } + + // NOLINTNEXTLINE(readability-identifier-naming) + static std::string HexDump(absl::string_view binary_data) { return quiche::HexDump(binary_data); } // NOLINTNEXTLINE(readability-identifier-naming) - static bool ContainsUpperCase(QuicheStringPieceImpl data) { + static bool ContainsUpperCase(absl::string_view data) { return std::any_of(data.begin(), data.end(), absl::ascii_isupper); } // NOLINTNEXTLINE(readability-identifier-naming) - static bool IsAllDigits(QuicheStringPieceImpl data) { + static bool IsAllDigits(absl::string_view data) { return std::all_of(data.begin(), data.end(), absl::ascii_isdigit); } // NOLINTNEXTLINE(readability-identifier-naming) - static std::vector Split(QuicheStringPieceImpl data, char delim) { + static std::vector Split(absl::string_view data, char delim) { return absl::StrSplit(data, delim); } }; diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc index 3260eafee4da..5387e059876a 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc @@ -9,7 +9,7 @@ namespace quiche { namespace { -QuicheOptional quicheUtcDateTimeToUnixSecondsInner(int year, int month, int day, int hour, +absl::optional quicheUtcDateTimeToUnixSecondsInner(int year, int month, int day, int hour, int minute, int second) { const absl::CivilSecond civil_time(year, month, day, hour, minute, second); if (second != 60 && (civil_time.year() != year || civil_time.month() != month || @@ -24,7 +24,7 @@ QuicheOptional quicheUtcDateTimeToUnixSecondsInner(int year, int month, } // namespace // NOLINTNEXTLINE(readability-identifier-naming) -QuicheOptional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, +absl::optional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, int minute, int second) { // Handle leap seconds without letting any other irregularities happen. if (second == 60) { diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h index a1b70b70a51e..5e2ef79567f5 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h @@ -10,12 +10,12 @@ #include "absl/time/civil_time.h" #include "absl/time/time.h" -#include "quiche/common/platform/api/quiche_optional.h" +#include "absl/types/optional.h" namespace quiche { // NOLINTNEXTLINE(readability-identifier-naming) -QuicheOptional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, +absl::optional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, int minute, int second); } // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_endianness_util_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_endianness_util_impl.h deleted file mode 100644 index 737b81ee2914..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/spdy_endianness_util_impl.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "envoy/common/platform.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -namespace spdy { - -inline uint16_t SpdyNetToHost16Impl(uint16_t x) { return ntohs(x); } - -inline uint32_t SpdyNetToHost32Impl(uint32_t x) { return ntohl(x); } - -// TODO: implement -inline uint64_t SpdyNetToHost64Impl(uint64_t /*x*/) { return 0; } - -inline uint16_t SpdyHostToNet16Impl(uint16_t x) { return htons(x); } - -inline uint32_t SpdyHostToNet32Impl(uint32_t x) { return htonl(x); } - -// TODO: implement -inline uint64_t SpdyHostToNet64Impl(uint64_t /*x*/) { return 0; } - -} // namespace spdy diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h index a3cbd680ffc5..833562fab5b9 100644 --- a/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h @@ -8,9 +8,9 @@ #include "extensions/quic_listeners/quiche/platform/flags_impl.h" -#define GetSpdyReloadableFlagImpl(flag) quiche::FLAGS_spdy_reloadable_flag_##flag->value() +#define GetSpdyReloadableFlagImpl(flag) quiche::FLAGS_quic_reloadable_flag_##flag->value() -#define GetSpdyRestartFlagImpl(flag) quiche::FLAGS_spdy_restart_flag_##flag->value() +#define GetSpdyRestartFlagImpl(flag) quiche::FLAGS_quic_restart_flag_##flag->value() #define SPDY_CODE_COUNT_N_IMPL(flag, instance, total) \ do { \ diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h index 41fa3cad815f..4b01b2dbddb3 100644 --- a/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h @@ -50,7 +50,7 @@ inline std::string SpdyHexEncodeUInt32AndTrimImpl(uint32_t data) { inline std::string SpdyHexDumpImpl(absl::string_view data) { return quiche::HexDump(data); } struct SpdyStringPieceCaseHashImpl { - size_t operator()(quiche::QuicheStringPiece data) const { + size_t operator()(absl::string_view data) const { std::string lower = absl::AsciiStrToLower(data); return absl::Hash()(lower); } diff --git a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h index b974a36725d1..142489b39d9b 100644 --- a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h +++ b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h @@ -24,7 +24,7 @@ class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory { NOT_REACHED_GCOVR_EXCL_LINE; } bool implementsSecureTransport() const override { return true; } - bool isReady() const override { return true; } + bool usesProxyProtocolOptions() const override { return false; } }; // TODO(danzh): when implement ProofSource, examine of it's necessary to diff --git a/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc b/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc index 3bd0bc2950b2..5ac5738c4bfe 100644 --- a/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc +++ b/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc @@ -12,25 +12,29 @@ using spdy::SpdyHeaderBlock; namespace quic { // static +// NOLINTNEXTLINE(readability-identifier-naming) std::string SpdyServerPushUtils::GetPromisedUrlFromHeaders(const SpdyHeaderBlock& /*headers*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } // static std::string +// NOLINTNEXTLINE(readability-identifier-naming) SpdyServerPushUtils::GetPromisedHostNameFromHeaders(const SpdyHeaderBlock& /*headers*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } // static +// NOLINTNEXTLINE(readability-identifier-naming) bool SpdyServerPushUtils::PromisedUrlIsValid(const SpdyHeaderBlock& /*headers*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } // static -std::string SpdyServerPushUtils::GetPushPromiseUrl(quiche::QuicheStringPiece /*scheme*/, - quiche::QuicheStringPiece /*authority*/, - quiche::QuicheStringPiece /*path*/) { +// NOLINTNEXTLINE(readability-identifier-naming) +std::string SpdyServerPushUtils::GetPushPromiseUrl(absl::string_view /*scheme*/, + absl::string_view /*authority*/, + absl::string_view /*path*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } diff --git a/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc b/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc index 95aac4447c1d..8b0b5461aeca 100644 --- a/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc +++ b/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc @@ -4,9 +4,7 @@ #include "common/api/os_sys_calls_impl.h" -#if defined(__linux__) #include "extensions/quic_listeners/quiche/udp_gso_batch_writer.h" -#endif namespace Envoy { namespace Quic { @@ -22,7 +20,7 @@ UdpGsoBatchWriterConfigFactory::createUdpPacketWriterFactory(const Protobuf::Mes "for UDP GSO. Reset udp_writer_config to default writer"); } -#if defined(__linux__) +#if UDP_GSO_BATCH_WRITER_COMPILETIME_SUPPORT return std::make_unique(); #else // On non-linux, `supportsUdpGso()` always returns false. diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc index a35f67a8d3f7..6f4a1808accf 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.cc +++ b/source/extensions/stat_sinks/hystrix/hystrix.cc @@ -339,7 +339,7 @@ void HystrixSink::flush(Stats::MetricSnapshot& snapshot) { } incCounter(); std::stringstream ss; - Upstream::ClusterManager::ClusterInfoMap clusters = server_.clusterManager().clusters(); + Upstream::ClusterManager::ClusterInfoMaps all_clusters = server_.clusterManager().clusters(); // Save a map of the relevant histograms per cluster in a convenient format. absl::node_hash_map time_histograms; @@ -370,7 +370,7 @@ void HystrixSink::flush(Stats::MetricSnapshot& snapshot) { } } - for (auto& cluster : clusters) { + for (auto& cluster : all_clusters.active_clusters_) { Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.second.get().info(); std::unique_ptr& cluster_stats_cache_ptr = @@ -407,9 +407,9 @@ void HystrixSink::flush(Stats::MetricSnapshot& snapshot) { } // check if any clusters were removed, and remove from cache - if (clusters.size() < cluster_stats_cache_map_.size()) { + if (all_clusters.active_clusters_.size() < cluster_stats_cache_map_.size()) { for (auto it = cluster_stats_cache_map_.begin(); it != cluster_stats_cache_map_.end();) { - if (clusters.find(it->first) == clusters.end()) { + if (all_clusters.active_clusters_.find(it->first) == all_clusters.active_clusters_.end()) { auto next_it = std::next(it); cluster_stats_cache_map_.erase(it); it = next_it; diff --git a/source/extensions/stat_sinks/metrics_service/config.cc b/source/extensions/stat_sinks/metrics_service/config.cc index db1998aefe5b..05228e9a67c4 100644 --- a/source/extensions/stat_sinks/metrics_service/config.cc +++ b/source/extensions/stat_sinks/metrics_service/config.cc @@ -36,7 +36,7 @@ MetricsServiceSinkFactory::createStatsSink(const Protobuf::Message& config, server.localInfo(), transport_api_version); return std::make_unique( - grpc_metrics_streamer, server.timeSource(), + grpc_metrics_streamer, PROTOBUF_GET_WRAPPED_OR_DEFAULT(sink_config, report_counters_as_deltas, false)); } 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 092e3fbe6fcf..d18bfbf20c83 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 @@ -1,5 +1,7 @@ #include "extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h" +#include + #include "envoy/common/exception.h" #include "envoy/event/dispatcher.h" #include "envoy/service/metrics/v3/metrics_service.pb.h" @@ -38,20 +40,17 @@ void GrpcMetricsStreamerImpl::send(envoy::service::metrics::v3::StreamMetricsMes } MetricsServiceSink::MetricsServiceSink(const GrpcMetricsStreamerSharedPtr& grpc_metrics_streamer, - TimeSource& time_source, const bool report_counters_as_deltas) - : grpc_metrics_streamer_(grpc_metrics_streamer), time_source_(time_source), + : grpc_metrics_streamer_(grpc_metrics_streamer), report_counters_as_deltas_(report_counters_as_deltas) {} void MetricsServiceSink::flushCounter( - const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot) { + const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot, int64_t snapshot_time_ms) { io::prometheus::client::MetricFamily* metrics_family = message_.add_envoy_metrics(); metrics_family->set_type(io::prometheus::client::MetricType::COUNTER); metrics_family->set_name(counter_snapshot.counter_.get().name()); auto* metric = metrics_family->add_metric(); - metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + metric->set_timestamp_ms(snapshot_time_ms); auto* counter_metric = metric->mutable_counter(); if (report_counters_as_deltas_) { counter_metric->set_value(counter_snapshot.delta_); @@ -60,19 +59,18 @@ void MetricsServiceSink::flushCounter( } } -void MetricsServiceSink::flushGauge(const Stats::Gauge& gauge) { +void MetricsServiceSink::flushGauge(const Stats::Gauge& gauge, int64_t snapshot_time_ms) { io::prometheus::client::MetricFamily* metrics_family = message_.add_envoy_metrics(); metrics_family->set_type(io::prometheus::client::MetricType::GAUGE); metrics_family->set_name(gauge.name()); auto* metric = metrics_family->add_metric(); - metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + metric->set_timestamp_ms(snapshot_time_ms); auto* gauge_metric = metric->mutable_gauge(); gauge_metric->set_value(gauge.value()); } -void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_histogram) { +void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_histogram, + int64_t snapshot_time_ms) { // TODO(ramaraochavali): Currently we are sending both quantile information and bucket // information. We should make this configurable if it turns out that sending both affects // performance. @@ -82,9 +80,7 @@ void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_hist summary_metrics_family->set_type(io::prometheus::client::MetricType::SUMMARY); summary_metrics_family->set_name(envoy_histogram.name()); auto* summary_metric = summary_metrics_family->add_metric(); - summary_metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + summary_metric->set_timestamp_ms(snapshot_time_ms); auto* summary = summary_metric->mutable_summary(); const Stats::HistogramStatistics& hist_stats = envoy_histogram.intervalStatistics(); for (size_t i = 0; i < hist_stats.supportedQuantiles().size(); i++) { @@ -98,9 +94,7 @@ void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_hist histogram_metrics_family->set_type(io::prometheus::client::MetricType::HISTOGRAM); histogram_metrics_family->set_name(envoy_histogram.name()); auto* histogram_metric = histogram_metrics_family->add_metric(); - histogram_metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + histogram_metric->set_timestamp_ms(snapshot_time_ms); auto* histogram = histogram_metric->mutable_histogram(); histogram->set_sample_count(hist_stats.sampleCount()); histogram->set_sample_sum(hist_stats.sampleSum()); @@ -119,21 +113,24 @@ void MetricsServiceSink::flush(Stats::MetricSnapshot& snapshot) { // preallocating the pointer array). message_.mutable_envoy_metrics()->Reserve(snapshot.counters().size() + snapshot.gauges().size() + snapshot.histograms().size()); + int64_t snapshot_time_ms = std::chrono::duration_cast( + snapshot.snapshotTime().time_since_epoch()) + .count(); for (const auto& counter : snapshot.counters()) { if (counter.counter_.get().used()) { - flushCounter(counter); + flushCounter(counter, snapshot_time_ms); } } for (const auto& gauge : snapshot.gauges()) { if (gauge.get().used()) { - flushGauge(gauge.get()); + flushGauge(gauge.get(), snapshot_time_ms); } } for (const auto& histogram : snapshot.histograms()) { if (histogram.get().used()) { - flushHistogram(histogram.get()); + flushHistogram(histogram.get(), snapshot_time_ms); } } diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h index d65bae27f9bb..668c7f3fb567 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h @@ -80,18 +80,18 @@ class MetricsServiceSink : public Stats::Sink { public: // MetricsService::Sink MetricsServiceSink(const GrpcMetricsStreamerSharedPtr& grpc_metrics_streamer, - TimeSource& time_system, const bool report_counters_as_deltas); + const bool report_counters_as_deltas); void flush(Stats::MetricSnapshot& snapshot) override; void onHistogramComplete(const Stats::Histogram&, uint64_t) override {} - void flushCounter(const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot); - void flushGauge(const Stats::Gauge& gauge); - void flushHistogram(const Stats::ParentHistogram& envoy_histogram); + void flushCounter(const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot, + int64_t snapshot_time_ms); + void flushGauge(const Stats::Gauge& gauge, int64_t snapshot_time_ms); + void flushHistogram(const Stats::ParentHistogram& envoy_histogram, int64_t snapshot_time_ms); private: GrpcMetricsStreamerSharedPtr grpc_metrics_streamer_; envoy::service::metrics::v3::StreamMetricsMessage message_; - TimeSource& time_source_; const bool report_counters_as_deltas_; }; diff --git a/source/extensions/stat_sinks/wasm/config.cc b/source/extensions/stat_sinks/wasm/config.cc index ba94937a3b3a..da07bbdd5880 100644 --- a/source/extensions/stat_sinks/wasm/config.cc +++ b/source/extensions/stat_sinks/wasm/config.cc @@ -22,14 +22,14 @@ WasmSinkFactory::createStatsSink(const Protobuf::Message& proto_config, MessageUtil::downcastAndValidate( proto_config, context.messageValidationContext().staticValidationVisitor()); - auto wasm_sink = std::make_unique(config.config().root_id(), nullptr); - auto plugin = std::make_shared( config.config().name(), config.config().root_id(), config.config().vm_config().vm_id(), config.config().vm_config().runtime(), Common::Wasm::anyToBytes(config.config().configuration()), config.config().fail_open(), envoy::config::core::v3::TrafficDirection::UNSPECIFIED, context.localInfo(), nullptr); + auto wasm_sink = std::make_unique(plugin, nullptr); + auto callback = [&wasm_sink, &context, plugin](Common::Wasm::WasmHandleSharedPtr base_wasm) { if (!base_wasm) { if (plugin->fail_open_) { @@ -40,7 +40,7 @@ WasmSinkFactory::createStatsSink(const Protobuf::Message& proto_config, return; } wasm_sink->setSingleton( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, context.dispatcher())); + Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, context.dispatcher())); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h b/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h index 5f2a9b6e13f9..5b339a6c80e9 100644 --- a/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h +++ b/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h @@ -9,20 +9,21 @@ namespace Extensions { namespace StatSinks { namespace Wasm { -using Envoy::Extensions::Common::Wasm::WasmHandle; +using Envoy::Extensions::Common::Wasm::PluginHandleSharedPtr; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; class WasmStatSink : public Stats::Sink { public: - WasmStatSink(absl::string_view root_id, Common::Wasm::WasmHandleSharedPtr singleton) - : root_id_(root_id), singleton_(std::move(singleton)) {} + WasmStatSink(const PluginSharedPtr& plugin, PluginHandleSharedPtr singleton) + : plugin_(plugin), singleton_(singleton) {} void flush(Stats::MetricSnapshot& snapshot) override { - singleton_->wasm()->onStatsUpdate(root_id_, snapshot); + singleton_->wasm()->onStatsUpdate(plugin_, snapshot); } - void setSingleton(Common::Wasm::WasmHandleSharedPtr singleton) { + void setSingleton(PluginHandleSharedPtr singleton) { ASSERT(singleton != nullptr); - singleton_ = std::move(singleton); + singleton_ = singleton; } void onHistogramComplete(const Stats::Histogram& histogram, uint64_t value) override { @@ -31,8 +32,8 @@ class WasmStatSink : public Stats::Sink { } private: - std::string root_id_; - Common::Wasm::WasmHandleSharedPtr singleton_; + PluginSharedPtr plugin_; + PluginHandleSharedPtr singleton_; }; } // namespace Wasm diff --git a/source/extensions/tracers/skywalking/BUILD b/source/extensions/tracers/skywalking/BUILD new file mode 100644 index 000000000000..5cf90c3f976f --- /dev/null +++ b/source/extensions/tracers/skywalking/BUILD @@ -0,0 +1,107 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +# Trace driver for Apache SkyWalking. + +envoy_extension_package() + +envoy_cc_library( + name = "trace_segment_reporter_lib", + srcs = ["trace_segment_reporter.cc"], + hdrs = ["trace_segment_reporter.h"], + deps = [ + ":skywalking_client_config_lib", + ":skywalking_stats_lib", + ":skywalking_types_lib", + "//include/envoy/grpc:async_client_manager_interface", + "//source/common/common:backoff_lib", + "//source/common/grpc:async_client_lib", + "@com_github_apache_skywalking_data_collect_protocol//:protocol_cc_proto", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_types_lib", + srcs = ["skywalking_types.cc"], + hdrs = ["skywalking_types.h"], + deps = [ + ":skywalking_stats_lib", + "//include/envoy/common:random_generator_interface", + "//include/envoy/common:time_interface", + "//include/envoy/http:header_map_interface", + "//include/envoy/tracing:http_tracer_interface", + "//source/common/common:base64_lib", + "//source/common/common:hex_lib", + "//source/common/common:utility_lib", + "@com_github_apache_skywalking_data_collect_protocol//:protocol_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_client_config_lib", + srcs = ["skywalking_client_config.cc"], + hdrs = ["skywalking_client_config.h"], + deps = [ + "//include/envoy/secret:secret_provider_interface", + "//include/envoy/server:factory_context_interface", + "//include/envoy/server:tracer_config_interface", + "//source/common/config:datasource_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_tracer_lib", + srcs = [ + "skywalking_tracer_impl.cc", + "tracer.cc", + ], + hdrs = [ + "skywalking_tracer_impl.h", + "tracer.h", + ], + deps = [ + ":skywalking_client_config_lib", + ":skywalking_types_lib", + ":trace_segment_reporter_lib", + "//include/envoy/common:time_interface", + "//include/envoy/server:tracer_config_interface", + "//include/envoy/tracing:http_tracer_interface", + "//source/common/common:macros", + "//source/common/http:header_map_lib", + "//source/common/runtime:runtime_lib", + "//source/common/tracing:http_tracer_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_stats_lib", + hdrs = [ + "skywalking_stats.h", + ], + deps = [ + "//include/envoy/stats:stats_macros", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + security_posture = "robust_to_untrusted_downstream", + status = "wip", + deps = [ + ":skywalking_tracer_lib", + "//source/common/config:datasource_lib", + "//source/extensions/tracers/common:factory_base_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/tracers/skywalking/config.cc b/source/extensions/tracers/skywalking/config.cc new file mode 100644 index 000000000000..4f9e15b12f2d --- /dev/null +++ b/source/extensions/tracers/skywalking/config.cc @@ -0,0 +1,36 @@ +#include "extensions/tracers/skywalking/config.h" + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.validate.h" +#include "envoy/registry/registry.h" + +#include "common/common/utility.h" +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/skywalking/skywalking_tracer_impl.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +SkyWalkingTracerFactory::SkyWalkingTracerFactory() : FactoryBase("envoy.tracers.skywalking") {} + +Tracing::HttpTracerSharedPtr SkyWalkingTracerFactory::createHttpTracerTyped( + const envoy::config::trace::v3::SkyWalkingConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) { + Tracing::DriverPtr skywalking_driver = + std::make_unique(proto_config, context); + return std::make_shared(std::move(skywalking_driver), + context.serverFactoryContext().localInfo()); +} + +/** + * Static registration for the SkyWalking tracer. @see RegisterFactory. + */ +REGISTER_FACTORY(SkyWalkingTracerFactory, Server::Configuration::TracerFactory); + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/config.h b/source/extensions/tracers/skywalking/config.h new file mode 100644 index 000000000000..abeffe373e5d --- /dev/null +++ b/source/extensions/tracers/skywalking/config.h @@ -0,0 +1,31 @@ +#pragma once + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.validate.h" + +#include "extensions/tracers/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +/** + * Config registration for the SkyWalking tracer. @see TracerFactory. + */ +class SkyWalkingTracerFactory + : public Common::FactoryBase { +public: + SkyWalkingTracerFactory(); + +private: + // FactoryBase + Tracing::HttpTracerSharedPtr + createHttpTracerTyped(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) override; +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_client_config.cc b/source/extensions/tracers/skywalking/skywalking_client_config.cc new file mode 100644 index 000000000000..ed692b3f72bd --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_client_config.cc @@ -0,0 +1,43 @@ +#include "extensions/tracers/skywalking/skywalking_client_config.h" + +#include "common/config/datasource.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +constexpr uint32_t DEFAULT_DELAYED_SEGMENTS_CACHE_SIZE = 1024; + +// When the user does not provide any available configuration, in order to ensure that the service +// name and instance name are not empty, use this value as the default identifier. In practice, +// user should provide accurate configuration as much as possible to avoid using the default value. +constexpr absl::string_view DEFAULT_SERVICE_AND_INSTANCE = "EnvoyProxy"; + +SkyWalkingClientConfig::SkyWalkingClientConfig(Server::Configuration::TracerFactoryContext& context, + const envoy::config::trace::v3::ClientConfig& config) + : factory_context_(context.serverFactoryContext()), + max_cache_size_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_cache_size, + DEFAULT_DELAYED_SEGMENTS_CACHE_SIZE)), + service_(config.service_name().empty() ? factory_context_.localInfo().clusterName().empty() + ? DEFAULT_SERVICE_AND_INSTANCE + : factory_context_.localInfo().clusterName() + : config.service_name()), + instance_(config.instance_name().empty() ? factory_context_.localInfo().nodeName().empty() + ? DEFAULT_SERVICE_AND_INSTANCE + : factory_context_.localInfo().nodeName() + : config.instance_name()) { + // Since the SDS API to get backend token is not supported yet, we can get the value of token + // from the backend_token field directly. If the user does not provide the configuration, the + // value of token is kept empty. + backend_token_ = config.backend_token(); +} + +// TODO(wbpcode): currently, backend authentication token can only be configured with inline string. +// It will be possible to get authentication through the SDS API later. +const std::string& SkyWalkingClientConfig::backendToken() const { return backend_token_; } + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_client_config.h b/source/extensions/tracers/skywalking/skywalking_client_config.h new file mode 100644 index 000000000000..8983a1dbf758 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_client_config.h @@ -0,0 +1,43 @@ +#pragma once + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/secret/secret_provider.h" +#include "envoy/server/factory_context.h" +#include "envoy/server/tracer_config.h" + +#include "absl/synchronization/mutex.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class SkyWalkingClientConfig { +public: + SkyWalkingClientConfig(Server::Configuration::TracerFactoryContext& context, + const envoy::config::trace::v3::ClientConfig& config); + + uint32_t maxCacheSize() const { return max_cache_size_; } + + const std::string& service() const { return service_; } + const std::string& serviceInstance() const { return instance_; } + + const std::string& backendToken() const; + +private: + Server::Configuration::ServerFactoryContext& factory_context_; + + const uint32_t max_cache_size_{0}; + + const std::string service_; + const std::string instance_; + + std::string backend_token_; +}; + +using SkyWalkingClientConfigPtr = std::unique_ptr; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_stats.h b/source/extensions/tracers/skywalking/skywalking_stats.h new file mode 100644 index 000000000000..0ad8a58f5ab5 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_stats.h @@ -0,0 +1,21 @@ +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +#define SKYWALKING_TRACER_STATS(COUNTER) \ + COUNTER(cache_flushed) \ + COUNTER(segments_dropped) \ + COUNTER(segments_flushed) \ + COUNTER(segments_sent) + +struct SkyWalkingTracerStats { + SKYWALKING_TRACER_STATS(GENERATE_COUNTER_STRUCT) +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc new file mode 100644 index 000000000000..130cbd6a9801 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc @@ -0,0 +1,63 @@ +#include "extensions/tracers/skywalking/skywalking_tracer_impl.h" + +#include + +#include "common/common/macros.h" +#include "common/common/utility.h" +#include "common/http/path_utility.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +Driver::Driver(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) + : tracing_stats_{SKYWALKING_TRACER_STATS( + POOL_COUNTER_PREFIX(context.serverFactoryContext().scope(), "tracing.skywalking."))}, + client_config_( + std::make_unique(context, proto_config.client_config())), + random_generator_(context.serverFactoryContext().api().randomGenerator()), + tls_slot_ptr_(context.serverFactoryContext().threadLocal().allocateSlot()) { + + auto& factory_context = context.serverFactoryContext(); + tls_slot_ptr_->set([proto_config, &factory_context, this](Event::Dispatcher& dispatcher) { + TracerPtr tracer = std::make_unique(factory_context.timeSource()); + tracer->setReporter(std::make_unique( + factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService( + proto_config.grpc_service(), factory_context.scope(), false), + dispatcher, factory_context.api().randomGenerator(), tracing_stats_, *client_config_)); + return std::make_shared(std::move(tracer)); + }); +} + +Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config, + Http::RequestHeaderMap& request_headers, + const std::string& operation_name, Envoy::SystemTime start_time, + const Tracing::Decision decision) { + auto& tracer = *tls_slot_ptr_->getTyped().tracer_; + + try { + SpanContextPtr previous_span_context = SpanContext::spanContextFromRequest(request_headers); + auto segment_context = std::make_shared(std::move(previous_span_context), + decision, random_generator_); + + // Initialize fields of current span context. + segment_context->setService(client_config_->service()); + segment_context->setServiceInstance(client_config_->serviceInstance()); + + return tracer.startSpan(config, start_time, operation_name, std::move(segment_context), + nullptr); + + } catch (const EnvoyException& e) { + ENVOY_LOG(warn, "New SkyWalking Span/Segment cannot be created for error: {}", e.what()); + return std::make_unique(); + } +} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h new file mode 100644 index 000000000000..f073461deb5d --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h @@ -0,0 +1,48 @@ +#pragma once + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/server/tracer_config.h" +#include "envoy/thread_local/thread_local.h" +#include "envoy/tracing/http_tracer.h" + +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/skywalking/skywalking_client_config.h" +#include "extensions/tracers/skywalking/tracer.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class Driver : public Tracing::Driver, public Logger::Loggable { +public: + explicit Driver(const envoy::config::trace::v3::SkyWalkingConfig& config, + Server::Configuration::TracerFactoryContext& context); + + Tracing::SpanPtr startSpan(const Tracing::Config& config, Http::RequestHeaderMap& request_headers, + const std::string& operation, Envoy::SystemTime start_time, + const Tracing::Decision decision) override; + +private: + struct TlsTracer : ThreadLocal::ThreadLocalObject { + TlsTracer(TracerPtr tracer) : tracer_(std::move(tracer)) {} + + TracerPtr tracer_; + }; + + SkyWalkingTracerStats tracing_stats_; + + SkyWalkingClientConfigPtr client_config_; + + // This random_generator_ will be used to create SkyWalking trace id and segment id. + Random::RandomGenerator& random_generator_; + ThreadLocal::SlotPtr tls_slot_ptr_; +}; + +using DriverPtr = std::unique_ptr; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_types.cc b/source/extensions/tracers/skywalking/skywalking_types.cc new file mode 100644 index 000000000000..9c750884bc11 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_types.cc @@ -0,0 +1,175 @@ +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "envoy/common/exception.h" + +#include "common/common/base64.h" +#include "common/common/empty_string.h" +#include "common/common/fmt.h" +#include "common/common/hex.h" +#include "common/common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +namespace { + +// The standard header name is "sw8", as mentioned in: +// https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md. +const Http::LowerCaseString& propagationHeader() { + CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, "sw8"); +} + +std::string generateId(Random::RandomGenerator& random_generator) { + return absl::StrCat(Hex::uint64ToHex(random_generator.random()), + Hex::uint64ToHex(random_generator.random())); +} + +std::string base64Encode(const absl::string_view input) { + return Base64::encode(input.data(), input.length()); +} + +// Decode and validate fields of propagation header. +std::string base64Decode(absl::string_view input) { + // The input can be Base64 string with or without padding. + std::string result = Base64::decodeWithoutPadding(input); + if (result.empty()) { + throw EnvoyException("Invalid propagation header for SkyWalking: parse error"); + } + return result; +} + +} // namespace + +SpanContextPtr SpanContext::spanContextFromRequest(Http::RequestHeaderMap& headers) { + auto propagation_header = headers.get(propagationHeader()); + if (propagation_header.empty()) { + // No propagation header then Envoy is first hop. + return nullptr; + } + + auto header_value_string = propagation_header[0]->value().getStringView(); + const auto parts = StringUtil::splitToken(header_value_string, "-", false, true); + // Reference: + // https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md. + if (parts.size() != 8) { + throw EnvoyException( + fmt::format("Invalid propagation header for SkyWalking: {}", header_value_string)); + } + + SpanContextPtr previous_span_context = std::unique_ptr(new SpanContext()); + + // Parse and validate sampling flag. + if (parts[0] == "0") { + previous_span_context->sampled_ = 0; + } else if (parts[0] == "1") { + previous_span_context->sampled_ = 1; + } else { + throw EnvoyException(fmt::format("Invalid propagation header for SkyWalking: sampling flag can " + "only be '0' or '1' but '{}' was provided", + parts[0])); + } + + // Parse trace id. + previous_span_context->trace_id_ = base64Decode(parts[1]); + // Parse segment id. + previous_span_context->trace_segment_id_ = base64Decode(parts[2]); + + // Parse span id. + if (!absl::SimpleAtoi(parts[3], &previous_span_context->span_id_)) { + throw EnvoyException(fmt::format( + "Invalid propagation header for SkyWalking: connot convert '{}' to valid span id", + parts[3])); + } + + // Parse service. + previous_span_context->service_ = base64Decode(parts[4]); + // Parse service instance. + previous_span_context->service_instance_ = base64Decode(parts[5]); + // Parse endpoint. Operation Name of the first entry span in the previous segment. + previous_span_context->endpoint_ = base64Decode(parts[6]); + // Parse target address used at downstream side of this request. + previous_span_context->target_address_ = base64Decode(parts[7]); + + return previous_span_context; +} + +SegmentContext::SegmentContext(SpanContextPtr&& previous_span_context, Tracing::Decision decision, + Random::RandomGenerator& random_generator) + : previous_span_context_(std::move(previous_span_context)) { + + if (previous_span_context_) { + trace_id_ = previous_span_context_->trace_id_; + sampled_ = previous_span_context_->sampled_; + } else { + trace_id_ = generateId(random_generator); + sampled_ = decision.traced; + } + trace_segment_id_ = generateId(random_generator); + + // Some detailed log for debugging. + ENVOY_LOG(trace, "{} and create new SkyWalking segment:", + previous_span_context_ ? "Has previous span context" : "No previous span context"); + + ENVOY_LOG(trace, " Trace ID: {}", trace_id_); + ENVOY_LOG(trace, " Segment ID: {}", trace_segment_id_); + ENVOY_LOG(trace, " Sampled: {}", sampled_); +} + +SpanStore* SegmentContext::createSpanStore(const SpanStore* parent_span_store) { + ENVOY_LOG(trace, "Create new SpanStore object for current segment: {}", trace_segment_id_); + SpanStorePtr new_span_store = std::make_unique(this); + new_span_store->setSpanId(span_list_.size()); + if (!parent_span_store) { + // The parent SpanStore object does not exist. Create the root SpanStore object in the current + // segment. + new_span_store->setSampled(sampled_); + new_span_store->setParentSpanId(-1); + // First span of current segment for Envoy Proxy must be a Entry Span. It is created for + // downstream HTTP request. + new_span_store->setAsEntrySpan(true); + } else { + // Create child SpanStore object. + new_span_store->setSampled(parent_span_store->sampled()); + new_span_store->setParentSpanId(parent_span_store->spanId()); + new_span_store->setAsEntrySpan(false); + } + SpanStore* ref = new_span_store.get(); + span_list_.emplace_back(std::move(new_span_store)); + return ref; +} + +void SpanStore::injectContext(Http::RequestHeaderMap& request_headers) const { + ASSERT(segment_context_); + + // For SkyWalking Entry Span, Envoy does not need to inject tracing context into the request + // headers. + if (is_entry_span_) { + ENVOY_LOG(debug, "Skip tracing context injection for SkyWalking Entry Span"); + return; + } + + ENVOY_LOG(debug, "Inject or update SkyWalking propagation header in upstream request headers"); + const_cast(this)->setPeerAddress(std::string(request_headers.getHostValue())); + + ENVOY_LOG(trace, "'sw8' header: '({}) - ({}) - ({}) - ({}) - ({}) - ({}) - ({}) - ({})'", + sampled_, segment_context_->traceId(), segment_context_->traceSegmentId(), span_id_, + segment_context_->service(), segment_context_->serviceInstance(), + segment_context_->rootSpanStore()->operation(), peer_address_); + + // Reference: + // https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md. + const auto value = absl::StrCat(sampled_, "-", base64Encode(segment_context_->traceId()), "-", + base64Encode(segment_context_->traceSegmentId()), "-", span_id_, + "-", base64Encode(segment_context_->service()), "-", + base64Encode(segment_context_->serviceInstance()), "-", + base64Encode(segment_context_->rootSpanStore()->operation()), "-", + base64Encode(peer_address_)); + request_headers.setReferenceKey(propagationHeader(), value); +} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_types.h b/source/extensions/tracers/skywalking/skywalking_types.h new file mode 100644 index 000000000000..eacd9a94a075 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_types.h @@ -0,0 +1,313 @@ +#pragma once + +#include +#include + +#include "envoy/common/random_generator.h" +#include "envoy/common/time.h" +#include "envoy/http/header_map.h" +#include "envoy/tracing/http_tracer.h" + +#include "language-agent/Tracing.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class SegmentContext; +using SegmentContextSharedPtr = std::shared_ptr; + +class SpanStore; +using SpanStorePtr = std::unique_ptr; + +class SpanContext; +using SpanContextPtr = std::unique_ptr; + +class SpanContext : public Logger::Loggable { +public: + /* + * Parse the context of the previous span from the request and decide whether to sample it or + * not. + * + * @param headers The request headers. + * @return SpanContextPtr The previous span context parsed from request headers. + */ + static SpanContextPtr spanContextFromRequest(Http::RequestHeaderMap& headers); + + // Sampling flag. This field can only be 0 or 1. 1 means this trace need to be sampled and send to + // backend. + int sampled_{0}; + + // This span id points to the parent span in parent trace segment. + int span_id_{0}; + + std::string trace_id_; + + // This trace segment id points to the parent trace segment. + std::string trace_segment_id_; + + std::string service_; + std::string service_instance_; + + // Operation Name of the first entry span in the parent segment. + std::string endpoint_; + + // Target address used at client side of this request. The network address(not must be IP + port) + // used at client side to access this target service. + std::string target_address_; + +private: + // Private default constructor. We can only create SpanContext by 'spanContextFromRequest'. + SpanContext() = default; +}; + +class SegmentContext : public Logger::Loggable { +public: + /* + * Create a new segment context based on the previous span context that parsed from request + * headers. + * + * @param previous_span_context The previous span context. + * @param random_generator The random generator that used to create trace id and segment id. + * @param decision The tracing decision. + */ + SegmentContext(SpanContextPtr&& previous_span_context, Tracing::Decision decision, + Random::RandomGenerator& random_generator); + + /* + * Set service name. + * + * @param service The service name. + */ + void setService(const std::string& service) { service_ = service; } + + /* + * Set service instance name. + * + * @param service_instance The service instance name. + */ + void setServiceInstance(const std::string& service_instance) { + service_instance_ = service_instance; + } + + /* + * Create a new SpanStore object and return its pointer. The ownership of the newly created + * SpanStore object belongs to the current segment context. + * + * @param parent_store The pointer that point to parent SpanStore object. + * @return SpanStore* The pointer that point to newly created SpanStore object. + */ + SpanStore* createSpanStore(const SpanStore* parent_store); + + /* + * Get all SpanStore objects in the current segment. + */ + const std::vector& spanList() const { return span_list_; } + + /* + * Get root SpanStore object in the current segment. + */ + const SpanStore* rootSpanStore() { return span_list_.empty() ? nullptr : span_list_[0].get(); } + + int sampled() const { return sampled_; } + const std::string& traceId() const { return trace_id_; } + const std::string& traceSegmentId() const { return trace_segment_id_; } + + const std::string& service() const { return service_; } + const std::string& serviceInstance() const { return service_instance_; } + + SpanContext* previousSpanContext() const { return previous_span_context_.get(); } + +private: + int sampled_{0}; + // This value is unique in the entire tracing link. If previous_context is null, we will use + // random_generator to create a trace id. + std::string trace_id_; + // Envoy creates a new span when it accepts a new HTTP request. This span and all of its child + // spans belong to the same segment and share the segment id. + std::string trace_segment_id_; + + std::string service_; + std::string service_instance_; + + // The SegmentContext parsed from the request headers. If no propagation headers in request then + // this will be nullptr. + SpanContextPtr previous_span_context_; + + std::vector span_list_; +}; + +using Tag = std::pair; + +/* + * A helper class for the SkyWalking span and is used to store all span-related data, including span + * id, parent span id, tags and so on. Whenever we create a new span, we create a new SpanStore + * object. The new span will hold a pointer to the newly created SpanStore object and write data to + * it or get data from it. + */ +class SpanStore : public Logger::Loggable { +public: + /* + * Construct a SpanStore object using span context and time source. + * + * @param segment_context The pointer that point to current span context. This can not be null. + * @param time_source A time source to get the span end time. + */ + explicit SpanStore(SegmentContext* segment_context) : segment_context_(segment_context) {} + + /* + * Get operation name of span. + */ + const std::string& operation() const { return operation_; } + + /* + * Get peer address. The peer in SkyWalking is different with the tag value of 'peer.address'. The + * tag value of 'peer.address' in Envoy is downstream address and the peer in SkyWalking is + * upstream address. + */ + const std::string& peerAddress() const { return peer_address_; } + + /* + * Get span start time. + */ + uint64_t startTime() const { return start_time_; } + + /* + * Get span end time. + */ + uint64_t endTime() const { return end_time_; } + + /* + * Get span tags. + */ + const std::vector& tags() const { return tags_; } + + /* + * Get span logs. + */ + const std::vector& logs() const { return logs_; } + + /* + * Get span sampling flag. + */ + int sampled() const { return sampled_; } + + /* + * Get span id. + */ + int spanId() const { return span_id_; } + + /* + * Get parent span id. + */ + int parentSpanId() const { return parent_span_id_; } + + /* + * Determines if an error has occurred in the current span. + */ + bool isError() const { return is_error_; } + + /* + * Determines if the current span is an entry span. + * + * Reference: + * https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Trace-Data-Protocol-v3.md + */ + bool isEntrySpan() const { return is_entry_span_; } + + /* + * Set span start time. This is the time when the HTTP request started, not the time when the span + * was created. + */ + void setStartTime(uint64_t start_time) { start_time_ = start_time; } + + /* + * Set span end time. It is meaningless for now. End time will be set by finish. + */ + void setEndTime(uint64_t end_time) { end_time_ = end_time; } + + /* + * Set operation name. + */ + void setOperation(const std::string& operation) { operation_ = operation; } + + /* + * Set peer address. In SkyWalking, the peer address is only set in Exit Span. And it should the + * upstream address. Since the upstream address cannot be obtained at the request stage, the + * request host is used instead. + */ + void setPeerAddress(const std::string& peer_address) { peer_address_ = peer_address; } + + /* + * Set if the current span has an error. + */ + void setAsError(bool is_error) { is_error_ = is_error; } + + /* + * Set if the current span is a entry span. + */ + void setAsEntrySpan(bool is_entry_span) { is_entry_span_ = is_entry_span; } + + /* + * Add a new tag entry to current span. + */ + void addTag(absl::string_view name, absl::string_view value) { tags_.emplace_back(name, value); } + + /* + * Add a new log entry to current span. Due to different data formats, log is temporarily not + * supported. + */ + void addLog(SystemTime, const std::string&) {} + + /* + * Set span id of current span. The span id in each segment is started from 0. When new span is + * created, its span id is the current max span id plus 1. + */ + void setSpanId(int span_id) { span_id_ = span_id; } + + /* + * Set parent span id. Notice that in SkyWalking, the parent span and the child span belong to the + * same segment. The first span of each segment has a parent span id of -1. + */ + void setParentSpanId(int parent_span_id) { parent_span_id_ = parent_span_id; } + + /* + * Set sampling flag. In general, the sampling flag of span is consistent with the current span + * context. + */ + void setSampled(int sampled) { sampled_ = sampled == 0 ? 0 : 1; } + + /* + * Inject current span context information to request headers. This will update original + * propagation headers. + * + * @param request_headers The request headers. + */ + void injectContext(Http::RequestHeaderMap& request_headers) const; + +private: + SegmentContext* segment_context_{nullptr}; + + int sampled_{0}; + + int span_id_{0}; + int parent_span_id_{-1}; + + uint64_t start_time_{0}; + uint64_t end_time_{0}; + + std::string operation_; + std::string peer_address_; + + bool is_error_{false}; + bool is_entry_span_{true}; + + std::vector tags_; + std::vector logs_; +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/trace_segment_reporter.cc b/source/extensions/tracers/skywalking/trace_segment_reporter.cc new file mode 100644 index 000000000000..5ef0046dc800 --- /dev/null +++ b/source/extensions/tracers/skywalking/trace_segment_reporter.cc @@ -0,0 +1,178 @@ +#include "extensions/tracers/skywalking/trace_segment_reporter.h" + +#include "envoy/http/header_map.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +namespace { + +Http::RegisterCustomInlineHeader + authentication_handle(Http::CustomHeaders::get().Authentication); + +// Convert SegmentContext to SegmentObject. +TraceSegmentPtr toSegmentObject(const SegmentContext& segment_context) { + auto new_segment_ptr = std::make_unique(); + SegmentObject& segment_object = *new_segment_ptr; + + segment_object.set_traceid(segment_context.traceId()); + segment_object.set_tracesegmentid(segment_context.traceSegmentId()); + segment_object.set_service(segment_context.service()); + segment_object.set_serviceinstance(segment_context.serviceInstance()); + + for (const auto& span_store : segment_context.spanList()) { + if (!span_store->sampled()) { + continue; + } + auto* span = segment_object.mutable_spans()->Add(); + + span->set_spanlayer(SpanLayer::Http); + span->set_spantype(span_store->isEntrySpan() ? SpanType::Entry : SpanType::Exit); + // Please check + // https://github.com/apache/skywalking/blob/master/oap-server/server-bootstrap/src/main/resources/component-libraries.yml + // get more information. + span->set_componentid(9000); + + if (!span_store->peerAddress().empty() && span_store->isEntrySpan()) { + span->set_peer(span_store->peerAddress()); + } + + span->set_spanid(span_store->spanId()); + span->set_parentspanid(span_store->parentSpanId()); + + span->set_starttime(span_store->startTime()); + span->set_endtime(span_store->endTime()); + + span->set_iserror(span_store->isError()); + + span->set_operationname(span_store->operation()); + + auto& tags = *span->mutable_tags(); + tags.Reserve(span_store->tags().size()); + + for (auto& span_tag : span_store->tags()) { + KeyStringValuePair* new_tag = tags.Add(); + new_tag->set_key(span_tag.first); + new_tag->set_value(span_tag.second); + } + + SpanContext* previous_span_context = segment_context.previousSpanContext(); + + if (!previous_span_context || !span_store->isEntrySpan()) { + continue; + } + + auto* ref = span->mutable_refs()->Add(); + ref->set_traceid(previous_span_context->trace_id_); + ref->set_parenttracesegmentid(previous_span_context->trace_segment_id_); + ref->set_parentspanid(previous_span_context->span_id_); + ref->set_parentservice(previous_span_context->service_); + ref->set_parentserviceinstance(previous_span_context->service_instance_); + ref->set_parentendpoint(previous_span_context->endpoint_); + ref->set_networkaddressusedatpeer(previous_span_context->target_address_); + } + return new_segment_ptr; +} + +} // namespace + +TraceSegmentReporter::TraceSegmentReporter(Grpc::AsyncClientFactoryPtr&& factory, + Event::Dispatcher& dispatcher, + Random::RandomGenerator& random_generator, + SkyWalkingTracerStats& stats, + const SkyWalkingClientConfig& client_config) + : tracing_stats_(stats), client_config_(client_config), client_(factory->create()), + service_method_(*Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "TraceSegmentReportService.collect")), + random_generator_(random_generator) { + + static constexpr uint32_t RetryInitialDelayMs = 500; + static constexpr uint32_t RetryMaxDelayMs = 30000; + backoff_strategy_ = std::make_unique( + RetryInitialDelayMs, RetryMaxDelayMs, random_generator_); + + retry_timer_ = dispatcher.createTimer([this]() -> void { establishNewStream(); }); + establishNewStream(); +} + +void TraceSegmentReporter::onCreateInitialMetadata(Http::RequestHeaderMap& metadata) { + if (!client_config_.backendToken().empty()) { + metadata.setInline(authentication_handle.handle(), client_config_.backendToken()); + } +} + +void TraceSegmentReporter::report(const SegmentContext& segment_context) { + sendTraceSegment(toSegmentObject(segment_context)); +} + +void TraceSegmentReporter::sendTraceSegment(TraceSegmentPtr request) { + ASSERT(request); + ENVOY_LOG(trace, "Try to report segment to SkyWalking Server:\n{}", request->DebugString()); + + if (stream_ != nullptr) { + tracing_stats_.segments_sent_.inc(); + stream_->sendMessage(*request, false); + return; + } + // Null stream_ and cache segment data temporarily. + delayed_segments_cache_.emplace(std::move(request)); + if (delayed_segments_cache_.size() > client_config_.maxCacheSize()) { + tracing_stats_.segments_dropped_.inc(); + delayed_segments_cache_.pop(); + } +} + +void TraceSegmentReporter::flushTraceSegments() { + ENVOY_LOG(debug, "Flush segments in cache to SkyWalking backend service"); + while (!delayed_segments_cache_.empty() && stream_ != nullptr) { + tracing_stats_.segments_sent_.inc(); + tracing_stats_.segments_flushed_.inc(); + stream_->sendMessage(*delayed_segments_cache_.front(), false); + delayed_segments_cache_.pop(); + } + tracing_stats_.cache_flushed_.inc(); +} + +void TraceSegmentReporter::closeStream() { + if (stream_ != nullptr) { + flushTraceSegments(); + stream_->closeStream(); + } +} + +void TraceSegmentReporter::onRemoteClose(Grpc::Status::GrpcStatus status, + const std::string& message) { + ENVOY_LOG(debug, "{} gRPC stream closed: {}, {}", service_method_.name(), status, message); + stream_ = nullptr; + handleFailure(); +} + +void TraceSegmentReporter::establishNewStream() { + ENVOY_LOG(debug, "Try to create new {} gRPC stream for reporter", service_method_.name()); + stream_ = client_->start(service_method_, *this, Http::AsyncClient::StreamOptions()); + if (stream_ == nullptr) { + ENVOY_LOG(debug, "Failed to create {} gRPC stream", service_method_.name()); + return; + } + // TODO(wbpcode): Even if stream_ is not empty, there is no guarantee that the connection will be + // established correctly. If there is a connection failure, the onRemoteClose method will be + // called. Currently, we lack a way to determine whether the connection is truly available. This + // may cause partial data loss. + if (!delayed_segments_cache_.empty()) { + flushTraceSegments(); + } + backoff_strategy_->reset(); +} + +void TraceSegmentReporter::handleFailure() { setRetryTimer(); } + +void TraceSegmentReporter::setRetryTimer() { + retry_timer_->enableTimer(std::chrono::milliseconds(backoff_strategy_->nextBackOffMs())); +} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/trace_segment_reporter.h b/source/extensions/tracers/skywalking/trace_segment_reporter.h new file mode 100644 index 000000000000..fd70d819917b --- /dev/null +++ b/source/extensions/tracers/skywalking/trace_segment_reporter.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/grpc/async_client_manager.h" + +#include "common/Common.pb.h" +#include "common/common/backoff_strategy.h" +#include "common/grpc/async_client_impl.h" + +#include "extensions/tracers/skywalking/skywalking_client_config.h" +#include "extensions/tracers/skywalking/skywalking_stats.h" +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "language-agent/Tracing.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +using TraceSegmentPtr = std::unique_ptr; + +class TraceSegmentReporter : public Logger::Loggable, + public Grpc::AsyncStreamCallbacks { +public: + explicit TraceSegmentReporter(Grpc::AsyncClientFactoryPtr&& factory, + Event::Dispatcher& dispatcher, Random::RandomGenerator& random, + SkyWalkingTracerStats& stats, + const SkyWalkingClientConfig& client_config); + + // Grpc::AsyncStreamCallbacks + void onCreateInitialMetadata(Http::RequestHeaderMap& metadata) override; + void onReceiveInitialMetadata(Http::ResponseHeaderMapPtr&&) override {} + void onReceiveMessage(std::unique_ptr&&) override {} + void onReceiveTrailingMetadata(Http::ResponseTrailerMapPtr&&) override {} + void onRemoteClose(Grpc::Status::GrpcStatus, const std::string&) override; + + /* + * Flush all cached segment objects to the back-end tracing service and close the GRPC stream. + */ + void closeStream(); + + /* + * Convert the current span context into a segment object and report it to the back-end tracing + * service through the GRPC stream. + * + * @param span_context The span context. + */ + void report(const SegmentContext& span_context); + +private: + void flushTraceSegments(); + + void sendTraceSegment(TraceSegmentPtr request); + void establishNewStream(); + void handleFailure(); + void setRetryTimer(); + + SkyWalkingTracerStats& tracing_stats_; + + const SkyWalkingClientConfig& client_config_; + + Grpc::AsyncClient client_; + Grpc::AsyncStream stream_{}; + const Protobuf::MethodDescriptor& service_method_; + + Random::RandomGenerator& random_generator_; + // If the connection is unavailable when reporting data, the created SegmentObject will be cached + // in the queue, and when a new connection is established, the cached data will be reported. + std::queue delayed_segments_cache_; + + Event::TimerPtr retry_timer_; + BackOffStrategyPtr backoff_strategy_; +}; + +using TraceSegmentReporterPtr = std::unique_ptr; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/tracer.cc b/source/extensions/tracers/skywalking/tracer.cc new file mode 100644 index 000000000000..f3845b9c4213 --- /dev/null +++ b/source/extensions/tracers/skywalking/tracer.cc @@ -0,0 +1,82 @@ +#include "extensions/tracers/skywalking/tracer.h" + +#include + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +constexpr absl::string_view StatusCodeTag = "status_code"; +constexpr absl::string_view UrlTag = "url"; + +namespace { + +uint64_t getTimestamp(SystemTime time) { + return std::chrono::duration_cast(time.time_since_epoch()).count(); +} + +} // namespace + +Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, SystemTime start_time, + const std::string& operation, + SegmentContextSharedPtr segment_context, Span* parent) { + SpanStore* span_store = segment_context->createSpanStore(parent ? parent->spanStore() : nullptr); + + span_store->setStartTime(getTimestamp(start_time)); + + span_store->setOperation(operation); + + return std::make_unique(std::move(segment_context), span_store, *this); +} + +void Span::setOperation(absl::string_view operation) { + span_store_->setOperation(std::string(operation)); +} + +void Span::setTag(absl::string_view name, absl::string_view value) { + if (name == Tracing::Tags::get().HttpUrl) { + span_store_->addTag(UrlTag, value); + return; + } + + if (name == Tracing::Tags::get().HttpStatusCode) { + span_store_->addTag(StatusCodeTag, value); + return; + } + + if (name == Tracing::Tags::get().Error) { + span_store_->setAsError(value == Tracing::Tags::get().True); + } + + span_store_->addTag(name, value); +} + +// Logs in the SkyWalking format are temporarily unsupported. +void Span::log(SystemTime, const std::string&) {} + +void Span::finishSpan() { + span_store_->setEndTime(DateUtil::nowToMilliseconds(tracer_.time_source_)); + tryToReportSpan(); +} + +void Span::injectContext(Http::RequestHeaderMap& request_headers) { + span_store_->injectContext(request_headers); +} + +Tracing::SpanPtr Span::spawnChild(const Tracing::Config& config, const std::string& operation_name, + SystemTime start_time) { + // The new child span will share the same context with the parent span. + return tracer_.startSpan(config, start_time, operation_name, segment_context_, this); +} + +void Span::setSampled(bool sampled) { span_store_->setSampled(sampled ? 1 : 0); } + +std::string Span::getBaggage(absl::string_view) { return EMPTY_STRING; } + +void Span::setBaggage(absl::string_view, absl::string_view) {} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/tracer.h b/source/extensions/tracers/skywalking/tracer.h new file mode 100644 index 000000000000..d28276b232cd --- /dev/null +++ b/source/extensions/tracers/skywalking/tracer.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" + +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" +#include "extensions/tracers/skywalking/trace_segment_reporter.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class Span; + +class Tracer { +public: + explicit Tracer(TimeSource& time_source) : time_source_(time_source) {} + virtual ~Tracer() { reporter_->closeStream(); } + + /* + * Set a trace segment reporter to the current Tracer. Whenever a SkyWalking segment ends, the + * reporter will be used to report segment data. + * + * @param reporter The unique ptr of trace segment reporter. + */ + void setReporter(TraceSegmentReporterPtr&& reporter) { reporter_ = std::move(reporter); } + + /* + * Report trace segment data to backend tracing service. + * + * @param segment_context The segment context. + */ + void report(const SegmentContext& segment_context) { return reporter_->report(segment_context); } + + /* + * Create a new span based on the segment context and parent span. + * + * @param config The tracing config. + * @param start_time Start time of span. + * @param operation Operation name of span. + * @param segment_context The SkyWalking segment context. The newly created span belongs to this + * segment. + * @param parent The parent span pointer. If parent is null, then the newly created span is first + * span of this segment. + * + * @return The unique ptr to the newly created span. + */ + Tracing::SpanPtr startSpan(const Tracing::Config& config, SystemTime start_time, + const std::string& operation, SegmentContextSharedPtr segment_context, + Span* parent); + + TimeSource& time_source_; + +private: + TraceSegmentReporterPtr reporter_; +}; + +using TracerPtr = std::unique_ptr; + +class Span : public Tracing::Span { +public: + /* + * Constructor of span. + * + * @param segment_context The SkyWalking segment context. + * @param span_store Pointer to a SpanStore object. Whenever a new span is created, a new + * SpanStore object is created and stored in the segment context. This parameter can never be + * null. + * @param tracer Reference to tracer. + */ + Span(SegmentContextSharedPtr segment_context, SpanStore* span_store, Tracer& tracer) + : segment_context_(std::move(segment_context)), span_store_(span_store), tracer_(tracer) {} + + // Tracing::Span + void setOperation(absl::string_view operation) override; + void setTag(absl::string_view name, absl::string_view value) override; + void log(SystemTime timestamp, const std::string& event) override; + void finishSpan() override; + void injectContext(Http::RequestHeaderMap& request_headers) override; + Tracing::SpanPtr spawnChild(const Tracing::Config& config, const std::string& name, + SystemTime start_time) override; + void setSampled(bool sampled) override; + std::string getBaggage(absl::string_view key) override; + void setBaggage(absl::string_view key, absl::string_view value) override; + + /* + * Get pointer to corresponding SpanStore object. This method is mainly used in testing. Used to + * check the internal data of the span. + */ + SpanStore* spanStore() const { return span_store_; } + SegmentContext* segmentContext() const { return segment_context_.get(); } + +private: + void tryToReportSpan() { + // If the current span is the root span of the entire segment and its sampling flag is not + // false, the data for the entire segment is reported. Please ensure that the root span is the + // last span to end in the entire segment. + if (span_store_->sampled() && span_store_->spanId() == 0) { + tracer_.report(*segment_context_); + } + } + + SegmentContextSharedPtr segment_context_; + SpanStore* span_store_; + + Tracer& tracer_; +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/alts/tsi_socket.cc b/source/extensions/transport_sockets/alts/tsi_socket.cc index 7ba6b2cab798..0fe5b752ceca 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.cc +++ b/source/extensions/transport_sockets/alts/tsi_socket.cc @@ -261,7 +261,6 @@ TsiSocketFactory::createTransportSocket(Network::TransportSocketOptionsSharedPtr return std::make_unique(handshaker_factory_, handshake_validator_); } -bool TsiSocketFactory::isReady() const { return true; } } // namespace Alts } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/alts/tsi_socket.h b/source/extensions/transport_sockets/alts/tsi_socket.h index 529e316c95ff..a4a7423bd927 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.h +++ b/source/extensions/transport_sockets/alts/tsi_socket.h @@ -98,9 +98,9 @@ class TsiSocketFactory : public Network::TransportSocketFactory { TsiSocketFactory(HandshakerFactory handshaker_factory, HandshakeValidator handshake_validator); bool implementsSecureTransport() const override; + bool usesProxyProtocolOptions() const override { return false; } Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; - bool isReady() const override; private: HandshakerFactory handshaker_factory_; diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index ac2716c96d72..3d4f716421e7 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -123,11 +123,7 @@ bool UpstreamProxyProtocolSocketFactory::implementsSecureTransport() const { return transport_socket_factory_->implementsSecureTransport(); } -bool UpstreamProxyProtocolSocketFactory::isReady() const { - return transport_socket_factory_->isReady(); -} - } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index cc7fcee7e79a..c4c9a80f629e 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -49,7 +49,7 @@ class UpstreamProxyProtocolSocketFactory : public Network::TransportSocketFactor Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; + bool usesProxyProtocolOptions() const override { return true; } private: Network::TransportSocketFactoryPtr transport_socket_factory_; @@ -59,4 +59,4 @@ class UpstreamProxyProtocolSocketFactory : public Network::TransportSocketFactor } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tap/tap.cc b/source/extensions/transport_sockets/tap/tap.cc index 5b8d83fc8186..2f58c23703ce 100644 --- a/source/extensions/transport_sockets/tap/tap.cc +++ b/source/extensions/transport_sockets/tap/tap.cc @@ -66,7 +66,9 @@ bool TapSocketFactory::implementsSecureTransport() const { return transport_socket_factory_->implementsSecureTransport(); } -bool TapSocketFactory::isReady() const { return transport_socket_factory_->isReady(); } +bool TapSocketFactory::usesProxyProtocolOptions() const { + return transport_socket_factory_->usesProxyProtocolOptions(); +} } // namespace Tap } // namespace TransportSockets diff --git a/source/extensions/transport_sockets/tap/tap.h b/source/extensions/transport_sockets/tap/tap.h index c2d91e1f571a..2971c3e846ba 100644 --- a/source/extensions/transport_sockets/tap/tap.h +++ b/source/extensions/transport_sockets/tap/tap.h @@ -41,7 +41,7 @@ class TapSocketFactory : public Network::TransportSocketFactory, Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; + bool usesProxyProtocolOptions() const override; private: Network::TransportSocketFactoryPtr transport_socket_factory_; diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 7e6a59d85919..348a02d6dd9e 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -169,14 +169,14 @@ ContextConfigImpl::ContextConfigImpl( const std::string& default_cipher_suites, const std::string& default_curves, Server::Configuration::TransportSocketFactoryContext& factory_context) : api_(factory_context.api()), - tls_certificate_providers_(getTlsCertificateConfigProviders(config, factory_context)), - certificate_validation_context_provider_( - getCertificateValidationContextConfigProvider(config, factory_context, &default_cvc_)), alpn_protocols_(RepeatedPtrUtil::join(config.alpn_protocols(), ",")), cipher_suites_(StringUtil::nonEmptyStringOrDefault( RepeatedPtrUtil::join(config.tls_params().cipher_suites(), ":"), default_cipher_suites)), ecdh_curves_(StringUtil::nonEmptyStringOrDefault( RepeatedPtrUtil::join(config.tls_params().ecdh_curves(), ":"), default_curves)), + tls_certificate_providers_(getTlsCertificateConfigProviders(config, factory_context)), + certificate_validation_context_provider_( + getCertificateValidationContextConfigProvider(config, factory_context, &default_cvc_)), min_protocol_version_(tlsVersionFromProto(config.tls_params().tls_minimum_protocol_version(), default_min_protocol_version)), max_protocol_version_(tlsVersionFromProto(config.tls_params().tls_maximum_protocol_version(), @@ -337,16 +337,8 @@ const std::string ClientContextConfigImpl::DEFAULT_CIPHER_SUITES = "ECDHE-ECDSA-AES128-GCM-SHA256:" "ECDHE-RSA-AES128-GCM-SHA256:" #endif - "ECDHE-ECDSA-AES128-SHA:" - "ECDHE-RSA-AES128-SHA:" - "AES128-GCM-SHA256:" - "AES128-SHA:" "ECDHE-ECDSA-AES256-GCM-SHA384:" - "ECDHE-RSA-AES256-GCM-SHA384:" - "ECDHE-ECDSA-AES256-SHA:" - "ECDHE-RSA-AES256-SHA:" - "AES256-GCM-SHA384:" - "AES256-SHA"; + "ECDHE-RSA-AES256-GCM-SHA384:"; const std::string ClientContextConfigImpl::DEFAULT_CURVES = #ifndef BORINGSSL_FIPS @@ -375,15 +367,6 @@ ClientContextConfigImpl::ClientContextConfigImpl( } } -bool ClientContextConfigImpl::isSecretReady() const { - for (const auto& provider : tls_certificate_providers_) { - if (provider->secret() == nullptr) { - return false; - } - } - return certificate_validation_context_provider_->secret() != nullptr; -} - const unsigned ServerContextConfigImpl::DEFAULT_MIN_VERSION = TLS1_VERSION; const unsigned ServerContextConfigImpl::DEFAULT_MAX_VERSION = TLS1_3_VERSION; diff --git a/source/extensions/transport_sockets/tls/context_config_impl.h b/source/extensions/transport_sockets/tls/context_config_impl.h index d40bb627a51d..44c5a8cc619d 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.h +++ b/source/extensions/transport_sockets/tls/context_config_impl.h @@ -69,14 +69,6 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { const std::string& default_cipher_suites, const std::string& default_curves, Server::Configuration::TransportSocketFactoryContext& factory_context); Api::Api& api_; - // If certificate validation context type is combined_validation_context. default_cvc_ - // holds a copy of CombinedCertificateValidationContext::default_validation_context. - // Otherwise, default_cvc_ is nullptr. - std::unique_ptr - default_cvc_; - std::vector tls_certificate_providers_; - Secret::CertificateValidationContextConfigProviderSharedPtr - certificate_validation_context_provider_; private: static unsigned tlsVersionFromProto( @@ -89,8 +81,16 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { std::vector tls_certificate_configs_; Ssl::CertificateValidationContextConfigPtr validation_context_config_; + // If certificate validation context type is combined_validation_context. default_cvc_ + // holds a copy of CombinedCertificateValidationContext::default_validation_context. + // Otherwise, default_cvc_ is nullptr. + std::unique_ptr + default_cvc_; + std::vector tls_certificate_providers_; // Handle for TLS certificate dynamic secret callback. Envoy::Common::CallbackHandle* tc_update_callback_handle_{}; + Secret::CertificateValidationContextConfigProviderSharedPtr + certificate_validation_context_provider_; // Handle for certificate validation context dynamic secret callback. Envoy::Common::CallbackHandle* cvc_update_callback_handle_{}; Envoy::Common::CallbackHandle* cvc_validation_callback_handle_{}; @@ -120,7 +120,6 @@ class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Cli bool allowRenegotiation() const override { return allow_renegotiation_; } size_t maxSessionKeys() const override { return max_session_keys_; } const std::string& signingAlgorithmsForTest() const override { return sigalgs_; } - bool isSecretReady() const override; private: static const unsigned DEFAULT_MIN_VERSION; diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 8be424e42d06..e79a1aeadb6d 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -364,10 +364,11 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ASSERT(rsa_public_key != nullptr); const unsigned rsa_key_length = RSA_size(rsa_public_key); #ifdef BORINGSSL_FIPS - if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8) { + if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8 && + rsa_key_length != 4096 / 8) { throw EnvoyException( fmt::format("Failed to load certificate chain from {}, only RSA certificates with " - "2048-bit or 3072-bit keys are supported in FIPS mode", + "2048-bit, 3072-bit or 4096-bit keys are supported in FIPS mode", ctx.cert_chain_file_path_)); } #else @@ -416,9 +417,11 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c !tls_certificate.password().empty() ? const_cast(tls_certificate.password().c_str()) : nullptr)); + if (pkey == nullptr || !SSL_CTX_use_PrivateKey(ctx.ssl_ctx_.get(), pkey.get())) { - throw EnvoyException( - absl::StrCat("Failed to load private key from ", tls_certificate.privateKeyPath())); + throw EnvoyException(fmt::format("Failed to load private key from {}, Cause: {}", + tls_certificate.privateKeyPath(), + Utility::getLastCryptoError().value_or("unknown"))); } #ifdef BORINGSSL_FIPS @@ -962,9 +965,9 @@ bssl::UniquePtr ClientContextImpl::newSsl(const Network::TransportSocketOpt has_alpn_defined |= parseAndSetAlpn(options->applicationProtocolListOverride(), *ssl_con); } - if (options && !has_alpn_defined && options->applicationProtocolFallback().has_value()) { + if (options && !has_alpn_defined && !options->applicationProtocolFallback().empty()) { // If ALPN hasn't already been set (either through TLS context or override), use the fallback. - parseAndSetAlpn({*options->applicationProtocolFallback()}, *ssl_con); + parseAndSetAlpn(options->applicationProtocolFallback(), *ssl_con); } if (allow_renegotiation_) { diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 772ab6fa2090..c533e0cc9c4f 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -169,7 +169,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { const Ocsp::OcspResponseWrapper* ocsp_response) const; struct TlsContext { - // Each certificate specified for the context has its own SSL_CTX. SSL_CTXs + // Each certificate specified for the context has its own SSL_CTX. `SSL_CTXs` // are identical with the exception of certificate material, and can be // safely substituted via SSL_set_SSL_CTX() during the // SSL_CTX_set_select_certificate_cb() callback following ClientHello. diff --git a/source/extensions/transport_sockets/tls/ssl_handshaker.h b/source/extensions/transport_sockets/tls/ssl_handshaker.h index 8eaec861a8f1..50090f6f43a7 100644 --- a/source/extensions/transport_sockets/tls/ssl_handshaker.h +++ b/source/extensions/transport_sockets/tls/ssl_handshaker.h @@ -67,7 +67,7 @@ class SslHandshakerImpl : public Ssl::ConnectionInfo, public Ssl::Handshaker { // Ssl::Handshaker Network::PostIoAction doHandshake() override; - Ssl::SocketState state() { return state_; } + Ssl::SocketState state() const { return state_; } void setState(Ssl::SocketState state) { state_ = state; } SSL* ssl() const { return ssl_.get(); } Ssl::HandshakeCallbacks* handshakeCallbacks() { return handshake_callbacks_; } diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index 99da62f9ae10..50b2d27926e2 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -140,10 +140,18 @@ Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { case SSL_ERROR_WANT_READ: break; case SSL_ERROR_ZERO_RETURN: + // Graceful shutdown using close_notify TLS alert. end_stream = true; break; + case SSL_ERROR_SYSCALL: + if (result.error_.value() == 0) { + // Non-graceful shutdown by closing the underlying socket. + end_stream = true; + break; + } + FALLTHRU; case SSL_ERROR_WANT_WRITE: - // Renegotiation has started. We don't handle renegotiation so just fall through. + // Renegotiation has started. We don't handle renegotiation so just fall through. default: drainErrorQueue(); action = PostIoAction::Close; @@ -375,8 +383,6 @@ void ClientSslSocketFactory::onAddOrUpdateSecret() { stats_.ssl_context_update_by_sds_.inc(); } -bool ClientSslSocketFactory::isReady() const { return config_->isSecretReady(); } - ServerSslSocketFactory::ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPtr config, Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope, @@ -418,8 +424,6 @@ void ServerSslSocketFactory::onAddOrUpdateSecret() { stats_.ssl_context_update_by_sds_.inc(); } -bool ServerSslSocketFactory::isReady() const { return true; } - } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 4ea674062142..4c5f38e0fb14 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -106,11 +106,11 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory, ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config, Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope); - // Network::TransportSocketFactory Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; + bool usesProxyProtocolOptions() const override { return false; } + // Secret::SecretCallbacks void onAddOrUpdateSecret() override; @@ -134,7 +134,8 @@ class ServerSslSocketFactory : public Network::TransportSocketFactory, Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; + bool usesProxyProtocolOptions() const override { return false; } + // Secret::SecretCallbacks void onAddOrUpdateSecret() override; diff --git a/source/extensions/upstreams/http/http/upstream_request.h b/source/extensions/upstreams/http/http/upstream_request.h index 588efa93c7f6..ff0650a3b231 100644 --- a/source/extensions/upstreams/http/http/upstream_request.h +++ b/source/extensions/upstreams/http/http/upstream_request.h @@ -66,8 +66,9 @@ class HttpUpstream : public Router::GenericUpstream, public Envoy::Http::StreamC void encodeMetadata(const Envoy::Http::MetadataMapVector& metadata_map_vector) override { request_encoder_->encodeMetadata(metadata_map_vector); } - void encodeHeaders(const Envoy::Http::RequestHeaderMap& headers, bool end_stream) override { - request_encoder_->encodeHeaders(headers, end_stream); + Envoy::Http::Status encodeHeaders(const Envoy::Http::RequestHeaderMap& headers, + bool end_stream) override { + return request_encoder_->encodeHeaders(headers, end_stream); } void encodeTrailers(const Envoy::Http::RequestTrailerMap& trailers) override { request_encoder_->encodeTrailers(trailers); diff --git a/source/extensions/upstreams/http/tcp/upstream_request.cc b/source/extensions/upstreams/http/tcp/upstream_request.cc index 4284a2e5a13d..0c4dd46ece64 100644 --- a/source/extensions/upstreams/http/tcp/upstream_request.cc +++ b/source/extensions/upstreams/http/tcp/upstream_request.cc @@ -43,7 +43,8 @@ void TcpUpstream::encodeData(Buffer::Instance& data, bool end_stream) { upstream_conn_data_->connection().write(data, end_stream); } -void TcpUpstream::encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool 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. ASSERT(upstream_request_->routeEntry().connectConfig().has_value()); @@ -64,6 +65,7 @@ void TcpUpstream::encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_s 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&) { diff --git a/source/extensions/upstreams/http/tcp/upstream_request.h b/source/extensions/upstreams/http/tcp/upstream_request.h index 4f7cc1c212de..3829fdead139 100644 --- a/source/extensions/upstreams/http/tcp/upstream_request.h +++ b/source/extensions/upstreams/http/tcp/upstream_request.h @@ -70,7 +70,7 @@ class TcpUpstream : public Router::GenericUpstream, // GenericUpstream void encodeData(Buffer::Instance& data, bool end_stream) override; void encodeMetadata(const Envoy::Http::MetadataMapVector&) override {} - void encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_stream) 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; diff --git a/source/extensions/upstreams/tcp/generic/BUILD b/source/extensions/upstreams/tcp/generic/BUILD new file mode 100644 index 000000000000..d6e2f2f3cae2 --- /dev/null +++ b/source/extensions/upstreams/tcp/generic/BUILD @@ -0,0 +1,25 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = [ + "config.cc", + ], + hdrs = [ + "config.h", + ], + security_posture = "robust_to_untrusted_downstream", + visibility = ["//visibility:public"], + deps = [ + "//source/common/tcp_proxy:upstream_lib", + "@envoy_api//envoy/extensions/upstreams/tcp/generic/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/upstreams/tcp/generic/config.cc b/source/extensions/upstreams/tcp/generic/config.cc new file mode 100644 index 000000000000..634862885f5c --- /dev/null +++ b/source/extensions/upstreams/tcp/generic/config.cc @@ -0,0 +1,45 @@ +#include "extensions/upstreams/tcp/generic/config.h" + +#include "envoy/upstream/cluster_manager.h" + +#include "common/tcp_proxy/upstream.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Tcp { +namespace Generic { + +TcpProxy::GenericConnPoolPtr GenericConnPoolFactory::createGenericConnPool( + const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, + const absl::optional& config, Upstream::LoadBalancerContext* context, + Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) const { + if (config.has_value()) { + auto* cluster = cluster_manager.get(cluster_name); + if (!cluster) { + return nullptr; + } + // TODO(snowp): Ideally we should prevent this from being configured, but that's tricky to get + // right since whether a cluster is invalid depends on both the tcp_proxy config + cluster + // config. + if ((cluster->info()->features() & Upstream::ClusterInfo::Features::HTTP2) == 0) { + ENVOY_LOG_MISC(error, "Attempted to tunnel over HTTP/1.1, this is not supported. Set " + "http2_protocol_options on the cluster."); + return nullptr; + } + auto ret = std::make_unique(cluster_name, cluster_manager, context, + config.value(), upstream_callbacks); + return (ret->valid() ? std::move(ret) : nullptr); + } + auto ret = std::make_unique(cluster_name, cluster_manager, context, + upstream_callbacks); + return (ret->valid() ? std::move(ret) : nullptr); +} + +REGISTER_FACTORY(GenericConnPoolFactory, TcpProxy::GenericConnPoolFactory); + +} // namespace Generic +} // namespace Tcp +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/upstreams/tcp/generic/config.h b/source/extensions/upstreams/tcp/generic/config.h new file mode 100644 index 000000000000..5ba6171ac691 --- /dev/null +++ b/source/extensions/upstreams/tcp/generic/config.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.pb.h" +#include "envoy/registry/registry.h" +#include "envoy/tcp/upstream.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Tcp { +namespace Generic { + +/** + * Config registration for the GenericConnPool. * @see TcpProxy::GenericConnPoolFactory + */ +class GenericConnPoolFactory : public TcpProxy::GenericConnPoolFactory { +public: + std::string name() const override { return "envoy.filters.connection_pools.tcp.generic"; } + std::string category() const override { return "envoy.upstreams"; } + TcpProxy::GenericConnPoolPtr createGenericConnPool( + const std::string& cluster_name, Upstream::ClusterManager& cm, + const absl::optional& config, Upstream::LoadBalancerContext* context, + Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) const override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::upstreams::tcp::generic::v3::GenericConnectionPoolProto>(); + } +}; + +DECLARE_FACTORY(GenericConnPoolFactory); + +} // namespace Generic +} // namespace Tcp +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/wasm_runtime/null/BUILD b/source/extensions/wasm_runtime/null/BUILD new file mode 100644 index 000000000000..63969c889c25 --- /dev/null +++ b/source/extensions/wasm_runtime/null/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + "@proxy_wasm_cpp_host//:null_lib", + ], +) diff --git a/source/extensions/wasm_runtime/null/config.cc b/source/extensions/wasm_runtime/null/config.cc new file mode 100644 index 000000000000..3515c9462ce1 --- /dev/null +++ b/source/extensions/wasm_runtime/null/config.cc @@ -0,0 +1,25 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/null.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class NullRuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createNullVm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.null"; } + absl::string_view shortName() override { return "null"; } +}; + +REGISTER_FACTORY(NullRuntimeFactory, WasmRuntimeFactory); + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/wasm_runtime/v8/BUILD b/source/extensions/wasm_runtime/v8/BUILD new file mode 100644 index 000000000000..8785616044d7 --- /dev/null +++ b/source/extensions/wasm_runtime/v8/BUILD @@ -0,0 +1,23 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_v8") + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + ] + envoy_select_wasm_v8([ + "@proxy_wasm_cpp_host//:v8_lib", + ]), +) diff --git a/source/extensions/wasm_runtime/v8/config.cc b/source/extensions/wasm_runtime/v8/config.cc new file mode 100644 index 000000000000..1061b17b2b9d --- /dev/null +++ b/source/extensions/wasm_runtime/v8/config.cc @@ -0,0 +1,27 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/v8.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class V8RuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createV8Vm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.v8"; } + absl::string_view shortName() override { return "v8"; } +}; + +#if defined(ENVOY_WASM_V8) +REGISTER_FACTORY(V8RuntimeFactory, WasmRuntimeFactory); +#endif + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/wasm_runtime/wasmtime/BUILD b/source/extensions/wasm_runtime/wasmtime/BUILD new file mode 100644 index 000000000000..d0adea5660c6 --- /dev/null +++ b/source/extensions/wasm_runtime/wasmtime/BUILD @@ -0,0 +1,23 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_wasmtime") + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + ] + envoy_select_wasm_wasmtime([ + "@proxy_wasm_cpp_host//:wasmtime_lib", + ]), +) diff --git a/source/extensions/wasm_runtime/wasmtime/config.cc b/source/extensions/wasm_runtime/wasmtime/config.cc new file mode 100644 index 000000000000..a407d847bdfd --- /dev/null +++ b/source/extensions/wasm_runtime/wasmtime/config.cc @@ -0,0 +1,27 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/wasmtime.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class WasmtimeRuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createWasmtimeVm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.wasmtime"; } + absl::string_view shortName() override { return "wasmtime"; } +}; + +#if defined(ENVOY_WASM_WASMTIME) +REGISTER_FACTORY(WasmtimeRuntimeFactory, WasmRuntimeFactory); +#endif + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/wasm_runtime/wavm/BUILD b/source/extensions/wasm_runtime/wavm/BUILD new file mode 100644 index 000000000000..c9a5153efe31 --- /dev/null +++ b/source/extensions/wasm_runtime/wavm/BUILD @@ -0,0 +1,23 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_wavm") + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + ] + envoy_select_wasm_wavm([ + "@proxy_wasm_cpp_host//:wavm_lib", + ]), +) diff --git a/source/extensions/wasm_runtime/wavm/config.cc b/source/extensions/wasm_runtime/wavm/config.cc new file mode 100644 index 000000000000..d50119cf784d --- /dev/null +++ b/source/extensions/wasm_runtime/wavm/config.cc @@ -0,0 +1,27 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/wavm.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class WavmRuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createWavmVm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.wavm"; } + absl::string_view shortName() override { return "wavm"; } +}; + +#if defined(ENVOY_WASM_WAVM) +REGISTER_FACTORY(WavmRuntimeFactory, WasmRuntimeFactory); +#endif + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/server/BUILD b/source/server/BUILD index 0b0ea13d6e75..c3404818a217 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -113,6 +113,7 @@ envoy_cc_library( ":watchdog_lib", "//include/envoy/api:api_interface", "//include/envoy/common:time_interface", + "//include/envoy/event:dispatcher_interface", "//include/envoy/event:timer_interface", "//include/envoy/server:configuration_interface", "//include/envoy/server:guarddog_config_interface", @@ -253,7 +254,7 @@ envoy_cc_library( srcs = ["overload_manager_impl.cc"], hdrs = ["overload_manager_impl.h"], deps = [ - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/stats:stats_interface", "//include/envoy/thread_local:thread_local_interface", "//source/common/common:logger_lib", @@ -473,11 +474,9 @@ envoy_cc_library( envoy_cc_library( name = "watchdog_lib", - srcs = ["watchdog_impl.cc"], hdrs = ["watchdog_impl.h"], deps = [ "//include/envoy/common:time_interface", - "//include/envoy/event:dispatcher_interface", "//include/envoy/server:watchdog_interface", "//source/common/common:assert_lib", ], diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index e16f827da776..de79c60b1992 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -14,7 +14,7 @@ #include "envoy/server/admin.h" #include "envoy/server/instance.h" #include "envoy/server/listener_manager.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/upstream/outlier_detection.h" #include "envoy/upstream/resource_manager.h" @@ -134,6 +134,7 @@ class AdminImpl : public Admin, uint32_t maxRequestHeadersCount() const override { return max_request_headers_count_; } std::chrono::milliseconds streamIdleTimeout() const override { return {}; } std::chrono::milliseconds requestTimeout() const override { return {}; } + std::chrono::milliseconds requestHeadersTimeout() const override { return {}; } std::chrono::milliseconds delayedCloseTimeout() const override { return {}; } absl::optional maxStreamDuration() const override { return max_stream_duration_; diff --git a/source/server/admin/clusters_handler.cc b/source/server/admin/clusters_handler.cc index e0e17350b7c9..801045e63ecd 100644 --- a/source/server/admin/clusters_handler.cc +++ b/source/server/admin/clusters_handler.cc @@ -100,7 +100,9 @@ void setHealthFlag(Upstream::Host::HealthFlag flag, const Upstream::Host& host, // TODO(efimki): Add support of text readouts stats. void ClustersHandler::writeClustersAsJson(Buffer::Instance& response) { envoy::admin::v3::Clusters clusters; - for (const auto& [name, cluster_ref] : server_.clusterManager().clusters()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) { const Upstream::Cluster& cluster = cluster_ref.get(); Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.info(); @@ -184,7 +186,9 @@ void ClustersHandler::writeClustersAsJson(Buffer::Instance& response) { // TODO(efimki): Add support of text readouts stats. void ClustersHandler::writeClustersAsText(Buffer::Instance& response) { - for (const auto& [name, cluster_ref] : server_.clusterManager().clusters()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) { const Upstream::Cluster& cluster = cluster_ref.get(); const std::string& cluster_name = cluster.info()->name(); addOutlierInfo(cluster_name, cluster.outlierDetector(), response); diff --git a/source/server/admin/config_dump_handler.cc b/source/server/admin/config_dump_handler.cc index dbfd13a01e2e..e255f084757c 100644 --- a/source/server/admin/config_dump_handler.cc +++ b/source/server/admin/config_dump_handler.cc @@ -149,12 +149,15 @@ ConfigDumpHandler::addResourceToDump(envoy::admin::v3::ConfigDump& dump, const std::string& resource, bool include_eds) const { Envoy::Server::ConfigTracker::CbsMap callbacks_map = config_tracker_.getCallbacksMap(); if (include_eds) { - if (!server_.clusterManager().clusters().empty()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + if (!all_clusters.active_clusters_.empty()) { callbacks_map.emplace("endpoint", [this] { return dumpEndpointConfigs(); }); } } for (const auto& [name, callback] : callbacks_map) { + UNREFERENCED_PARAMETER(name); ProtobufTypes::MessagePtr message = callback(); ASSERT(message); @@ -194,12 +197,15 @@ void ConfigDumpHandler::addAllConfigToDump(envoy::admin::v3::ConfigDump& dump, bool include_eds) const { Envoy::Server::ConfigTracker::CbsMap callbacks_map = config_tracker_.getCallbacksMap(); if (include_eds) { - if (!server_.clusterManager().clusters().empty()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + if (!all_clusters.active_clusters_.empty()) { callbacks_map.emplace("endpoint", [this] { return dumpEndpointConfigs(); }); } } for (const auto& [name, callback] : callbacks_map) { + UNREFERENCED_PARAMETER(name); ProtobufTypes::MessagePtr message = callback(); ASSERT(message); @@ -218,8 +224,10 @@ void ConfigDumpHandler::addAllConfigToDump(envoy::admin::v3::ConfigDump& dump, ProtobufTypes::MessagePtr ConfigDumpHandler::dumpEndpointConfigs() const { auto endpoint_config_dump = std::make_unique(); - - for (const auto& [name, cluster_ref] : server_.clusterManager().clusters()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) { + UNREFERENCED_PARAMETER(name); const Upstream::Cluster& cluster = cluster_ref.get(); Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.info(); envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; diff --git a/source/server/filter_chain_manager_impl.cc b/source/server/filter_chain_manager_impl.cc index 0e006561ce09..65edde071124 100644 --- a/source/server/filter_chain_manager_impl.cc +++ b/source/server/filter_chain_manager_impl.cc @@ -600,6 +600,7 @@ const Network::FilterChain* FilterChainManagerImpl::findFilterChainForSourceIpAn void FilterChainManagerImpl::convertIPsToTries() { for (auto& [destination_port, destination_ips_pair] : destination_ports_map_) { + UNREFERENCED_PARAMETER(destination_port); // These variables are used as we build up the destination CIDRs used for the trie. auto& [destination_ips_map, destination_ips_trie] = destination_ips_pair; std::vector>> @@ -613,8 +614,11 @@ void FilterChainManagerImpl::convertIPsToTries() { // We need to get access to all of the source IP strings so that we can convert them into // a trie like we did for the destination IPs above. for (auto& [server_name, transport_protocols_map] : *server_names_map_ptr) { + UNREFERENCED_PARAMETER(server_name); for (auto& [transport_protocol, application_protocols_map] : transport_protocols_map) { + UNREFERENCED_PARAMETER(transport_protocol); for (auto& [application_protocol, source_arrays] : application_protocols_map) { + UNREFERENCED_PARAMETER(application_protocol); for (auto& [source_ips_map, source_ips_trie] : source_arrays) { std::vector< std::pair>> diff --git a/source/server/guarddog_impl.cc b/source/server/guarddog_impl.cc index c831b198a1bf..4a49c593166d 100644 --- a/source/server/guarddog_impl.cc +++ b/source/server/guarddog_impl.cc @@ -185,19 +185,22 @@ void GuardDogImpl::step() { } WatchDogSharedPtr GuardDogImpl::createWatchDog(Thread::ThreadId thread_id, - const std::string& thread_name) { + const std::string& thread_name, + Event::Dispatcher& dispatcher) { // Timer started by WatchDog will try to fire at 1/2 of the interval of the // minimum timeout specified. loop_interval_ is const so all shared state // accessed out of the locked section below is const (time_source_ has no // state). const auto wd_interval = loop_interval_ / 2; - auto new_watchdog = std::make_shared(std::move(thread_id), wd_interval); + auto new_watchdog = std::make_shared(std::move(thread_id)); WatchedDogPtr watched_dog = std::make_unique(stats_scope_, thread_name, new_watchdog); new_watchdog->touch(); { Thread::LockGuard guard(wd_lock_); watched_dogs_.push_back(std::move(watched_dog)); } + dispatcher.registerWatchdog(new_watchdog, wd_interval); + new_watchdog->touch(); return new_watchdog; } diff --git a/source/server/guarddog_impl.h b/source/server/guarddog_impl.h index ebc4040e1c81..50e72347441d 100644 --- a/source/server/guarddog_impl.h +++ b/source/server/guarddog_impl.h @@ -92,8 +92,8 @@ class GuardDogImpl : public GuardDog { } // Server::GuardDog - WatchDogSharedPtr createWatchDog(Thread::ThreadId thread_id, - const std::string& thread_name) override; + WatchDogSharedPtr createWatchDog(Thread::ThreadId thread_id, const std::string& thread_name, + Event::Dispatcher& dispatcher) override; void stopWatching(WatchDogSharedPtr wd) override; private: diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index b7c031e914a2..9a488b1fe2e7 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -111,11 +111,6 @@ OptionsImpl::OptionsImpl(std::vector args, TCLAP::SwitchArg enable_fine_grain_logging( "", "enable-fine-grain-logging", "Logger mode: enable file level log control(Fancy Logger)or not", cmd, false); - TCLAP::ValueArg log_format_prefix_with_location( - "", "log-format-prefix-with-location", - "Prefix all occurrences of '%v' in log format with with '[%g:%#] ' ('[path/to/file.cc:99] " - "').", - false, false, "bool", cmd); TCLAP::ValueArg log_path("", "log-path", "Path to logfile", false, "", "string", cmd); TCLAP::ValueArg restart_epoch("", "restart-epoch", "hot restart epoch #", false, 0, @@ -201,9 +196,6 @@ OptionsImpl::OptionsImpl(std::vector args, } log_format_ = log_format.getValue(); - if (log_format_prefix_with_location.getValue()) { - log_format_ = absl::StrReplaceAll(log_format_, {{"%%", "%%"}, {"%v", "[%g:%#] %v"}}); - } log_format_escaped_ = log_format_escaped.getValue(); enable_fine_grain_logging_ = enable_fine_grain_logging.getValue(); diff --git a/source/server/overload_manager_impl.cc b/source/server/overload_manager_impl.cc index fe6a6746361b..f399bb96ecfe 100644 --- a/source/server/overload_manager_impl.cc +++ b/source/server/overload_manager_impl.cc @@ -5,7 +5,6 @@ #include "envoy/common/exception.h" #include "envoy/config/overload/v3/overload.pb.h" #include "envoy/config/overload/v3/overload.pb.validate.h" -#include "envoy/server/overload_manager.h" #include "envoy/stats/scope.h" #include "common/common/fmt.h" @@ -22,6 +21,54 @@ namespace Envoy { namespace Server { +/** + * Thread-local copy of the state of each configured overload action. + */ +class ThreadLocalOverloadStateImpl : public ThreadLocalOverloadState { +public: + ThreadLocalOverloadStateImpl( + Event::ScaledRangeTimerManagerPtr scaled_timer_manager, + const NamedOverloadActionSymbolTable& action_symbol_table, + const absl::flat_hash_map& timer_minimums) + : action_symbol_table_(action_symbol_table), timer_minimums_(timer_minimums), + actions_(action_symbol_table.size(), OverloadActionState(0)), + scaled_timer_action_(action_symbol_table.lookup(OverloadActionNames::get().ReduceTimeouts)), + scaled_timer_manager_(std::move(scaled_timer_manager)) {} + + const OverloadActionState& getState(const std::string& action) override { + if (const auto symbol = action_symbol_table_.lookup(action); symbol != absl::nullopt) { + return actions_[symbol->index()]; + } + return always_inactive_; + } + + Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, + Event::TimerCb callback) override { + auto minimum_it = timer_minimums_.find(timer_type); + const Event::ScaledTimerMinimum minimum = + minimum_it != timer_minimums_.end() ? minimum_it->second + : Event::ScaledTimerMinimum(Event::ScaledMinimum(1.0)); + return scaled_timer_manager_->createTimer(minimum, std::move(callback)); + } + + void setState(NamedOverloadActionSymbolTable::Symbol action, OverloadActionState state) { + actions_[action.index()] = state; + if (scaled_timer_action_.has_value() && scaled_timer_action_.value() == action) { + scaled_timer_manager_->setScaleFactor(1 - state.value()); + } + } + +private: + static const OverloadActionState always_inactive_; + const NamedOverloadActionSymbolTable& action_symbol_table_; + const absl::flat_hash_map& timer_minimums_; + std::vector actions_; + absl::optional scaled_timer_action_; + const Event::ScaledRangeTimerManagerPtr scaled_timer_manager_; +}; + +const OverloadActionState ThreadLocalOverloadStateImpl::always_inactive_{0.0}; + namespace { class ThresholdTriggerImpl final : public OverloadAction::Trigger { @@ -75,54 +122,6 @@ class ScaledTriggerImpl final : public OverloadAction::Trigger { OverloadActionState state_; }; -/** - * Thread-local copy of the state of each configured overload action. - */ -class ThreadLocalOverloadStateImpl : public ThreadLocalOverloadState { -public: - ThreadLocalOverloadStateImpl( - Event::ScaledRangeTimerManagerPtr scaled_timer_manager, - const NamedOverloadActionSymbolTable& action_symbol_table, - const absl::flat_hash_map& timer_minimums) - : action_symbol_table_(action_symbol_table), timer_minimums_(timer_minimums), - actions_(action_symbol_table.size(), OverloadActionState(0)), - scaled_timer_action_(action_symbol_table.lookup(OverloadActionNames::get().ReduceTimeouts)), - scaled_timer_manager_(std::move(scaled_timer_manager)) {} - - const OverloadActionState& getState(const std::string& action) override { - if (const auto symbol = action_symbol_table_.lookup(action); symbol != absl::nullopt) { - return actions_[symbol->index()]; - } - return always_inactive_; - } - - Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, - Event::TimerCb callback) override { - auto minimum_it = timer_minimums_.find(timer_type); - const Event::ScaledTimerMinimum minimum = - minimum_it != timer_minimums_.end() ? minimum_it->second - : Event::ScaledTimerMinimum(Event::ScaledMinimum(1.0)); - return scaled_timer_manager_->createTimer(minimum, std::move(callback)); - } - - void setState(NamedOverloadActionSymbolTable::Symbol action, OverloadActionState state) { - actions_[action.index()] = state; - if (scaled_timer_action_.has_value() && scaled_timer_action_.value() == action) { - scaled_timer_manager_->setScaleFactor(1 - state.value()); - } - } - -private: - static const OverloadActionState always_inactive_; - const NamedOverloadActionSymbolTable& action_symbol_table_; - const absl::flat_hash_map& timer_minimums_; - std::vector actions_; - absl::optional scaled_timer_action_; - const Event::ScaledRangeTimerManagerPtr scaled_timer_manager_; -}; - -const OverloadActionState ThreadLocalOverloadStateImpl::always_inactive_{0.0}; - Stats::Counter& makeCounter(Stats::Scope& scope, absl::string_view a, absl::string_view b) { Stats::StatNameManagedStorage stat_name(absl::StrCat("overload.", a, ".", b), scope.symbolTable()); @@ -275,7 +274,7 @@ OverloadManagerImpl::OverloadManagerImpl(Event::Dispatcher& dispatcher, Stats::S const envoy::config::overload::v3::OverloadManager& config, ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api) - : started_(false), dispatcher_(dispatcher), tls_(slot_allocator.allocateSlot()), + : started_(false), dispatcher_(dispatcher), tls_(slot_allocator), refresh_interval_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(config, refresh_interval, 1000))) { Configuration::ResourceMonitorFactoryContextImpl context(dispatcher, api, validation_visitor); @@ -331,7 +330,7 @@ void OverloadManagerImpl::start() { ASSERT(!started_); started_ = true; - tls_->set([this](Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.set([this](Event::Dispatcher& dispatcher) { return std::make_shared(createScaledRangeTimerManager(dispatcher), action_symbol_table_, timer_minimums_); }); @@ -384,9 +383,7 @@ bool OverloadManagerImpl::registerForAction(const std::string& action, return true; } -ThreadLocalOverloadState& OverloadManagerImpl::getThreadLocalOverloadState() { - return tls_->getTyped(); -} +ThreadLocalOverloadState& OverloadManagerImpl::getThreadLocalOverloadState() { return *tls_; } Event::ScaledRangeTimerManagerPtr OverloadManagerImpl::createScaledRangeTimerManager(Event::Dispatcher& dispatcher) const { @@ -443,13 +440,11 @@ void OverloadManagerImpl::flushResourceUpdates() { absl::flat_hash_map>(); std::swap(*shared_updates, state_updates_to_flush_); - tls_->runOnAllThreads( - [updates = std::move(shared_updates)](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.runOnAllThreads( + [updates = std::move(shared_updates)](OptRef overload_state) { for (const auto& [action, state] : *updates) { - object->asType().setState(action, state); + overload_state->setState(action, state); } - return object; }); } diff --git a/source/server/overload_manager_impl.h b/source/server/overload_manager_impl.h index 59902bc08e75..b86162fb0224 100644 --- a/source/server/overload_manager_impl.h +++ b/source/server/overload_manager_impl.h @@ -8,7 +8,7 @@ #include "envoy/event/dispatcher.h" #include "envoy/event/scaled_range_timer_manager.h" #include "envoy/protobuf/message_validator.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/server/resource_monitor.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats.h" @@ -100,6 +100,8 @@ class NamedOverloadActionSymbolTable { std::vector names_; }; +class ThreadLocalOverloadStateImpl; + class OverloadManagerImpl : Logger::Loggable, public OverloadManager { public: OverloadManagerImpl(Event::Dispatcher& dispatcher, Stats::Scope& stats_scope, @@ -161,7 +163,7 @@ class OverloadManagerImpl : Logger::Loggable, public OverloadM bool started_; Event::Dispatcher& dispatcher_; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; NamedOverloadActionSymbolTable action_symbol_table_; const std::chrono::milliseconds refresh_interval_; Event::TimerPtr timer_; diff --git a/source/server/server.cc b/source/server/server.cc index b0a16884d982..ee7792c719c7 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -2,12 +2,14 @@ #include #include +#include #include #include #include #include "envoy/admin/v3/config_dump.pb.h" #include "envoy/common/exception.h" +#include "envoy/common/time.h" #include "envoy/config/bootstrap/v2/bootstrap.pb.h" #include "envoy/config/bootstrap/v2/bootstrap.pb.validate.h" #include "envoy/config/bootstrap/v3/bootstrap.pb.h" @@ -147,7 +149,7 @@ void InstanceImpl::failHealthcheck(bool fail) { server_stats_->live_.set(live_.load()); } -MetricSnapshotImpl::MetricSnapshotImpl(Stats::Store& store) { +MetricSnapshotImpl::MetricSnapshotImpl(Stats::Store& store, TimeSource& time_source) { snapped_counters_ = store.counters(); counters_.reserve(snapped_counters_.size()); for (const auto& counter : snapped_counters_) { @@ -172,15 +174,17 @@ MetricSnapshotImpl::MetricSnapshotImpl(Stats::Store& store) { for (const auto& text_readout : snapped_text_readouts_) { text_readouts_.push_back(*text_readout); } + + snapshot_time_ = time_source.systemTime(); } -void InstanceUtil::flushMetricsToSinks(const std::list& sinks, - Stats::Store& store) { +void InstanceUtil::flushMetricsToSinks(const std::list& sinks, Stats::Store& store, + TimeSource& time_source) { // Create a snapshot and flush to all sinks. // NOTE: Even if there are no sinks, creating the snapshot has the important property that it // latches all counters on a periodic basis. The hot restart code assumes this is being // done so this should not be removed. - MetricSnapshotImpl snapshot(store); + MetricSnapshotImpl snapshot(store, time_source); for (const auto& sink : sinks) { sink->flush(snapshot); } @@ -231,7 +235,7 @@ void InstanceImpl::updateServerStats() { void InstanceImpl::flushStatsInternal() { updateServerStats(); - InstanceUtil::flushMetricsToSinks(config_.statsSinks(), stats_store_); + InstanceUtil::flushMetricsToSinks(config_.statsSinks(), stats_store_, timeSource()); // TODO(ramaraochavali): consider adding different flush interval for histograms. if (stat_flush_timer_ != nullptr) { stat_flush_timer_->enableTimer(config_.statsFlushInterval()); @@ -254,7 +258,7 @@ void loadBootstrap(absl::optional bootstrap_version, envoy::config::bootstrap::v2::Bootstrap bootstrap_v2; load_function(bootstrap_v2, false); Config::VersionConverter::upgrade(bootstrap_v2, bootstrap); - MessageUtil::onVersionUpgradeWarn("v2 bootstrap"); + MessageUtil::onVersionUpgradeDeprecation("v2 bootstrap", false); } else { throw EnvoyException(fmt::format("Unknown bootstrap version {}.", *bootstrap_version)); } @@ -678,8 +682,7 @@ void InstanceImpl::run() { // Run the main dispatch loop waiting to exit. ENVOY_LOG(info, "starting main dispatch loop"); auto watchdog = main_thread_guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), - "main_thread"); - watchdog->startWatchdog(*dispatcher_); + "main_thread", *dispatcher_); dispatcher_->post([this] { notifyCallbacksForStage(Stage::Startup); }); dispatcher_->run(Event::Dispatcher::RunType::Block); ENVOY_LOG(info, "main dispatch loop exited"); diff --git a/source/server/server.h b/source/server/server.h index f9ce2954d581..1c06562b5a0e 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -113,7 +113,8 @@ class InstanceUtil : Logger::Loggable { * @param sinks supplies the list of sinks. * @param store provides the store being flushed. */ - static void flushMetricsToSinks(const std::list& sinks, Stats::Store& store); + static void flushMetricsToSinks(const std::list& sinks, Stats::Store& store, + TimeSource& time_source); /** * Load a bootstrap config and perform validation. @@ -383,7 +384,7 @@ class InstanceImpl final : Logger::Loggable, // copying and probably be a cleaner API in general. class MetricSnapshotImpl : public Stats::MetricSnapshot { public: - explicit MetricSnapshotImpl(Stats::Store& store); + explicit MetricSnapshotImpl(Stats::Store& store, TimeSource& time_source); // Stats::MetricSnapshot const std::vector& counters() override { return counters_; } @@ -396,6 +397,7 @@ class MetricSnapshotImpl : public Stats::MetricSnapshot { const std::vector>& textReadouts() override { return text_readouts_; } + SystemTime snapshotTime() const override { return snapshot_time_; } private: std::vector snapped_counters_; @@ -406,6 +408,7 @@ class MetricSnapshotImpl : public Stats::MetricSnapshot { std::vector> histograms_; std::vector snapped_text_readouts_; std::vector> text_readouts_; + SystemTime snapshot_time_; }; } // namespace Server diff --git a/source/server/watchdog_impl.cc b/source/server/watchdog_impl.cc deleted file mode 100644 index 8a7e53b7880c..000000000000 --- a/source/server/watchdog_impl.cc +++ /dev/null @@ -1,19 +0,0 @@ -#include "server/watchdog_impl.h" - -#include "envoy/event/dispatcher.h" - -#include "common/common/assert.h" - -namespace Envoy { -namespace Server { - -void WatchDogImpl::startWatchdog(Event::Dispatcher& dispatcher) { - timer_ = dispatcher.createTimer([this]() -> void { - this->touch(); - timer_->enableTimer(timer_interval_); - }); - timer_->enableTimer(timer_interval_); -} - -} // namespace Server -} // namespace Envoy diff --git a/source/server/watchdog_impl.h b/source/server/watchdog_impl.h index a18034745885..4ebe954a9f52 100644 --- a/source/server/watchdog_impl.h +++ b/source/server/watchdog_impl.h @@ -1,10 +1,7 @@ #pragma once #include -#include -#include "envoy/common/time.h" -#include "envoy/event/dispatcher.h" #include "envoy/server/watchdog.h" namespace Envoy { @@ -17,17 +14,15 @@ namespace Server { class WatchDogImpl : public WatchDog { public: /** - * @param interval WatchDog timer interval (used after startWatchdog()) + * @param thread_id ThreadId of the monitored thread */ - WatchDogImpl(Thread::ThreadId thread_id, std::chrono::milliseconds interval) - : thread_id_(thread_id), timer_interval_(interval) {} + WatchDogImpl(Thread::ThreadId thread_id) : thread_id_(thread_id) {} Thread::ThreadId threadId() const override { return thread_id_; } // Used by GuardDogImpl determine if the watchdog was touched recently and reset the touch status. bool getTouchedAndReset() { return touched_.exchange(false, std::memory_order_relaxed); } // Server::WatchDog - void startWatchdog(Event::Dispatcher& dispatcher) override; void touch() override { // Set touched_ if not already set. bool expected = false; @@ -37,8 +32,6 @@ class WatchDogImpl : public WatchDog { private: const Thread::ThreadId thread_id_; std::atomic touched_{false}; - Event::TimerPtr timer_; - const std::chrono::milliseconds timer_interval_; }; } // namespace Server diff --git a/source/server/worker_impl.cc b/source/server/worker_impl.cc index b659ffec6e06..760b7ca630bc 100644 --- a/source/server/worker_impl.cc +++ b/source/server/worker_impl.cc @@ -40,8 +40,8 @@ void WorkerImpl::addListener(absl::optional overridden_listener, Network::ListenerConfig& listener, AddListenerCompletion completion) { // All listener additions happen via post. However, we must deal with the case where the listener // can not be created on the worker. There is a race condition where 2 processes can successfully - // bind to an address, but then fail to listen() with EADDRINUSE. During initial startup, we want - // to surface this. + // bind to an address, but then fail to listen() with `EADDRINUSE`. During initial startup, we + // want to surface this. dispatcher_->post([this, overridden_listener, &listener, completion]() -> void { try { handler_->addListener(overridden_listener, listener); @@ -128,9 +128,8 @@ void WorkerImpl::threadRoutine(GuardDog& guard_dog) { // The watch dog must be created after the dispatcher starts running and has post events flushed, // as this is when TLS stat scopes start working. dispatcher_->post([this, &guard_dog]() { - watch_dog_ = - guard_dog.createWatchDog(api_.threadFactory().currentThreadId(), dispatcher_->name()); - watch_dog_->startWatchdog(*dispatcher_); + watch_dog_ = guard_dog.createWatchDog(api_.threadFactory().currentThreadId(), + dispatcher_->name(), *dispatcher_); }); dispatcher_->run(Event::Dispatcher::RunType::Block); ENVOY_LOG(debug, "worker exited dispatch loop"); diff --git a/test/common/access_log/access_log_impl_test.cc b/test/common/access_log/access_log_impl_test.cc index 0b6ce4734f9d..0f9d94467d5c 100644 --- a/test/common/access_log/access_log_impl_test.cc +++ b/test/common/access_log/access_log_impl_test.cc @@ -69,7 +69,7 @@ TEST_F(AccessLogImplTest, LogMoreData) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -92,7 +92,7 @@ TEST_F(AccessLogImplTest, DownstreamDisconnect) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -114,9 +114,10 @@ TEST_F(AccessLogImplTest, RouteName) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null - format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH):256% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %ROUTE_NAME% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\"\n" + log_format: + text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH):256% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %ROUTE_NAME% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\"\n" )EOF"; InstanceSharedPtr log = AccessLogFactory::fromProto(parseAccessLogFromV3Yaml(yaml), context_); @@ -141,7 +142,7 @@ TEST_F(AccessLogImplTest, EnvoyUpstreamServiceTime) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -160,7 +161,7 @@ TEST_F(AccessLogImplTest, NoFilter) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -180,7 +181,7 @@ TEST_F(AccessLogImplTest, UpstreamHost) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -212,7 +213,7 @@ name: accesslog default_value: 1000000 runtime_key: key_b typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -250,7 +251,7 @@ name: accesslog default_value: 1000000 runtime_key: key_c typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -275,7 +276,7 @@ name: accesslog runtime_filter: runtime_key: access_log.test_key typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -318,7 +319,7 @@ name: accesslog numerator: 5 denominator: TEN_THOUSAND typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -362,7 +363,7 @@ name: accesslog denominator: MILLION use_independent_randomness: true typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -389,7 +390,7 @@ TEST_F(AccessLogImplTest, PathRewrite) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -408,7 +409,7 @@ name: accesslog filter: not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -427,7 +428,7 @@ name: accesslog filter: not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/dev/null" )EOF"; @@ -447,7 +448,7 @@ name: accesslog filter: traceable_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -484,7 +485,7 @@ name: accesslog filter: or_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -498,7 +499,7 @@ name: accesslog filter: and_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -521,7 +522,7 @@ name: accesslog runtime_key: key - not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -557,7 +558,7 @@ name: accesslog runtime_key: key - not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -600,7 +601,7 @@ name: accesslog runtime_key: key_b - not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -700,7 +701,7 @@ name: accesslog default_value: 499 runtime_key: hello typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -725,7 +726,7 @@ name: accesslog header: name: test-header typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -749,7 +750,7 @@ name: accesslog exact_match: exact-match-value typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -779,7 +780,7 @@ name: accesslog google_re2: {} regex: "\\d{3}" typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -814,7 +815,7 @@ name: accesslog start: -10 end: 0 typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -854,7 +855,7 @@ name: accesslog filter: response_flag_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -876,7 +877,7 @@ name: accesslog flags: - UO typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -903,7 +904,7 @@ name: accesslog - UO - RL typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -951,7 +952,7 @@ name: accesslog - NFCF - DT typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1001,7 +1002,7 @@ name: accesslog flags: - UnsupportedFlag typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1017,7 +1018,8 @@ name: accesslog "\"accesslog\"\nfilter {\n " " " "response_flag_filter {\n flags: \"UnsupportedFlag\"\n }\n}\ntyped_config {\n " - "[type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog] {\n path: \"/dev/null\"\n " + "[type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog] {\n path: " + "\"/dev/null\"\n " "}\n}\n"); } @@ -1029,7 +1031,7 @@ name: accesslog flags: - UnsupportedFlag typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1045,7 +1047,8 @@ name: accesslog "\"accesslog\"\nfilter {\n " " " "response_flag_filter {\n flags: \"UnsupportedFlag\"\n }\n}\ntyped_config {\n " - "[type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog] {\n path: \"/dev/null\"\n " + "[type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog] {\n path: " + "\"/dev/null\"\n " "}\n}\n"); } @@ -1053,9 +1056,10 @@ TEST_F(AccessLogImplTest, ValidGrpcStatusMessage) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null - format: "%GRPC_STATUS%\n" + log_format: + text_format: "%GRPC_STATUS%\n" )EOF"; InstanceSharedPtr log = AccessLogFactory::fromProto(parseAccessLogFromV3Yaml(yaml), context_); @@ -1092,7 +1096,7 @@ name: accesslog statuses: - {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1123,7 +1127,7 @@ name: accesslog statuses: - NOT_A_VALID_CODE typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1139,7 +1143,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1160,7 +1164,7 @@ name: accesslog statuses: - {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1192,7 +1196,7 @@ name: accesslog statuses: - UNKNOWN typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1212,7 +1216,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1237,7 +1241,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1258,7 +1262,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1286,7 +1290,7 @@ name: accesslog bool_match: true typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1322,7 +1326,7 @@ name: accesslog metadata_filter: match_if_key_not_found: false typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1351,7 +1355,7 @@ name: accesslog bool_match: true typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1369,7 +1373,7 @@ name: accesslog value: false typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1426,11 +1430,11 @@ name: accesslog extension_filter: name: test_header_filter typed_config: - "@type": type.googleapis.com/envoy.config.filter.accesslog.v2.HeaderFilter + "@type": type.googleapis.com/envoy.config.accesslog.v3.HeaderFilter header: name: test-header typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1507,7 +1511,7 @@ name: accesslog value: rate: 5 typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1535,7 +1539,7 @@ name: accesslog value: foo: bar typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1550,7 +1554,7 @@ name: accesslog extension_filter: name: bar typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; diff --git a/test/common/common/BUILD b/test/common/common/BUILD index 20392d66be19..c002da716b0a 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -187,6 +187,11 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "optref_test", + srcs = ["optref_test.cc"], +) + envoy_cc_test( name = "random_generator_test", srcs = ["random_generator_test.cc"], @@ -264,6 +269,17 @@ envoy_cc_test( deps = ["//source/common/common:callback_impl_lib"], ) +envoy_cc_benchmark_binary( + name = "re_speed_test", + srcs = ["re_speed_test.cc"], + external_deps = ["benchmark"], + deps = [ + "//source/common/common:assert_lib", + "//source/common/common:utility_lib", + "@com_googlesource_code_re2//:re2", + ], +) + envoy_cc_benchmark_binary( name = "utility_speed_test", srcs = ["utility_speed_test.cc"], diff --git a/test/common/common/logger_test.cc b/test/common/common/logger_test.cc index 8e4d8839852d..dc1ec0ba110a 100644 --- a/test/common/common/logger_test.cc +++ b/test/common/common/logger_test.cc @@ -1,3 +1,4 @@ +#include #include #include "common/common/logger.h" @@ -43,5 +44,42 @@ TEST(LoggerEscapeTest, WhitespaceOnly) { TEST(LoggerEscapeTest, Empty) { EXPECT_EQ("", DelegatingLogSink::escapeLogLine("")); } +class LoggerCustomFlagsTest : public testing::Test { +public: + LoggerCustomFlagsTest() : logger_(Registry::getSink()) {} + + void expectLogMessage(const std::string& pattern, const std::string& message, + const std::string& expected) { + auto formatter = std::make_unique(); + formatter + ->add_flag( + CustomFlagFormatter::EscapeMessageNewLine::Placeholder) + .set_pattern(pattern); + logger_->set_formatter(std::move(formatter)); + + testing::internal::CaptureStderr(); + logger_->log(spdlog::details::log_msg("test", spdlog::level::info, message)); +#ifdef WIN32 + EXPECT_EQ(expected + "\r\n", testing::internal::GetCapturedStderr()); +#else + EXPECT_EQ(expected + "\n", testing::internal::GetCapturedStderr()); +#endif + } + +protected: + DelegatingLogSinkSharedPtr logger_; +}; + +TEST_F(LoggerCustomFlagsTest, LogMessageAsIs) { + // This uses "%v", the default flag for printing the actual text to log. + // https://github.com/gabime/spdlog/wiki/3.-Custom-formatting#pattern-flags. + expectLogMessage("%v", "\n\nmessage\n\n", "\n\nmessage\n\n"); +} + +TEST_F(LoggerCustomFlagsTest, LogMessageAsEscaped) { + // This uses "%_", the added custom flag that escapes newlines from the actual text to log. + expectLogMessage("%_", "\n\nmessage\n\n", "\\n\\nmessage\\n\\n"); +} + } // namespace Logger } // namespace Envoy diff --git a/test/common/common/optref_test.cc b/test/common/common/optref_test.cc new file mode 100644 index 000000000000..343d4506bba0 --- /dev/null +++ b/test/common/common/optref_test.cc @@ -0,0 +1,37 @@ +#include + +#include "envoy/common/optref.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +// Helper function for returning the string reference from an OptRef. Calling +// value() inline at the EXPECT_EQ callsites does not compile due to template +// specialization ambiguities, that this wrapper resolves. +static std::string& strref(const OptRef optref) { return optref.value(); } + +TEST(OptRefTest, Empty) { + OptRef optref; + EXPECT_FALSE(optref.has_value()); +} + +TEST(OptRefTest, NonConst) { + std::string str("Hello"); + OptRef optref(str); + EXPECT_TRUE(optref.has_value()); + EXPECT_EQ("Hello", strref(optref)); + EXPECT_EQ(5, optref->size()); + optref->append(", World!"); + EXPECT_EQ("Hello, World!", strref(optref)); +} + +TEST(OptRefTest, Const) { + std::string str("Hello"); + const OptRef optref(str); + EXPECT_TRUE(optref.has_value()); + EXPECT_EQ("Hello", strref(optref)); + EXPECT_EQ(5, optref->size()); +} + +} // namespace Envoy diff --git a/test/common/common/re_speed_test.cc b/test/common/common/re_speed_test.cc new file mode 100644 index 000000000000..0db62906e281 --- /dev/null +++ b/test/common/common/re_speed_test.cc @@ -0,0 +1,128 @@ +// Note: this should be run with --compilation_mode=opt, and would benefit from +// a quiescent system with disabled cstate power management. + +#include + +#include "common/common/assert.h" + +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" +#include "re2/re2.h" + +// NOLINT(namespace-envoy) + +static const char* ClusterInputs[] = { + "cluster.no_trailing_dot", + "cluster.match.", + "cluster.match.normal", + "cluster.match.and.a.whole.lot.of.things.coming.after.the.matches.really.too.much.stuff", +}; + +static const char ClusterRePattern[] = "^cluster\\.((.*?)\\.)"; +static const char ClusterReAltPattern[] = "^cluster\\.(([^\\.]+)\\.).*"; + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_StdRegex(benchmark::State& state) { + std::regex re(ClusterRePattern); + uint32_t passes = 0; + std::vector inputs; + for (const char* cluster_input : ClusterInputs) { + inputs.push_back(cluster_input); + } + + for (auto _ : state) { // NOLINT + for (const std::string& cluster_input : inputs) { + std::smatch match; + if (std::regex_search(cluster_input, match, re)) { + ASSERT(match.size() >= 3); + ASSERT(match[1] == "match."); + ASSERT(match[2] == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_StdRegex); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_StdRegexStringView(benchmark::State& state) { + std::regex re(ClusterRePattern); + std::vector inputs; + for (const char* cluster_input : ClusterInputs) { + inputs.push_back(cluster_input); + } + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (absl::string_view cluster_input : inputs) { + std::match_results smatch; + if (std::regex_search(cluster_input.begin(), cluster_input.end(), smatch, re)) { + ASSERT(smatch.size() >= 3); + ASSERT(smatch[1] == "match."); + ASSERT(smatch[2] == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_StdRegexStringView); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_StdRegexStringViewAltPattern(benchmark::State& state) { + std::regex re(ClusterReAltPattern); + std::vector inputs; + for (const char* cluster_input : ClusterInputs) { + inputs.push_back(cluster_input); + } + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (absl::string_view cluster_input : inputs) { + std::match_results smatch; + if (std::regex_search(cluster_input.begin(), cluster_input.end(), smatch, re)) { + ASSERT(smatch.size() >= 3); + ASSERT(smatch[1] == "match."); + ASSERT(smatch[2] == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_StdRegexStringViewAltPattern); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_RE2(benchmark::State& state) { + re2::RE2 re(ClusterRePattern); + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (const char* cluster_input : ClusterInputs) { + re2::StringPiece match1, match2; + if (re2::RE2::PartialMatch(cluster_input, re, &match1, &match2)) { + ASSERT(match1 == "match."); + ASSERT(match2 == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_RE2); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_RE2_AltPattern(benchmark::State& state) { + re2::RE2 re(ClusterReAltPattern); + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (const char* cluster_input : ClusterInputs) { + re2::StringPiece match1, match2; + if (re2::RE2::PartialMatch(cluster_input, re, &match1, &match2)) { + ASSERT(match1 == "match."); + ASSERT(match2 == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_RE2_AltPattern); diff --git a/test/common/config/BUILD b/test/common/config/BUILD index f53ca9aacb69..c95c4b8bb72e 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -36,6 +36,17 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "ttl_test", + srcs = ["ttl_test.cc"], + deps = [ + "//source/common/config:ttl_lib", + "//test/mocks/event:event_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "delta_subscription_impl_test", srcs = ["delta_subscription_impl_test.cc"], @@ -74,6 +85,7 @@ envoy_cc_test( "//test/mocks/local_info:local_info_mocks", "//test/mocks/runtime:runtime_mocks", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], @@ -490,3 +502,13 @@ envoy_cc_test( "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "watched_directory_test", + srcs = ["watched_directory_test.cc"], + deps = [ + "//source/common/config:watched_directory_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/filesystem:filesystem_mocks", + ], +) diff --git a/test/common/config/config_provider_impl_test.cc b/test/common/config/config_provider_impl_test.cc index c8308e42e500..b9d507062384 100644 --- a/test/common/config/config_provider_impl_test.cc +++ b/test/common/config/config_provider_impl_test.cc @@ -3,6 +3,7 @@ #include "envoy/config/core/v3/config_source.pb.h" #include "envoy/service/discovery/v3/discovery.pb.h" +#include "common/common/assert.h" #include "common/config/config_provider_impl.h" #include "common/protobuf/utility.h" @@ -89,12 +90,12 @@ class DummyConfigSubscription : public ConfigSubscriptionInstance, ConfigSubscriptionCommonBase::onConfigUpdate(); } + // Envoy::Config::SubscriptionCallbacks void onConfigUpdate(const std::vector&, const Protobuf::RepeatedPtrField&, const std::string&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - // Envoy::Config::SubscriptionCallbacks void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason, const EnvoyException*) override {} diff --git a/test/common/config/decoded_resource_impl_test.cc b/test/common/config/decoded_resource_impl_test.cc index 938d29611d33..2bb1020869c7 100644 --- a/test/common/config/decoded_resource_impl_test.cc +++ b/test/common/config/decoded_resource_impl_test.cc @@ -23,12 +23,13 @@ TEST(DecodedResourceImplTest, All) { []() -> ProtobufTypes::MessagePtr { return std::make_unique(); })); EXPECT_CALL(resource_decoder, resourceName(ProtoEq(ProtobufWkt::Empty()))) .WillOnce(Return("some_name")); - DecodedResourceImpl decoded_resource(resource_decoder, some_opaque_resource, "foo"); - EXPECT_EQ("some_name", decoded_resource.name()); - EXPECT_TRUE(decoded_resource.aliases().empty()); - EXPECT_EQ("foo", decoded_resource.version()); - EXPECT_THAT(decoded_resource.resource(), ProtoEq(ProtobufWkt::Empty())); - EXPECT_TRUE(decoded_resource.hasResource()); + auto decoded_resource = + DecodedResourceImpl::fromResource(resource_decoder, some_opaque_resource, "foo"); + EXPECT_EQ("some_name", decoded_resource->name()); + EXPECT_TRUE(decoded_resource->aliases().empty()); + EXPECT_EQ("foo", decoded_resource->version()); + EXPECT_THAT(decoded_resource->resource(), ProtoEq(ProtobufWkt::Empty())); + EXPECT_TRUE(decoded_resource->hasResource()); } { diff --git a/test/common/config/delta_subscription_state_test.cc b/test/common/config/delta_subscription_state_test.cc index 554ebe3884df..92193cbe143c 100644 --- a/test/common/config/delta_subscription_state_test.cc +++ b/test/common/config/delta_subscription_state_test.cc @@ -1,3 +1,5 @@ +#include + #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/service/discovery/v3/discovery.pb.h" @@ -8,6 +10,8 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/local_info/mocks.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/test_runtime.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -22,9 +26,11 @@ namespace { const char TypeUrl[] = "type.googleapis.com/envoy.api.v2.Cluster"; -class DeltaSubscriptionStateTest : public testing::Test { +class DeltaSubscriptionStateTestBase : public testing::Test { protected: - DeltaSubscriptionStateTest() : state_(TypeUrl, callbacks_, local_info_) { + DeltaSubscriptionStateTestBase(const std::string& type_url) + : timer_(new Event::MockTimer(&dispatcher_)), + state_(type_url, callbacks_, local_info_, dispatcher_) { state_.updateSubscriptionInterest({"name1", "name2", "name3"}, {}); envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = state_.getNextRequestAckless(); @@ -36,7 +42,7 @@ class DeltaSubscriptionStateTest : public testing::Test { const Protobuf::RepeatedPtrField& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string& version_info, absl::optional nonce = absl::nullopt, - bool expect_config_update_call = true) { + bool expect_config_update_call = true, absl::optional updated_resources = {}) { envoy::service::discovery::v3::DeltaDiscoveryResponse message; *message.mutable_resources() = added_resources; *message.mutable_removed_resources() = removed_resources; @@ -44,7 +50,13 @@ class DeltaSubscriptionStateTest : public testing::Test { if (nonce.has_value()) { message.set_nonce(nonce.value()); } - EXPECT_CALL(callbacks_, onConfigUpdate(_, _, _)).Times(expect_config_update_call ? 1 : 0); + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, _)) + .Times(expect_config_update_call ? 1 : 0) + .WillRepeatedly(Invoke([updated_resources](const auto& added, const auto&, const auto&) { + if (updated_resources) { + EXPECT_EQ(added.size(), *updated_resources); + } + })); return state_.handleResponse(message); } @@ -64,6 +76,7 @@ class DeltaSubscriptionStateTest : public testing::Test { NiceMock callbacks_; NiceMock local_info_; NiceMock dispatcher_; + Event::MockTimer* timer_; // We start out interested in three resources: name1, name2, and name3. DeltaSubscriptionState state_; }; @@ -79,6 +92,11 @@ populateRepeatedResource(std::vector> items) return add_to; } +class DeltaSubscriptionStateTest : public DeltaSubscriptionStateTestBase { +public: + DeltaSubscriptionStateTest() : DeltaSubscriptionStateTestBase(TypeUrl) {} +}; + // Basic gaining/losing interest in resources should lead to subscription updates. TEST_F(DeltaSubscriptionStateTest, SubscribeAndUnsubscribe) { { @@ -177,6 +195,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource({{"name1", "version1A"}, {"name2", "version2A"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverDiscoveryResponse(added_resources, {}, "debug1", "nonce1"); EXPECT_EQ("nonce1", ack.nonce_); EXPECT_EQ(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -186,6 +205,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1B"}, {"name2", "version2B"}, {"name3", "version3A"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverDiscoveryResponse(added_resources, {}, "debug2", "nonce2"); EXPECT_EQ("nonce2", ack.nonce_); EXPECT_EQ(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -195,6 +215,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1C"}, {"name2", "version2C"}, {"name3", "version3B"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverBadDiscoveryResponse(added_resources, {}, "debug3", "nonce3", "oh no"); EXPECT_EQ("nonce3", ack.nonce_); EXPECT_NE(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -204,6 +225,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1D"}, {"name2", "version2D"}, {"name3", "version3C"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverDiscoveryResponse(added_resources, {}, "debug4", "nonce4"); EXPECT_EQ("nonce4", ack.nonce_); EXPECT_EQ(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -214,6 +236,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1D"}, {"name2", "version2D"}, {"name3", "version3D"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverBadDiscoveryResponse(added_resources, {}, "debug5", "nonce5", very_large_error_message); EXPECT_EQ("nonce5", ack.nonce_); @@ -233,6 +256,7 @@ TEST_F(DeltaSubscriptionStateTest, ResourceGoneLeadsToBlankInitialVersion) { // The xDS server's first update includes items for name1 and 2, but not 3. Protobuf::RepeatedPtrField add1_2 = populateRepeatedResource({{"name1", "version1A"}, {"name2", "version2A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add1_2, {}, "debugversion1"); state_.markStreamFresh(); // simulate a stream reconnection envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = @@ -249,6 +273,7 @@ TEST_F(DeltaSubscriptionStateTest, ResourceGoneLeadsToBlankInitialVersion) { populateRepeatedResource({{"name1", "version1B"}, {"name3", "version3A"}}); Protobuf::RepeatedPtrField remove2; *remove2.Add() = "name2"; + EXPECT_CALL(*timer_, disableTimer()).Times(2); deliverDiscoveryResponse(add1_3, remove2, "debugversion2"); state_.markStreamFresh(); // simulate a stream reconnection envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = @@ -293,6 +318,7 @@ TEST_F(DeltaSubscriptionStateTest, ResourceGoneLeadsToBlankInitialVersion) { TEST_F(DeltaSubscriptionStateTest, SubscribeAndUnsubscribeAfterReconnect) { Protobuf::RepeatedPtrField add1_2 = populateRepeatedResource({{"name1", "version1A"}, {"name2", "version2A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add1_2, {}, "debugversion1"); state_.updateSubscriptionInterest({"name4"}, {"name1"}); @@ -318,6 +344,7 @@ TEST_F(DeltaSubscriptionStateTest, InitialVersionMapFirstMessageOnly) { Protobuf::RepeatedPtrField add_all = populateRepeatedResource( {{"name1", "version1A"}, {"name2", "version2A"}, {"name3", "version3A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add_all, {}, "debugversion1"); state_.markStreamFresh(); // simulate a stream reconnection envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = @@ -336,6 +363,7 @@ TEST_F(DeltaSubscriptionStateTest, InitialVersionMapFirstMessageOnly) { {"name2", "version2B"}, {"name3", "version3B"}, {"name4", "version4A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add_all, {}, "debugversion2"); envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = state_.getNextRequestAckless(); @@ -388,6 +416,117 @@ TEST_F(DeltaSubscriptionStateTest, AddedAndRemoved) { ack.error_detail_.message()); } +TEST_F(DeltaSubscriptionStateTest, ResourceTTL) { + Event::SimulatedTimeSystem time_system; + time_system.setSystemTime(std::chrono::milliseconds(0)); + + auto create_resource_with_ttl = [](absl::optional ttl_s, + bool include_resource) { + Protobuf::RepeatedPtrField added_resources; + auto* resource = added_resources.Add(); + resource->set_name("name1"); + resource->set_version("version1A"); + + if (include_resource) { + resource->mutable_resource(); + } + + if (ttl_s) { + ProtobufWkt::Duration ttl; + ttl.set_seconds(ttl_s->count()); + resource->mutable_ttl()->CopyFrom(ttl); + } + + return added_resources; + }; + + { + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000), _)); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(1), true), {}, "debug1", + "nonce1"); + } + + { + // Increase the TTL. + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(2000), _)); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(2), true), {}, "debug1", + "nonce1", true, 1); + } + + { + // Refresh the TTL with a heartbeat. The resource should not be passed to the update callbacks. + EXPECT_CALL(*timer_, enabled()); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(2), false), {}, "debug1", + "nonce1", true, 0); + } + + // Remove the TTL. + EXPECT_CALL(*timer_, disableTimer()); + deliverDiscoveryResponse(create_resource_with_ttl(absl::nullopt, true), {}, "debug1", "nonce1", + true, 1); + + // Add back the TTL. + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(_, _)); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(2), true), {}, "debug1", + "nonce1"); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, _)); + EXPECT_CALL(*timer_, disableTimer()); + time_system.setSystemTime(std::chrono::seconds(2)); + + // Invoke the TTL. + timer_->invokeCallback(); +} + +class VhdsDeltaSubscriptionStateTest : public DeltaSubscriptionStateTestBase { +public: + VhdsDeltaSubscriptionStateTest() + : DeltaSubscriptionStateTestBase("envoy.config.route.v3.VirtualHost") {} +}; + +TEST_F(VhdsDeltaSubscriptionStateTest, ResourceTTL) { + Event::SimulatedTimeSystem time_system; + time_system.setSystemTime(std::chrono::milliseconds(0)); + + TestScopedRuntime scoped_runtime; + + auto create_resource_with_ttl = [](bool include_resource) { + Protobuf::RepeatedPtrField added_resources; + auto* resource = added_resources.Add(); + resource->set_name("name1"); + resource->set_version("version1A"); + + if (include_resource) { + resource->mutable_resource(); + } + + ProtobufWkt::Duration ttl; + ttl.set_seconds(1); + resource->mutable_ttl()->CopyFrom(ttl); + + return added_resources; + }; + + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000), _)); + deliverDiscoveryResponse(create_resource_with_ttl(true), {}, "debug1", "nonce1", true, 1); + + // Heartbeat update should not be propagated to the subscription callback. + EXPECT_CALL(*timer_, enabled()); + deliverDiscoveryResponse(create_resource_with_ttl(false), {}, "debug1", "nonce1", true, 0); + + // When runtime flag is disabled, maintain old behavior where we do propagate + // the update to the subscription callback. + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.vhds_heartbeats", "false"}}); + + EXPECT_CALL(*timer_, enabled()); + deliverDiscoveryResponse(create_resource_with_ttl(false), {}, "debug1", "nonce1", true, 1); +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/common/config/delta_subscription_test_harness.h b/test/common/config/delta_subscription_test_harness.h index 04fca753ab05..b05d43ae87db 100644 --- a/test/common/config/delta_subscription_test_harness.h +++ b/test/common/config/delta_subscription_test_harness.h @@ -40,7 +40,7 @@ class DeltaSubscriptionTestHarness : public SubscriptionTestHarness { async_client_(new Grpc::MockAsyncClient()) { node_.set_id("fo0"); EXPECT_CALL(local_info_, node()).WillRepeatedly(testing::ReturnRef(node_)); - EXPECT_CALL(dispatcher_, createTimer_(_)); + EXPECT_CALL(dispatcher_, createTimer_(_)).Times(2); xds_context_ = std::make_shared( std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, envoy::config::core::v3::ApiVersion::AUTO, random_, stats_store_, rate_limit_settings_, @@ -144,7 +144,7 @@ class DeltaSubscriptionTestHarness : public SubscriptionTestHarness { auto* resource = response->add_resources(); resource->set_name(cluster); resource->set_version(version); - resource->mutable_resource()->PackFrom(API_DOWNGRADE(*load_assignment)); + resource->mutable_resource()->PackFrom(*load_assignment); } } Protobuf::RepeatedPtrField removed_resources; diff --git a/test/common/config/filesystem_subscription_test_harness.h b/test/common/config/filesystem_subscription_test_harness.h index 2f52f2629497..d19da3bbed6f 100644 --- a/test/common/config/filesystem_subscription_test_harness.h +++ b/test/common/config/filesystem_subscription_test_harness.h @@ -82,7 +82,7 @@ class FilesystemSubscriptionTestHarness : public SubscriptionTestHarness { std::string file_json = "{\"versionInfo\":\"" + version + "\",\"resources\":["; for (const auto& cluster : cluster_names) { file_json += "{\"@type\":\"type.googleapis.com/" - "envoy.api.v2.ClusterLoadAssignment\",\"clusterName\":\"" + + "envoy.config.endpoint.v3.ClusterLoadAssignment\",\"clusterName\":\"" + cluster + "\"},"; } file_json.pop_back(); diff --git a/test/common/config/grpc_mux_impl_test.cc b/test/common/config/grpc_mux_impl_test.cc index 8c869aa44b1f..6544290bc7aa 100644 --- a/test/common/config/grpc_mux_impl_test.cc +++ b/test/common/config/grpc_mux_impl_test.cc @@ -143,14 +143,11 @@ TEST_F(GrpcMuxImplTest, MultipleTypeUrlStreams) { TEST_F(GrpcMuxImplTest, ResetStream) { InSequence s; - Event::MockTimer* timer = nullptr; - Event::TimerCb timer_cb; - EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })); + auto* timer = new Event::MockTimer(&dispatcher_); + // TTL timers. + new Event::MockTimer(&dispatcher_); + new Event::MockTimer(&dispatcher_); + new Event::MockTimer(&dispatcher_); setup(); auto foo_sub = grpc_mux_->addWatch("foo", {"x", "y"}, callbacks_, resource_decoder_); @@ -166,7 +163,6 @@ TEST_F(GrpcMuxImplTest, ResetStream) { onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)) .Times(3); EXPECT_CALL(random_, random()); - ASSERT_TRUE(timer != nullptr); // initialized from dispatcher mock. EXPECT_CALL(*timer, enableTimer(_, _)); grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Canceled, ""); EXPECT_EQ(0, control_plane_connected_state_.value()); @@ -175,7 +171,7 @@ TEST_F(GrpcMuxImplTest, ResetStream) { expectSendMessage("foo", {"x", "y"}, "", true); expectSendMessage("bar", {}, ""); expectSendMessage("baz", {"z"}, ""); - timer_cb(); + timer->invokeCallback(); expectSendMessage("baz", {}, ""); expectSendMessage("foo", {}, ""); @@ -230,11 +226,13 @@ TEST_F(GrpcMuxImplTest, TypeUrlMismatch) { { auto response = std::make_unique(); response->set_type_url("bar"); + response->set_version_info("bar-version"); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } { invalid_response->set_type_url("foo"); + invalid_response->set_version_info("foo-version"); invalid_response->mutable_resources()->Add()->set_type_url("bar"); EXPECT_CALL(callbacks_, onConfigUpdateFailed(_, _)) .WillOnce(Invoke([](Envoy::Config::ConfigUpdateFailureReason, const EnvoyException* e) { @@ -265,6 +263,7 @@ TEST_F(GrpcMuxImplTest, RpcErrorMessageTruncated) { { // Large error message sent back to management server is truncated. const std::string very_large_type_url(1 << 20, 'A'); invalid_response->set_type_url("foo"); + invalid_response->set_version_info("invalid"); invalid_response->mutable_resources()->Add()->set_type_url(very_large_type_url); EXPECT_CALL(callbacks_, onConfigUpdateFailed(_, _)) .WillOnce(Invoke([&very_large_type_url](Envoy::Config::ConfigUpdateFailureReason, @@ -282,6 +281,152 @@ TEST_F(GrpcMuxImplTest, RpcErrorMessageTruncated) { expectSendMessage("foo", {}, ""); } +envoy::service::discovery::v3::Resource heartbeatResource(std::chrono::milliseconds ttl, + const std::string& name) { + envoy::service::discovery::v3::Resource resource; + + resource.mutable_ttl()->CopyFrom(Protobuf::util::TimeUtil::MillisecondsToDuration(ttl.count())); + resource.set_name(name); + + return resource; +} + +envoy::service::discovery::v3::Resource +resourceWithTtl(std::chrono::milliseconds ttl, + envoy::config::endpoint::v3::ClusterLoadAssignment& cla) { + envoy::service::discovery::v3::Resource resource; + resource.mutable_resource()->PackFrom(cla); + resource.mutable_ttl()->CopyFrom(Protobuf::util::TimeUtil::MillisecondsToDuration(ttl.count())); + + resource.set_name(cla.cluster_name()); + + return resource; +} +// Validates the behavior when the TTL timer expires. +TEST_F(GrpcMuxImplTest, ResourceTTL) { + setup(); + + time_system_.setSystemTime(std::chrono::seconds(0)); + + TestUtility::TestOpaqueResourceDecoderImpl + resource_decoder("cluster_name"); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + auto* ttl_timer = new Event::MockTimer(&dispatcher_); + auto eds_sub = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + + auto wrapped_resource = resourceWithTtl(std::chrono::milliseconds(1000), load_assignment); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1000), _)); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Increase the TTL. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + auto wrapped_resource = resourceWithTtl(std::chrono::milliseconds(10000), load_assignment); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(10000), _)); + // No update, just a change in TTL. + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Refresh the TTL with a heartbeat response. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + auto wrapped_resource = heartbeatResource(std::chrono::milliseconds(10000), "x"); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(*ttl_timer, enabled()); + + // No update, just a change in TTL. + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Remove the TTL. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, disableTimer()); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Put the TTL back. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + auto wrapped_resource = resourceWithTtl(std::chrono::milliseconds(10000), load_assignment); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(10000), _)); + // No update, just a change in TTL. + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + time_system_.setSystemTime(std::chrono::seconds(11)); + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "")) + .WillOnce(Invoke([](auto, const auto& removed, auto) { + EXPECT_EQ(1, removed.size()); + EXPECT_EQ("x", removed.Get(0)); + })); + // Fire the TTL timer. + EXPECT_CALL(*ttl_timer, disableTimer()); + ttl_timer->invokeCallback(); + + expectSendMessage(type_url, {}, "1"); +} + // Validate behavior when watches has an unknown resource name. TEST_F(GrpcMuxImplTest, WildcardWatch) { setup(); @@ -301,7 +446,7 @@ TEST_F(GrpcMuxImplTest, WildcardWatch) { response->set_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& resources, const std::string&) { @@ -338,7 +483,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { response->set_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(bar_callbacks, onConfigUpdate(_, "1")).Times(0); EXPECT_CALL(foo_callbacks, onConfigUpdate(_, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& resources, @@ -359,13 +504,13 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { response->set_version_info("2"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment_x; load_assignment_x.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment_x)); + response->add_resources()->PackFrom(load_assignment_x); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment_y; load_assignment_y.set_cluster_name("y"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment_y)); + response->add_resources()->PackFrom(load_assignment_y); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment_z; load_assignment_z.set_cluster_name("z"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment_z)); + response->add_resources()->PackFrom(load_assignment_z); EXPECT_CALL(bar_callbacks, onConfigUpdate(_, "2")) .WillOnce(Invoke([&load_assignment_y, &load_assignment_z]( const std::vector& resources, const std::string&) { @@ -455,15 +600,10 @@ class GrpcMuxImplTestWithMockTimeSystem : public GrpcMuxImplTestBase { // Verifies that rate limiting is not enforced with defaults. TEST_F(GrpcMuxImplTestWithMockTimeSystem, TooManyRequestsWithDefaultSettings) { - // Validate that only connection retry timer is enabled. - Event::MockTimer* timer = nullptr; - Event::TimerCb timer_cb; - EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })); + + auto ttl_timer = new Event::MockTimer(&dispatcher_); + // Retry timer, + new Event::MockTimer(&dispatcher_); // Validate that rate limiter is not created. EXPECT_CALL(*mock_time_system_, monotonicTime()).Times(0); @@ -479,6 +619,7 @@ TEST_F(GrpcMuxImplTestWithMockTimeSystem, TooManyRequestsWithDefaultSettings) { response->set_version_info("baz"); response->set_nonce("bar"); response->set_type_url("foo"); + EXPECT_CALL(*ttl_timer, disableTimer()); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } }; @@ -498,24 +639,10 @@ TEST_F(GrpcMuxImplTestWithMockTimeSystem, TooManyRequestsWithDefaultSettings) { // Verifies that default rate limiting is enforced with empty RateLimitSettings. TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { // Validate that request drain timer is created. - Event::MockTimer* timer = nullptr; - Event::MockTimer* drain_request_timer = nullptr; - - Event::TimerCb timer_cb; - Event::TimerCb drain_timer_cb; - EXPECT_CALL(dispatcher_, createTimer_(_)) - .WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })) - .WillOnce(Invoke([&drain_request_timer, &drain_timer_cb](Event::TimerCb cb) { - drain_timer_cb = cb; - EXPECT_EQ(nullptr, drain_request_timer); - drain_request_timer = new Event::MockTimer(); - return drain_request_timer; - })); + + auto ttl_timer = new Event::MockTimer(&dispatcher_); + Event::MockTimer* drain_request_timer = new Event::MockTimer(&dispatcher_); + Event::MockTimer* retry_timer = new Event::MockTimer(&dispatcher_); RateLimitSettings custom_rate_limit_settings; custom_rate_limit_settings.enabled_ = true; @@ -531,6 +658,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { response->set_version_info("baz"); response->set_nonce("bar"); response->set_type_url("foo"); + EXPECT_CALL(*ttl_timer, disableTimer()); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } }; @@ -552,15 +680,14 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { EXPECT_CALL(callbacks_, onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)); EXPECT_CALL(random_, random()); - ASSERT_TRUE(timer != nullptr); // initialized from dispatcher mock. - EXPECT_CALL(*timer, enableTimer(_, _)); + EXPECT_CALL(*retry_timer, enableTimer(_, _)); grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Canceled, ""); EXPECT_EQ(11, control_plane_pending_requests_.value()); EXPECT_EQ(0, control_plane_connected_state_.value()); EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); time_system_.setMonotonicTime(std::chrono::seconds(30)); - timer_cb(); + retry_timer->invokeCallback(); EXPECT_EQ(0, control_plane_pending_requests_.value()); // One more message on the way out when the watch is destroyed. EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); @@ -569,25 +696,12 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { // Verifies that rate limiting is enforced with custom RateLimitSettings. TEST_F(GrpcMuxImplTest, TooManyRequestsWithCustomRateLimitSettings) { // Validate that request drain timer is created. - Event::MockTimer* timer = nullptr; - Event::MockTimer* drain_request_timer = nullptr; - - Event::TimerCb timer_cb; - Event::TimerCb drain_timer_cb; - - EXPECT_CALL(dispatcher_, createTimer_(_)) - .WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })) - .WillOnce(Invoke([&drain_request_timer, &drain_timer_cb](Event::TimerCb cb) { - drain_timer_cb = cb; - EXPECT_EQ(nullptr, drain_request_timer); - drain_request_timer = new Event::MockTimer(); - return drain_request_timer; - })); + + // TTL timer. + auto ttl_timer = new Event::MockTimer(&dispatcher_); + Event::MockTimer* drain_request_timer = new Event::MockTimer(&dispatcher_); + // Retry timer. + new Event::MockTimer(&dispatcher_); RateLimitSettings custom_rate_limit_settings; custom_rate_limit_settings.enabled_ = true; @@ -604,6 +718,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithCustomRateLimitSettings) { response->set_version_info("baz"); response->set_nonce("bar"); response->set_type_url("foo"); + EXPECT_CALL(*ttl_timer, disableTimer()); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } }; @@ -625,7 +740,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithCustomRateLimitSettings) { // Validate that drain requests call when there are multiple requests in queue. time_system_.setMonotonicTime(std::chrono::seconds(10)); - drain_timer_cb(); + drain_request_timer->invokeCallback(); // Check that the pending_requests stat is updated with the queue drain. EXPECT_EQ(0, control_plane_pending_requests_.value()); @@ -792,7 +907,7 @@ TEST_F(GrpcMuxImplTest, WatchV3ResourceV2) { response->set_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& resources, const std::string&) { diff --git a/test/common/config/grpc_subscription_impl_test.cc b/test/common/config/grpc_subscription_impl_test.cc index eb51a0d051d7..74860c19e859 100644 --- a/test/common/config/grpc_subscription_impl_test.cc +++ b/test/common/config/grpc_subscription_impl_test.cc @@ -31,7 +31,7 @@ TEST_F(GrpcSubscriptionImplTest, StreamCreationFailure) { EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); expectSendMessage({"cluster2"}, "", true); - timer_cb_(); + timer_->invokeCallback(); EXPECT_TRUE(statsAre(3, 0, 0, 1, 0, 0, 0, "")); verifyControlPlaneStats(1); } @@ -53,7 +53,7 @@ TEST_F(GrpcSubscriptionImplTest, RemoteStreamClose) { // Retry and succeed. EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); expectSendMessage({"cluster0", "cluster1"}, "", true); - timer_cb_(); + timer_->invokeCallback(); EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, "")); } diff --git a/test/common/config/grpc_subscription_test_harness.h b/test/common/config/grpc_subscription_test_harness.h index 9f00b2622afa..f0950a8dca31 100644 --- a/test/common/config/grpc_subscription_test_harness.h +++ b/test/common/config/grpc_subscription_test_harness.h @@ -42,13 +42,12 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { GrpcSubscriptionTestHarness(std::chrono::milliseconds init_fetch_timeout) : method_descriptor_(Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.api.v2.EndpointDiscoveryService.StreamEndpoints")), - async_client_(new NiceMock()), timer_(new Event::MockTimer()) { + async_client_(new NiceMock()) { node_.set_id("fo0"); EXPECT_CALL(local_info_, node()).WillOnce(testing::ReturnRef(node_)); - EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb timer_cb) { - timer_cb_ = timer_cb; - return timer_; - })); + ttl_timer_ = new NiceMock(&dispatcher_); + + timer_ = new Event::MockTimer(&dispatcher_); mux_ = std::make_shared( local_info_, std::unique_ptr(async_client_), dispatcher_, @@ -59,7 +58,11 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { dispatcher_, init_fetch_timeout, false); } - ~GrpcSubscriptionTestHarness() override { EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); } + ~GrpcSubscriptionTestHarness() override { + EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + dispatcher_.clearDeferredDeleteList(); + } void expectSendMessage(const std::set& cluster_names, const std::string& version, bool expect_node = false) override { @@ -113,14 +116,16 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { last_cluster_names_.end()) { envoy::config::endpoint::v3::ClusterLoadAssignment* load_assignment = typed_resources.Add(); load_assignment->set_cluster_name(cluster); - response->add_resources()->PackFrom(API_DOWNGRADE(*load_assignment)); + response->add_resources()->PackFrom(*load_assignment); } } const auto decoded_resources = TestUtility::decodeResources( *response, "cluster_name"); + EXPECT_CALL(callbacks_, onConfigUpdate(DecodedResourcesEq(decoded_resources.refvec_), version)) .WillOnce(ThrowOnRejectedConfig(accept)); + if (accept) { expectSendMessage(last_cluster_names_, version, false); version_ = version; @@ -180,7 +185,7 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { Event::MockDispatcher dispatcher_; Random::MockRandomGenerator random_; Event::MockTimer* timer_; - Event::TimerCb timer_cb_; + Event::MockTimer* ttl_timer_; envoy::config::core::v3::Node node_; NiceMock callbacks_; TestUtility::TestOpaqueResourceDecoderImpl diff --git a/test/common/config/http_subscription_test_harness.h b/test/common/config/http_subscription_test_harness.h index 9e64b0d944e2..89826a049b4b 100644 --- a/test/common/config/http_subscription_test_harness.h +++ b/test/common/config/http_subscription_test_harness.h @@ -93,8 +93,8 @@ class HttpSubscriptionTestHarness : public SubscriptionTestHarness { } expected_request += "\"resource_names\":[\"" + joined_cluster_names + "\"]"; } - expected_request += - ",\"type_url\":\"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment\""; + expected_request += ",\"type_url\":\"type.googleapis.com/" + "envoy.config.endpoint.v3.ClusterLoadAssignment\""; expected_request += "}"; EXPECT_EQ(expected_request, request->bodyAsString()); EXPECT_EQ(fmt::format_int(expected_request.size()).str(), @@ -129,7 +129,7 @@ class HttpSubscriptionTestHarness : public SubscriptionTestHarness { std::string response_json = "{\"version_info\":\"" + version + "\",\"resources\":["; for (const auto& cluster : cluster_names) { response_json += "{\"@type\":\"type.googleapis.com/" - "envoy.api.v2.ClusterLoadAssignment\",\"cluster_name\":\"" + + "envoy.config.endpoint.v3.ClusterLoadAssignment\",\"cluster_name\":\"" + cluster + "\"},"; } response_json.pop_back(); diff --git a/test/common/config/new_grpc_mux_impl_test.cc b/test/common/config/new_grpc_mux_impl_test.cc index 2bcf1ecd75de..f143530f5ae9 100644 --- a/test/common/config/new_grpc_mux_impl_test.cc +++ b/test/common/config/new_grpc_mux_impl_test.cc @@ -101,7 +101,7 @@ TEST_F(NewGrpcMuxImplTest, DiscoveryResponseNonexistentSub) { response->set_system_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->mutable_resource()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->mutable_resource()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, const Protobuf::RepeatedPtrField&, @@ -204,7 +204,7 @@ TEST_F(NewGrpcMuxImplTest, V3ResourceResponseV2ResourceWatch) { envoy::config::cluster::v3::Cluster cluster; unexpected_response->set_type_url(Config::TypeUrl::get().Cluster); unexpected_response->set_system_version_info("0"); - unexpected_response->add_resources()->mutable_resource()->PackFrom(API_DOWNGRADE(cluster)); + unexpected_response->add_resources()->mutable_resource()->PackFrom(cluster); EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "0")).Times(0); grpc_mux_->onDiscoveryResponse(std::move(unexpected_response), control_plane_stats_); } @@ -252,7 +252,7 @@ TEST_F(NewGrpcMuxImplTest, V2ResourceResponseV3ResourceWatch) { response->set_system_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->mutable_resource()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->mutable_resource()->PackFrom(load_assignment); // Send response that contains resource with v3 type url. response->set_type_url(v2_type_url); EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) diff --git a/test/common/config/subscription_factory_impl_test.cc b/test/common/config/subscription_factory_impl_test.cc index 65a7e1b7bd12..f7a27a0da449 100644 --- a/test/common/config/subscription_factory_impl_test.cc +++ b/test/common/config/subscription_factory_impl_test.cc @@ -311,7 +311,7 @@ TEST_F(SubscriptionFactoryTest, GrpcSubscription) { return async_client_factory; })); EXPECT_CALL(random_, random()); - EXPECT_CALL(dispatcher_, createTimer_(_)).Times(2); + EXPECT_CALL(dispatcher_, createTimer_(_)).Times(3); // onConfigUpdateFailed() should not be called for gRPC stream connection failure EXPECT_CALL(callbacks_, onConfigUpdateFailed(_, _)).Times(0); subscriptionFromConfigSource(config)->start({"static_cluster"}); diff --git a/test/common/config/subscription_impl_test.cc b/test/common/config/subscription_impl_test.cc index d8e48bcb820c..84bc88f6cea6 100644 --- a/test/common/config/subscription_impl_test.cc +++ b/test/common/config/subscription_impl_test.cc @@ -1,3 +1,4 @@ +#include #include #include "test/common/config/delta_subscription_test_harness.h" @@ -23,6 +24,10 @@ class SubscriptionImplTest : public testing::TestWithParam { public: SubscriptionImplTest() : SubscriptionImplTest(std::chrono::milliseconds(0)) {} SubscriptionImplTest(std::chrono::milliseconds init_fetch_timeout) { + initialize(init_fetch_timeout); + } + + void initialize(std::chrono::milliseconds init_fetch_timeout = std::chrono::milliseconds(0)) { switch (GetParam()) { case SubscriptionType::Grpc: test_harness_ = std::make_unique(init_fetch_timeout); diff --git a/test/common/config/ttl_test.cc b/test/common/config/ttl_test.cc new file mode 100644 index 000000000000..094c800ccba3 --- /dev/null +++ b/test/common/config/ttl_test.cc @@ -0,0 +1,84 @@ +#include + +#include "common/config/ttl.h" + +#include "test/mocks/event/mocks.h" +#include "test/test_common/simulated_time_system.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Config { +namespace { + +class TtlManagerTest : public testing::Test { +public: + TtlManagerTest() { test_time_.setSystemTime(std::chrono::milliseconds(0)); } + Event::MockDispatcher dispatcher_; + Event::SimulatedTimeSystem test_time_; +}; + +TEST_F(TtlManagerTest, BasicUsage) { + absl::optional> maybe_expired; + auto cb = [&](const auto& expired) { maybe_expired = expired; }; + auto ttl_timer = new Event::MockTimer(&dispatcher_); + TtlManager ttl(cb, dispatcher_, dispatcher_.timeSource()); + + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1), _)); + EXPECT_CALL(*ttl_timer, enabled()); + ttl.add(std::chrono::milliseconds(1), "hello"); + + // Adding another expiration after the first one should not update the timer. + EXPECT_CALL(*ttl_timer, enabled()); + ttl.add(std::chrono::milliseconds(5), "not hello"); + + // Advance time by 3 ms, this should only trigger the first TTL. + test_time_.setSystemTime(std::chrono::milliseconds(3)); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(2), _)); + ttl_timer->invokeCallback(); + EXPECT_EQ(maybe_expired.value(), std::vector({"hello"})); + + // Add in a TTL entry that comes before the scheduled timer run, this should update the timer. + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1), _)); + ttl.add(std::chrono::milliseconds(1), "hello"); + + // Clearing the first TTL entry should reset the timer to match the next in line. + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(2), _)); + ttl.clear("hello"); + + // Removing all the TTLs should disable the timer. + EXPECT_CALL(*ttl_timer, disableTimer()); + ttl.clear("not hello"); +} + +TEST_F(TtlManagerTest, ScopedUpdate) { + absl::optional> maybe_expired; + auto cb = [&](const auto& expired) { maybe_expired = expired; }; + auto ttl_timer = new Event::MockTimer(&dispatcher_); + TtlManager ttl(cb, dispatcher_, dispatcher_.timeSource()); + + { + const auto scoped = ttl.scopedTtlUpdate(); + ttl.add(std::chrono::milliseconds(1), "hello"); + ttl.add(std::chrono::milliseconds(5), "not hello"); + + // There should only be a single update due to the scoping. + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1), _)); + EXPECT_CALL(*ttl_timer, enabled()); + } + + { + const auto scoped = ttl.scopedTtlUpdate(); + ttl.clear("hello"); + ttl.clear("not hello"); + + // There should only be a single update due to the scoping. + EXPECT_CALL(*ttl_timer, disableTimer()); + } +} +} // namespace +} // namespace Config +} // namespace Envoy \ No newline at end of file diff --git a/test/common/config/version_converter_test.cc b/test/common/config/version_converter_test.cc index d3cc29c5f475..1c7e949fb625 100644 --- a/test/common/config/version_converter_test.cc +++ b/test/common/config/version_converter_test.cc @@ -64,13 +64,25 @@ TEST(VersionConverterTest, Upgrade) { // Empty upgrade between version_converter.proto entities. TODO(htuch): consider migrating all the // upgrades in this test to version_converter.proto to reduce dependence on APIs that will be -// removed at EOY. +// removed at `EOY`. TEST(VersionConverterProto, UpgradeNextVersion) { test::common::config::PreviousVersion source; test::common::config::NextVersion dst; VersionConverter::upgrade(source, dst); } +// Validate that even if we pass in a newer proto version that is being passed off as an older +// version (e.g. via a type URL mistake), we don't crash. This is a regression test for +// https://github.com/envoyproxy/envoy/issues/13681. +TEST(VersionConverterProto, UpgradeWithConfusedTypes) { + test::common::config::NextVersion source_next; + source_next.mutable_new_message_in_this_version(); + test::common::config::PreviousVersion source; + ASSERT_TRUE(source.ParseFromString(source_next.SerializeAsString())); + test::common::config::NextVersion dst; + VersionConverter::upgrade(source, dst); +} + // Bad UTF-8 can fail wire cast during upgrade. TEST(VersionConverterTest, UpgradeException) { API_NO_BOOST(envoy::api::v2::Cluster) source; diff --git a/test/common/config/watched_directory_test.cc b/test/common/config/watched_directory_test.cc new file mode 100644 index 000000000000..8988306d4ad4 --- /dev/null +++ b/test/common/config/watched_directory_test.cc @@ -0,0 +1,33 @@ +#include "envoy/filesystem/watcher.h" + +#include "common/config/watched_directory.h" + +#include "test/mocks/event/mocks.h" +#include "test/mocks/filesystem/mocks.h" + +#include "gtest/gtest.h" + +using testing::Return; +using testing::SaveArg; + +namespace Envoy { +namespace Config { + +TEST(WatchedDirectory, All) { + Event::MockDispatcher dispatcher; + envoy::config::core::v3::WatchedDirectory config; + config.set_path("foo/bar"); + auto* watcher = new Filesystem::MockWatcher(); + EXPECT_CALL(dispatcher, createFilesystemWatcher_()).WillOnce(Return(watcher)); + Filesystem::Watcher::OnChangedCb cb; + EXPECT_CALL(*watcher, addWatch("foo/bar/", Filesystem::Watcher::Events::MovedTo, _)) + .WillOnce(SaveArg<2>(&cb)); + WatchedDirectory wd(config, dispatcher); + bool called = false; + wd.setCallback([&called] { called = true; }); + cb(Filesystem::Watcher::Events::MovedTo); + EXPECT_TRUE(called); +} + +} // namespace Config +} // namespace Envoy diff --git a/test/common/event/BUILD b/test/common/event/BUILD index 882d2afa9506..2eaba5a4f461 100644 --- a/test/common/event/BUILD +++ b/test/common/event/BUILD @@ -19,6 +19,7 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//source/common/stats:isolated_store_lib", "//test/mocks:common_lib", + "//test/mocks/server:watch_dog_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index c6c6a7a96272..cbb0119f0ff2 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -3,6 +3,7 @@ #include "envoy/thread/thread.h" #include "common/api/api_impl.h" +#include "common/api/os_sys_calls_impl.h" #include "common/common/lock_guard.h" #include "common/event/deferred_task.h" #include "common/event/dispatcher_impl.h" @@ -10,6 +11,7 @@ #include "common/stats/isolated_store_impl.h" #include "test/mocks/common.h" +#include "test/mocks/server/watch_dog.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" @@ -1147,6 +1149,75 @@ TEST_F(TimerUtilsTest, TimerValueConversion) { checkConversion(std::chrono::milliseconds(600014), 600, 14000); } +class DispatcherWithWatchdogTest : public testing::Test { +protected: + DispatcherWithWatchdogTest() + : api_(Api::createApiForTest(time_system_)), + dispatcher_(api_->allocateDispatcher("test_thread")), + os_sys_calls_(Api::OsSysCallsSingleton::get()) { + dispatcher_->registerWatchdog(watchdog_, min_touch_interval_); + } + + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; + DispatcherPtr dispatcher_; + Api::OsSysCalls& os_sys_calls_; + std::shared_ptr watchdog_ = std::make_shared(); + std::chrono::milliseconds min_touch_interval_ = std::chrono::seconds(10); +}; + +// The dispatcher creates a periodic touch timer for each registered watchdog. +TEST_F(DispatcherWithWatchdogTest, PeriodicTouchTimer) { + // Advance by min_touch_interval_, verify that watchdog_ is touched. + EXPECT_CALL(*watchdog_, touch()); + time_system_.advanceTimeAndRun(min_touch_interval_, *dispatcher_, Dispatcher::RunType::NonBlock); + + // Advance by min_touch_interval_ again, verify that watchdog_ is touched. + EXPECT_CALL(*watchdog_, touch()); + time_system_.advanceTimeAndRun(min_touch_interval_, *dispatcher_, Dispatcher::RunType::NonBlock); +} + +TEST_F(DispatcherWithWatchdogTest, TouchBeforeSchedulableCallback) { + ReadyWatcher watcher; + + auto cb = dispatcher_->createSchedulableCallback([&]() { watcher.ready(); }); + cb->scheduleCallbackCurrentIteration(); + + InSequence s; + EXPECT_CALL(*watchdog_, touch()); + EXPECT_CALL(watcher, ready()); + dispatcher_->run(Dispatcher::RunType::NonBlock); +} + +TEST_F(DispatcherWithWatchdogTest, TouchBeforeTimer) { + ReadyWatcher watcher; + + auto timer = dispatcher_->createTimer([&]() { watcher.ready(); }); + timer->enableTimer(std::chrono::milliseconds(0)); + + InSequence s; + EXPECT_CALL(*watchdog_, touch()); + EXPECT_CALL(watcher, ready()); + dispatcher_->run(Dispatcher::RunType::NonBlock); +} + +TEST_F(DispatcherWithWatchdogTest, TouchBeforeFdEvent) { + os_fd_t fd = os_sys_calls_.socket(AF_INET6, SOCK_STREAM, 0).rc_; + ASSERT_TRUE(SOCKET_VALID(fd)); + + ReadyWatcher watcher; + + const FileTriggerType trigger = Event::PlatformDefaultTriggerType; + Event::FileEventPtr file_event = dispatcher_->createFileEvent( + fd, [&](uint32_t) -> void { watcher.ready(); }, trigger, FileReadyType::Read); + file_event->activate(FileReadyType::Read); + + InSequence s; + EXPECT_CALL(*watchdog_, touch()); + EXPECT_CALL(watcher, ready()); + dispatcher_->run(Dispatcher::RunType::NonBlock); +} + } // namespace } // namespace Event } // namespace Envoy diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index eac8fbefa8a5..af6bf2f58106 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -184,3 +184,13 @@ envoy_cc_test_library( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "google_async_client_cache_test", + srcs = ["google_async_client_cache_test.cc"], + deps = [ + "//source/common/grpc:google_async_client_cache", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:global_lib", + ], +) diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index 55d5c14e2fb1..448f52aa6140 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -38,10 +38,10 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcOk) { envoy::config::core::v3::GrpcService grpc_service; grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); - Upstream::ClusterManager::ClusterInfoMap cluster_map; + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; Upstream::MockClusterMockPrioritySet cluster; - cluster_map.emplace("foo", cluster); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(cluster_map)); + cluster_maps.active_clusters_.emplace("foo", cluster); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(cluster_maps)); EXPECT_CALL(cluster, info()); EXPECT_CALL(*cluster.info_, addedViaApi()); @@ -65,7 +65,8 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcDynamicCluster) { Upstream::ClusterManager::ClusterInfoMap cluster_map; Upstream::MockClusterMockPrioritySet cluster; cluster_map.emplace("foo", cluster); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(cluster_map)); + EXPECT_CALL(cm_, clusters()) + .WillOnce(Return(Upstream::ClusterManager::ClusterInfoMaps{cluster_map, {}})); EXPECT_CALL(cluster, info()); EXPECT_CALL(*cluster.info_, addedViaApi()).WillOnce(Return(true)); EXPECT_THROW_WITH_MESSAGE( diff --git a/test/common/grpc/codec_test.cc b/test/common/grpc/codec_test.cc index 2093ce37261e..e85b973be4a9 100644 --- a/test/common/grpc/codec_test.cc +++ b/test/common/grpc/codec_test.cc @@ -106,6 +106,57 @@ TEST(GrpcCodecTest, decodeInvalidFrame) { EXPECT_EQ(size, buffer.length()); } +// This test shows that null bytes in the bytestring successfully decode into a frame with length 0. +// Should this test really pass? +TEST(GrpcCodecTest, DecodeMultipleFramesInvalid) { + // A frame constructed from null bytes followed by an invalid frame + const std::string data("\000\000\000\000\0000000", 9); + Buffer::OwnedImpl buffer(data.data(), data.size()); + + size_t size = buffer.length(); + + std::vector frames; + Decoder decoder; + EXPECT_FALSE(decoder.decode(buffer, frames)); + // When the decoder doesn't successfully decode, it puts decoded frames up until + // an invalid frame into output frame vector. + EXPECT_EQ(1, frames.size()); + // Buffer does not get drained due to it returning false. + EXPECT_EQ(size, buffer.length()); + // Only part of the buffer represented a frame. Thus, the frame length should not equal the buffer + // length. The frame put into the output vector has no length. + EXPECT_EQ(0, frames[0].length_); +} + +// If there is a valid frame followed by an invalid frame, the decoder will successfully put the +// valid frame in the output and return false due to the invalid frame +TEST(GrpcCodecTest, DecodeValidFrameWithInvalidFrameAfterward) { + // Decode a valid encoded structured request plus invalid data afterward + helloworld::HelloRequest request; + request.set_name("hello"); + + Buffer::OwnedImpl buffer; + std::array header; + Encoder encoder; + encoder.newFrame(GRPC_FH_DEFAULT, request.ByteSize(), header); + buffer.add(header.data(), 5); + buffer.add(request.SerializeAsString()); + buffer.add("000000", 6); + size_t size = buffer.length(); + + std::vector frames; + Decoder decoder; + EXPECT_FALSE(decoder.decode(buffer, frames)); + // When the decoder doesn't successfully decode, it puts valid frames up until + // an invalid frame into output frame vector. + EXPECT_EQ(1, frames.size()); + // Buffer does not get drained due to it returning false. + EXPECT_EQ(size, buffer.length()); + // Only part of the buffer represented a valid frame. Thus, the frame length should not equal the + // buffer length. + EXPECT_NE(size, frames[0].length_); +} + TEST(GrpcCodecTest, decodeEmptyFrame) { Buffer::OwnedImpl buffer("\0\0\0\0", 5); diff --git a/test/common/grpc/google_async_client_cache_test.cc b/test/common/grpc/google_async_client_cache_test.cc new file mode 100644 index 000000000000..6d36ff2efb74 --- /dev/null +++ b/test/common/grpc/google_async_client_cache_test.cc @@ -0,0 +1,104 @@ +#include "common/grpc/google_async_client_cache.h" + +#include "test/mocks/server/factory_context.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Grpc { +namespace { + +class AsyncClientCacheTest : public testing::Test { +public: + AsyncClientCacheTest() { + client_cache_singleton_ = std::make_unique(); + } + + void expectClientCreation() { + factory_ = new Grpc::MockAsyncClientFactory; + async_client_ = new Grpc::MockAsyncClient; + EXPECT_CALL(async_client_manager_, factoryForGrpcService(_, _, true)) + .WillOnce(Invoke([this](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { + EXPECT_CALL(*factory_, create()).WillOnce(Invoke([this] { + return Grpc::RawAsyncClientPtr{async_client_}; + })); + return Grpc::AsyncClientFactoryPtr{factory_}; + })); + } + + void expectCacheAndClientEqual(const AsyncClientCacheSharedPtr& expected_client_cache, + const RawAsyncClientSharedPtr& expected_client, + const ::envoy::config::core::v3::GrpcService config, + const std::string& error_message) { + AsyncClientCacheSharedPtr actual_client_cache = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + EXPECT_EQ(expected_client_cache, actual_client_cache) << error_message; + EXPECT_EQ(expected_client, actual_client_cache->getAsyncClient()) << error_message; + } + + void expectCacheAndClientNotEqual(const AsyncClientCacheSharedPtr& expected_client_cache, + const RawAsyncClientSharedPtr& expected_client, + const ::envoy::config::core::v3::GrpcService config, + const std::string& error_message) { + AsyncClientCacheSharedPtr actual_client_cache = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + EXPECT_NE(expected_client_cache, actual_client_cache) << error_message; + EXPECT_NE(expected_client, actual_client_cache->getAsyncClient()) << error_message; + } + + NiceMock tls_; + Grpc::MockAsyncClientManager async_client_manager_; + Grpc::MockAsyncClient* async_client_ = nullptr; + Grpc::MockAsyncClientFactory* factory_ = nullptr; + NiceMock scope_; + std::unique_ptr client_cache_singleton_; + std::unique_ptr client_cache_; +}; + +TEST_F(AsyncClientCacheTest, Deduplication) { + Stats::IsolatedStoreImpl scope; + testing::InSequence s; + + ::envoy::config::core::v3::GrpcService config; + config.mutable_google_grpc()->set_target_uri("dns://test01"); + config.mutable_google_grpc()->set_credentials_factory_name("test_credential01"); + + expectClientCreation(); + AsyncClientCacheSharedPtr test_client_cache_01 = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + RawAsyncClientSharedPtr test_client_01 = test_client_cache_01->getAsyncClient(); + // Fetches the existing client and they should be equal. + std::string error_message = "Fetched client should be same as the existing one."; + expectCacheAndClientEqual(test_client_cache_01, test_client_01, config, error_message); + + config.mutable_google_grpc()->set_credentials_factory_name("test_credential02"); + expectClientCreation(); + // Different credentials use different clients. + error_message = "different config should use different clients."; + expectCacheAndClientNotEqual(test_client_cache_01, test_client_01, config, error_message); + + AsyncClientCacheSharedPtr test_client_cache_02 = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + RawAsyncClientSharedPtr test_client_02 = test_client_cache_02->getAsyncClient(); + + config.mutable_google_grpc()->set_credentials_factory_name("test_credential02"); + // No creation, fetching the existing one. + error_message = "Fetched existing client should be the same."; + expectCacheAndClientEqual(test_client_cache_02, test_client_02, config, error_message); + + // Different targets use different clients. + error_message = "different config with different targets should use different clients."; + config.mutable_google_grpc()->set_target_uri("dns://test02"); + expectClientCreation(); + expectCacheAndClientNotEqual(test_client_cache_01, test_client_01, config, error_message); + expectCacheAndClientNotEqual(test_client_cache_02, test_client_02, config, error_message); +} + +} // namespace +} // namespace Grpc +} // namespace Envoy diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index d610a221f8b7..be23d01b2791 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -298,7 +298,7 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { EXPECT_CALL(*mock_host_, createConnection_(_, _)).WillRepeatedly(Return(connection_data)); EXPECT_CALL(*mock_host_, cluster()).WillRepeatedly(ReturnRef(*cluster_info_ptr_)); EXPECT_CALL(*mock_host_description_, locality()).WillRepeatedly(ReturnRef(host_locality_)); - http_conn_pool_ = std::make_unique( + http_conn_pool_ = Http::Http2::allocateConnPool( *dispatcher_, random_, host_ptr_, Upstream::ResourcePriority::Default, nullptr, nullptr); EXPECT_CALL(cm_, httpConnPoolForCluster(_, _, _, _)) .WillRepeatedly(Return(http_conn_pool_.get())); diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 7b19f269bbdd..ea256d23386d 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -397,6 +397,22 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "mixed_conn_pool_test", + srcs = ["mixed_conn_pool_test.cc"], + deps = [ + "//source/common/http:conn_pool_base_lib", + "//test/common/upstream:utility_lib", + "//test/mocks:common_lib", + "//test/mocks/buffer:buffer_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/router:router_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/stats:stats_mocks", + ], +) + envoy_proto_library( name = "path_utility_fuzz_proto", srcs = ["path_utility_fuzz.proto"], diff --git a/test/common/http/codec_client_test.cc b/test/common/http/codec_client_test.cc index 48a1dad0da8e..25ba456a87fe 100644 --- a/test/common/http/codec_client_test.cc +++ b/test/common/http/codec_client_test.cc @@ -374,6 +374,7 @@ TEST_P(CodecNetworkTest, SendData) { upstream_connection_->write(data, false); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ(full_data, data.toString()); + data.drain(data.length()); dispatcher_->exit(); return Http::okStatus(); })); @@ -397,10 +398,12 @@ TEST_P(CodecNetworkTest, SendHeadersAndClose) { .Times(2) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ(full_data, data.toString()); + data.drain(data.length()); return Http::okStatus(); })) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ("", data.toString()); + data.drain(data.length()); return Http::okStatus(); })); // Because the headers are not complete, the disconnect will reset the stream. @@ -435,6 +438,7 @@ TEST_P(CodecNetworkTest, SendHeadersAndCloseUnderReadDisable) { .Times(2) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ(full_data, data.toString()); + data.drain(data.length()); return Http::okStatus(); })) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { diff --git a/test/common/http/codec_impl_fuzz_test.cc b/test/common/http/codec_impl_fuzz_test.cc index 3829d69b5dc4..6f9ee8c57a4b 100644 --- a/test/common/http/codec_impl_fuzz_test.cc +++ b/test/common/http/codec_impl_fuzz_test.cc @@ -182,7 +182,7 @@ class HttpStream : public LinkedObject { request_.request_encoder_->getStream().addCallbacks(request_.stream_callbacks_); } - request_.request_encoder_->encodeHeaders(request_headers, end_stream); + request_.request_encoder_->encodeHeaders(request_headers, end_stream).IgnoreError(); request_.stream_state_ = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; response_.stream_state_ = StreamState::PendingHeaders; } @@ -219,9 +219,11 @@ class HttpStream : public LinkedObject { } state.response_encoder_->encodeHeaders(headers, end_stream); } else { - state.request_encoder_->encodeHeaders( - fromSanitizedHeaders(directional_action.headers()), - end_stream); + state.request_encoder_ + ->encodeHeaders( + fromSanitizedHeaders(directional_action.headers()), + end_stream) + .IgnoreError(); } if (end_stream) { state.closeLocal(); diff --git a/test/common/http/codec_wrappers_test.cc b/test/common/http/codec_wrappers_test.cc index ea481bfa2247..02e47262da65 100644 --- a/test/common/http/codec_wrappers_test.cc +++ b/test/common/http/codec_wrappers_test.cc @@ -25,8 +25,12 @@ TEST(RequestEncoderWrapper, HeaderOnlyEncode) { MockRequestEncoderWrapper wrapper; EXPECT_CALL(wrapper.innerEncoder(), encodeHeaders(_, true)); - wrapper.encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, true); + EXPECT_TRUE( + wrapper + .encodeHeaders( + TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, + true) + .ok()); EXPECT_TRUE(wrapper.encodeComplete()); } @@ -34,8 +38,12 @@ TEST(RequestEncoderWrapper, HeaderAndBodyEncode) { MockRequestEncoderWrapper wrapper; EXPECT_CALL(wrapper.innerEncoder(), encodeHeaders(_, false)); - wrapper.encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, false); + EXPECT_TRUE( + wrapper + .encodeHeaders( + TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, + false) + .ok()); EXPECT_FALSE(wrapper.encodeComplete()); Buffer::OwnedImpl data; @@ -48,8 +56,12 @@ TEST(RequestEncoderWrapper, HeaderAndBodyAndTrailersEncode) { MockRequestEncoderWrapper wrapper; EXPECT_CALL(wrapper.innerEncoder(), encodeHeaders(_, false)); - wrapper.encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, false); + EXPECT_TRUE( + wrapper + .encodeHeaders( + TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, + false) + .ok()); EXPECT_FALSE(wrapper.encodeComplete()); Buffer::OwnedImpl data; diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 2e97cfbd42f1..fdf1bbe7ef2a 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -154,6 +154,9 @@ class FuzzConfig : public ConnectionManagerConfig { } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } + std::chrono::milliseconds requestHeadersTimeout() const override { + return request_headers_timeout_; + } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } Router::RouteConfigProvider* routeConfigProvider() override { if (use_srds_) { @@ -232,6 +235,7 @@ class FuzzConfig : public ConnectionManagerConfig { absl::optional max_stream_duration_; std::chrono::milliseconds stream_idle_timeout_{}; std::chrono::milliseconds request_timeout_{}; + std::chrono::milliseconds request_headers_timeout_{}; std::chrono::milliseconds delayed_close_timeout_{}; bool use_remote_address_{true}; Http::ForwardClientCertType forward_client_cert_; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index a022f18c2cd8..95cb95f2acf7 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -11,6 +11,7 @@ using testing::HasSubstr; using testing::InSequence; using testing::Invoke; using testing::InvokeWithoutArgs; +using testing::Mock; using testing::Return; using testing::ReturnRef; @@ -2835,6 +2836,68 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnConnectionTermin filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } +TEST_F(HttpConnectionManagerImplTest, RequestHeaderTimeoutDisarmedAfterHeaders) { + request_headers_timeout_ = std::chrono::milliseconds(10); + setup(false, ""); + + Event::MockTimer* request_header_timer; + EXPECT_CALL(*codec_, dispatch(_)) + .WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { + request_header_timer = setUpTimer(); + EXPECT_CALL(*request_header_timer, enableTimer(request_headers_timeout_, _)); + + decoder_ = &conn_manager_->newStream(response_encoder_); + return Http::okStatus(); + })) + .WillOnce(Return(Http::okStatus())) + .WillOnce([&](Buffer::Instance&) { + RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ + {":authority", "localhost:8080"}, {":path", "/"}, {":method", "GET"}}}; + + EXPECT_CALL(*request_header_timer, disableTimer).Times(1); + decoder_->decodeHeaders(std::move(headers), false); + return Http::okStatus(); + }); + + Buffer::OwnedImpl first_line("GET /HTTP/1.1\r\n"); + Buffer::OwnedImpl second_line("Host: localhost:8080\r\n"); + Buffer::OwnedImpl empty_line("\r\n"); + conn_manager_->onData(first_line, false); + EXPECT_TRUE(request_header_timer->enabled_); + conn_manager_->onData(second_line, false); + EXPECT_TRUE(request_header_timer->enabled_); + conn_manager_->onData(empty_line, false); + Mock::VerifyAndClearExpectations(codec_); + Mock::VerifyAndClearExpectations(request_header_timer); + + expectOnDestroy(); + filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); +} + +TEST_F(HttpConnectionManagerImplTest, RequestHeaderTimeoutCallbackDisarmsAndReturns408) { + request_headers_timeout_ = std::chrono::milliseconds(10); + setup(false, ""); + + Event::MockTimer* request_header_timer; + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { + request_header_timer = setUpTimer(); + EXPECT_CALL(*request_header_timer, enableTimer(request_headers_timeout_, _)).Times(1); + + conn_manager_->newStream(response_encoder_); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, setTrackedObject(_)).Times(2); + return Http::okStatus(); + })); + + Buffer::OwnedImpl fake_input("GET /resource HTTP/1.1\r\n\r\n"); + conn_manager_->onData(fake_input, false); // kick off request + + // The client took too long to send headers. + EXPECT_CALL(*request_header_timer, disableTimer).Times(1); + request_header_timer->invokeCallback(); + + EXPECT_EQ(1U, stats_.named_.downstream_rq_header_timeout_.value()); +} + TEST_F(HttpConnectionManagerImplTest, MaxStreamDurationDisabledIfSetToZero) { max_stream_duration_ = std::chrono::milliseconds(0); 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 6a7ed3ab088a..57bcd5d0697e 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -309,6 +309,28 @@ TEST_F(HttpConnectionManagerImplTest, IdleTimeout) { EXPECT_EQ(1U, stats_.named_.downstream_cx_idle_timeout_.value()); } +TEST_F(HttpConnectionManagerImplTest, ConnectionDurationResponseFlag) { + // Not used in the test. + delete codec_; + + max_connection_duration_ = (std::chrono::milliseconds(10)); + Event::MockTimer* connection_duration_timer = setUpTimer(); + EXPECT_CALL(*connection_duration_timer, enableTimer(_, _)); + setup(false, ""); + + EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); + filter_callbacks_.connection_.streamInfo().setResponseFlag( + StreamInfo::ResponseFlag::DurationTimeout); + EXPECT_CALL(*connection_duration_timer, disableTimer()); + + connection_duration_timer->invokeCallback(); + + EXPECT_TRUE(filter_callbacks_.connection_.streamInfo().hasResponseFlag( + StreamInfo::ResponseFlag::DurationTimeout)); + + EXPECT_EQ(1U, stats_.named_.downstream_cx_max_duration_reached_.value()); +} + TEST_F(HttpConnectionManagerImplTest, ConnectionDurationNoCodec) { // Not used in the test. delete codec_; @@ -2908,7 +2930,7 @@ TEST_F(HttpConnectionManagerImplTest, ConnectionFilterState) { Buffer::OwnedImpl fake_input; conn_manager_->onData(fake_input, false); - decoder_filters_[0]->callbacks_->recreateStream(); + decoder_filters_[0]->callbacks_->recreateStream(nullptr); conn_manager_->onData(fake_input, false); // The connection life time data should have been written to the connection filter state. diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 067ca0a1f369..35cd115e38ee 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -83,6 +83,9 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } + std::chrono::milliseconds requestHeadersTimeout() const override { + return request_headers_timeout_; + } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } absl::optional maxStreamDuration() const override { return max_stream_duration_; @@ -170,6 +173,7 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan absl::optional max_connection_duration_; std::chrono::milliseconds stream_idle_timeout_{}; std::chrono::milliseconds request_timeout_{}; + std::chrono::milliseconds request_headers_timeout_{}; std::chrono::milliseconds delayed_close_timeout_{}; absl::optional max_stream_duration_{}; NiceMock random_; diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index 4b4348710844..10f755873a2b 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -109,6 +109,7 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(absl::optional, maxStreamDuration, (), (const)); MOCK_METHOD(std::chrono::milliseconds, streamIdleTimeout, (), (const)); MOCK_METHOD(std::chrono::milliseconds, requestTimeout, (), (const)); + MOCK_METHOD(std::chrono::milliseconds, requestHeadersTimeout, (), (const)); MOCK_METHOD(std::chrono::milliseconds, delayedCloseTimeout, (), (const)); MOCK_METHOD(Router::RouteConfigProvider*, routeConfigProvider, ()); MOCK_METHOD(Config::ConfigProvider*, scopedRouteConfigProvider, ()); diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index 47c39a36311e..9119f9cc5420 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -145,7 +145,8 @@ exact_match: value EXPECT_EQ("value", header_data.value_); } -TEST(HeaderDataConstructorTest, RegexMatchSpecifier) { +TEST(HeaderDataConstructorTest, DEPRECATED_FEATURE_TEST(RegexMatchSpecifier)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( name: test-header regex_match: value @@ -249,7 +250,8 @@ invert_match: true EXPECT_EQ(true, header_data.invert_match_); } -TEST(MatchHeadersTest, MayMatchOneOrMoreRequestHeader) { +TEST(MatchHeadersTest, DEPRECATED_FEATURE_TEST(MayMatchOneOrMoreRequestHeader)) { + TestDeprecatedV2Api _deprecated_v2_api; TestRequestHeaderMapImpl headers{{"some-header", "a"}, {"other-header", "b"}}; const std::string yaml = R"EOF( @@ -277,7 +279,6 @@ exact_match: a,b // Make sure that an exact match on "a,b" does in fact work. EXPECT_TRUE(HeaderUtility::matchHeaders(headers, header_data)); - TestScopedRuntime runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.http_match_on_all_headers", "false"}}); // Flipping runtime to false should make "a,b" no longer match because we will match on the first @@ -370,7 +371,8 @@ invert_match: true EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } -TEST(MatchHeadersTest, HeaderRegexMatch) { +TEST(MatchHeadersTest, DEPRECATED_FEATURE_TEST(HeaderRegexMatch)) { + TestDeprecatedV2Api _deprecated_v2_api; TestRequestHeaderMapImpl matching_headers{{"match-header", "123"}}; TestRequestHeaderMapImpl unmatching_headers{{"match-header", "1234"}, {"match-header", "123.456"}}; @@ -404,7 +406,8 @@ name: match-header EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } -TEST(MatchHeadersTest, HeaderRegexInverseMatch) { +TEST(MatchHeadersTest, DEPRECATED_FEATURE_TEST(HeaderRegexInverseMatch)) { + TestDeprecatedV2Api _deprecated_v2_api; TestRequestHeaderMapImpl matching_headers{{"match-header", "1234"}, {"match-header", "123.456"}}; TestRequestHeaderMapImpl unmatching_headers{{"match-header", "123"}}; diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 200ba2423191..3b3138173c95 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -2010,7 +2010,7 @@ void Http1ClientConnectionImplTest::testClientAllowChunkedContentLength(uint32_t Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); TestResponseHeaderMapImpl expected_headers{{":status", "200"}, {"transfer-encoding", "chunked"}}; Buffer::OwnedImpl expected_data("Hello World"); @@ -2051,7 +2051,7 @@ TEST_P(Http1ClientConnectionImplTest, SimpleGet) { ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\ncontent-length: 0\r\n\r\n", output); } @@ -2067,7 +2067,7 @@ TEST_P(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {"my-custom-header", "hey"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nMy-Custom-Header: hey\r\nContent-Length: 0\r\n\r\n", output); } @@ -2081,7 +2081,7 @@ TEST_P(Http1ClientConnectionImplTest, HostHeaderTranslate) { ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } @@ -2114,7 +2114,7 @@ TEST_P(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { // Request. TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder->encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder->encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); output.clear(); @@ -2142,7 +2142,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse503) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n"); @@ -2156,7 +2156,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse200) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); @@ -2170,7 +2170,7 @@ TEST_P(Http1ClientConnectionImplTest, HeadRequest) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "HEAD"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n"); @@ -2184,7 +2184,7 @@ TEST_P(Http1ClientConnectionImplTest, 204Response) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\n\r\n"); @@ -2201,7 +2201,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 20\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2219,7 +2219,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 20\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2236,7 +2236,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 0\r\n\r\n"); @@ -2253,7 +2253,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 0\r\n\r\n"); @@ -2271,7 +2271,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2289,7 +2289,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2304,7 +2304,7 @@ TEST_P(Http1ClientConnectionImplTest, ContinueHeaders) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decode100ContinueHeaders_(_)); EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0); @@ -2326,7 +2326,7 @@ TEST_P(Http1ClientConnectionImplTest, MultipleContinueHeaders) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decode100ContinueHeaders_(_)); EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0); @@ -2355,7 +2355,7 @@ TEST_P(Http1ClientConnectionImplTest, 1xxNonContinueHeaders) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); Buffer::OwnedImpl response("HTTP/1.1 102 Processing\r\n\r\n"); @@ -2372,7 +2372,7 @@ TEST_P(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response( "HTTP/1.1 101 Switching Protocols\r\nTransfer-Encoding: chunked\r\n\r\n"); @@ -2391,7 +2391,7 @@ TEST_P(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response( "HTTP/1.1 101 Switching Protocols\r\nTransfer-Encoding: chunked\r\n\r\n"); @@ -2405,21 +2405,26 @@ TEST_P(Http1ClientConnectionImplTest, BadEncodeParams) { NiceMock response_decoder; - // Need to set :method and :path. - // New and legacy codecs will behave differently on errors from processing outbound data. The - // legacy codecs will throw an exception (that presently will be uncaught in contexts like - // sendLocalReply), while the new codecs temporarily RELEASE_ASSERT until Envoy handles errors on - // outgoing data. + // Invalid outbound data errors are impossible to trigger in normal processing, since bad + // downstream data would have been rejected by the codec, and erroneous filter processing would + // cause a direct response by the filter manager. An invalid status is returned from new codecs + // which protects against future extensions or header modifications after the filter chain. The + // old codecs will still throw an exception (that presently will be uncaught in contexts like + // sendLocalReply). Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); if (testingNewCodec()) { - EXPECT_DEATH(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true), - ":method and :path must be specified"); - EXPECT_DEATH(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true), - ":method and :path must be specified"); + EXPECT_THAT( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true).message(), + testing::HasSubstr("missing required")); + EXPECT_THAT( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true).message(), + testing::HasSubstr("missing required")); } else { - EXPECT_THROW(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true), - CodecClientException); - EXPECT_THROW(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true), + EXPECT_THROW( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true).IgnoreError(), + CodecClientException); + EXPECT_THROW(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true) + .IgnoreError(), CodecClientException); } } @@ -2430,7 +2435,7 @@ TEST_P(Http1ClientConnectionImplTest, NoContentLengthResponse) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl expected_data1("Hello World"); EXPECT_CALL(response_decoder, decodeData(BufferEqual(&expected_data1), false)); @@ -2452,7 +2457,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseWithTrailers) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\n\r\ntransfer-encoding: chunked\r\n\r\nb\r\nHello " "World\r\n0\r\nhello: world\r\nsecond: header\r\n\r\n"); @@ -2468,7 +2473,7 @@ TEST_P(Http1ClientConnectionImplTest, GiantPath) { Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{ {":method", "GET"}, {":path", "/" + std::string(16384, 'a')}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n"); @@ -2498,7 +2503,7 @@ TEST_P(Http1ClientConnectionImplTest, UpgradeResponse) { {":authority", "host"}, {"connection", "upgrade"}, {"upgrade", "websocket"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send upgrade headers EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2534,7 +2539,7 @@ TEST_P(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { {":authority", "host"}, {"connection", "upgrade"}, {"upgrade", "websocket"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send upgrade headers EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2554,7 +2559,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectResponse) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "CONNECT"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send response headers EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2585,7 +2590,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectResponseWithEarlyData) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "CONNECT"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send response headers and payload EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2604,7 +2609,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "CONNECT"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); Buffer::OwnedImpl expected_data("12345abcd"); @@ -2635,7 +2640,7 @@ TEST_P(Http1ClientConnectionImplTest, WatermarkTest) { EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); EXPECT_CALL(stream_callbacks, onBelowWriteBufferLowWatermark()); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Fake out the underlying Network::Connection buffer being drained. EXPECT_CALL(stream_callbacks, onBelowWriteBufferLowWatermark()); @@ -2660,7 +2665,7 @@ TEST_P(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) { request_encoder.getStream().addCallbacks(stream_callbacks); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Fake a call from the underlying Network::Connection and verify the stream is notified. EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); @@ -2694,7 +2699,7 @@ TEST_P(Http1ClientConnectionImplTest, LowWatermarkDuringClose) { request_encoder.getStream().addCallbacks(stream_callbacks); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Fake a call from the underlying Network::Connection and verify the stream is notified. EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); @@ -2867,7 +2872,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2887,7 +2892,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeFieldRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2906,7 +2911,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeResponseHeadersAccepted) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2928,7 +2933,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeMethodRequestEncode) { {":method", long_method}, {":path", "/"}, {":authority", "host"}}; std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ(long_method + " / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } @@ -2946,7 +2951,7 @@ TEST_P(Http1ClientConnectionImplTest, LargePathRequestEncode) { {":method", "GET"}, {":path", long_path}, {":authority", "host"}}; std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET " + long_path + " HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } @@ -2962,7 +2967,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeHeaderRequestEncode) { {":method", "GET"}, {"foo", long_header_value}, {":path", "/"}, {":authority", "host"}}; std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\nfoo: " + long_header_value + "\r\ncontent-length: 0\r\n\r\n", output); @@ -2975,7 +2980,7 @@ TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2995,7 +3000,7 @@ TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index c919f9499cea..3803add0f724 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -48,15 +48,20 @@ namespace { /** * A test version of ConnPoolImpl that allows for mocking beneath the codec clients. */ -class ConnPoolImplForTest : public ConnPoolImpl { +class ConnPoolImplForTest : public FixedHttpConnPoolImpl { public: ConnPoolImplForTest(Event::MockDispatcher& dispatcher, Upstream::ClusterInfoConstSharedPtr cluster, - NiceMock* upstream_ready_cb) - : ConnPoolImpl(dispatcher, random_, Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), - Upstream::ResourcePriority::Default, nullptr, nullptr), - api_(Api::createApiForTest()), mock_dispatcher_(dispatcher), - mock_upstream_ready_cb_(upstream_ready_cb) {} + Random::RandomGenerator& random_generator) + : FixedHttpConnPoolImpl( + Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), + Upstream::ResourcePriority::Default, dispatcher, nullptr, nullptr, random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData&, HttpConnPoolImplBase*) { + return nullptr; // Not used: createCodecClient overloaded. + }, + std::vector{Protocol::Http11}), + api_(Api::createApiForTest()), mock_dispatcher_(dispatcher) {} ~ConnPoolImplForTest() override { EXPECT_EQ(0U, ready_clients_.size()); @@ -111,22 +116,14 @@ class ConnPoolImplForTest : public ConnPoolImpl { } void expectEnableUpstreamReady() { - EXPECT_FALSE(upstream_ready_enabled_); - EXPECT_CALL(*mock_upstream_ready_cb_, scheduleCallbackCurrentIteration()) - .Times(1) - .RetiresOnSaturation(); + EXPECT_CALL(mock_dispatcher_, post(_)).WillOnce(SaveArg<0>(&post_cb_)); } - void expectAndRunUpstreamReady() { - EXPECT_TRUE(upstream_ready_enabled_); - mock_upstream_ready_cb_->invokeCallback(); - EXPECT_FALSE(upstream_ready_enabled_); - } + void expectAndRunUpstreamReady() { post_cb_(); } Api::ApiPtr api_; Event::MockDispatcher& mock_dispatcher_; - NiceMock random_; - NiceMock* mock_upstream_ready_cb_; + Event::PostCb post_cb_; std::vector test_clients_; }; @@ -136,17 +133,15 @@ class ConnPoolImplForTest : public ConnPoolImpl { class Http1ConnPoolImplTest : public testing::Test { public: Http1ConnPoolImplTest() - : upstream_ready_cb_(new NiceMock(&dispatcher_)), - conn_pool_( - std::make_unique(dispatcher_, cluster_, upstream_ready_cb_)) {} + : conn_pool_(std::make_unique(dispatcher_, cluster_, random_)) {} ~Http1ConnPoolImplTest() override { EXPECT_EQ("", TestUtility::nonZeroedGauges(cluster_->stats_store_.gauges())); } + NiceMock random_; NiceMock dispatcher_; std::shared_ptr cluster_{new NiceMock()}; - NiceMock* upstream_ready_cb_; std::unique_ptr conn_pool_; NiceMock runtime_; }; @@ -212,8 +207,10 @@ struct ActiveTestRequest { } void startRequest() { - callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); } Http1ConnPoolImplTest& parent_; @@ -284,18 +281,16 @@ TEST_F(Http1ConnPoolImplTest, VerifyAlpnFallback) { .WillOnce(Invoke( [](Network::TransportSocketOptionsSharedPtr options) -> Network::TransportSocketPtr { EXPECT_TRUE(options != nullptr); - EXPECT_EQ(options->applicationProtocolFallback(), + EXPECT_EQ(options->applicationProtocolFallback()[0], Http::Utility::AlpnNames::get().Http11); return std::make_unique(); })); cluster_->transport_socket_matcher_ = std::make_unique>(std::move(factory)); - new NiceMock(&dispatcher_); - // Recreate the conn pool so that the host re-evaluates the transport socket match, arriving at // our test transport socket factory. - conn_pool_ = std::make_unique(dispatcher_, cluster_, upstream_ready_cb_); + conn_pool_ = std::make_unique(dispatcher_, cluster_, random_); NiceMock outer_decoder; ConnPoolCallbacks callbacks; conn_pool_->expectClientCreate(Protocol::Http11); @@ -640,14 +635,19 @@ TEST_F(Http1ConnPoolImplTest, MaxConnections) { .WillOnce(DoAll(SaveArgAddress(&inner_decoder), ReturnRef(request_encoder))); EXPECT_CALL(callbacks2.pool_ready_, ready()); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); Http::ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); inner_decoder->decodeHeaders(std::move(response_headers), true); conn_pool_->expectAndRunUpstreamReady(); - callbacks2.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + conn_pool_->expectEnableUpstreamReady(); + EXPECT_TRUE( + callbacks2.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. response_headers = std::make_unique( std::initializer_list>{{":status", "200"}}); @@ -693,9 +693,10 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseWithoutHeader) { // Finishing request 1 will schedule binding the connection to request 2. conn_pool_->expectEnableUpstreamReady(); - - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); Http::ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); inner_decoder->decodeHeaders(std::move(response_headers), true); @@ -712,8 +713,11 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseWithoutHeader) { EXPECT_CALL(callbacks2.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks2.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + conn_pool_->expectEnableUpstreamReady(); + EXPECT_TRUE( + callbacks2.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. response_headers = std::make_unique( std::initializer_list>{{":status", "200"}}); @@ -745,8 +749,10 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseHeader) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response with 'connection: close' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -779,8 +785,10 @@ TEST_F(Http1ConnPoolImplTest, ProxyConnectionCloseHeader) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(*conn_pool_, onClientDestroy()); // Response with 'proxy-connection: close' which should cause the connection to go away, even if @@ -817,8 +825,10 @@ TEST_F(Http1ConnPoolImplTest, ProxyConnectionCloseHeaderLegacy) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response with 'proxy-connection: close' which should cause the connection to go away, even if // there are other tokens in that header. @@ -852,8 +862,10 @@ TEST_F(Http1ConnPoolImplTest, Http10NoConnectionKeepAlive) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response without 'connection: keep-alive' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -889,8 +901,10 @@ TEST_F(Http1ConnPoolImplTest, Http10NoConnectionKeepAliveLegacy) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response without 'connection: keep-alive' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -925,8 +939,10 @@ TEST_F(Http1ConnPoolImplTest, MaxRequestsPerConnection) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response with 'connection: close' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -959,7 +975,9 @@ TEST_F(Http1ConnPoolImplTest, ConcurrentConnections) { r3.startRequest(); EXPECT_EQ(3U, cluster_->stats_.upstream_rq_total_.value()); + conn_pool_->expectEnableUpstreamReady(); r2.completeResponse(false); + conn_pool_->expectEnableUpstreamReady(); r3.completeResponse(false); // Disconnect both clients. @@ -1030,8 +1048,10 @@ TEST_F(Http1ConnPoolImplTest, RemoteCloseToCompleteResponse) { EXPECT_CALL(*conn_pool_->test_clients_[0].connect_timer_, disableTimer()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); inner_decoder->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, false); diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index d8ec1e0adb55..e2c749c55940 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -285,7 +285,7 @@ class Http2CodecImplTest : public ::testing::TestWithParamencodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); nghttp2_priority_spec spec = {0, 10, 0}; // HTTP/2 codec adds 1 to the number of active streams when computing PRIORITY frames limit @@ -302,7 +302,7 @@ class Http2CodecImplTest : public ::testing::TestWithParamencodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); // Send one DATA frame back EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); @@ -329,7 +329,7 @@ class Http2CodecImplTest : public ::testing::TestWithParamencodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // HTTP/2 codec does not send empty DATA frames with no END_STREAM flag. // To make this work, send raw bytes representing empty DATA frames bypassing client codec. @@ -349,7 +349,7 @@ TEST_P(Http2CodecImplTest, ShutdownNotice) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); EXPECT_CALL(client_callbacks_, onGoAway(_)); server_->shutdownNotice(); @@ -367,7 +367,7 @@ TEST_P(Http2CodecImplTest, ContinueHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -385,7 +385,7 @@ TEST_P(Http2CodecImplTest, TrailerStatus) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -407,7 +407,7 @@ TEST_P(Http2CodecImplTest, MultipleContinueHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -428,7 +428,7 @@ TEST_P(Http2CodecImplTest, 1xxNonContinueHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl other_headers{{":status", "102"}}; EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); @@ -442,7 +442,7 @@ TEST_P(Http2CodecImplTest, Invalid101SwitchingProtocols) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl upgrade_headers{{":status", "101"}}; EXPECT_CALL(response_decoder_, decodeHeaders_(_, _)).Times(0); @@ -456,7 +456,7 @@ TEST_P(Http2CodecImplTest, InvalidContinueWithFin) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_THROW(response_encoder_->encodeHeaders(continue_headers, true), ClientCodecError); @@ -473,7 +473,7 @@ TEST_P(Http2CodecImplTest, InvalidContinueWithFinAllowed) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); // Buffer client data to avoid mock recursion causing lifetime issues. ON_CALL(server_connection_, write(_, _)) @@ -499,7 +499,7 @@ TEST_P(Http2CodecImplTest, CodecHasCorrectStreamErrorIfFalse) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); EXPECT_FALSE(response_encoder_->streamErrorOnInvalidHttpMessage()); } @@ -511,7 +511,7 @@ TEST_P(Http2CodecImplTest, CodecHasCorrectStreamErrorIfTrue) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); EXPECT_TRUE(response_encoder_->streamErrorOnInvalidHttpMessage()); } @@ -522,7 +522,7 @@ TEST_P(Http2CodecImplTest, InvalidRepeatContinue) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -542,7 +542,7 @@ TEST_P(Http2CodecImplTest, InvalidRepeatContinueAllowed) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -571,7 +571,7 @@ TEST_P(Http2CodecImplTest, Invalid204WithContentLength) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl response_headers{{":status", "204"}, {"content-length", "3"}}; // What follows is a hack to get headers that should span into continuation frames. The default @@ -600,7 +600,7 @@ TEST_P(Http2CodecImplTest, Invalid204WithContentLengthAllowed) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); // Buffer client data to avoid mock recursion causing lifetime issues. ON_CALL(server_connection_, write(_, _)) @@ -635,7 +635,7 @@ TEST_P(Http2CodecImplTest, RefusedStreamReset) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); MockStreamCallbacks callbacks; request_encoder_->getStream().addCallbacks(callbacks); @@ -648,27 +648,16 @@ TEST_P(Http2CodecImplTest, RefusedStreamReset) { TEST_P(Http2CodecImplTest, InvalidHeadersFrame) { initialize(); - EXPECT_THROW(request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true), ServerCodecError); - EXPECT_EQ(1, server_stats_store_.counter("http2.rx_messaging_error").value()); -} - -TEST_P(Http2CodecImplTest, InvalidHeadersFrameAllowed) { - stream_error_on_invalid_http_messaging_ = true; - initialize(); - - MockStreamCallbacks request_callbacks; - request_encoder_->getStream().addCallbacks(request_callbacks); - - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { + const auto status = request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true); - request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true); - EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::LocalReset, _)); - EXPECT_CALL(request_callbacks, onResetStream(StreamResetReason::RemoteReset, _)); - auto status = server_wrapper_.dispatch(Buffer::OwnedImpl(), *server_); - EXPECT_TRUE(status.ok()); - expectDetailsResponse("http2.violation.of.messaging.rule"); + EXPECT_FALSE(status.ok()); + EXPECT_THAT(status.message(), testing::HasSubstr("missing required")); + } else { + EXPECT_THROW(request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true).IgnoreError(), + ServerCodecError); + EXPECT_EQ(1, server_stats_store_.counter("http2.rx_messaging_error").value()); + } } TEST_P(Http2CodecImplTest, TrailingHeaders) { @@ -677,7 +666,7 @@ TEST_P(Http2CodecImplTest, TrailingHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl hello("hello"); request_encoder_->encodeData(hello, false); @@ -707,7 +696,7 @@ TEST_P(Http2CodecImplTest, IgnoreTrailingEmptyHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl hello("hello"); request_encoder_->encodeData(hello, false); @@ -736,7 +725,7 @@ TEST_P(Http2CodecImplTest, SubmitTrailingEmptyHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl hello("hello"); request_encoder_->encodeData(hello, false); @@ -764,7 +753,7 @@ TEST_P(Http2CodecImplTest, TrailingHeadersLargeClientBody) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)).Times(AtLeast(1)); Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); request_encoder_->encodeData(body, false); @@ -794,7 +783,7 @@ TEST_P(Http2CodecImplTest, SmallMetadataVecTest) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); MetadataMapVector metadata_map_vector; const int size = 10; @@ -824,7 +813,7 @@ TEST_P(Http2CodecImplTest, LargeMetadataVecTest) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); MetadataMapVector metadata_map_vector; const int size = 10; @@ -851,7 +840,7 @@ TEST_P(Http2CodecImplTest, BadMetadataVecReceivedTest) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); MetadataMap metadata_map = { {"header_key1", "header_value1"}, @@ -894,7 +883,7 @@ TEST_P(Http2CodecImplTest, EncodeMetadataWhileDispatchingTest) { response_encoder_->encodeMetadata(metadata_map_vector); })); EXPECT_CALL(response_decoder_, decodeMetadata_(_)).Times(size); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } // Validate the keepalive PINGs are sent and received correctly. @@ -972,7 +961,7 @@ TEST_P(Http2CodecImplDeferredResetTest, DeferredResetClient) { Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); EXPECT_CALL(client_stream_callbacks, onAboveWriteBufferHighWatermark()).Times(AnyNumber()); request_encoder_->encodeData(body, true); @@ -1004,7 +993,7 @@ TEST_P(Http2CodecImplDeferredResetTest, DeferredResetServer) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // In this case we do the same thing as DeferredResetClient but on the server side. ON_CALL(server_connection_, write(_, _)) @@ -1048,7 +1037,7 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Force the server stream to be read disabled. This will cause it to stop sending window // updates to the client. @@ -1109,7 +1098,7 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { return request_decoder2; })); EXPECT_CALL(request_decoder2, decodeHeaders_(_, false)); - request_encoder2->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder2->encodeHeaders(request_headers, false).ok()); // Add the stream callbacks belatedly. On creation the stream should have // been noticed that the connection was backed up. Any new subscriber to @@ -1155,7 +1144,7 @@ TEST_P(Http2CodecImplFlowControlTest, EarlyResetRestoresWindow) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Force the server stream to be read disabled. This will cause it to stop sending window // updates to the client. @@ -1214,7 +1203,7 @@ TEST_P(Http2CodecImplFlowControlTest, FlowControlPendingRecvData) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Set artificially small watermarks to make the recv buffer easy to overrun. In production, // the recv buffer can be overrun by a client which negotiates a larger @@ -1236,7 +1225,7 @@ TEST_P(Http2CodecImplFlowControlTest, TrailingHeadersLargeServerBody) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1273,7 +1262,7 @@ TEST_P(Http2CodecImplFlowControlTest, TrailingHeadersLargeServerBodyFlushTimeout TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1308,7 +1297,7 @@ TEST_P(Http2CodecImplFlowControlTest, LargeServerBodyFlushTimeout) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1341,7 +1330,7 @@ TEST_P(Http2CodecImplFlowControlTest, LargeServerBodyFlushTimeoutAfterGoaway) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1375,7 +1364,7 @@ TEST_P(Http2CodecImplFlowControlTest, WindowUpdateOnReadResumingFlood) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -1440,7 +1429,7 @@ TEST_P(Http2CodecImplFlowControlTest, RstStreamOnPendingFlushTimeoutFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -1492,7 +1481,7 @@ TEST_P(Http2CodecImplTest, WatermarkUnderEndStream) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // The 'true' on encodeData will set local_end_stream_ on the client but not // the server. Verify that client watermark callbacks will not be called, but @@ -1567,7 +1556,7 @@ TEST_P(Http2CodecImplStreamLimitTest, MaxClientStreams) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } } @@ -1745,7 +1734,7 @@ TEST_P(Http2CustomSettingsTest, UserDefinedSettings) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); uint32_t hpack_table_size = ::testing::get(getSettingsTuple()); if (hpack_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { @@ -1784,7 +1773,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersInvokeResetStream) { std::string long_string = std::string(63 * 1024, 'q'); request_headers.addCopy("big", long_string); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Large request headers are accepted when max limit configured. @@ -1799,7 +1788,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersAccepted) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests request headers with name containing underscore are dropped when the option is set to drop @@ -1813,7 +1802,7 @@ TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAreDropped) { TestRequestHeaderMapImpl expected_headers(request_headers); request_headers.addCopy("bad_header", "something"); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), _)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_EQ(1, server_stats_store_.counter("http2.dropped_headers_with_underscores").value()); } @@ -1827,7 +1816,7 @@ TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAreRejectedByDefault) { HttpTestUtility::addDefaultHeaders(request_headers); request_headers.addCopy("bad_header", "something"); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_EQ( 1, server_stats_store_.counter("http2.requests_rejected_with_underscores_in_headers").value()); @@ -1845,7 +1834,7 @@ TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAllowed) { TestRequestHeaderMapImpl expected_headers(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_EQ(0, server_stats_store_.counter("http2.dropped_headers_with_underscores").value()); } @@ -1865,7 +1854,7 @@ TEST_P(Http2CodecImplTest, LargeMethodRequestEncode) { request_headers.setReferenceKey(Headers::get().Method, long_method); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&request_headers), false)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests stream reset when the number of request headers exceeds the default maximum of 100. @@ -1878,7 +1867,7 @@ TEST_P(Http2CodecImplTest, ManyRequestHeadersInvokeResetStream) { request_headers.addCopy(std::to_string(i), ""); } EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests that max number of request headers is configurable. @@ -1893,7 +1882,7 @@ TEST_P(Http2CodecImplTest, ManyRequestHeadersAccepted) { } EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests that max number of response headers is configurable. @@ -1904,7 +1893,7 @@ TEST_P(Http2CodecImplTest, ManyResponseHeadersAccepted) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"compression", "test"}}; for (int i = 0; i < 105; i++) { @@ -1934,7 +1923,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersAtLimitAccepted) { ASSERT_EQ(request_headers.byteSize() + head_room, codec_limit_kb * 1024); EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, LargeRequestHeadersOverDefaultCodecLibraryLimit) { @@ -1948,7 +1937,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersOverDefaultCodecLibraryLimit) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, LargeRequestHeadersExceedPerHeaderLimit) { @@ -1968,7 +1957,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersExceedPerHeaderLimit) { EXPECT_CALL(client_callbacks_, onGoAway(_)); server_->shutdownNotice(); server_->goAway(); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, ManyLargeRequestHeadersUnderPerHeaderLimit) { @@ -1984,7 +1973,7 @@ TEST_P(Http2CodecImplTest, ManyLargeRequestHeadersUnderPerHeaderLimit) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, LargeRequestHeadersAtMaxConfigurable) { @@ -2002,7 +1991,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersAtMaxConfigurable) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } // Note this is Http2CodecImplTestAll not Http2CodecImplTest, to test @@ -2013,7 +2002,7 @@ TEST_P(Http2CodecImplTestAll, TestCodecHeaderCompression) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"compression", "test"}}; EXPECT_CALL(response_decoder_, decodeHeaders_(_, true)); @@ -2044,7 +2033,7 @@ TEST_P(Http2CodecImplTest, PingFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Send one frame above the outbound control queue size limit for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES + 1; @@ -2077,7 +2066,7 @@ TEST_P(Http2CodecImplTest, PingFloodMitigationDisabled) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Send one frame above the outbound control queue size limit for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES + 1; @@ -2102,7 +2091,7 @@ TEST_P(Http2CodecImplTest, PingFloodCounterReset) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); for (int i = 0; i < kMaxOutboundControlFrames; ++i) { EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); @@ -2149,7 +2138,7 @@ TEST_P(Http2CodecImplTest, ResponseHeadersFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2181,7 +2170,7 @@ TEST_P(Http2CodecImplTest, ResponseDataFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2218,7 +2207,7 @@ TEST_P(Http2CodecImplTest, ResponseDataFloodMitigationDisabled) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // +2 is to account for HEADERS and PING ACK, that is used to trigger mitigation EXPECT_CALL(server_connection_, write(_, _)) @@ -2248,7 +2237,7 @@ TEST_P(Http2CodecImplTest, ResponseDataFloodCounterReset) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2290,7 +2279,7 @@ TEST_P(Http2CodecImplTest, PingStacksWithDataFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2322,7 +2311,7 @@ TEST_P(Http2CodecImplTest, ResponseTrailersFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2362,7 +2351,7 @@ TEST_P(Http2CodecImplTest, MetadataFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2472,7 +2461,7 @@ TEST_P(Http2CodecImplTest, GoAwayCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2512,7 +2501,7 @@ TEST_P(Http2CodecImplTest, ShudowNoticeCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2565,7 +2554,7 @@ TEST_P(Http2CodecImplTest, KeepAliveCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2611,7 +2600,7 @@ TEST_P(Http2CodecImplTest, ResetStreamCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2662,7 +2651,7 @@ TEST_P(Http2CodecImplTest, ConnectTest) { Http::Headers::get().MethodValues.Connect); expected_headers.setReferenceKey(Headers::get().Protocol, "bytestream"); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(callbacks, onResetStream(StreamResetReason::ConnectError, _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::ConnectError, _)); diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index c8ea9f0c89c7..90b7a48350bf 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -36,9 +36,18 @@ namespace Envoy { namespace Http { namespace Http2 { -class TestConnPoolImpl : public ConnPoolImpl { +class TestConnPoolImpl : public FixedHttpConnPoolImpl { public: - using ConnPoolImpl::ConnPoolImpl; + TestConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, + Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, + const Network::ConnectionSocket::OptionsSharedPtr& options, + const Network::TransportSocketOptionsSharedPtr& transport_socket_options) + : FixedHttpConnPoolImpl( + std::move(host), std::move(priority), dispatcher, options, transport_socket_options, + random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData&, HttpConnPoolImplBase*) { return nullptr; }, + std::vector{Protocol::Http2}) {} CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override { // We expect to own the connection, but already have it, so just release it to prevent it from @@ -269,8 +278,10 @@ void Http2ConnPoolImplTest::closeAllClients() { void Http2ConnPoolImplTest::completeRequest(ActiveTestRequest& r) { EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r.decoder_, decodeHeaders_(_, true)); r.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -327,7 +338,7 @@ TEST_F(Http2ConnPoolImplTest, VerifyAlpnFallback) { .WillOnce(Invoke( [](Network::TransportSocketOptionsSharedPtr options) -> Network::TransportSocketPtr { EXPECT_TRUE(options != nullptr); - EXPECT_EQ(options->applicationProtocolFallback(), + EXPECT_EQ(options->applicationProtocolFallback()[0], Http::Utility::AlpnNames::get().Http2); return std::make_unique(); })); @@ -335,8 +346,10 @@ TEST_F(Http2ConnPoolImplTest, VerifyAlpnFallback) { ActiveTestRequest r(*this, 0, false); expectClientConnect(0, r); EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r.decoder_, decodeHeaders_(_, true)); EXPECT_CALL(*this, onClientDestroy()); @@ -359,8 +372,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnectionReadyWithRequest) { ActiveTestRequest r(*this, 0, false); expectClientConnect(0, r); EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); pool_->drainConnections(); @@ -382,8 +397,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnectionBusy) { ActiveTestRequest r(*this, 0, false); expectClientConnect(0, r); EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); pool_->drainConnections(); @@ -570,8 +587,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnections) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // With max_streams == 1, the second request moves the first connection // to draining. @@ -579,8 +598,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnections) { ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // This will move the second connection to draining. pool_->drainConnections(); @@ -665,16 +686,21 @@ TEST_F(Http2ConnPoolImplTest, PendingStreams) { // Send a request through each stream. EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r3.inner_encoder_, encodeHeaders(_, true)); - r3.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r3.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Since we now have an active connection, subsequent requests should connect immediately. ActiveTestRequest r4(*this, 0, true); @@ -709,16 +735,21 @@ TEST_F(Http2ConnPoolImplTest, PendingStreamsNumberConnectingTotalRequestsPerConn // Send a request through each stream. EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r3.inner_encoder_, encodeHeaders(_, true)); - r3.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r3.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Clean up everything. test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -748,16 +779,21 @@ TEST_F(Http2ConnPoolImplTest, PendingStreamsNumberConnectingConcurrentRequestsPe // Send a request through each stream. EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r3.inner_encoder_, encodeHeaders(_, true)); - r3.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r3.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Clean up everything. test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -899,8 +935,10 @@ TEST_F(Http2ConnPoolImplTest, VerifyConnectionTimingStats) { deliverHistogramToSinks(Property(&Stats::Metric::name, "upstream_cx_connect_ms"), _)); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -925,8 +963,10 @@ TEST_F(Http2ConnPoolImplTest, VerifyBufferLimits) { expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -946,8 +986,10 @@ TEST_F(Http2ConnPoolImplTest, RequestAndResponse) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_EQ(1U, cluster_->stats_.upstream_cx_active_.value()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( @@ -955,8 +997,10 @@ TEST_F(Http2ConnPoolImplTest, RequestAndResponse) { ActiveTestRequest r2(*this, 0, true); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -977,8 +1021,10 @@ TEST_F(Http2ConnPoolImplTest, LocalReset) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, false)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false) + .ok()); r1.callbacks_.outer_encoder_->getStream().resetStream(Http::StreamResetReason::LocalReset); test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -998,8 +1044,10 @@ TEST_F(Http2ConnPoolImplTest, RemoteReset) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, false)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false) + .ok()); r1.inner_encoder_.stream_.resetStream(Http::StreamResetReason::RemoteReset); test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1020,9 +1068,10 @@ TEST_F(Http2ConnPoolImplTest, DrainDisconnectWithActiveRequest) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ReadyWatcher drained; pool_->addDrainedCallback([&]() -> void { drained.ready(); }); @@ -1046,15 +1095,18 @@ TEST_F(Http2ConnPoolImplTest, DrainDisconnectDrainingWithActiveRequest) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); expectClientCreate(); ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ReadyWatcher drained; pool_->addDrainedCallback([&]() -> void { drained.ready(); }); @@ -1086,15 +1138,18 @@ TEST_F(Http2ConnPoolImplTest, DrainPrimary) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); expectClientCreate(); ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ReadyWatcher drained; pool_->addDrainedCallback([&]() -> void { drained.ready(); }); @@ -1126,8 +1181,10 @@ TEST_F(Http2ConnPoolImplTest, DrainPrimaryNoActiveRequest) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(dispatcher_, deferredDelete_(_)); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( @@ -1139,8 +1196,10 @@ TEST_F(Http2ConnPoolImplTest, DrainPrimaryNoActiveRequest) { EXPECT_CALL(*this, onClientDestroy()); dispatcher_.clearDeferredDeleteList(); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(dispatcher_, deferredDelete_(_)); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( @@ -1174,8 +1233,10 @@ TEST_F(Http2ConnPoolImplTest, ConnectTimeout) { ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -1201,9 +1262,10 @@ TEST_F(Http2ConnPoolImplTest, MaxGlobalRequests) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ConnPoolCallbacks callbacks; MockResponseDecoder decoder; EXPECT_CALL(callbacks.pool_failure_, ready()); @@ -1224,8 +1286,10 @@ TEST_F(Http2ConnPoolImplTest, GoAway) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -1236,8 +1300,10 @@ TEST_F(Http2ConnPoolImplTest, GoAway) { ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); diff --git a/test/common/http/http2/frame_replay_test.cc b/test/common/http/http2/frame_replay_test.cc index b1931d350bb8..510002cf3fde 100644 --- a/test/common/http/http2/frame_replay_test.cc +++ b/test/common/http/http2/frame_replay_test.cc @@ -33,7 +33,7 @@ void setupStream(ClientCodecFrameInjector& codec, TestClientConnectionImplNew& c // Setup a single stream to inject frames as a reply to. TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); - codec.request_encoder_->encodeHeaders(request_headers, true); + codec.request_encoder_->encodeHeaders(request_headers, true).IgnoreError(); } // Validate that a simple Huffman encoded request HEADERS frame can be decoded. diff --git a/test/common/http/http2/response_header_fuzz_test.cc b/test/common/http/http2/response_header_fuzz_test.cc index 4559aa06419b..112f1392921a 100644 --- a/test/common/http/http2/response_header_fuzz_test.cc +++ b/test/common/http/http2/response_header_fuzz_test.cc @@ -27,7 +27,7 @@ void Replay(const Frame& frame, ClientCodecFrameInjector& codec) { // Setup a single stream to inject frames as a reply to. TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); - codec.request_encoder_->encodeHeaders(request_headers, true); + codec.request_encoder_->encodeHeaders(request_headers, true).IgnoreError(); // Send frames. status = codec.write(WellKnownFrames::defaultSettingsFrame(), connection); diff --git a/test/common/http/mixed_conn_pool_test.cc b/test/common/http/mixed_conn_pool_test.cc new file mode 100644 index 000000000000..f48b37afdcf7 --- /dev/null +++ b/test/common/http/mixed_conn_pool_test.cc @@ -0,0 +1,65 @@ +#include + +#include "common/http/conn_pool_base.h" +#include "common/http/utility.h" + +#include "test/common/upstream/utility.h" +#include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Http { +namespace { + +// TODO(alyssawilk) replace this with the MixedConnectionPool once it lands. +class ConnPoolImplForTest : public HttpConnPoolImplBase { +public: + ConnPoolImplForTest(Event::MockDispatcher& dispatcher, Random::RandomGenerator& random, + Upstream::ClusterInfoConstSharedPtr cluster) + : HttpConnPoolImplBase(Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), + Upstream::ResourcePriority::Default, dispatcher, nullptr, nullptr, + random, {Http::Protocol::Http2, Http::Protocol::Http11}) {} + + Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override { return nullptr; } + Http::Protocol protocol() const override { return Http::Protocol::Http2; } + CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData&) override { + return nullptr; + } +}; + +/** + * Test fixture for a connection pool which can have HTTP/2 or HTTP/1.1 connections. + */ +class MixedConnPoolImplTest : public testing::Test { +public: + MixedConnPoolImplTest() + : conn_pool_(std::make_unique(dispatcher_, random_, cluster_)) {} + + ~MixedConnPoolImplTest() override { + EXPECT_EQ("", TestUtility::nonZeroedGauges(cluster_->stats_store_.gauges())); + } + + NiceMock dispatcher_; + std::shared_ptr cluster_{new NiceMock()}; + std::unique_ptr conn_pool_; + NiceMock runtime_; + NiceMock random_; +}; + +TEST_F(MixedConnPoolImplTest, AlpnTest) { + auto& fallback = conn_pool_->transportSocketOptions()->applicationProtocolFallback(); + ASSERT_EQ(2, fallback.size()); + EXPECT_EQ(fallback[0], Http::Utility::AlpnNames::get().Http2); + EXPECT_EQ(fallback[1], Http::Utility::AlpnNames::get().Http11); +} + +} // namespace +} // namespace Http +} // namespace Envoy diff --git a/test/common/network/BUILD b/test/common/network/BUILD index 55ddafdbe198..29be5a7f4e52 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -109,8 +109,10 @@ envoy_cc_test( "//include/envoy/network:dns_interface", "//source/common/event:dispatcher_includes", "//include/envoy/event:file_event_interface", + "//source/common/stats:isolated_store_lib", "//source/common/event:dispatcher_lib", "//source/common/network:address_lib", + "//source/common/common:random_generator_lib", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", "//test/test_common:utility_lib", @@ -229,6 +231,15 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "raw_buffer_socket_test", + srcs = ["raw_buffer_socket_test.cc"], + deps = [ + "//source/common/network:raw_buffer_socket_lib", + "//test/test_common:network_utility_lib", + ], +) + envoy_cc_test_library( name = "udp_listener_impl_test_base_lib", hdrs = ["udp_listener_impl_test_base.h"], diff --git a/test/common/network/apple_dns_impl_test.cc b/test/common/network/apple_dns_impl_test.cc index acaed880d758..9d9fbb534d12 100644 --- a/test/common/network/apple_dns_impl_test.cc +++ b/test/common/network/apple_dns_impl_test.cc @@ -10,10 +10,11 @@ #include "envoy/network/address.h" #include "envoy/network/dns.h" -#include "common/event/dispatcher_impl.h" +#include "common/common/random_generator.h" #include "common/network/address_impl.h" #include "common/network/apple_dns_impl.h" #include "common/network/utility.h" +#include "common/stats/isolated_store_impl.h" #include "test/mocks/event/mocks.h" #include "test/test_common/environment.h" @@ -32,6 +33,8 @@ using testing::SaveArg; using testing::StrEq; using testing::WithArgs; +struct _DNSServiceRef_t {}; + namespace Envoy { namespace Network { namespace { @@ -200,6 +203,8 @@ TEST_F(AppleDnsImplTest, LocalResolution) { // error conditions, and callback firing. class AppleDnsImplFakeApiTest : public testing::Test { public: + AppleDnsImplFakeApiTest() : initialize_failure_timer_(new Event::MockTimer) {} + ~AppleDnsImplFakeApiTest() override { if (resolver_) { EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)); @@ -209,37 +214,50 @@ class AppleDnsImplFakeApiTest : public testing::Test { void createResolver() { file_event_ = new NiceMock; + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)) - .WillOnce(Return(kDNSServiceErr_NoError)); + .WillOnce(DoAll( + WithArgs<0>(Invoke([](DNSServiceRef* ref) -> void { *ref = new _DNSServiceRef_t{}; })), + Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(DoAll(SaveArg<1>(&file_ready_cb_), Return(file_event_))); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); - resolver_ = std::make_unique(dispatcher_); + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); } protected: MockDnsService dns_service_; TestThreadsafeSingletonInjector dns_service_injector_{&dns_service_}; + Stats::IsolatedStoreImpl stats_store_; std::unique_ptr resolver_{}; NiceMock dispatcher_; + Event::MockTimer* initialize_failure_timer_; NiceMock* file_event_; Event::FileReadyCb file_ready_cb_; + Random::RandomGeneratorImpl random_; }; TEST_F(AppleDnsImplFakeApiTest, ErrorInConnectionCreation) { - ON_CALL(dns_service_, dnsServiceCreateConnection(_)) - .WillByDefault(Return(kDNSServiceErr_Unknown)); - EXPECT_DEATH(std::make_unique(dispatcher_), - "error=-65537 in DNSServiceCreateConnection"); + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_Unknown)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + EXPECT_EQ(1, TestUtility::findCounter(stats_store_, "dns.apple.connection_failure")->value()); } TEST_F(AppleDnsImplFakeApiTest, ErrorInSocketAccess) { - ON_CALL(dns_service_, dnsServiceCreateConnection(_)) - .WillByDefault(Return(kDNSServiceErr_NoError)); - ON_CALL(dns_service_, dnsServiceRefSockFD(_)).WillByDefault(Return(-1)); - EXPECT_DEATH(std::make_unique(dispatcher_), - "error in DNSServiceRefSockFD"); + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_NoError)); + EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(-1)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + EXPECT_EQ(1, TestUtility::findCounter(stats_store_, "dns.apple.socket_failure")->value()); } TEST_F(AppleDnsImplFakeApiTest, InvalidFileEvent) { @@ -260,8 +278,11 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResult) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); file_ready_cb_(Event::FileReadyType::Read); + + EXPECT_EQ(1, TestUtility::findCounter(stats_store_, "dns.apple.processing_failure")->value()); } TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { @@ -282,6 +303,7 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); auto query = resolver_->resolve(hostname, Network::DnsLookupFamily::Auto, @@ -312,6 +334,7 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); file_ready_cb_(Event::FileReadyType::Read); @@ -321,6 +344,7 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { TEST_F(AppleDnsImplFakeApiTest, SynchronousErrorInGetAddrInfo) { createResolver(); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, _, _, _, _, _, _)) .WillOnce(Return(kDNSServiceErr_Unknown)); // The Query's sd ref will be deallocated. @@ -348,6 +372,7 @@ TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { // The query's ref is going to be deallocated when the query is destroyed. EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -386,6 +411,7 @@ TEST_F(AppleDnsImplFakeApiTest, IncorrectInterfaceIndexReturned) { Network::Address::Ipv4Instance address(&addr4); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -421,10 +447,15 @@ TEST_F(AppleDnsImplFakeApiTest, QueryCompletedWithError) { // to be deallocated due to the error. EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)).Times(2); // A new main ref is created on error. - EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_NoError)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)) + .WillOnce(DoAll( + WithArgs<0>(Invoke([](DNSServiceRef* ref) -> void { *ref = new _DNSServiceRef_t{}; })), + Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -468,6 +499,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddresses) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -510,6 +542,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddressesSecondOneFails) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -538,6 +571,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddressesSecondOneFails) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); + reply_callback(nullptr, 0, 0, kDNSServiceErr_Unknown, hostname.c_str(), nullptr, 30, query); dns_callback_executed.WaitForNotification(); @@ -565,6 +600,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { absl::Notification dns_callback_executed2; // Start first query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -584,6 +620,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { ASSERT_NE(nullptr, query); // Start second query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4, StrEq(hostname2.c_str()), _, _)) @@ -632,6 +669,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { absl::Notification dns_callback_executed2; // Start first query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -653,6 +691,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { ASSERT_NE(nullptr, query); // Start second query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4, StrEq(hostname2.c_str()), _, _)) @@ -680,6 +719,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); // The second query fails. reply_callback2(nullptr, 0, 0, kDNSServiceErr_Unknown, hostname2.c_str(), nullptr, 30, query2); @@ -700,6 +740,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithOnlyNonAdditiveReplies) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -735,6 +776,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { Network::Address::Ipv4Instance address(&addr4); DNSServiceGetAddrInfoReply reply_callback; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -751,6 +793,83 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { "invalid to add null address"); } +TEST_F(AppleDnsImplFakeApiTest, ErrorInConnectionCreationImmediateCallback) { + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_Unknown)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + EXPECT_CALL(*initialize_failure_timer_, enabled()).Times(2).WillRepeatedly(Return(true)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_NoError)); + EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(-1)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + absl::Notification dns_callback_executed; + EXPECT_EQ(nullptr, + resolver_->resolve("foo.com", Network::DnsLookupFamily::Auto, + [&dns_callback_executed](DnsResolver::ResolutionStatus status, + std::list&& response) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Failure, status); + EXPECT_TRUE(response.empty()); + dns_callback_executed.Notify(); + })); + dns_callback_executed.WaitForNotification(); +} + +TEST_F(AppleDnsImplFakeApiTest, ErrorInConnectionCreationQueryDispatched) { + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_Unknown)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + file_event_ = new NiceMock; + EXPECT_CALL(*initialize_failure_timer_, enabled()) + .Times(2) + .WillOnce(Return(true)) + .WillOnce(Return(false)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)) + .WillOnce(DoAll( + WithArgs<0>(Invoke([](DNSServiceRef* ref) -> void { *ref = new _DNSServiceRef_t{}; })), + Return(kDNSServiceErr_NoError))); + EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); + EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) + .WillOnce(DoAll(SaveArg<1>(&file_ready_cb_), Return(file_event_))); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); + + absl::Notification dns_callback_executed; + const std::string hostname = "foo.com"; + DNSServiceGetAddrInfoReply reply_callback; + sockaddr_in addr4; + addr4.sin_family = AF_INET; + EXPECT_EQ(1, inet_pton(AF_INET, "1.2.3.4", &addr4.sin_addr)); + addr4.sin_port = htons(6502); + Network::Address::Ipv4Instance address(&addr4); + + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, + kDNSServiceProtocol_IPv4, StrEq(hostname.c_str()), _, _)) + .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); + + auto query = + resolver_->resolve(hostname, Network::DnsLookupFamily::V4Only, + [&dns_callback_executed](DnsResolver::ResolutionStatus status, + std::list&& response) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); + EXPECT_EQ(1, response.size()); + dns_callback_executed.Notify(); + }); + EXPECT_NE(nullptr, query); + + // The query's sd ref will be deallocated on completion. + EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)); + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), + address.sockAddr(), 30, query); + + dns_callback_executed.WaitForNotification(); +} + } // namespace } // namespace Network } // namespace Envoy diff --git a/test/common/network/dns_impl_test.cc b/test/common/network/dns_impl_test.cc index 0489bc067d39..e6282f2e6405 100644 --- a/test/common/network/dns_impl_test.cc +++ b/test/common/network/dns_impl_test.cc @@ -705,7 +705,7 @@ TEST_P(DnsImplTest, DestroyChannelOnRefused) { DnsResolver::ResolutionStatus::Failure, {}, {}, absl::nullopt)); dispatcher_->run(Event::Dispatcher::RunType::Block); // However, the fresh channel initialized by production code does not point to the TestDnsServer. - // This means that resolution will return ARES_ENOTFOUND. This should not dirty the channel. + // This means that resolution will return `ARES_ENOTFOUND`. This should not dirty the channel. EXPECT_FALSE(peer_->isChannelDirty()); // Reset the channel to point to the TestDnsServer, and make sure resolution is healthy. diff --git a/test/common/network/listener_impl_test.cc b/test/common/network/listener_impl_test.cc index 21e4673ce643..9bbfe6643c42 100644 --- a/test/common/network/listener_impl_test.cc +++ b/test/common/network/listener_impl_test.cc @@ -412,13 +412,13 @@ TEST_P(TcpListenerImplTest, SetListenerRejectFractionIntermediate) { { testing::InSequence s1; EXPECT_CALL(random_generator, random()).WillOnce(Return(std::numeric_limits::max())); - EXPECT_CALL(listener_callbacks, onAccept_(_)); + // Exiting dispatcher on client side connect event can cause a race, listener accept callback + // may not have been triggered, exit dispatcher here to prevent this. + EXPECT_CALL(listener_callbacks, onAccept_(_)).WillOnce([&] { dispatcher_->exit(); }); } { testing::InSequence s2; - EXPECT_CALL(connection_callbacks, onEvent(ConnectionEvent::Connected)).WillOnce([&] { - dispatcher_->exit(); - }); + EXPECT_CALL(connection_callbacks, onEvent(ConnectionEvent::Connected)); EXPECT_CALL(connection_callbacks, onEvent(ConnectionEvent::RemoteClose)).Times(0); } diff --git a/test/common/network/raw_buffer_socket_test.cc b/test/common/network/raw_buffer_socket_test.cc new file mode 100644 index 000000000000..f28e54f53bc9 --- /dev/null +++ b/test/common/network/raw_buffer_socket_test.cc @@ -0,0 +1,16 @@ +#include "common/network/raw_buffer_socket.h" + +#include "test/test_common/network_utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Network { + +TEST(RawBufferSocketFactory, RawBufferSocketFactory) { + TransportSocketFactoryPtr factory = Envoy::Network::Test::createRawBufferSocketFactory(); + EXPECT_FALSE(factory->usesProxyProtocolOptions()); +} + +} // namespace Network +} // namespace Envoy diff --git a/test/common/protobuf/BUILD b/test/common/protobuf/BUILD index 4aa4300922dc..bad3b85b77ab 100644 --- a/test/common/protobuf/BUILD +++ b/test/common/protobuf/BUILD @@ -35,6 +35,7 @@ envoy_cc_test( "//test/proto:sensitive_proto_cc_proto", "//test/test_common:environment_lib", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/api/v2:pkg_cc_proto", "@envoy_api//envoy/api/v2/core:pkg_cc_proto", diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index e135905f361b..eee8bffdfb23 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -26,6 +26,7 @@ #include "test/proto/sensitive.pb.h" #include "test/test_common/environment.h" #include "test/test_common/logging.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/container/node_hash_set.h" @@ -36,34 +37,32 @@ using namespace std::chrono_literals; namespace Envoy { -class RuntimeStatsHelper { +class RuntimeStatsHelper : public TestScopedRuntime { public: - RuntimeStatsHelper() - : api_(Api::createApiForTest(store_)), - runtime_deprecated_feature_use_(store_.counter("runtime.deprecated_feature_use")), + RuntimeStatsHelper(bool allow_deprecated_v2_api = false) + : runtime_deprecated_feature_use_(store_.counter("runtime.deprecated_feature_use")), deprecated_feature_seen_since_process_start_( store_.gauge("runtime.deprecated_feature_seen_since_process_start", Stats::Gauge::ImportMode::NeverImport)) { - envoy::config::bootstrap::v3::LayeredRuntime config; - config.add_layers()->mutable_admin_layer(); - loader_ = std::make_unique( - Runtime::LoaderPtr{new Runtime::LoaderImpl(dispatcher_, tls_, config, local_info_, store_, - generator_, validation_visitor_, *api_)}); + if (allow_deprecated_v2_api) { + Runtime::LoaderSingleton::getExisting()->mergeValues({ + {"envoy.reloadable_features.enable_deprecated_v2_api", "true"}, + {"envoy.features.enable_all_deprecated_features", "true"}, + }); + } } - Event::MockDispatcher dispatcher_; - NiceMock tls_; - Stats::TestUtil::TestStore store_; - Random::MockRandomGenerator generator_; - Api::ApiPtr api_; - std::unique_ptr loader_; Stats::Counter& runtime_deprecated_feature_use_; Stats::Gauge& deprecated_feature_seen_since_process_start_; - NiceMock local_info_; - NiceMock validation_visitor_; }; class ProtobufUtilityTest : public testing::Test, protected RuntimeStatsHelper {}; +// TODO(htuch): During/before the v2 removal, cleanup the various examples that explicitly refer to +// v2 API protos and replace with upgrade examples not tie to the concrete API. +class ProtobufV2ApiUtilityTest : public testing::Test, protected RuntimeStatsHelper { +public: + ProtobufV2ApiUtilityTest() : RuntimeStatsHelper(true) {} +}; TEST_F(ProtobufUtilityTest, ConvertPercentNaNDouble) { envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; @@ -267,7 +266,7 @@ TEST_F(ProtobufUtilityTest, LoadBinaryProtoFromFile) { EXPECT_TRUE(TestUtility::protoEqual(bootstrap, proto_from_file)); } -TEST_F(ProtobufUtilityTest, DEPRECATED_FEATURE_TEST(LoadBinaryV2ProtoFromFile)) { +TEST_F(ProtobufV2ApiUtilityTest, DEPRECATED_FEATURE_TEST(LoadBinaryV2ProtoFromFile)) { // Allow the use of v2.Bootstrap.runtime. Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.deprecated_features:envoy.config.bootstrap.v2.Bootstrap.runtime", "True "}}); @@ -370,7 +369,7 @@ TEST_F(ProtobufUtilityTest, LoadJsonFromFileNoBoosting) { EXPECT_TRUE(TestUtility::protoEqual(bootstrap, proto_from_file)); } -TEST_F(ProtobufUtilityTest, DEPRECATED_FEATURE_TEST(LoadV2TextProtoFromFile)) { +TEST_F(ProtobufV2ApiUtilityTest, DEPRECATED_FEATURE_TEST(LoadV2TextProtoFromFile)) { API_NO_BOOST(envoy::config::bootstrap::v2::Bootstrap) bootstrap; bootstrap.mutable_node()->set_build_version("foo"); @@ -1234,7 +1233,7 @@ TEST_F(ProtobufUtilityTest, AnyConvertAndValidateFailedValidation) { } // MessageUtility::unpackTo() with the wrong type throws. -TEST_F(ProtobufUtilityTest, UnpackToWrongType) { +TEST_F(ProtobufV2ApiUtilityTest, UnpackToWrongType) { ProtobufWkt::Duration source_duration; source_duration.set_seconds(42); ProtobufWkt::Any source_any; @@ -1268,7 +1267,7 @@ TEST_F(ProtobufUtilityTest, UnpackToSameVersion) { } // MessageUtility::unpackTo() with API message works across version. -TEST_F(ProtobufUtilityTest, UnpackToNextVersion) { +TEST_F(ProtobufV2ApiUtilityTest, UnpackToNextVersion) { API_NO_BOOST(envoy::api::v2::Cluster) source; source.set_drain_connections_on_host_removal(true); ProtobufWkt::Any source_any; @@ -1280,7 +1279,7 @@ TEST_F(ProtobufUtilityTest, UnpackToNextVersion) { } // Validate warning messages on v2 upgrades. -TEST_F(ProtobufUtilityTest, V2UpgradeWarningLogs) { +TEST_F(ProtobufV2ApiUtilityTest, V2UpgradeWarningLogs) { API_NO_BOOST(envoy::config::cluster::v3::Cluster) dst; // First attempt works. EXPECT_LOG_CONTAINS("warn", "Configuration does not parse cleanly as v3", @@ -1295,7 +1294,8 @@ TEST_F(ProtobufUtilityTest, V2UpgradeWarningLogs) { EXPECT_LOG_CONTAINS("warn", "Configuration does not parse cleanly as v3", MessageUtil::loadFromJson("{drain_connections_on_host_removal: false}", dst, ProtobufMessage::getNullValidationVisitor())); - // This is kind of terrible, but it's hard to do dependency injection at onVersionUpgradeWarn(). + // This is kind of terrible, but it's hard to do dependency injection at + // onVersionUpgradeDeprecation(). std::this_thread::sleep_for(5s); // NOLINT // We can log the original warning again. EXPECT_LOG_CONTAINS("warn", "Configuration does not parse cleanly as v3", @@ -1353,7 +1353,7 @@ TEST_F(ProtobufUtilityTest, LoadFromJsonNoBoosting) { } // MessageUtility::loadFromJson() with API message works across version. -TEST_F(ProtobufUtilityTest, LoadFromJsonNextVersion) { +TEST_F(ProtobufV2ApiUtilityTest, LoadFromJsonNextVersion) { { API_NO_BOOST(envoy::config::cluster::v3::Cluster) dst; MessageUtil::loadFromJson("{use_tcp_for_dns_lookups: true}", dst, diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 3b147b46477c..13659b472a85 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -141,9 +141,17 @@ Http::TestRequestHeaderMapImpl genHeaders(const std::string& host, const std::st envoy::config::route::v3::RouteConfiguration parseRouteConfigurationFromYaml(const std::string& yaml) { envoy::config::route::v3::RouteConfiguration route_config; + // Most tests should be v3 and not boost. + bool avoid_boosting = true; + // If we're under TestDeprecatedV2Api, allow boosting. + auto* runtime = Runtime::LoaderSingleton::getExisting(); + if (runtime != nullptr && runtime->threadsafeSnapshot()->runtimeFeatureEnabled( + "envoy.reloadable_features.enable_deprecated_v2_api")) { + avoid_boosting = false; + } // Load the file and keep the annotations (in case of an upgrade) to make sure - // validate() observes the upgrade - TestUtility::loadFromYaml(yaml, route_config, true); + // validate() observes the upgrade. + TestUtility::loadFromYaml(yaml, route_config, true, avoid_boosting); TestUtility::validate(route_config); return route_config; } @@ -316,6 +324,7 @@ class RouteMatcherTest : public testing::Test, public ConfigImplTestBase {}; // When removing legacy fields this test can be removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestLegacyRoutes)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: regex @@ -1242,6 +1251,7 @@ TEST_F(RouteMatcherTest, TestRoutesWithWildcardAndDefaultOnly) { // When deprecating regex: this test can be removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestRoutesWithInvalidRegexLegacy)) { + TestDeprecatedV2Api _deprecated_v2_api; std::string invalid_route = R"EOF( virtual_hosts: - name: regex @@ -1954,6 +1964,7 @@ TEST_F(RouteMatcherTest, HeaderMatchedRouting) { // Verify the fixes for https://github.com/envoyproxy/envoy/issues/2406 // When removing regex_match this test can be removed entirely. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(InvalidHeaderMatchedRoutingConfigLegacy)) { + TestDeprecatedV2Api _deprecated_v2_api; std::string value_with_regex_chars = R"EOF( virtual_hosts: - name: local_service @@ -2028,6 +2039,7 @@ TEST_F(RouteMatcherTest, InvalidHeaderMatchedRoutingConfig) { // When removing value: simply remove that section of the config and the relevant test. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(QueryParamMatchedRouting)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: local_service @@ -2145,6 +2157,7 @@ TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(QueryParamMatchedRouting)) { // When removing value: this test can be removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(InvalidQueryParamMatchedRoutingConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; std::string value_with_regex_chars = R"EOF( virtual_hosts: - name: local_service @@ -3166,6 +3179,7 @@ TEST_F(RouteMatcherTest, ClusterNotFoundResponseCodeConfig404) { // TODO(dereka) DEPRECATED_FEATURE_TEST can be removed when `request_mirror_policy` is removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(Shadow)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -3236,6 +3250,7 @@ TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(Shadow)) { } TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(ShadowPolicyAndPolicies)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -3261,6 +3276,7 @@ class RouteConfigurationV2 : public testing::Test, public ConfigImplTestBase {}; // When removing runtime_key: this test can be removed. TEST_F(RouteConfigurationV2, DEPRECATED_FEATURE_TEST(RequestMirrorPolicy)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: mirror @@ -4023,7 +4039,7 @@ TEST_F(RouteMatcherTest, TestInvalidCharactersInHostRewrites) { routes: - match: { prefix: "/foo" } route: - host_rewrite: "new_host\ndroptable" + host_rewrite_literal: "new_host\ndroptable" cluster: www )EOF"; @@ -4040,7 +4056,7 @@ TEST_F(RouteMatcherTest, TestInvalidCharactersInAutoHostRewrites) { routes: - match: { prefix: "/foo" } route: - auto_host_rewrite_header: "x-host\ndroptable" + host_rewrite_header: "x-host\ndroptable" cluster: www )EOF"; @@ -5292,7 +5308,8 @@ TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigMissingPathSpecifier) "RouteValidationError.Match: \\[\"value is required\"\\]"); } -TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPrefixAndRegex) { +TEST_F(BadHttpRouteConfigurationsTest, DEPRECATED_FEATURE_TEST(BadRouteEntryConfigPrefixAndRegex)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -5340,7 +5357,8 @@ TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigNoAction) { "caused by field: \"action\", reason: is required"); } -TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPathAndRegex) { +TEST_F(BadHttpRouteConfigurationsTest, DEPRECATED_FEATURE_TEST(BadRouteEntryConfigPathAndRegex)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -5514,6 +5532,7 @@ TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(ExcludeVHRateLimits)) { // When allow_origin: and allow_origin_regex: are removed, simply remove them // and the relevant checks below. TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestVHostCorsConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: "default" @@ -5631,6 +5650,7 @@ TEST_F(RoutePropertyTest, TestRouteCorsConfig) { // When allow-origin: is removed, this test can be removed. TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TTestVHostCorsLegacyConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -5671,6 +5691,7 @@ TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TTestVHostCorsLegacyConfig)) { // When allow-origin: is removed, this test can be removed. TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestRouteCorsLegacyConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -5706,7 +5727,8 @@ TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestRouteCorsLegacyConfig)) { EXPECT_EQ(cors_policy->allowCredentials(), true); } -TEST_F(RoutePropertyTest, TestBadCorsConfig) { +TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestBadCorsConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -7120,6 +7142,29 @@ TEST_F(RouteConfigurationV2, UpgradeConfigs) { EXPECT_FALSE(upgrade_map.find("disabled")->second); } +TEST_F(RouteConfigurationV2, EmptyFilterConfigRejected) { + 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 + upgrade_configs: + - upgrade_type: Websocket + - upgrade_type: disabled + - enabled: false + )EOF"; + + EXPECT_THROW_WITH_REGEX( + TestConfigImpl(parseRouteConfigurationFromYaml(yaml), factory_context_, true), EnvoyException, + "Proto constraint validation failed.*"); +} + TEST_F(RouteConfigurationV2, DuplicateUpgradeConfigs) { const std::string yaml = R"EOF( virtual_hosts: @@ -7411,6 +7456,7 @@ class PerFilterConfigsTest : public testing::Test, public ConfigImplTestBase { }; TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(TypedConfigFilterError)) { + TestDeprecatedV2Api _deprecated_v2_api; { const std::string yaml = R"EOF( virtual_hosts: @@ -7451,6 +7497,7 @@ TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(TypedConfigFilterError)) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(UnknownFilterStruct)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7487,6 +7534,7 @@ TEST_F(PerFilterConfigsTest, UnknownFilterAny) { // Test that a trivially specified NamedHttpFilterConfigFactory ignores per_filter_config without // error. TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(DefaultFilterImplementationStruct)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7519,6 +7567,7 @@ TEST_F(PerFilterConfigsTest, DefaultFilterImplementationAny) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(RouteLocalConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7557,6 +7606,7 @@ TEST_F(PerFilterConfigsTest, RouteLocalTypedConfig) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(WeightedClusterConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7603,6 +7653,7 @@ TEST_F(PerFilterConfigsTest, WeightedClusterTypedConfig) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(WeightedClusterFallthroughConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index 8adc57d7ac94..2bbb87733bf8 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -100,7 +100,7 @@ codec_type: auto stat_prefix: foo http_filters: - name: http_dynamo_filter - config: {} + typed_config: {} )EOF"; EXPECT_CALL(outer_init_manager_, add(_)); @@ -166,7 +166,7 @@ TEST_F(RdsImplTest, Basic) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", "name": "foo_route_config", "virtual_hosts": null } @@ -196,7 +196,7 @@ TEST_F(RdsImplTest, Basic) { "version_info": "2", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", "name": "foo_route_config", "virtual_hosts": [ { @@ -248,7 +248,7 @@ TEST_F(RdsImplTest, FailureInvalidConfig) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", "name": "INVALID_NAME_FOR_route_config", "virtual_hosts": null } @@ -730,7 +730,7 @@ TEST_F(RouteConfigProviderManagerImplTest, ConfigDumpAfterConfigRejected) { const std::string response1_yaml = R"EOF( version_info: '1' resources: -- "@type": type.googleapis.com/envoy.api.v2.RouteConfiguration +- "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration name: foo_route_config virtual_hosts: - name: integration diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 00fdd783afc9..20ada9e6c527 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -462,6 +462,36 @@ TEST_F(RouterTest, RouteNotFound) { EXPECT_EQ(callbacks_.details(), "route_not_found"); } +TEST_F(RouterTest, MissingRequiredHeaders) { + NiceMock encoder; + Http::ResponseDecoder* response_decoder = nullptr; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke( + [&](Http::ResponseDecoder& decoder, + Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + expectResponseTimerCreate(); + + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + headers.removeMethod(); + + EXPECT_CALL(encoder, encodeHeaders(_, _)) + .WillOnce(Invoke([](const Http::RequestHeaderMap& headers, bool) -> Http::Status { + return Http::HeaderUtility::checkRequiredHeaders(headers); + })); + EXPECT_CALL(callbacks_, + sendLocalReply(Http::Code::ServiceUnavailable, + testing::Eq("missing required header: :method"), _, _, + "filter_removed_required_headers{missing required header: :method}")) + .WillOnce(testing::InvokeWithoutArgs([] {})); + router_.decodeHeaders(headers, true); + router_.onDestroy(); +} + TEST_F(RouterTest, ClusterNotFound) { EXPECT_CALL(callbacks_.stream_info_, setResponseFlag(StreamInfo::ResponseFlag::NoRouteFound)); @@ -1507,8 +1537,9 @@ TEST_F(RouterTest, ResetDuringEncodeHeaders) { EXPECT_CALL(callbacks_, removeDownstreamWatermarkCallbacks(_)); EXPECT_CALL(callbacks_, addDownstreamWatermarkCallbacks(_)); EXPECT_CALL(encoder, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> void { + .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> Http::Status { encoder.stream_.resetStream(Http::StreamResetReason::RemoteReset); + return Http::okStatus(); })); Http::TestRequestHeaderMapImpl headers; @@ -4482,7 +4513,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWhenReachingMaxInternalRedirect) { setNumPreviousRedirect(3); sendRequest(); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4501,7 +4532,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithEmptyLocation) { redirect_headers_->setLocation(""); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4519,7 +4550,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithInvalidLocation) { redirect_headers_->setLocation("h"); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4536,7 +4567,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithoutCompleteRequest) { sendRequest(false); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4554,7 +4585,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithoutLocation) { redirect_headers_->removeLocation(); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); Buffer::OwnedImpl data("1234567890"); @@ -4571,7 +4602,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithBody) { Buffer::InstancePtr body_data(new Buffer::OwnedImpl("random_fake_data")); EXPECT_CALL(callbacks_, decodingBuffer()).WillOnce(Return(body_data.get())); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); Buffer::OwnedImpl data("1234567890"); @@ -4589,7 +4620,7 @@ TEST_F(RouterTest, CrossSchemeRedirectRejectedByPolicy) { redirect_headers_->setLocation("https://www.foo.com"); EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + 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_ @@ -4613,7 +4644,7 @@ TEST_F(RouterTest, InternalRedirectRejectedByPredicate) { .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); + 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_ @@ -4636,7 +4667,7 @@ TEST_F(RouterTest, HttpInternalRedirectSucceeded) { EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); EXPECT_CALL(callbacks_, clearRouteCache()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(1).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") @@ -4661,7 +4692,7 @@ TEST_F(RouterTest, HttpsInternalRedirectSucceeded) { EXPECT_CALL(connection_, ssl()).Times(1).WillOnce(Return(ssl_connection)); EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); EXPECT_CALL(callbacks_, clearRouteCache()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(1).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") @@ -4684,7 +4715,7 @@ TEST_F(RouterTest, CrossSchemeRedirectAllowedByPolicy) { isCrossSchemeRedirectAllowed()) .WillOnce(Return(true)); EXPECT_CALL(callbacks_, clearRouteCache()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(1).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") @@ -5837,8 +5868,9 @@ TEST_F(RouterTest, AutoHostRewriteEnabled) { // :authority header in the outgoing request should match the DNS name of // the selected upstream host EXPECT_CALL(encoder, encodeHeaders(HeaderMapEqualRef(&outgoing_headers), true)) - .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> void { + .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> Http::Status { encoder.stream_.resetStream(Http::StreamResetReason::RemoteReset); + return Http::okStatus(); })); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) @@ -5874,8 +5906,9 @@ TEST_F(RouterTest, AutoHostRewriteDisabled) { // :authority header in the outgoing request should match the :authority header of // the incoming request EXPECT_CALL(encoder, encodeHeaders(HeaderMapEqualRef(&incoming_headers), true)) - .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> void { + .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> Http::Status { encoder.stream_.resetStream(Http::StreamResetReason::RemoteReset); + return Http::okStatus(); })); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) diff --git a/test/common/router/router_upstream_log_test.cc b/test/common/router/router_upstream_log_test.cc index 64829ff6cec8..768d2f8eaad7 100644 --- a/test/common/router/router_upstream_log_test.cc +++ b/test/common/router/router_upstream_log_test.cc @@ -40,8 +40,9 @@ absl::optional testUpstreamLog() { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog - format: "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + log_format: + text_format: "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %REQ(:AUTHORITY)% %UPSTREAM_HOST% %UPSTREAM_LOCAL_ADDRESS% %RESP(X-UPSTREAM-HEADER)% %TRAILER(X-TRAILER)%\n" path: "/dev/null" @@ -286,8 +287,9 @@ TEST_F(RouterUpstreamLogTest, LogTimestampsAndDurations) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog - format: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + log_format: + text_format: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %DURATION% %RESPONSE_DURATION% %REQUEST_DURATION%" path: "/dev/null" )EOF"; diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index c0e71530f317..4618e47f4deb 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -697,7 +697,7 @@ codec_type: auto stat_prefix: foo http_filters: - name: http_dynamo_filter - config: + typed_config: scoped_routes: name: $0 scope_key_builder: diff --git a/test/common/secret/BUILD b/test/common/secret/BUILD index 48572641a39b..6161773b7b8c 100644 --- a/test/common/secret/BUILD +++ b/test/common/secret/BUILD @@ -43,12 +43,15 @@ envoy_cc_test( "//source/common/secret:sds_api_lib", "//source/common/ssl:certificate_validation_context_config_impl_lib", "//source/common/ssl:tls_certificate_config_impl_lib", + "//test/common/stats:stat_test_utility_lib", + "//test/mocks/config:config_mocks", + "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/init:init_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/secret:secret_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:environment_lib", + "//test/test_common:logging_lib", "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index 8d5cdb00294a..12386bbf535e 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -12,29 +12,38 @@ #include "common/ssl/certificate_validation_context_config_impl.h" #include "common/ssl/tls_certificate_config_impl.h" +#include "test/common/stats/stat_test_utility.h" +#include "test/mocks/config/mocks.h" +#include "test/mocks/filesystem/mocks.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/init/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/secret/mocks.h" -#include "test/mocks/server/instance.h" #include "test/test_common/environment.h" +#include "test/test_common/logging.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::_; +using ::testing::InSequence; using ::testing::Invoke; using ::testing::InvokeWithoutArgs; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::Throw; namespace Envoy { namespace Secret { namespace { -class SdsApiTest : public testing::Test { +class SdsApiTestBase { protected: - SdsApiTest() - : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")) {} + SdsApiTestBase() { + api_ = Api::createApiForTest(); + dispatcher_ = api_->allocateDispatcher("test_thread"); + } void initialize() { init_target_handle_->initialize(init_watcher_); } void setupMocks() { @@ -51,19 +60,21 @@ class SdsApiTest : public testing::Test { Event::GlobalTimeSystem time_system_; Init::TargetHandlePtr init_target_handle_; Event::DispatcherPtr dispatcher_; + Stats::TestUtil::TestStore stats_; }; +class SdsApiTest : public testing::Test, public SdsApiTestBase {}; + // Validate that SdsApi object is created and initialized successfully. TEST_F(SdsApiTest, BasicTest) { ::testing::InSequence s; const envoy::service::secret::v3::SdsDummy dummy; - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); initialize(); } @@ -72,7 +83,6 @@ TEST_F(SdsApiTest, BasicTest) { // has been already initialized. This is a regression test for // https://github.com/envoyproxy/envoy/issues/12013 TEST_F(SdsApiTest, InitManagerInitialised) { - NiceMock server; std::string sds_config = R"EOF( resources: @@ -90,7 +100,7 @@ TEST_F(SdsApiTest, InitManagerInitialised) { NiceMock callbacks; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("name"); - Config::SubscriptionStats stats(Config::Utility::generateStats(server.stats())); + Config::SubscriptionStats stats(Config::Utility::generateStats(stats_)); NiceMock validation_visitor; envoy::config::core::v3::ConfigSource config_source; @@ -113,8 +123,8 @@ TEST_F(SdsApiTest, InitManagerInitialised) { EXPECT_EQ(Init::Manager::State::Initializing, init_manager.state()); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); EXPECT_NO_THROW(sds_api.registerInitTarget(init_manager)); } @@ -122,7 +132,6 @@ TEST_F(SdsApiTest, InitManagerInitialised) { // https://github.com/envoyproxy/envoy/issues/10976. TEST_F(SdsApiTest, BadConfigSource) { ::testing::InSequence s; - NiceMock server; envoy::config::core::v3::ConfigSource config_source; EXPECT_CALL(subscription_factory_, subscriptionFromConfigSource(_, _, _, _, _)) .WillOnce(InvokeWithoutArgs([]() -> Config::SubscriptionPtr { @@ -131,19 +140,18 @@ TEST_F(SdsApiTest, BadConfigSource) { })); EXPECT_THROW_WITH_MESSAGE(TlsCertificateSdsApi( config_source, "abc.com", subscription_factory_, time_system_, - validation_visitor_, server.stats(), []() {}, *dispatcher_, *api_), + validation_visitor_, stats_, []() {}, *dispatcher_, *api_), EnvoyException, "bad config"); } // Validate that TlsCertificateSdsApi updates secrets successfully if a good secret // is passed to onConfigUpdate(). TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); initialize(); NiceMock secret_callback; @@ -180,15 +188,317 @@ TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { handle->remove(); } +class SdsRotationApiTest : public SdsApiTestBase { +protected: + SdsRotationApiTest() { + api_ = Api::createApiForTest(filesystem_); + setupMocks(); + EXPECT_CALL(filesystem_, splitPathFromFilename(_)) + .WillRepeatedly(Invoke([](absl::string_view path) -> Filesystem::PathSplitResult { + return Filesystem::fileSystemForTest().splitPathFromFilename(path); + })); + } + + Secret::MockSecretCallbacks secret_callback_; + Common::CallbackHandle* handle_{}; + std::vector watch_cbs_; + Event::MockDispatcher mock_dispatcher_; + Filesystem::MockInstance filesystem_; +}; + +class TlsCertificateSdsRotationApiTest : public testing::TestWithParam, + public SdsRotationApiTest { +protected: + TlsCertificateSdsRotationApiTest() + : watched_directory_(GetParam()), cert_path_("/foo/bar/cert.pem"), + key_path_("/foo/bar/key.pem"), expected_watch_path_("/foo/bar/"), trigger_path_("/foo") { + envoy::config::core::v3::ConfigSource config_source; + sds_api_ = std::make_unique( + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, mock_dispatcher_, *api_); + sds_api_->registerInitTarget(init_manager_); + initialize(); + handle_ = sds_api_->addUpdateCallback([this]() { secret_callback_.onAddOrUpdateSecret(); }); + } + + ~TlsCertificateSdsRotationApiTest() override { handle_->remove(); } + + void onConfigUpdate(const std::string& cert_value, const std::string& key_value) { + const std::string yaml = fmt::format( + R"EOF( + name: "abc.com" + tls_certificate: + certificate_chain: + filename: "{}" + private_key: + filename: "{}" + )EOF", + cert_path_, key_path_); + envoy::extensions::transport_sockets::tls::v3::Secret typed_secret; + TestUtility::loadFromYaml(yaml, typed_secret); + if (watched_directory_) { + typed_secret.mutable_tls_certificate()->mutable_watched_directory()->set_path(trigger_path_); + } + const auto decoded_resources = TestUtility::decodeResources({typed_secret}); + + auto* watcher = new Filesystem::MockWatcher(); + if (watched_directory_) { + EXPECT_CALL(mock_dispatcher_, createFilesystemWatcher_()).WillOnce(Return(watcher)); + EXPECT_CALL(*watcher, addWatch(trigger_path_ + "/", Filesystem::Watcher::Events::MovedTo, _)) + .WillOnce( + Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + watch_cbs_.push_back(cb); + })); + EXPECT_CALL(filesystem_, fileReadToEnd(cert_path_)).WillOnce(Return(cert_value)); + EXPECT_CALL(filesystem_, fileReadToEnd(key_path_)).WillOnce(Return(key_value)); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + } else { + EXPECT_CALL(filesystem_, fileReadToEnd(cert_path_)).WillOnce(Return(cert_value)); + EXPECT_CALL(filesystem_, fileReadToEnd(key_path_)).WillOnce(Return(key_value)); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + EXPECT_CALL(mock_dispatcher_, createFilesystemWatcher_()).WillOnce(Return(watcher)); + EXPECT_CALL(*watcher, addWatch(expected_watch_path_, Filesystem::Watcher::Events::MovedTo, _)) + .Times(2) + .WillRepeatedly( + Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + watch_cbs_.push_back(cb); + })); + } + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + } + + const bool watched_directory_; + std::string cert_path_; + std::string key_path_; + std::string expected_watch_path_; + std::string trigger_path_; + std::unique_ptr sds_api_; +}; + +INSTANTIATE_TEST_SUITE_P(TlsCertificateSdsRotationApiTestParams, TlsCertificateSdsRotationApiTest, + testing::Values(false, true)); + +class CertificateValidationContextSdsRotationApiTest : public testing::TestWithParam, + public SdsRotationApiTest { +protected: + CertificateValidationContextSdsRotationApiTest() { + envoy::config::core::v3::ConfigSource config_source; + sds_api_ = std::make_unique( + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, mock_dispatcher_, *api_); + sds_api_->registerInitTarget(init_manager_); + initialize(); + handle_ = sds_api_->addUpdateCallback([this]() { secret_callback_.onAddOrUpdateSecret(); }); + } + + ~CertificateValidationContextSdsRotationApiTest() override { handle_->remove(); } + + void onConfigUpdate(const std::string& trusted_ca_path, const std::string& trusted_ca_value, + const std::string& watch_path) { + const std::string yaml = fmt::format( + R"EOF( + name: "abc.com" + validation_context: + trusted_ca: + filename: "{}" + allow_expired_certificate: true + )EOF", + trusted_ca_path); + envoy::extensions::transport_sockets::tls::v3::Secret typed_secret; + TestUtility::loadFromYaml(yaml, typed_secret); + const auto decoded_resources = TestUtility::decodeResources({typed_secret}); + + auto* watcher = new Filesystem::MockWatcher(); + EXPECT_CALL(filesystem_, fileReadToEnd(trusted_ca_path)).WillOnce(Return(trusted_ca_value)); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + EXPECT_CALL(mock_dispatcher_, createFilesystemWatcher_()).WillOnce(Return(watcher)); + EXPECT_CALL(*watcher, addWatch(watch_path, Filesystem::Watcher::Events::MovedTo, _)) + .WillOnce(Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + watch_cbs_.push_back(cb); + })); + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + } + + std::unique_ptr sds_api_; +}; + +INSTANTIATE_TEST_SUITE_P(CertificateValidationContextSdsRotationApiTestParams, + CertificateValidationContextSdsRotationApiTest, + testing::Values(false, true)); + +// Initial onConfigUpdate() of TlsCertificate secret. +TEST_P(TlsCertificateSdsRotationApiTest, InitialUpdate) { + InSequence s; + onConfigUpdate("a", "b"); + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); +} + +// Two distinct updates with onConfigUpdate() of TlsCertificate secret. +TEST_P(TlsCertificateSdsRotationApiTest, MultiUpdate) { + InSequence s; + onConfigUpdate("a", "b"); + { + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); + } + + cert_path_ = "/new/foo/bar/cert.pem"; + key_path_ = "/new/foo/bar/key.pem"; + expected_watch_path_ = "/new/foo/bar/"; + onConfigUpdate("c", "d"); + { + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("d", secret.private_key().inline_bytes()); + } +} + +// Watch trigger without file change has no effect. +TEST_P(TlsCertificateSdsRotationApiTest, NopWatchTrigger) { + InSequence s; + onConfigUpdate("a", "b"); + + for (const auto& cb : watch_cbs_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("b")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("b")); + cb(Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); +} + +// Basic rotation of TlsCertificate. +TEST_P(TlsCertificateSdsRotationApiTest, RotationWatchTrigger) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo); + if (!watched_directory_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + watch_cbs_[1](Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("d", secret.private_key().inline_bytes()); +} + +// Failed rotation of TlsCertificate. +TEST_P(TlsCertificateSdsRotationApiTest, FailedRotation) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")) + .WillOnce(Throw(EnvoyException("fail"))); + EXPECT_LOG_CONTAINS("warn", "Failed to reload certificates: ", + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo)); + EXPECT_EQ(1U, stats_.counter("sds.abc.com.key_rotation_failed").value()); + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); +} + +// Basic rotation of CertificateValidationContext. +TEST_P(CertificateValidationContextSdsRotationApiTest, CertificateValidationContext) { + InSequence s; + onConfigUpdate("/foo/bar/ca.pem", "a", "/foo/bar/"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/ca.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/ca.pem")).WillOnce(Return("c")); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo); + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.trusted_ca().inline_bytes()); +} + +// Hash consistency verification prevents races. +TEST_P(TlsCertificateSdsRotationApiTest, RotationConsistency) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo); + if (!watched_directory_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + watch_cbs_[1](Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("d", secret.private_key().inline_bytes()); +} + +// Hash consistency verification failure, no callback. +TEST_P(TlsCertificateSdsRotationApiTest, RotationConsistencyExhaustion) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("e")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("g")); + // We've exhausted the bounded retries, but continue with the non-atomic rotation. + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + EXPECT_LOG_CONTAINS( + "warn", "Unable to atomically refresh secrets due to > 5 non-atomic rotations observed", + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo)); + if (!watched_directory_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("g")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("g")); + watch_cbs_[1](Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("f", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("g", secret.private_key().inline_bytes()); +} + class PartialMockSds : public SdsApi { public: - PartialMockSds(NiceMock& server, NiceMock& init_manager, + PartialMockSds(Stats::Store& stats, NiceMock& init_manager, envoy::config::core::v3::ConfigSource& config_source, Config::SubscriptionFactory& subscription_factory, TimeSource& time_source, Event::Dispatcher& dispatcher, Api::Api& api) : SdsApi( - config_source, "abc.com", subscription_factory, time_source, validation_visitor_, - server.stats(), []() {}, dispatcher, api) { + config_source, "abc.com", subscription_factory, time_source, validation_visitor_, stats, + []() {}, dispatcher, api) { registerInitTarget(init_manager); } @@ -202,6 +512,7 @@ class PartialMockSds : public SdsApi { void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} std::vector getDataSourceFilenames() override { return {}; } + Config::WatchedDirectory* getWatchedDirectory() override { return nullptr; } NiceMock validation_visitor_; }; @@ -214,11 +525,10 @@ TEST_F(SdsApiTest, Delta) { Config::DecodedResourceImpl resource(std::move(secret), "name", {}, "version1"); std::vector resources{resource}; - NiceMock server; envoy::config::core::v3::ConfigSource config_source; Event::GlobalTimeSystem time_system; setupMocks(); - PartialMockSds sds(server, init_manager_, config_source, subscription_factory_, time_system, + PartialMockSds sds(stats_, init_manager_, config_source, subscription_factory_, time_system, *dispatcher_, *api_); initialize(); EXPECT_CALL(sds, onConfigUpdate(DecodedResourcesEq(resources), "version1")); @@ -238,12 +548,11 @@ TEST_F(SdsApiTest, Delta) { // Tests SDS's use of the delta variant of onConfigUpdate(). TEST_F(SdsApiTest, DeltaUpdateSuccess) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -284,12 +593,11 @@ TEST_F(SdsApiTest, DeltaUpdateSuccess) { // Validate that CertificateValidationContextSdsApi updates secrets successfully if // a good secret is passed to onConfigUpdate(). TEST_F(SdsApiTest, DynamicCertificateValidationContextUpdateSuccess) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); CertificateValidationContextSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -339,12 +647,11 @@ class MockCvcValidationCallback : public CvcValidationCallback { // a good secret is passed to onConfigUpdate(), and that merged CertificateValidationContext // provides correct information. TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); CertificateValidationContextSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -428,12 +735,11 @@ class MockGenericSecretValidationCallback : public GenericSecretValidationCallba // Validate that GenericSecretSdsApi updates secrets successfully if // a good secret is passed to onConfigUpdate(). TEST_F(SdsApiTest, GenericSecretSdsApiTest) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); GenericSecretSdsApi sds_api( config_source, "encryption_key", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + stats_, []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -474,12 +780,11 @@ name: "encryption_key" // Validate that SdsApi throws exception if an empty secret is passed to onConfigUpdate(). TEST_F(SdsApiTest, EmptyResource) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); initialize(); @@ -490,12 +795,11 @@ TEST_F(SdsApiTest, EmptyResource) { // Validate that SdsApi throws exception if multiple secrets are passed to onConfigUpdate(). TEST_F(SdsApiTest, SecretUpdateWrongSize) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); std::string yaml = @@ -521,12 +825,11 @@ TEST_F(SdsApiTest, SecretUpdateWrongSize) { // Validate that SdsApi throws exception if secret name passed to onConfigUpdate() // does not match configured name. TEST_F(SdsApiTest, SecretUpdateWrongSecretName) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); std::string yaml = diff --git a/test/common/stats/tag_extractor_impl_test.cc b/test/common/stats/tag_extractor_impl_test.cc index 18ff4d6ec88c..13fd4de172c9 100644 --- a/test/common/stats/tag_extractor_impl_test.cc +++ b/test/common/stats/tag_extractor_impl_test.cc @@ -15,7 +15,21 @@ namespace Envoy { namespace Stats { TEST(TagExtractorTest, TwoSubexpressions) { - TagExtractorImpl tag_extractor("cluster_name", "^cluster\\.((.+?)\\.)"); + TagExtractorStdRegexImpl tag_extractor("cluster_name", "^cluster\\.((.+?)\\.)"); + EXPECT_EQ("cluster_name", tag_extractor.name()); + std::string name = "cluster.test_cluster.upstream_cx_total"; + TagVector tags; + IntervalSetImpl remove_characters; + ASSERT_TRUE(tag_extractor.extractTag(name, tags, remove_characters)); + std::string tag_extracted_name = StringUtil::removeCharacters(name, remove_characters); + EXPECT_EQ("cluster.upstream_cx_total", tag_extracted_name); + ASSERT_EQ(1, tags.size()); + EXPECT_EQ("test_cluster", tags.at(0).value_); + EXPECT_EQ("cluster_name", tags.at(0).name_); +} + +TEST(TagExtractorTest, RE2Variants) { + TagExtractorRe2Impl tag_extractor("cluster_name", "^cluster\\.(([^\\.]+)\\.).*"); EXPECT_EQ("cluster_name", tag_extractor.name()); std::string name = "cluster.test_cluster.upstream_cx_total"; TagVector tags; @@ -29,7 +43,7 @@ TEST(TagExtractorTest, TwoSubexpressions) { } TEST(TagExtractorTest, SingleSubexpression) { - TagExtractorImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)"); + TagExtractorStdRegexImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)"); std::string name = "listener.80.downstream_cx_total"; TagVector tags; IntervalSetImpl remove_characters; @@ -42,24 +56,26 @@ TEST(TagExtractorTest, SingleSubexpression) { } TEST(TagExtractorTest, substrMismatch) { - TagExtractorImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\.", ".foo."); + TagExtractorStdRegexImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\.", + ".foo."); EXPECT_TRUE(tag_extractor.substrMismatch("listener.80.downstream_cx_total")); EXPECT_FALSE(tag_extractor.substrMismatch("listener.80.downstream_cx_total.foo.bar")); } TEST(TagExtractorTest, noSubstrMismatch) { - TagExtractorImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\."); + TagExtractorStdRegexImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\."); EXPECT_FALSE(tag_extractor.substrMismatch("listener.80.downstream_cx_total")); EXPECT_FALSE(tag_extractor.substrMismatch("listener.80.downstream_cx_total.foo.bar")); } TEST(TagExtractorTest, EmptyName) { - EXPECT_THROW_WITH_MESSAGE(TagExtractorImpl::createTagExtractor("", "^listener\\.(\\d+?\\.)"), - EnvoyException, "tag_name cannot be empty"); + EXPECT_THROW_WITH_MESSAGE( + TagExtractorStdRegexImpl::createTagExtractor("", "^listener\\.(\\d+?\\.)"), EnvoyException, + "tag_name cannot be empty"); } TEST(TagExtractorTest, BadRegex) { - EXPECT_THROW_WITH_REGEX(TagExtractorImpl::createTagExtractor("cluster_name", "+invalid"), + EXPECT_THROW_WITH_REGEX(TagExtractorStdRegexImpl::createTagExtractor("cluster_name", "+invalid"), EnvoyException, "Invalid regex '\\+invalid':"); } @@ -361,7 +377,7 @@ TEST(TagExtractorTest, DefaultTagExtractors) { TEST(TagExtractorTest, ExtractRegexPrefix) { TagExtractorPtr tag_extractor; // Keep tag_extractor in this scope to prolong prefix lifetime. auto extractRegexPrefix = [&tag_extractor](const std::string& regex) -> absl::string_view { - tag_extractor = TagExtractorImpl::createTagExtractor("foo", regex); + tag_extractor = TagExtractorStdRegexImpl::createTagExtractor("foo", regex); return tag_extractor->prefixToken(); }; @@ -376,7 +392,7 @@ TEST(TagExtractorTest, ExtractRegexPrefix) { } TEST(TagExtractorTest, CreateTagExtractorNoRegex) { - EXPECT_THROW_WITH_REGEX(TagExtractorImpl::createTagExtractor("no such default tag", ""), + EXPECT_THROW_WITH_REGEX(TagExtractorStdRegexImpl::createTagExtractor("no such default tag", ""), EnvoyException, "^No regex specified for tag specifier and no default"); } diff --git a/test/common/stats/thread_local_store_speed_test.cc b/test/common/stats/thread_local_store_speed_test.cc index 3eff52a78d97..5208fdaed9f5 100644 --- a/test/common/stats/thread_local_store_speed_test.cc +++ b/test/common/stats/thread_local_store_speed_test.cc @@ -79,6 +79,7 @@ class ThreadLocalStorePerf { // Tests the single-threaded performance of the thread-local-store stats caches // without having initialized tls. +// NOLINTNEXTLINE(readability-identifier-naming) static void BM_StatsNoTls(benchmark::State& state) { Envoy::ThreadLocalStorePerf context; @@ -91,6 +92,7 @@ BENCHMARK(BM_StatsNoTls); // Tests the single-threaded performance of the thread-local-store stats caches // with tls. Note that this test is still single-threaded, and so there's only // one replica of the tls cache. +// NOLINTNEXTLINE(readability-identifier-naming) static void BM_StatsWithTls(benchmark::State& state) { Envoy::ThreadLocalStorePerf context; context.initThreading(); diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index 9e97d323d43d..71bf69be9df7 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -55,8 +55,8 @@ class ThreadLocalStoreTestingPeer { const std::function& num_tls_hist_cb) { auto num_tls_histograms = std::make_shared>(0); thread_local_store_impl.tls_cache_->runOnAllThreads( - [num_tls_histograms](ThreadLocalStoreImpl::TlsCache& tls_cache) { - *num_tls_histograms += tls_cache.tls_histogram_cache_.size(); + [num_tls_histograms](OptRef tls_cache) { + *num_tls_histograms += tls_cache->tls_histogram_cache_.size(); }, [num_tls_hist_cb, num_tls_histograms]() { num_tls_hist_cb(*num_tls_histograms); }); } diff --git a/test/common/tcp/conn_pool_test.cc b/test/common/tcp/conn_pool_test.cc index 838cc9a64035..f81d9ffcd334 100644 --- a/test/common/tcp/conn_pool_test.cc +++ b/test/common/tcp/conn_pool_test.cc @@ -67,6 +67,22 @@ struct ConnPoolCallbacks : public Tcp::ConnectionPool::Callbacks { Upstream::HostDescriptionConstSharedPtr host_; }; +class TestActiveTcpClient : public ActiveTcpClient { +public: + using ActiveTcpClient::ActiveTcpClient; + + ~TestActiveTcpClient() override { parent().onConnDestroyed(); } + void clearCallbacks() override { + if (state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY || + state_ == Envoy::ConnectionPool::ActiveClient::State::DRAINING) { + parent().onConnReleased(*this); + } + ActiveTcpClient::clearCallbacks(); + } + + Envoy::Tcp::ConnPoolImpl& parent() { return *static_cast(&parent_); } +}; + /** * A wrapper around a ConnectionPoolImpl which tracks when the bridge between * the pool and the consumer of the connection is released and destroyed. @@ -143,7 +159,13 @@ class ConnPoolBase : public Tcp::ConnectionPool::Instance { parent_.onConnReleasedForTest(); } + Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override { + return std::make_unique( + *this, Envoy::ConnectionPool::ConnPoolImplBase::host(), 1); + } + void onConnDestroyed() override { parent_.onConnDestroyedForTest(); } + Event::PostCb post_cb_; ConnPoolBase& parent_; }; @@ -202,12 +224,11 @@ void ConnPoolBase::expectEnableUpstreamReady(bool run) { if (!test_new_connection_pool_) { dynamic_cast(conn_pool_.get())->expectEnableUpstreamReady(run); } else { - if (!run) { - EXPECT_CALL(*mock_upstream_ready_cb_, scheduleCallbackCurrentIteration()) - .Times(1) - .RetiresOnSaturation(); + Event::PostCb& post_cb = dynamic_cast(conn_pool_.get())->post_cb_; + if (run) { + post_cb(); } else { - mock_upstream_ready_cb_->invokeCallback(); + EXPECT_CALL(mock_dispatcher_, post(_)).WillOnce(testing::SaveArg<0>(&post_cb)); } } } @@ -219,7 +240,9 @@ class TcpConnPoolImplTest : public testing::TestWithParam { public: TcpConnPoolImplTest() : test_new_connection_pool_(GetParam()), - upstream_ready_cb_(new NiceMock(&dispatcher_)), + upstream_ready_cb_(test_new_connection_pool_ + ? nullptr + : new NiceMock(&dispatcher_)), host_(Upstream::makeTestHost(cluster_, "tcp://127.0.0.1:9000")), conn_pool_(dispatcher_, host_, upstream_ready_cb_, test_new_connection_pool_) {} @@ -244,7 +267,9 @@ class TcpConnPoolImplDestructorTest : public testing::TestWithParam { public: TcpConnPoolImplDestructorTest() : test_new_connection_pool_(GetParam()), - upstream_ready_cb_(new NiceMock(&dispatcher_)) { + upstream_ready_cb_(test_new_connection_pool_ + ? nullptr + : new NiceMock(&dispatcher_)) { host_ = Upstream::makeTestHost(cluster_, "tcp://127.0.0.1:9000"); if (test_new_connection_pool_) { conn_pool_ = std::make_unique( diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index f9db96cbaf13..769badd15355 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -24,6 +24,7 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//source/extensions/access_loggers:well_known_names", "//source/extensions/access_loggers/file:config", + "//source/extensions/upstreams/http/generic:config", "//test/common/upstream:utility_lib", "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", @@ -33,9 +34,12 @@ envoy_cc_test( "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/upstream:host_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/http/generic/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/tcp/generic/v3:pkg_cc_proto", ], ) diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 803381c4192c..bd3111fa7dc7 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -9,6 +9,8 @@ #include "envoy/extensions/access_loggers/file/v3/file.pb.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.validate.h" +#include "envoy/extensions/upstreams/http/generic/v3/generic_connection_pool.pb.h" +#include "envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.pb.h" #include "common/buffer/buffer_impl.h" #include "common/network/address_impl.h" @@ -31,6 +33,7 @@ #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/upstream/host.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -152,6 +155,7 @@ TEST(ConfigTest, DEPRECATED_FEATURE_TEST(EmptyRouteConfig)) { } TEST(ConfigTest, DEPRECATED_FEATURE_TEST(Routes)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( stat_prefix: name cluster: cluster @@ -364,6 +368,7 @@ TEST(ConfigTest, DEPRECATED_FEATURE_TEST(Routes)) { // Tests that a deprecated_v1 route gets the top-level endpoint selector. TEST(ConfigTest, DEPRECATED_FEATURE_TEST(RouteWithTopLevelMetadataMatchConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( stat_prefix: name cluster: cluster @@ -994,7 +999,7 @@ class TcpProxyTest : public testing::Test { Upstream::HostDescriptionConstSharedPtr upstream_host_{}; }; -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DefaultRoutes)) { +TEST_F(TcpProxyTest, DefaultRoutes) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy::WeightedCluster::ClusterWeight* @@ -1009,7 +1014,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DefaultRoutes)) { } // Tests that half-closes are proxied and don't themselves cause any connection to be closed. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(HalfCloseProxy)) { +TEST_F(TcpProxyTest, HalfCloseProxy) { setup(1); EXPECT_CALL(filter_callbacks_.connection_, close(_)).Times(0); @@ -1029,8 +1034,71 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(HalfCloseProxy)) { upstream_callbacks_->onEvent(Network::ConnectionEvent::RemoteClose); } +// Test with an explicitly configured upstream. +TEST_F(TcpProxyTest, ExplicitFactory) { + // Explicitly configure an HTTP upstream, to test factory creation. + auto& info = factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_; + info->upstream_config_ = absl::make_optional(); + envoy::extensions::upstreams::tcp::generic::v3::GenericConnectionPoolProto generic_config; + info->upstream_config_.value().mutable_typed_config()->PackFrom(generic_config); + setup(1); + + raiseEventUpstreamConnected(0); + + Buffer::OwnedImpl buffer("hello"); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferEqual(&buffer), false)); + filter_->onData(buffer, false); + + Buffer::OwnedImpl response("world"); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferEqual(&response), _)); + upstream_callbacks_->onUpstreamData(response, false); + + EXPECT_CALL(filter_callbacks_.connection_, close(_)); + upstream_callbacks_->onEvent(Network::ConnectionEvent::LocalClose); +} + +// Test nothing bad happens if an invalid factory is configured. +TEST_F(TcpProxyTest, BadFactory) { + auto& info = factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_; + info->upstream_config_ = absl::make_optional(); + // The HTTP Generic connection pool is not a valid type for TCP upstreams. + envoy::extensions::upstreams::http::generic::v3::GenericConnectionPoolProto generic_config; + info->upstream_config_.value().mutable_typed_config()->PackFrom(generic_config); + + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); + + configure(config); + + upstream_connections_.push_back(std::make_unique>()); + upstream_connection_data_.push_back( + std::make_unique>()); + ON_CALL(*upstream_connection_data_.back(), connection()) + .WillByDefault(ReturnRef(*upstream_connections_.back())); + upstream_hosts_.push_back(std::make_shared>()); + conn_pool_handles_.push_back( + std::make_unique>()); + + ON_CALL(*upstream_hosts_.at(0), cluster()) + .WillByDefault( + ReturnPointee(factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_)); + EXPECT_CALL(*upstream_connections_.at(0), dispatcher()) + .WillRepeatedly(ReturnRef(filter_callbacks_.connection_.dispatcher_)); + + filter_ = std::make_unique(config_, factory_context_.cluster_manager_); + EXPECT_CALL(filter_callbacks_.connection_, enableHalfClose(true)); + EXPECT_CALL(filter_callbacks_.connection_, readDisable(true)); + filter_->initializeReadFilterCallbacks(filter_callbacks_); + filter_callbacks_.connection_.streamInfo().setDownstreamSslConnection( + filter_callbacks_.connection_.ssl()); + filter_callbacks_.connection_.streamInfo().setDownstreamLocalAddress( + filter_callbacks_.connection_.localAddress()); + filter_callbacks_.connection_.streamInfo().setDownstreamRemoteAddress( + filter_callbacks_.connection_.remoteAddress()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onNewConnection()); +} + // Test that downstream is closed after an upstream LocalClose. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamLocalDisconnect)) { +TEST_F(TcpProxyTest, UpstreamLocalDisconnect) { setup(1); raiseEventUpstreamConnected(0); @@ -1048,7 +1116,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamLocalDisconnect)) { } // Test that downstream is closed after an upstream RemoteClose. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamRemoteDisconnect)) { +TEST_F(TcpProxyTest, UpstreamRemoteDisconnect) { setup(1); raiseEventUpstreamConnected(0); @@ -1066,7 +1134,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamRemoteDisconnect)) { } // Test that reconnect is attempted after a local connect failure -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFail)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -1081,7 +1149,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFail)) } // Make sure that the tcp proxy code handles reentrant calls to onPoolFailure. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFailReentrant)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -1106,7 +1174,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFailRee } // Test that reconnect is attempted after a remote connect failure -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamRemoteFail)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -1120,7 +1188,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamRemoteFail)) } // Test that reconnect is attempted after a connect timeout -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamTimeout)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -1134,7 +1202,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamTimeout)) { } // Test that only the configured number of connect attempts occur -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsLimit)) { +TEST_F(TcpProxyTest, ConnectAttemptsLimit) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = accessLogConfig("%RESPONSE_FLAGS%"); config.mutable_max_connect_attempts()->set_value(3); @@ -1186,7 +1254,7 @@ TEST_F(TcpProxyTest, OutlierDetection) { raiseEventUpstreamConnected(2); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamDisconnectDownstreamFlowControl)) { +TEST_F(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { setup(1); raiseEventUpstreamConnected(0); @@ -1208,7 +1276,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamDisconnectDownstreamFlowCon filter_callbacks_.connection_.runLowWatermarkCallbacks(); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectRemote)) { +TEST_F(TcpProxyTest, DownstreamDisconnectRemote) { setup(1); raiseEventUpstreamConnected(0); @@ -1225,7 +1293,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectRemote)) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectLocal)) { +TEST_F(TcpProxyTest, DownstreamDisconnectLocal) { setup(1); raiseEventUpstreamConnected(0); @@ -1242,7 +1310,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectLocal)) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectTimeout)) { +TEST_F(TcpProxyTest, UpstreamConnectTimeout) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); @@ -1252,14 +1320,14 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectTimeout)) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(NoHost)) { +TEST_F(TcpProxyTest, NoHost) { EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); setup(0, accessLogConfig("%RESPONSE_FLAGS%")); filter_.reset(); EXPECT_EQ(access_log_data_, "UH"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(RouteWithMetadataMatch)) { +TEST_F(TcpProxyTest, RouteWithMetadataMatch) { auto v1 = ProtobufWkt::Value(); v1.set_string_value("v1"); auto v2 = ProtobufWkt::Value(); @@ -1475,7 +1543,7 @@ TEST_F(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { EXPECT_EQ(hv2, effective_criterions[2]->value()); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DisconnectBeforeData)) { +TEST_F(TcpProxyTest, DisconnectBeforeData) { configure(defaultConfig()); filter_ = std::make_unique(config_, factory_context_.cluster_manager_); filter_->initializeReadFilterCallbacks(filter_callbacks_); @@ -1485,7 +1553,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DisconnectBeforeData)) { // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(RemoteClosedBeforeUpstreamConnected)) { +TEST_F(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1493,13 +1561,13 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(RemoteClosedBeforeUpstreamConnected // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(LocalClosetBeforeUpstreamConnected)) { +TEST_F(TcpProxyTest, LocalClosetBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectFailure)) { +TEST_F(TcpProxyTest, UpstreamConnectFailure) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); @@ -1509,7 +1577,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectFailure)) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectionLimit)) { +TEST_F(TcpProxyTest, UpstreamConnectionLimit) { configure(accessLogConfig("%RESPONSE_FLAGS%")); factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_->resetResourceManager( 0, 0, 0, 0, 0); @@ -1527,7 +1595,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectionLimit)) { // Tests that the idle timer closes both connections, and gets updated when either // connection has activity. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeout)) { +TEST_F(TcpProxyTest, IdleTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1557,7 +1625,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeout)) { } // Tests that the idle timer is disabled when the downstream connection is closed. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledDownstreamClose)) { +TEST_F(TcpProxyTest, IdleTimerDisabledDownstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1571,7 +1639,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledDownstreamClose)) } // Tests that the idle timer is disabled when the upstream connection is closed. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledUpstreamClose)) { +TEST_F(TcpProxyTest, IdleTimerDisabledUpstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1585,7 +1653,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledUpstreamClose)) { } // Tests that flushing data during an idle timeout doesn't cause problems. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeoutWithOutstandingDataFlushed)) { +TEST_F(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1634,7 +1702,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeoutWithOutstandingDataFlush } // Test that access log fields %UPSTREAM_HOST% and %UPSTREAM_CLUSTER% are correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamHost)) { +TEST_F(TcpProxyTest, AccessLogUpstreamHost) { setup(1, accessLogConfig("%UPSTREAM_HOST% %UPSTREAM_CLUSTER%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1643,7 +1711,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamHost)) { } // Test that access log field %UPSTREAM_LOCAL_ADDRESS% is correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamLocalAddress)) { +TEST_F(TcpProxyTest, AccessLogUpstreamLocalAddress) { setup(1, accessLogConfig("%UPSTREAM_LOCAL_ADDRESS%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1652,7 +1720,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamLocalAddress)) { } // Test that access log fields %DOWNSTREAM_PEER_URI_SAN% is correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogPeerUriSan)) { +TEST_F(TcpProxyTest, AccessLogPeerUriSan) { filter_callbacks_.connection_.local_address_ = Network::Utility::resolveUrl("tcp://1.1.1.2:20000"); filter_callbacks_.connection_.remote_address_ = @@ -1670,7 +1738,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogPeerUriSan)) { } // Test that access log fields %DOWNSTREAM_TLS_SESSION_ID% is correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogTlsSessionId)) { +TEST_F(TcpProxyTest, AccessLogTlsSessionId) { filter_callbacks_.connection_.local_address_ = Network::Utility::resolveUrl("tcp://1.1.1.2:20000"); filter_callbacks_.connection_.remote_address_ = @@ -1690,7 +1758,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogTlsSessionId)) { // Test that access log fields %DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% and // %DOWNSTREAM_LOCAL_ADDRESS% are correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogDownstreamAddress)) { +TEST_F(TcpProxyTest, AccessLogDownstreamAddress) { filter_callbacks_.connection_.local_address_ = Network::Utility::resolveUrl("tcp://1.1.1.2:20000"); filter_callbacks_.connection_.remote_address_ = @@ -1701,7 +1769,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogDownstreamAddress)) { EXPECT_EQ(access_log_data_, "1.1.1.1 1.1.1.2:20000"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamSSLConnection)) { +TEST_F(TcpProxyTest, AccessLogUpstreamSSLConnection) { setup(1); NiceMock stream_info; @@ -1717,7 +1785,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamSSLConnection)) { } // Tests that upstream flush works properly with no idle timeout configured. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushNoTimeout)) { +TEST_F(TcpProxyTest, UpstreamFlushNoTimeout) { setup(1); raiseEventUpstreamConnected(0); @@ -1742,7 +1810,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushNoTimeout)) { // Tests that upstream flush works with an idle timeout configured, but the connection // finishes draining before the timer expires. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutConfigured)) { +TEST_F(TcpProxyTest, UpstreamFlushTimeoutConfigured) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1774,7 +1842,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutConfigured)) { } // Tests that upstream flush closes the connection when the idle timeout fires. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutExpired)) { +TEST_F(TcpProxyTest, UpstreamFlushTimeoutExpired) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1803,7 +1871,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutExpired)) { // Tests that upstream flush will close a connection if it reads data from the upstream // connection after the downstream connection is closed (nowhere to send it). -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushReceiveUpstreamData)) { +TEST_F(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { setup(1); raiseEventUpstreamConnected(0); @@ -1890,6 +1958,7 @@ class TcpProxyRoutingTest : public testing::Test { }; TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(NonRoutableConnection)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); const uint32_t total_cx = config_->stats().downstream_cx_total_.value(); @@ -1911,6 +1980,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(NonRoutableConnection)) { } TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(RoutableConnection)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); const uint32_t total_cx = config_->stats().downstream_cx_total_.value(); @@ -1933,6 +2003,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(RoutableConnection)) { // Test that the tcp proxy uses the cluster from FilterState if set TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UseClusterFromPerConnectionCluster)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); initializeFilter(); @@ -1950,6 +2021,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UseClusterFromPerConnectionC // Test that the tcp proxy forwards the requested server name from FilterState if set TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UpstreamServerName)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); initializeFilter(); @@ -1980,6 +2052,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UpstreamServerName)) { // Test that the tcp proxy override ALPN from FilterState if set TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(ApplicationProtocols)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); initializeFilter(); diff --git a/test/common/thread_local/thread_local_impl_test.cc b/test/common/thread_local/thread_local_impl_test.cc index 0e203e77a94f..59bdd6d0080b 100644 --- a/test/common/thread_local/thread_local_impl_test.cc +++ b/test/common/thread_local/thread_local_impl_test.cc @@ -33,7 +33,7 @@ class ThreadLocalInstanceImplTest : public testing::Test { MOCK_METHOD(ThreadLocalObjectSharedPtr, createThreadLocal, (Event::Dispatcher & dispatcher)); - TestThreadLocalObject& setObject(Slot& slot) { + TestThreadLocalObject& setObject(TypedSlot<>& slot) { std::shared_ptr object(new TestThreadLocalObject()); TestThreadLocalObject& object_ref = *object; EXPECT_CALL(thread_dispatcher_, post(_)); @@ -57,13 +57,13 @@ TEST_F(ThreadLocalInstanceImplTest, All) { // Free a slot without ever calling set. EXPECT_CALL(thread_dispatcher_, post(_)); - SlotPtr slot1 = tls_.allocateSlot(); + TypedSlotPtr<> slot1 = TypedSlot<>::makeUnique(tls_); slot1.reset(); EXPECT_EQ(freeSlotIndexesListSize(), 1); // Create a new slot which should take the place of the old slot. ReturnPointee() is used to // avoid "leaks" when using InSequence and shared_ptr. - SlotPtr slot2 = tls_.allocateSlot(); + TypedSlotPtr<> slot2 = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref2 = setObject(*slot2); EXPECT_EQ(freeSlotIndexesListSize(), 0); @@ -75,9 +75,9 @@ TEST_F(ThreadLocalInstanceImplTest, All) { // Make two new slots, shutdown global threading, and delete them. We should not see any // cross-thread posts at this point. We should also see destruction in reverse order. - SlotPtr slot3 = tls_.allocateSlot(); + TypedSlotPtr<> slot3 = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref3 = setObject(*slot3); - SlotPtr slot4 = tls_.allocateSlot(); + TypedSlotPtr<> slot4 = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref4 = setObject(*slot4); tls_.shutdownGlobalThreading(); @@ -95,76 +95,81 @@ struct ThreadStatus { bool all_threads_complete_ = false; }; -TEST_F(ThreadLocalInstanceImplTest, CallbackNotInvokedAfterDeletion) { - InSequence s; +// Test helper class for running two similar tests, covering 4 variants of +// runOnAllThreads: with/without completion callback, and with/without the slot +// data as an argument. +class CallbackNotInvokedAfterDeletionTest : public ThreadLocalInstanceImplTest { +protected: + CallbackNotInvokedAfterDeletionTest() : slot_(TypedSlot<>::makeUnique(tls_)) { + EXPECT_CALL(thread_dispatcher_, post(_)).Times(4).WillRepeatedly(Invoke([&](Event::PostCb cb) { + // Holds the posted callback. + holder_.push_back(cb); + })); + + slot_->set([this](Event::Dispatcher&) { + // Callbacks happen on the main thread but not the workers, so track the total. + total_callbacks_++; + return std::make_shared(); + }); + } + + ~CallbackNotInvokedAfterDeletionTest() override { + EXPECT_FALSE(thread_status_.all_threads_complete_); + EXPECT_EQ(2, total_callbacks_); + slot_.reset(); + EXPECT_EQ(freeSlotIndexesListSize(), 1); + + EXPECT_CALL(main_dispatcher_, post(_)); + while (!holder_.empty()) { + holder_.front()(); + holder_.pop_front(); + } + EXPECT_EQ(2, total_callbacks_); + EXPECT_TRUE(thread_status_.all_threads_complete_); + + tls_.shutdownGlobalThreading(); + } // Allocate a slot and invoke all callback variants. Hold all callbacks and destroy the slot. // Make sure that recycling happens appropriately. - SlotPtr slot = tls_.allocateSlot(); - - std::list holder; - EXPECT_CALL(thread_dispatcher_, post(_)).Times(4).WillRepeatedly(Invoke([&](Event::PostCb cb) { - // Holds the posted callback. - holder.push_back(cb); - })); + TypedSlotPtr<> slot_; + std::list holder_; + uint32_t total_callbacks_{0}; + ThreadStatus thread_status_; +}; - uint32_t total_callbacks = 0; - slot->set([&total_callbacks](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - // Callbacks happen on the main thread but not the workers, so track the total. - total_callbacks++; - return nullptr; - }); - slot->runOnAllThreads([&total_callbacks](ThreadLocal::ThreadLocalObjectSharedPtr) - -> ThreadLocal::ThreadLocalObjectSharedPtr { +TEST_F(CallbackNotInvokedAfterDeletionTest, WithData) { + InSequence s; + slot_->runOnAllThreads([this](OptRef obj) { + EXPECT_TRUE(obj.has_value()); // Callbacks happen on the main thread but not the workers, so track the total. - total_callbacks++; - return nullptr; + total_callbacks_++; }); - ThreadStatus thread_status; - slot->runOnAllThreads( - [&thread_status]( - ThreadLocal::ThreadLocalObjectSharedPtr) -> ThreadLocal::ThreadLocalObjectSharedPtr { - ++thread_status.thread_local_calls_; - return nullptr; + slot_->runOnAllThreads( + [this](OptRef obj) { + EXPECT_TRUE(obj.has_value()); + ++thread_status_.thread_local_calls_; }, - [&thread_status]() -> void { + [this]() { // Callbacks happen on the main thread but not the workers. - EXPECT_EQ(thread_status.thread_local_calls_, 1); - thread_status.all_threads_complete_ = true; + EXPECT_EQ(thread_status_.thread_local_calls_, 1); + thread_status_.all_threads_complete_ = true; }); - EXPECT_FALSE(thread_status.all_threads_complete_); - - EXPECT_EQ(2, total_callbacks); - slot.reset(); - EXPECT_EQ(freeSlotIndexesListSize(), 1); - - EXPECT_CALL(main_dispatcher_, post(_)); - while (!holder.empty()) { - holder.front()(); - holder.pop_front(); - } - EXPECT_EQ(2, total_callbacks); - EXPECT_TRUE(thread_status.all_threads_complete_); - - tls_.shutdownGlobalThreading(); } // Test that the update callback is called as expected, for the worker and main threads. TEST_F(ThreadLocalInstanceImplTest, UpdateCallback) { InSequence s; - SlotPtr slot = tls_.allocateSlot(); + TypedSlot<> slot(tls_); uint32_t update_called = 0; - TestThreadLocalObject& object_ref = setObject(*slot); - auto update_cb = [&update_called](ThreadLocalObjectSharedPtr obj) -> ThreadLocalObjectSharedPtr { - ++update_called; - return obj; - }; + TestThreadLocalObject& object_ref = setObject(slot); + auto update_cb = [&update_called](OptRef) { ++update_called; }; EXPECT_CALL(thread_dispatcher_, post(_)); EXPECT_CALL(object_ref, onDestroy()); - slot->runOnAllThreads(update_cb); + slot.runOnAllThreads(update_cb); EXPECT_EQ(2, update_called); // 1 worker, 1 main thread. @@ -178,7 +183,6 @@ struct StringSlotObject : public ThreadLocalObject { TEST_F(ThreadLocalInstanceImplTest, TypedUpdateCallback) { InSequence s; - TypedSlot slot(tls_); uint32_t update_called = 0; @@ -188,16 +192,43 @@ TEST_F(ThreadLocalInstanceImplTest, TypedUpdateCallback) { s->str_ = "hello"; return s; }); - EXPECT_EQ("hello", slot.get().str_); + EXPECT_EQ("hello", slot.get()->str_); - auto update_cb = [&update_called](StringSlotObject& s) { + auto update_cb = [&update_called](OptRef s) { ++update_called; - s.str_ = "goodbye"; + EXPECT_TRUE(s.has_value()); + s->str_ = "goodbye"; }; EXPECT_CALL(thread_dispatcher_, post(_)); slot.runOnAllThreads(update_cb); - EXPECT_EQ("goodbye", slot.get().str_); + // Tests a few different ways of getting at the slot data. + EXPECT_EQ("goodbye", slot.get()->str_); + EXPECT_EQ("goodbye", slot->str_); + EXPECT_EQ("goodbye", (*slot).str_); + EXPECT_EQ(2, update_called); // 1 worker, 1 main thread. + + tls_.shutdownGlobalThreading(); + tls_.shutdownThread(); +} + +TEST_F(ThreadLocalInstanceImplTest, NoDataCallback) { + InSequence s; + TypedSlot slot(tls_); + + uint32_t update_called = 0; + EXPECT_CALL(thread_dispatcher_, post(_)); + slot.set([](Event::Dispatcher&) -> std::shared_ptr { return nullptr; }); + EXPECT_FALSE(slot.get().has_value()); + + auto update_cb = [&update_called](OptRef s) { + ++update_called; + EXPECT_FALSE(s.has_value()); + }; + EXPECT_CALL(thread_dispatcher_, post(_)); + slot.runOnAllThreads(update_cb); + + EXPECT_FALSE(slot.get().has_value()); EXPECT_EQ(2, update_called); // 1 worker, 1 main thread. tls_.shutdownGlobalThreading(); @@ -209,7 +240,7 @@ TEST_F(ThreadLocalInstanceImplTest, TypedUpdateCallback) { // Validate ThreadLocal::runOnAllThreads behavior with all_thread_complete call back. TEST_F(ThreadLocalInstanceImplTest, RunOnAllThreads) { - SlotPtr tlsptr = tls_.allocateSlot(); + TypedSlotPtr<> tlsptr = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref = setObject(*tlsptr); EXPECT_CALL(thread_dispatcher_, post(_)); @@ -218,12 +249,8 @@ TEST_F(ThreadLocalInstanceImplTest, RunOnAllThreads) { // Ensure that the thread local call back and all_thread_complete call back are called. ThreadStatus thread_status; tlsptr->runOnAllThreads( - [&thread_status](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - ++thread_status.thread_local_calls_; - return object; - }, - [&thread_status]() -> void { + [&thread_status](OptRef) { ++thread_status.thread_local_calls_; }, + [&thread_status]() { EXPECT_EQ(thread_status.thread_local_calls_, 2); thread_status.all_threads_complete_ = true; }); diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index e1561389bacb..65eeb64c1070 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -51,7 +51,6 @@ envoy_cc_test( "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], ) @@ -133,6 +132,7 @@ envoy_cc_benchmark_binary( "//test/mocks/server:instance_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -147,12 +147,12 @@ envoy_benchmark_test( ) envoy_cc_test_library( - name = "health_checker_impl_test_lib", + name = "health_check_fuzz_utils_lib", srcs = [ - "health_checker_impl_test_utils.cc", + "health_check_fuzz_test_utils.cc", ], hdrs = [ - "health_checker_impl_test_utils.h", + "health_check_fuzz_test_utils.h", ], deps = [ ":utility_lib", @@ -171,7 +171,7 @@ envoy_cc_test( "health_checker_impl_test.cc", ], deps = [ - ":health_checker_impl_test_lib", + ":utility_lib", "//source/common/buffer:buffer_lib", "//source/common/event:dispatcher_lib", "//source/common/http:headers_lib", @@ -405,6 +405,7 @@ envoy_cc_test( "//test/mocks/server:instance_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", ], @@ -607,6 +608,7 @@ envoy_cc_test( "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:priority_set_mocks", "//test/test_common:registry_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], ) @@ -712,7 +714,7 @@ envoy_cc_test_library( hdrs = ["health_check_fuzz.h"], deps = [ ":health_check_fuzz_proto_cc_proto", - ":health_checker_impl_test_lib", + ":health_check_fuzz_utils_lib", ":utility_lib", "//test/fuzz:utility_lib", ], @@ -737,3 +739,63 @@ envoy_cc_fuzz_test( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) + +envoy_proto_library( + name = "zone_aware_load_balancer_fuzz_proto", + srcs = ["zone_aware_load_balancer_fuzz.proto"], + deps = [ + "//test/common/upstream:load_balancer_fuzz_proto", + ], +) + +envoy_cc_test_library( + name = "zone_aware_load_balancer_fuzz_lib", + srcs = ["zone_aware_load_balancer_fuzz_base.cc"], + hdrs = ["zone_aware_load_balancer_fuzz_base.h"], + deps = [ + ":load_balancer_fuzz_lib", + ":zone_aware_load_balancer_fuzz_proto_cc_proto", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:priority_set_mocks", + ], +) + +envoy_proto_library( + name = "round_robin_load_balancer_fuzz_proto", + srcs = ["round_robin_load_balancer_fuzz.proto"], + deps = [ + "//test/common/upstream:zone_aware_load_balancer_fuzz_proto", + ], +) + +envoy_cc_fuzz_test( + name = "round_robin_load_balancer_fuzz_test", + srcs = ["round_robin_load_balancer_fuzz_test.cc"], + corpus = "//test/common/upstream:round_robin_load_balancer_corpus", + deps = [ + ":round_robin_load_balancer_fuzz_proto_cc_proto", + ":utility_lib", + ":zone_aware_load_balancer_fuzz_lib", + "//test/fuzz:utility_lib", + ], +) + +envoy_proto_library( + name = "least_request_load_balancer_fuzz_proto", + srcs = ["least_request_load_balancer_fuzz.proto"], + deps = [ + "//test/common/upstream:zone_aware_load_balancer_fuzz_proto", + "@envoy_api//envoy/config/cluster/v3:pkg", + ], +) + +envoy_cc_fuzz_test( + name = "least_request_load_balancer_fuzz_test", + srcs = ["least_request_load_balancer_fuzz_test.cc"], + corpus = "//test/common/upstream:least_request_load_balancer_corpus", + deps = [ + ":least_request_load_balancer_fuzz_proto_cc_proto", + ":utility_lib", + ":zone_aware_load_balancer_fuzz_lib", + ], +) diff --git a/test/common/upstream/cds_api_impl_test.cc b/test/common/upstream/cds_api_impl_test.cc index f052749677fb..a914225dd9d0 100644 --- a/test/common/upstream/cds_api_impl_test.cc +++ b/test/common/upstream/cds_api_impl_test.cc @@ -54,16 +54,20 @@ class CdsApiImplTest : public testing::Test { .WillOnce(Throw(EnvoyException(exception_msg))); } - ClusterManager::ClusterInfoMap makeClusterMap(const std::vector& clusters) { - ClusterManager::ClusterInfoMap map; - for (const auto& cluster : clusters) { - map.emplace(cluster, cm_.thread_local_cluster_.cluster_); + ClusterManager::ClusterInfoMaps + makeClusterInfoMaps(const std::vector& active_clusters, + const std::vector& warming_clusters = {}) { + ClusterManager::ClusterInfoMaps maps; + for (const auto& cluster : active_clusters) { + maps.active_clusters_.emplace(cluster, cm_.thread_local_cluster_.cluster_); } - return map; + for (const auto& cluster : warming_clusters) { + maps.warming_clusters_.emplace(cluster, cm_.thread_local_cluster_.cluster_); + } + return maps; } NiceMock cm_; - Upstream::ClusterManager::ClusterInfoMap cluster_map_; Upstream::MockClusterMockPrioritySet mock_cluster_; Stats::IsolatedStoreImpl store_; CdsApiPtr cds_; @@ -92,7 +96,7 @@ version_info: '0' auto response1 = TestUtility::parseYaml(response1_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); expectAdd("cluster1", "0"); EXPECT_CALL(initialized_, ready()); EXPECT_EQ("", cds_->versionInfo()); @@ -108,7 +112,7 @@ version_info: '1' )EOF"; auto response2 = TestUtility::parseYaml(response2_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterMap({"cluster1"}))); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({"cluster1"}))); EXPECT_CALL(cm_, removeCluster("cluster1")).WillOnce(Return(true)); const auto decoded_resources_2 = TestUtility::decodeResources(response2); @@ -126,7 +130,7 @@ TEST_F(CdsApiImplTest, ValidateDuplicateClusters) { cluster_1.set_name("duplicate_cluster"); const auto decoded_resources = TestUtility::decodeResources({cluster_1, cluster_1}); - EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(cluster_map_)); + EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); EXPECT_THROW_WITH_MESSAGE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), EnvoyException, @@ -139,7 +143,7 @@ TEST_F(CdsApiImplTest, EmptyConfigUpdate) { setup(); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); cds_callbacks_->onConfigUpdate({}, ""); @@ -151,7 +155,7 @@ TEST_F(CdsApiImplTest, ConfigUpdateWith2ValidClusters) { setup(); } - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); envoy::config::cluster::v3::Cluster cluster_1; @@ -224,7 +228,7 @@ TEST_F(CdsApiImplTest, ConfigUpdateAddsSecondClusterEvenIfFirstThrows) { setup(); } - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); envoy::config::cluster::v3::Cluster cluster_1; @@ -269,7 +273,7 @@ version_info: '0' auto response1 = TestUtility::parseYaml(response1_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); expectAdd("cluster1", "0"); expectAdd("cluster2", "0"); EXPECT_CALL(initialized_, ready()); @@ -298,7 +302,7 @@ version_info: '1' auto response2 = TestUtility::parseYaml(response2_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterMap({"cluster1", "cluster2"}))); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({"cluster1", "cluster2"}))); expectAdd("cluster1", "1"); expectAdd("cluster3", "1"); EXPECT_CALL(cm_, removeCluster("cluster2")); @@ -334,7 +338,7 @@ version_info: '0' auto response1 = TestUtility::parseYaml(response1_yaml); - EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(cluster_map_)); + EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); const auto decoded_resources = TestUtility::decodeResources(response1); diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 34d8403cf5d0..1e3217285dea 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -3,7 +3,6 @@ #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/config/cluster/v3/cluster.pb.validate.h" #include "envoy/config/core/v3/base.pb.h" -#include "envoy/extensions/transport_sockets/tls/v3/secret.pb.h" #include "test/common/upstream/test_cluster_manager.h" #include "test/mocks/upstream/cds_api.h" @@ -362,7 +361,7 @@ TEST_F(ClusterManagerImplTest, ValidClusterName) { create(parseBootstrapFromV3Yaml(yaml)); cluster_manager_->clusters() - .find("cluster:name") + .active_clusters_.find("cluster:name") ->second.get() .info() ->statsScope() @@ -435,7 +434,8 @@ TEST_F(ClusterManagerImplTest, OriginalDstLbRestriction) { "'CLUSTER_PROVIDED' or 'ORIGINAL_DST_LB' is allowed with cluster type 'ORIGINAL_DST'"); } -TEST_F(ClusterManagerImplTest, OriginalDstLbRestriction2) { +TEST_F(ClusterManagerImplTest, DEPRECATED_FEATURE_TEST(OriginalDstLbRestriction2)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( static_resources: clusters: @@ -556,7 +556,8 @@ INSTANTIATE_TEST_SUITE_P(ClusterManagerSubsetInitializationTest, testing::ValuesIn(ClusterManagerSubsetInitializationTest::lbPolicies()), ClusterManagerSubsetInitializationTest::paramName); -TEST_F(ClusterManagerImplTest, SubsetLoadBalancerOriginalDstRestriction) { +TEST_F(ClusterManagerImplTest, DEPRECATED_FEATURE_TEST(SubsetLoadBalancerOriginalDstRestriction)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( static_resources: clusters: @@ -1235,6 +1236,105 @@ TEST_F(ClusterManagerImplTest, RemoveWarmingCluster) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); } +TEST_F(ClusterManagerImplTest, TestModifyWarmingClusterDuringInitialization) { + const std::string json = fmt::sprintf( + R"EOF( + { + "dynamic_resources": { + "cds_config": { + "api_config_source": { + "api_type": "0", + "refresh_delay": "30s", + "cluster_names": ["cds_cluster"] + } + } + }, + "static_resources": { + %s + } + } + )EOF", + clustersJson({ + defaultStaticClusterJson("cds_cluster"), + })); + + MockCdsApi* cds = new MockCdsApi(); + std::shared_ptr cds_cluster( + new NiceMock()); + cds_cluster->info_->name_ = "cds_cluster"; + + // This part tests static init. + InSequence s; + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) + .WillOnce(Return(std::make_pair(cds_cluster, nullptr))); + ON_CALL(*cds_cluster, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(factory_, createCds_()).WillOnce(Return(cds)); + EXPECT_CALL(*cds, setInitializedCb(_)); + EXPECT_CALL(*cds_cluster, initialize(_)); + + create(parseBootstrapFromV3Json(json)); + + ReadyWatcher cm_initialized; + cluster_manager_->setInitializedCb([&]() -> void { cm_initialized.ready(); }); + + const std::string ready_cluster_yaml = R"EOF( + name: fake_cluster + connect_timeout: 0.250s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: fake_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11001 + )EOF"; + + const std::string warming_cluster_yaml = R"EOF( + name: fake_cluster + connect_timeout: 0.250s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: fake_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.com + port_value: 11001 + )EOF"; + + { + SCOPED_TRACE("Add a primary cluster staying in warming."); + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)); + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(parseClusterFromV3Yaml(warming_cluster_yaml), + "warming")); + + // Mark all the rest of the clusters ready. Now the only warming cluster is the above one. + EXPECT_CALL(cm_initialized, ready()).Times(0); + cds_cluster->initialize_callback_(); + } + + { + SCOPED_TRACE("Modify the only warming primary cluster to immediate ready."); + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)); + EXPECT_CALL(*cds, initialize()); + EXPECT_TRUE( + cluster_manager_->addOrUpdateCluster(parseClusterFromV3Yaml(ready_cluster_yaml), "ready")); + } + { + SCOPED_TRACE("All clusters are ready."); + EXPECT_CALL(cm_initialized, ready()); + cds->initialized_callback_(); + } + EXPECT_TRUE(Mock::VerifyAndClearExpectations(cds_cluster.get())); +} + TEST_F(ClusterManagerImplTest, ModifyWarmingCluster) { time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); create(defaultConfig()); @@ -1390,7 +1490,7 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(update_cluster, "")); EXPECT_EQ(cluster2->info_, cluster_manager_->get("fake_cluster")->info()); - EXPECT_EQ(1UL, cluster_manager_->clusters().size()); + EXPECT_EQ(1UL, cluster_manager_->clusters().active_clusters_.size()); Http::ConnectionPool::MockInstance* cp = new Http::ConnectionPool::MockInstance(); EXPECT_CALL(factory_, allocateConnPool_(_, _, _)).WillOnce(Return(cp)); EXPECT_EQ(cp, cluster_manager_->httpConnPoolForCluster("fake_cluster", ResourcePriority::Default, @@ -1420,7 +1520,7 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { EXPECT_CALL(*cp2, addDrainedCallback(_)).WillOnce(SaveArg<0>(&drained_cb2)); EXPECT_TRUE(cluster_manager_->removeCluster("fake_cluster")); EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - EXPECT_EQ(0UL, cluster_manager_->clusters().size()); + EXPECT_EQ(0UL, cluster_manager_->clusters().active_clusters_.size()); // Close the TCP connection. Success is no ASSERT or crash due to referencing // the removed cluster. @@ -2308,154 +2408,6 @@ TEST_F(ClusterManagerImplTest, DynamicHostRemoveDefaultPriority) { factory_.tls_.shutdownThread(); } -TEST_F(ClusterManagerImplTest, - DynamicAddedAndKeepWarmingWithoutCertificateValidationContextEntity) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.cluster_keep_warming_no_secret_entity", "true"}}); - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - -TEST_F(ClusterManagerImplTest, - DynamicAddedAndKeepWarmingDisabledWithoutCertificateValidationContextEntity) { - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 1 /*active*/, 0 /*warming*/); - EXPECT_EQ(0, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - -TEST_F(ClusterManagerImplTest, DynamicAddedAndKeepWarmingWithoutTlsCertificateEntity) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.cluster_keep_warming_no_secret_entity", "true"}}); - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - -TEST_F(ClusterManagerImplTest, DynamicAddedAndKeepWarmingDisabledWithoutTlsCertificateEntity) { - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 1 /*active*/, 0 /*warming*/); - EXPECT_EQ(0, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - class MockConnPoolWithDestroy : public Http::ConnectionPool::MockInstance { public: ~MockConnPoolWithDestroy() override { onDestroy(); } @@ -3083,21 +3035,39 @@ TEST_F(ClusterManagerImplTest, AddUpstreamFilters) { class ClusterManagerInitHelperTest : public testing::Test { public: - MOCK_METHOD(void, onClusterInit, (Cluster & cluster)); + MOCK_METHOD(void, onClusterInit, (ClusterManagerCluster & cluster)); NiceMock cm_; - ClusterManagerInitHelper init_helper_{cm_, [this](Cluster& cluster) { onClusterInit(cluster); }}; + ClusterManagerInitHelper init_helper_{ + cm_, [this](ClusterManagerCluster& cluster) { onClusterInit(cluster); }}; +}; + +class MockClusterManagerCluster : public ClusterManagerCluster { +public: + MockClusterManagerCluster() { ON_CALL(*this, cluster()).WillByDefault(ReturnRef(cluster_)); } + + MOCK_METHOD(Cluster&, cluster, ()); + MOCK_METHOD(LoadBalancerFactorySharedPtr, loadBalancerFactory, ()); + bool addedOrUpdated() override { return added_or_updated_; } + void setAddedOrUpdated() override { + ASSERT(!added_or_updated_); + added_or_updated_ = true; + } + + NiceMock cluster_; + bool added_or_updated_{}; }; TEST_F(ClusterManagerInitHelperTest, ImmediateInitialize) { InSequence s; - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster1, initialize(_)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.addCluster(cluster1); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); init_helper_.onStaticLoadComplete(); init_helper_.startInitializingSecondaryClusters(); @@ -3110,20 +3080,21 @@ TEST_F(ClusterManagerInitHelperTest, ImmediateInitialize) { TEST_F(ClusterManagerInitHelperTest, StaticSdsInitialize) { InSequence s; - NiceMock sds; - ON_CALL(sds, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(sds, initialize(_)); + NiceMock sds; + ON_CALL(sds.cluster_, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(sds.cluster_, initialize(_)); init_helper_.addCluster(sds); EXPECT_CALL(*this, onClusterInit(Ref(sds))); - sds.initialize_callback_(); + sds.cluster_.initialize_callback_(); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster1); init_helper_.onStaticLoadComplete(); - EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.startInitializingSecondaryClusters(); ReadyWatcher cm_initialized; @@ -3131,7 +3102,36 @@ TEST_F(ClusterManagerInitHelperTest, StaticSdsInitialize) { EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(cm_initialized, ready()); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); +} + +// Verify that primary cluster can be updated in warming state. +TEST_F(ClusterManagerInitHelperTest, TestUpdateWarming) { + InSequence s; + + auto sds = std::make_unique>(); + ON_CALL(sds->cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(sds->cluster_, initialize(_)); + init_helper_.addCluster(*sds); + init_helper_.onStaticLoadComplete(); + + NiceMock updated_sds; + ON_CALL(updated_sds.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(updated_sds.cluster_, initialize(_)); + init_helper_.addCluster(updated_sds); + + // The override cluster is added. Manually drop the previous cluster. In production flow this is + // achieved by ClusterManagerImpl. + sds.reset(); + + ReadyWatcher primary_initialized; + init_helper_.setPrimaryClustersInitializedCb([&]() -> void { primary_initialized.ready(); }); + + EXPECT_CALL(*this, onClusterInit(Ref(updated_sds))); + EXPECT_CALL(primary_initialized, ready()); + updated_sds.cluster_.initialize_callback_(); } TEST_F(ClusterManagerInitHelperTest, UpdateAlreadyInitialized) { @@ -3143,25 +3143,27 @@ TEST_F(ClusterManagerInitHelperTest, UpdateAlreadyInitialized) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster1, initialize(_)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.addCluster(cluster1); - NiceMock cluster2; - ON_CALL(cluster2, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster2, initialize(_)); + NiceMock cluster2; + ON_CALL(cluster2.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster2.cluster_, initialize(_)); init_helper_.addCluster(cluster2); init_helper_.onStaticLoadComplete(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); init_helper_.removeCluster(cluster1); EXPECT_CALL(*this, onClusterInit(Ref(cluster2))); EXPECT_CALL(primary_clusters_initialized, ready()); - cluster2.initialize_callback_(); + cluster2.cluster_.initialize_callback_(); EXPECT_CALL(cm_initialized, ready()); init_helper_.startInitializingSecondaryClusters(); @@ -3180,18 +3182,19 @@ TEST_F(ClusterManagerInitHelperTest, InitSecondaryWithoutEdsPaused) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster1); EXPECT_CALL(primary_clusters_initialized, ready()); init_helper_.onStaticLoadComplete(); - EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.startInitializingSecondaryClusters(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(cm_initialized, ready()); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); } // If secondary clusters initialization triggered inside of CdsApiImpl::onConfigUpdate()'s @@ -3207,19 +3210,20 @@ TEST_F(ClusterManagerInitHelperTest, InitSecondaryWithEdsPaused) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster1); EXPECT_CALL(primary_clusters_initialized, ready()); init_helper_.onStaticLoadComplete(); - EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.startInitializingSecondaryClusters(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(cm_initialized, ready()); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); } TEST_F(ClusterManagerInitHelperTest, AddSecondaryAfterSecondaryInit) { @@ -3231,41 +3235,48 @@ TEST_F(ClusterManagerInitHelperTest, AddSecondaryAfterSecondaryInit) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster1, initialize(_)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.addCluster(cluster1); - NiceMock cluster2; - ON_CALL(cluster2, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster2; + cluster2.cluster_.info_->name_ = "cluster2"; + ON_CALL(cluster2.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster2); init_helper_.onStaticLoadComplete(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(primary_clusters_initialized, ready()); - EXPECT_CALL(cluster2, initialize(_)); - cluster1.initialize_callback_(); + EXPECT_CALL(cluster2.cluster_, initialize(_)); + cluster1.cluster_.initialize_callback_(); init_helper_.startInitializingSecondaryClusters(); - NiceMock cluster3; - ON_CALL(cluster3, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); - EXPECT_CALL(cluster3, initialize(_)); + NiceMock cluster3; + cluster3.cluster_.info_->name_ = "cluster3"; + + ON_CALL(cluster3.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); + EXPECT_CALL(cluster3.cluster_, initialize(_)); init_helper_.addCluster(cluster3); EXPECT_CALL(*this, onClusterInit(Ref(cluster3))); - cluster3.initialize_callback_(); + cluster3.cluster_.initialize_callback_(); EXPECT_CALL(*this, onClusterInit(Ref(cluster2))); EXPECT_CALL(cm_initialized, ready()); - cluster2.initialize_callback_(); + cluster2.cluster_.initialize_callback_(); } // Tests the scenario encountered in Issue 903: The cluster was removed from // the secondary init list while traversing the list. TEST_F(ClusterManagerInitHelperTest, RemoveClusterWithinInitLoop) { InSequence s; - NiceMock cluster; - ON_CALL(cluster, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster; + ON_CALL(cluster.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster); // onStaticLoadComplete() must not initialize secondary clusters @@ -3274,7 +3285,7 @@ TEST_F(ClusterManagerInitHelperTest, RemoveClusterWithinInitLoop) { // Set up the scenario seen in Issue 903 where initialize() ultimately results // in the removeCluster() call. In the real bug this was a long and complex call // chain. - EXPECT_CALL(cluster, initialize(_)).WillOnce(Invoke([&](std::function) -> void { + EXPECT_CALL(cluster.cluster_, initialize(_)).WillOnce(Invoke([&](std::function) -> void { init_helper_.removeCluster(cluster); })); diff --git a/test/common/upstream/eds_speed_test.cc b/test/common/upstream/eds_speed_test.cc index 41b56317653a..50ee28cdd0a4 100644 --- a/test/common/upstream/eds_speed_test.cc +++ b/test/common/upstream/eds_speed_test.cc @@ -25,6 +25,7 @@ #include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "benchmark/benchmark.h" @@ -124,6 +125,7 @@ class EdsSpeedTest { auto response = std::make_unique(); response->set_type_url(type_url_); + response->set_version_info(fmt::format("version-{}", version_++)); auto* resource = response->mutable_resources()->Add(); resource->PackFrom(cluster_load_assignment); if (v2_config_) { @@ -138,9 +140,11 @@ class EdsSpeedTest { num_hosts); } + TestDeprecatedV2Api _deprecated_v2_api_; State& state_; const bool v2_config_; const std::string type_url_; + uint64_t version_{}; bool initialized_{}; Stats::IsolatedStoreImpl stats_; Config::SubscriptionStats subscription_stats_; diff --git a/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5704648171978752 b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5704648171978752 new file mode 100644 index 000000000000..31117664f39d --- /dev/null +++ b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5704648171978752 @@ -0,0 +1,84 @@ +health_check_config { + timeout { + nanos: 5 + } + interval { + nanos: 65 + } + unhealthy_threshold { + value: 538968064 + } + healthy_threshold { + value: 538968064 + } + reuse_connection { + value: true + } + grpc_health_check { + service_name: "\004" + } + unhealthy_interval { + seconds: 4227858432 + } + event_log_path: "?(" + tls_options { + } +} +actions { + trigger_timeout_timer { + } +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: REMOTE_CLOSE +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: CONNECTED +} +actions { + trigger_timeout_timer { + } +} +actions { + trigger_interval_timer { + } +} +actions { + raise_event: LOCAL_CLOSE +} +http_verify_cluster: true diff --git a/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5713695386370048 b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5713695386370048 new file mode 100644 index 000000000000..020ca3fc0c7c --- /dev/null +++ b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5713695386370048 @@ -0,0 +1,40 @@ +health_check_config { + timeout { + seconds: 512 + nanos: 8 + } + interval { + nanos: 8 + } + unhealthy_threshold { + value: 36 + } + healthy_threshold { + } + grpc_health_check { + } + no_traffic_interval { + seconds: 512 + nanos: 8 + } + initial_jitter { + seconds: 512 + nanos: 262152 + } +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + trigger_interval_timer { + } +} +actions { + trigger_timeout_timer { + } +} +http_verify_cluster: true diff --git a/test/common/upstream/health_check_corpus/grpc_Success b/test/common/upstream/health_check_corpus/grpc_Success index c33d7837d660..93e78a6acf35 100644 --- a/test/common/upstream/health_check_corpus/grpc_Success +++ b/test/common/upstream/health_check_corpus/grpc_Success @@ -58,3 +58,4 @@ actions { } } } +upstream_cx_success: true diff --git a/test/common/upstream/health_check_corpus/grpc_double_reset b/test/common/upstream/health_check_corpus/grpc_double_reset new file mode 100644 index 000000000000..aaeca5b1d548 --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_double_reset @@ -0,0 +1,46 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold { + value: 2 + } + grpc_health_check { + service_name: "service" + authority: "wwnvoyproxy.io" + } + event_log_path: "200" +} +actions { + respond { + http_respond { + status: 200 + } + tcp_respond { + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + grpc_respond_unstructured_bytes { + data: "\000\000\000\000\000\000\000\000\000\000\000\000\000\00000000000000000000000000000000000\000\000\000\000\000\000\000\000\000\000\000\000\000c_r000\000\000\000\000\000\000\000" + data: "200" + } + } + } + } +} +http_verify_cluster: true diff --git a/test/common/upstream/health_check_corpus/grpc_end_stream_headers b/test/common/upstream/health_check_corpus/grpc_end_stream_headers new file mode 100644 index 000000000000..17c167a285eb --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_end_stream_headers @@ -0,0 +1,49 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + grpc_health_check { + service_name: "service" + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/grpc_generalized-crash b/test/common/upstream/health_check_corpus/grpc_generalized-crash new file mode 100644 index 000000000000..0336f7d7e42e --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_generalized-crash @@ -0,0 +1,39 @@ +health_check_config { + timeout { + nanos: 5 + } + interval { + nanos: 65 + } + unhealthy_threshold { + value: 538968064 + } + healthy_threshold { + value: 538968064 + } + reuse_connection { + value: true + } + grpc_health_check { + service_name: "\004" + } + unhealthy_interval { + seconds: 4227858432 + } + event_log_path: "?(" + tls_options { + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: LOCAL_CLOSE +} diff --git a/test/common/upstream/health_check_corpus/grpc_raise-go-away b/test/common/upstream/health_check_corpus/grpc_raise-go-away new file mode 100644 index 000000000000..f2df96683e38 --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_raise-go-away @@ -0,0 +1,66 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + grpc_health_check { + service_name: "service" + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + status: SERVING + chunk_size_for_structured_response: 3 + } + grpc_respond_trailers { + trailers { + headers { + key: "grpc-status" + value: "0" + } + } + } + } + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_go_away: ERROR +} diff --git a/test/common/upstream/health_check_corpus/grpc_trigger-interval b/test/common/upstream/health_check_corpus/grpc_trigger-interval new file mode 100644 index 000000000000..e17472a9767b --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_trigger-interval @@ -0,0 +1,33 @@ +health_check_config { + timeout { + seconds: 512 + nanos: 8 + } + interval { + nanos: 8 + } + unhealthy_threshold { + value: 36 + } + healthy_threshold { + } + grpc_health_check { + } + no_traffic_interval { + seconds: 512 + nanos: 8 + } + initial_jitter { + seconds: 512 + nanos: 262152 + } +} +actions { + trigger_timeout_timer { + } +} + +actions { + trigger_interval_timer { + } +} diff --git a/test/common/upstream/health_check_corpus/http_Success b/test/common/upstream/health_check_corpus/http_Success index 56ac0b7128d2..7692c8e08549 100644 --- a/test/common/upstream/health_check_corpus/http_Success +++ b/test/common/upstream/health_check_corpus/http_Success @@ -45,3 +45,4 @@ actions { } } } +upstream_cx_success: true diff --git a/test/common/upstream/health_check_corpus/http_headers-with-connection b/test/common/upstream/health_check_corpus/http_headers-with-connection new file mode 100644 index 000000000000..53e7d349ec1c --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_headers-with-connection @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "connection" + value: "close" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/http_headers-with-proxy-connection b/test/common/upstream/health_check_corpus/http_headers-with-proxy-connection new file mode 100644 index 000000000000..d2a35501610b --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_headers-with-proxy-connection @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "proxy-connection" + value: "close" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/http_raise-go-away b/test/common/upstream/health_check_corpus/http_raise-go-away new file mode 100644 index 000000000000..60862f80bf95 --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_raise-go-away @@ -0,0 +1,69 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + status: SERVING + chunk_size_for_structured_response: 3 + } + grpc_respond_trailers { + trailers { + headers { + key: "grpc-status" + value: "0" + } + } + } + } + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_go_away: ERROR +} diff --git a/test/common/upstream/health_check_corpus/http_status-over-600 b/test/common/upstream/health_check_corpus/http_status-over-600 new file mode 100644 index 000000000000..f8c98fb6a7f8 --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_status-over-600 @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + expected_statuses { + start: 200 + end: 700 + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/http_status-under-100 b/test/common/upstream/health_check_corpus/http_status-under-100 new file mode 100644 index 000000000000..441d9433ff53 --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_status-under-100 @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + expected_statuses { + start: 50 + end: 500 + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/tcp_Success b/test/common/upstream/health_check_corpus/tcp_Success index 3ea16b92943c..b63b25cfa02e 100644 --- a/test/common/upstream/health_check_corpus/tcp_Success +++ b/test/common/upstream/health_check_corpus/tcp_Success @@ -38,3 +38,4 @@ actions { } } } +upstream_cx_success: true diff --git a/test/common/upstream/health_check_corpus/tcp_raise-go-away b/test/common/upstream/health_check_corpus/tcp_raise-go-away new file mode 100644 index 000000000000..518c9a716d5f --- /dev/null +++ b/test/common/upstream/health_check_corpus/tcp_raise-go-away @@ -0,0 +1,71 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + tcp_health_check { + send { + text: "01" + } + receive [{ + text: "02" + }] + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + status: SERVING + chunk_size_for_structured_response: 3 + } + grpc_respond_trailers { + trailers { + headers { + key: "grpc-status" + value: "0" + } + } + } + } + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_go_away: ERROR +} diff --git a/test/common/upstream/health_check_fuzz.cc b/test/common/upstream/health_check_fuzz.cc index b6cd1171a8f2..e564e2fb5a48 100644 --- a/test/common/upstream/health_check_fuzz.cc +++ b/test/common/upstream/health_check_fuzz.cc @@ -104,6 +104,9 @@ void HttpHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase .WillByDefault(testing::Return(input.http_verify_cluster())); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + if (input.upstream_cx_success()) { + cluster_->info_->stats().upstream_cx_total_.inc(); + } expectSessionCreate(); expectStreamCreate(0); // This sets up the possibility of testing hosts that never become healthy @@ -212,6 +215,9 @@ void TcpHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase allocTcpHealthCheckerFromProto(input.health_check_config()); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + if (input.upstream_cx_success()) { + cluster_->info_->stats().upstream_cx_total_.inc(); + } expectSessionCreate(); expectClientCreate(); health_checker_->start(); @@ -306,7 +312,7 @@ void TcpHealthCheckFuzz::raiseEvent(const Network::ConnectionEvent& event_type, void GrpcHealthCheckFuzz::allocGrpcHealthCheckerFromProto( const envoy::config::core::v3::HealthCheck& config) { - health_checker_ = std::make_shared( + health_checker_ = std::make_shared>( *cluster_, config, dispatcher_, runtime_, random_, HealthCheckEventLoggerPtr(event_logger_storage_.release())); ENVOY_LOG_MISC(trace, "Created Test Grpc Health Checker"); @@ -317,6 +323,9 @@ void GrpcHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase allocGrpcHealthCheckerFromProto(input.health_check_config()); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + if (input.upstream_cx_success()) { + cluster_->info_->stats().upstream_cx_total_.inc(); + } expectSessionCreate(); ON_CALL(dispatcher_, createClientConnection_(_, _, _, _)) .WillByDefault(testing::InvokeWithoutArgs( @@ -462,6 +471,7 @@ void GrpcHealthCheckFuzz::triggerTimeoutTimer(bool last_action) { void GrpcHealthCheckFuzz::raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) { test_session_->client_connection_->raiseEvent(event_type); if (!last_action && event_type != Network::ConnectionEvent::Connected) { + received_no_error_goaway_ = false; // from resetState() // Close events will always blow away the client ENVOY_LOG_MISC(trace, "Triggering interval timer after close event"); // Interval timer is guaranteed to be enabled from a close event - calls @@ -475,6 +485,7 @@ void GrpcHealthCheckFuzz::raiseGoAway(bool no_error) { test_session_->codec_client_->raiseGoAway(Http::GoAwayErrorCode::NoError); // Will cause other events to blow away client, because this is a "graceful" go away received_no_error_goaway_ = true; + triggerIntervalTimer(true); } else { // go away events without no error flag explicitly blow away client test_session_->codec_client_->raiseGoAway(Http::GoAwayErrorCode::Other); @@ -554,6 +565,10 @@ void HealthCheckFuzz::replay(const test::common::upstream::HealthCheckTestCase& raiseEvent(getEventTypeFromProto(event.raise_event()), last_action); break; } + case test::common::upstream::Action::kRaiseGoAway: { + raiseGoAway(event.raise_go_away() == test::common::upstream::RaiseGoAway::NO_ERROR); + break; + } default: break; } diff --git a/test/common/upstream/health_check_fuzz.h b/test/common/upstream/health_check_fuzz.h index 627e44ea1bcc..82052ff0c7d9 100644 --- a/test/common/upstream/health_check_fuzz.h +++ b/test/common/upstream/health_check_fuzz.h @@ -3,7 +3,7 @@ #include #include "test/common/upstream/health_check_fuzz.pb.validate.h" -#include "test/common/upstream/health_checker_impl_test_utils.h" +#include "test/common/upstream/health_check_fuzz_test_utils.h" #include "test/fuzz/common.pb.h" namespace Envoy { @@ -28,6 +28,8 @@ class HealthCheckFuzz { virtual void triggerIntervalTimer(bool expect_client_create) PURE; virtual void triggerTimeoutTimer(bool last_action) PURE; virtual void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) PURE; + // Only implemented by gRPC, otherwise no-op + virtual void raiseGoAway(bool no_error) PURE; virtual ~HealthCheckFuzz() = default; @@ -45,6 +47,8 @@ class HttpHealthCheckFuzz : public HealthCheckFuzz, HttpHealthCheckerImplTestBas void triggerIntervalTimer(bool expect_client_create) override; void triggerTimeoutTimer(bool last_action) override; void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) override; + // No op + void raiseGoAway(bool) override {} ~HttpHealthCheckFuzz() override = default; // Determines whether the client gets reused or not after response @@ -59,6 +63,8 @@ class TcpHealthCheckFuzz : public HealthCheckFuzz, TcpHealthCheckerImplTestBase void triggerIntervalTimer(bool expect_client_create) override; void triggerTimeoutTimer(bool last_action) override; void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) override; + // No op + void raiseGoAway(bool) override {} ~TcpHealthCheckFuzz() override = default; // Determines whether the client gets reused or not after response @@ -78,7 +84,7 @@ class GrpcHealthCheckFuzz : public HealthCheckFuzz, public HealthCheckerTestBase void triggerIntervalTimer(bool expect_client_create) override; void triggerTimeoutTimer(bool last_action) override; void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) override; - void raiseGoAway(bool no_error); + void raiseGoAway(bool no_error) override; ~GrpcHealthCheckFuzz() override = default; // Determines whether the client gets reused or not after response @@ -108,7 +114,7 @@ class GrpcHealthCheckFuzz : public HealthCheckFuzz, public HealthCheckerTestBase void expectClientCreate(); void expectStreamCreate(); - std::shared_ptr health_checker_; + std::shared_ptr> health_checker_; }; } // namespace Upstream diff --git a/test/common/upstream/health_check_fuzz.proto b/test/common/upstream/health_check_fuzz.proto index f4d0e26dcacb..2f1d3294a0cd 100644 --- a/test/common/upstream/health_check_fuzz.proto +++ b/test/common/upstream/health_check_fuzz.proto @@ -72,6 +72,11 @@ enum RaiseEvent { LOCAL_CLOSE = 2; } +enum RaiseGoAway { + NO_ERROR = 0; + ERROR = 1; +} + message Action { oneof action_selector { option (validate.required) = true; @@ -80,6 +85,7 @@ message Action { //TODO: respondBody, respondTrailers google.protobuf.Empty trigger_timeout_timer = 3; RaiseEvent raise_event = 4 [(validate.rules).enum.defined_only = true]; + RaiseGoAway raise_go_away = 5; } } @@ -89,4 +95,5 @@ message HealthCheckTestCase { repeated Action actions = 2; bool http_verify_cluster = 3; //Determines if verify cluster setting is on bool start_failed = 4; + bool upstream_cx_success = 5; } diff --git a/test/common/upstream/health_checker_impl_test_utils.cc b/test/common/upstream/health_check_fuzz_test_utils.cc similarity index 96% rename from test/common/upstream/health_checker_impl_test_utils.cc rename to test/common/upstream/health_check_fuzz_test_utils.cc index 1551ffb602c5..e142e5d592d6 100644 --- a/test/common/upstream/health_checker_impl_test_utils.cc +++ b/test/common/upstream/health_check_fuzz_test_utils.cc @@ -1,4 +1,4 @@ -#include "test/common/upstream/health_checker_impl_test_utils.h" +#include "test/common/upstream/health_check_fuzz_test_utils.h" #include "test/common/upstream/utility.h" @@ -14,8 +14,8 @@ void HttpHealthCheckerImplTestBase::expectSessionCreate( TestSessionPtr new_test_session(new TestSession()); test_sessions_.emplace_back(std::move(new_test_session)); TestSession& test_session = *test_sessions_.back(); - test_session.timeout_timer_ = new Event::MockTimer(&dispatcher_); - test_session.interval_timer_ = new Event::MockTimer(&dispatcher_); + test_session.timeout_timer_ = new NiceMock(&dispatcher_); + test_session.interval_timer_ = new NiceMock(&dispatcher_); expectClientCreate(test_sessions_.size() - 1, health_check_map); } diff --git a/test/common/upstream/health_checker_impl_test_utils.h b/test/common/upstream/health_check_fuzz_test_utils.h similarity index 93% rename from test/common/upstream/health_checker_impl_test_utils.h rename to test/common/upstream/health_check_fuzz_test_utils.h index c516743704cd..a87eb51232f9 100644 --- a/test/common/upstream/health_checker_impl_test_utils.h +++ b/test/common/upstream/health_check_fuzz_test_utils.h @@ -17,9 +17,9 @@ class HealthCheckerTestBase { std::shared_ptr cluster_{ std::make_shared>()}; NiceMock dispatcher_; - std::unique_ptr event_logger_storage_{ - std::make_unique()}; - MockHealthCheckEventLogger& event_logger_{*event_logger_storage_}; + std::unique_ptr> event_logger_storage_{ + std::make_unique>()}; + NiceMock& event_logger_{*event_logger_storage_}; NiceMock random_; NiceMock runtime_; }; @@ -41,8 +41,8 @@ class TestHttpHealthCheckerImpl : public HttpHealthCheckerImpl { class HttpHealthCheckerImplTestBase : public HealthCheckerTestBase { public: struct TestSession { - Event::MockTimer* interval_timer_{}; - Event::MockTimer* timeout_timer_{}; + NiceMock* interval_timer_{}; + NiceMock* timeout_timer_{}; Http::MockClientConnection* codec_{}; Stats::IsolatedStoreImpl stats_store_; Network::MockClientConnection* client_connection_{}; diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 6c615730918a..97aaca50cfb1 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -15,10 +15,10 @@ #include "common/json/json_loader.h" #include "common/network/utility.h" #include "common/protobuf/utility.h" +#include "common/upstream/health_checker_impl.h" #include "common/upstream/upstream_impl.h" #include "test/common/http/common.h" -#include "test/common/upstream/health_checker_impl_test_utils.h" #include "test/common/upstream/utility.h" #include "test/mocks/access_log/mocks.h" #include "test/mocks/api/mocks.h" @@ -29,6 +29,7 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_priority_set.h" +#include "test/mocks/upstream/health_check_event_logger.h" #include "test/mocks/upstream/host_set.h" #include "test/mocks/upstream/transport_socket_match.h" #include "test/test_common/printers.h" @@ -98,8 +99,50 @@ TEST(HealthCheckerFactoryTest, CreateGrpc) { .get())); } -class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthCheckerImplTestBase { +class HealthCheckerTestBase { public: + std::shared_ptr cluster_{ + std::make_shared>()}; + NiceMock dispatcher_; + std::unique_ptr event_logger_storage_{ + std::make_unique()}; + MockHealthCheckEventLogger& event_logger_{*event_logger_storage_}; + NiceMock random_; + NiceMock runtime_; +}; + +class TestHttpHealthCheckerImpl : public HttpHealthCheckerImpl { +public: + using HttpHealthCheckerImpl::HttpHealthCheckerImpl; + + Http::CodecClient* createCodecClient(Upstream::Host::CreateConnectionData& conn_data) override { + return createCodecClient_(conn_data); + }; + + // HttpHealthCheckerImpl + MOCK_METHOD(Http::CodecClient*, createCodecClient_, (Upstream::Host::CreateConnectionData&)); + + Http::CodecClient::Type codecClientType() { return codec_client_type_; } +}; + +class HttpHealthCheckerImplTest : public testing::Test, public HealthCheckerTestBase { +public: + struct TestSession { + Event::MockTimer* interval_timer_{}; + Event::MockTimer* timeout_timer_{}; + Http::MockClientConnection* codec_{}; + Stats::IsolatedStoreImpl stats_store_; + Network::MockClientConnection* client_connection_{}; + NiceMock request_encoder_; + Http::ResponseDecoder* stream_response_callbacks_{}; + CodecClientForTest* codec_client_{}; + }; + + using TestSessionPtr = std::unique_ptr; + using HostWithHealthCheckMap = + absl::node_hash_map; + void allocHealthChecker(const std::string& yaml, bool avoid_boosting = true) { health_checker_ = std::make_shared( *cluster_, parseHealthCheckFromV3Yaml(yaml, avoid_boosting), dispatcher_, runtime_, random_, @@ -496,6 +539,61 @@ class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthChecker addCompletionCallback(); } + void expectSessionCreate(const HostWithHealthCheckMap& health_check_map) { + // Expectations are in LIFO order. + TestSessionPtr new_test_session(new TestSession()); + new_test_session->timeout_timer_ = new Event::MockTimer(&dispatcher_); + new_test_session->interval_timer_ = new Event::MockTimer(&dispatcher_); + test_sessions_.emplace_back(std::move(new_test_session)); + expectClientCreate(test_sessions_.size() - 1, health_check_map); + } + + void expectClientCreate(size_t index, const HostWithHealthCheckMap& health_check_map) { + TestSession& test_session = *test_sessions_[index]; + test_session.codec_ = new NiceMock(); + ON_CALL(*test_session.codec_, protocol()).WillByDefault(Return(Http::Protocol::Http11)); + test_session.client_connection_ = new NiceMock(); + connection_index_.push_back(index); + codec_index_.push_back(index); + + EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)) + .Times(testing::AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([&]() -> Network::ClientConnection* { + const uint32_t index = connection_index_.front(); + connection_index_.pop_front(); + return test_sessions_[index]->client_connection_; + })); + EXPECT_CALL(*health_checker_, createCodecClient_(_)) + .WillRepeatedly( + Invoke([&](Upstream::Host::CreateConnectionData& conn_data) -> Http::CodecClient* { + if (!health_check_map.empty()) { + const auto& health_check_config = + health_check_map.at(conn_data.host_description_->address()->asString()); + // To make sure health checker checks the correct port. + EXPECT_EQ(health_check_config.port_value(), + conn_data.host_description_->healthCheckAddress()->ip()->port()); + } + const uint32_t index = codec_index_.front(); + codec_index_.pop_front(); + TestSession& test_session = *test_sessions_[index]; + std::shared_ptr cluster{ + new NiceMock()}; + Event::MockDispatcher dispatcher_; + test_session.codec_client_ = new CodecClientForTest( + Http::CodecClient::Type::HTTP1, std::move(conn_data.connection_), + test_session.codec_, nullptr, + Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), dispatcher_); + return test_session.codec_client_; + })); + } + + void expectStreamCreate(size_t index) { + test_sessions_[index]->request_encoder_.stream_.callbacks_.clear(); + EXPECT_CALL(*test_sessions_[index]->codec_, newStream(_)) + .WillOnce(DoAll(SaveArgAddress(&test_sessions_[index]->stream_response_callbacks_), + ReturnRef(test_sessions_[index]->request_encoder_))); + } + void respond(size_t index, const std::string& code, bool conn_close, bool proxy_close = false, bool body = false, bool trailers = false, const absl::optional& service_cluster = absl::optional(), @@ -531,6 +629,9 @@ class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthChecker } } + void expectSessionCreate() { expectSessionCreate(health_checker_map_); } + void expectClientCreate(size_t index) { expectClientCreate(index, health_checker_map_); } + void expectSuccessStartFailedFailFirst( const absl::optional& health_checked_cluster = absl::optional()) { cluster_->prioritySet().getMockHostSet(0)->hosts_ = { @@ -609,6 +710,12 @@ class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthChecker EXPECT_CALL(*test_sessions_[index]->timeout_timer_, disableTimer()); EXPECT_CALL(*test_sessions_[index]->interval_timer_, enableTimer(_, _)); } + + std::vector test_sessions_; + std::shared_ptr health_checker_; + std::list connection_index_{}; + std::list codec_index_{}; + const HostWithHealthCheckMap health_checker_map_{}; }; TEST_F(HttpHealthCheckerImplTest, Success) { @@ -943,10 +1050,11 @@ TEST_F(HttpHealthCheckerImplTest, ZeroRetryInterval) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1018,10 +1126,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1052,10 +1161,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServicePrefixPatternCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1086,10 +1196,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceExactPatternCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1120,10 +1231,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceRegexPatternCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1162,9 +1274,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValueOnTheHos expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -1206,9 +1319,10 @@ TEST_F(HttpHealthCheckerImplTest, expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -1240,9 +1354,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -1303,7 +1418,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.get(header_ok)[0]->value().getStringView(), value_ok); EXPECT_EQ(headers.get(header_cool)[0]->value().getStringView(), value_cool); EXPECT_EQ(headers.get(header_awesome)[0]->value().getStringView(), value_awesome); @@ -1326,6 +1441,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { std::string current_start_time = date_formatter.fromTime(dispatcher_.timeSource().systemTime()); EXPECT_EQ(headers.get(start_time)[0]->value().getStringView(), current_start_time); + return Http::okStatus(); })); health_checker_->start(); @@ -1366,8 +1482,9 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithoutUserAgent) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.UserAgent(), nullptr); + return Http::okStatus(); })); health_checker_->start(); @@ -2373,9 +2490,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAltPort) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -2904,6 +3022,7 @@ TEST_F(ProdHttpHealthCheckerTest, ProdHttpHealthCheckerH1HealthChecking) { } TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(Http1CodecClient)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s @@ -2924,6 +3043,7 @@ TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(Http1CodecClient)) { } TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(Http2CodecClient)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s @@ -2959,10 +3079,11 @@ TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(ServiceNameMatch)) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -3782,8 +3903,36 @@ TEST_F(TcpHealthCheckerImplTest, ConnectionLocalFailure) { EXPECT_EQ(0UL, cluster_->info_->stats_store_.counter("health_check.passive_failure").value()); } -class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils { +class TestGrpcHealthCheckerImpl : public GrpcHealthCheckerImpl { +public: + using GrpcHealthCheckerImpl::GrpcHealthCheckerImpl; + + Http::CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& conn_data) override { + auto codec_client = createCodecClient_(conn_data); + return Http::CodecClientPtr(codec_client); + }; + + // GrpcHealthCheckerImpl + MOCK_METHOD(Http::CodecClient*, createCodecClient_, (Upstream::Host::CreateConnectionData&)); +}; + +class GrpcHealthCheckerImplTestBase : public HealthCheckerTestBase { public: + struct TestSession { + TestSession() = default; + + Event::MockTimer* interval_timer_{}; + Event::MockTimer* timeout_timer_{}; + Http::MockClientConnection* codec_{}; + Stats::IsolatedStoreImpl stats_store_; + Network::MockClientConnection* client_connection_{}; + NiceMock request_encoder_; + Http::ResponseDecoder* stream_response_callbacks_{}; + CodecClientForTest* codec_client_{}; + }; + + using TestSessionPtr = std::unique_ptr; + struct ResponseSpec { struct ChunkSpec { bool valid; @@ -3808,6 +3957,30 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils } return spec; } + // Null dereference from health check fuzzer + static ChunkSpec badData() { + std::string data("\000\000\000\000\0000000", 9); + std::vector chunk(data.begin(), data.end()); + ChunkSpec spec; + spec.valid = true; + spec.data = chunk; + return spec; + } + static ChunkSpec validFramesThenInvalidFrames() { + grpc::health::v1::HealthCheckResponse response; + response.set_status(grpc::health::v1::HealthCheckResponse::SERVING); + const auto data = Grpc::Common::serializeToGrpcFrame(response); + std::vector buffer_vector = std::vector(data->length(), 0); + data->copyOut(0, data->length(), &buffer_vector[0]); + // Invalid frame here + for (size_t i = 0; i < 6; i++) { + buffer_vector.push_back(48); // Represents ASCII Character of 0 + } + ChunkSpec spec; + spec.valid = true; + spec.data = buffer_vector; + return spec; + } static ChunkSpec validChunk(grpc::health::v1::HealthCheckResponse::ServingStatus status) { ChunkSpec spec; spec.valid = true; @@ -3833,12 +4006,16 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils return ret; } - std::vector> - response_headers; // Encapsulates all three types of responses + std::vector> response_headers; std::vector body_chunks; std::vector> trailers; }; + GrpcHealthCheckerImplTestBase() { + EXPECT_CALL(*cluster_->info_, features()) + .WillRepeatedly(Return(Upstream::ClusterInfo::Features::HTTP2)); + } + void allocHealthChecker(const envoy::config::core::v3::HealthCheck& config) { health_checker_ = std::make_shared( *cluster_, config, dispatcher_, runtime_, random_, @@ -3896,6 +4073,55 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils addCompletionCallback(); } + void expectSessionCreate() { + // Expectations are in LIFO order. + TestSessionPtr new_test_session(new TestSession()); + new_test_session->timeout_timer_ = new Event::MockTimer(&dispatcher_); + new_test_session->interval_timer_ = new Event::MockTimer(&dispatcher_); + test_sessions_.emplace_back(std::move(new_test_session)); + expectClientCreate(test_sessions_.size() - 1); + } + + void expectClientCreate(size_t index) { + TestSession& test_session = *test_sessions_[index]; + test_session.codec_ = new NiceMock(); + test_session.client_connection_ = new NiceMock(); + connection_index_.push_back(index); + codec_index_.push_back(index); + + EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)) + .Times(testing::AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([&]() -> Network::ClientConnection* { + const uint32_t index = connection_index_.front(); + connection_index_.pop_front(); + return test_sessions_[index]->client_connection_; + })); + + EXPECT_CALL(*health_checker_, createCodecClient_(_)) + .WillRepeatedly( + Invoke([&](Upstream::Host::CreateConnectionData& conn_data) -> Http::CodecClient* { + const uint32_t index = codec_index_.front(); + codec_index_.pop_front(); + TestSession& test_session = *test_sessions_[index]; + std::shared_ptr cluster{ + new NiceMock()}; + Event::MockDispatcher dispatcher_; + + test_session.codec_client_ = new CodecClientForTest( + Http::CodecClient::Type::HTTP1, std::move(conn_data.connection_), + test_session.codec_, nullptr, + Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), dispatcher_); + return test_session.codec_client_; + })); + } + + void expectStreamCreate(size_t index) { + test_sessions_[index]->request_encoder_.stream_.callbacks_.clear(); + EXPECT_CALL(*test_sessions_[index]->codec_, newStream(_)) + .WillOnce(DoAll(SaveArgAddress(&test_sessions_[index]->stream_response_callbacks_), + ReturnRef(test_sessions_[index]->request_encoder_))); + } + // Starts healthchecker and sets up timer expectations, leaving up future specification of // healthcheck response for the caller. Useful when there is only one healthcheck attempt // performed during test case (but possibly on many hosts). @@ -4010,7 +4236,7 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils expectHealthcheckStart(0); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, false)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(Http::Headers::get().ContentTypeValues.Grpc, headers.getContentTypeValue()); EXPECT_EQ(std::string("/grpc.health.v1.Health/Check"), headers.getPathValue()); EXPECT_EQ(Http::Headers::get().SchemeValues.Http, headers.getSchemeValue()); @@ -4018,6 +4244,7 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils EXPECT_EQ(expected_host, headers.getHostValue()); EXPECT_EQ(std::chrono::milliseconds(1000).count(), Envoy::Grpc::Common::getGrpcTimeout(headers).value().count()); + return Http::okStatus(); })); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeData(_, true)) .WillOnce(Invoke([&](Buffer::Instance& data, bool) { @@ -4046,6 +4273,11 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils } MOCK_METHOD(void, onHostStatus, (HostSharedPtr host, HealthTransition changed_state)); + + std::vector test_sessions_; + std::shared_ptr health_checker_; + std::list connection_index_{}; + std::list codec_index_{}; }; class GrpcHealthCheckerImplTest : public testing::Test, public GrpcHealthCheckerImplTestBase {}; @@ -4746,6 +4978,37 @@ TEST_F(GrpcHealthCheckerImplTest, GrpcFailUnknown) { cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); } +// This used to cause a null dereference +TEST_F(GrpcHealthCheckerImplTest, GrpcFailNullBytes) { + setupHC(); + expectSingleHealthcheck(HealthTransition::Changed); + EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _)); + EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true)); + respondResponseSpec(0, ResponseSpec{{{":status", "200"}, {"content-type", "application/grpc"}}, + {GrpcHealthCheckerImplTest::ResponseSpec::badData()}, + {}}); + EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( + Host::HealthFlag::FAILED_ACTIVE_HC)); + EXPECT_EQ(Host::Health::Unhealthy, + cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); +} + +// This used to cause a null dereference +TEST_F(GrpcHealthCheckerImplTest, GrpcValidFramesThenInvalidFrames) { + setupHC(); + expectSingleHealthcheck(HealthTransition::Changed); + EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _)); + EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true)); + respondResponseSpec( + 0, ResponseSpec{{{":status", "200"}, {"content-type", "application/grpc"}}, + {GrpcHealthCheckerImplTest::ResponseSpec::validFramesThenInvalidFrames()}, + {}}); + EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( + Host::HealthFlag::FAILED_ACTIVE_HC)); + EXPECT_EQ(Host::Health::Unhealthy, + cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); +} + // Test SERVICE_UNKNOWN health status is considered unhealthy. TEST_F(GrpcHealthCheckerImplTest, GrpcFailServiceUnknown) { setupHC(); diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts b/test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts new file mode 100644 index 000000000000..3c3f884753fa --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts @@ -0,0 +1,66 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01\x02" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-no-config b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-config new file mode 100644 index 000000000000..470a160224c8 --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-config @@ -0,0 +1,49 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts new file mode 100644 index 000000000000..f27f2002d8a7 --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts @@ -0,0 +1,32 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +seed_for_prng: 2 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-normal b/test/common/upstream/least_request_load_balancer_corpus/least_request-normal new file mode 100644 index 000000000000..75417693b31b --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-normal @@ -0,0 +1,52 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts b/test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts new file mode 100644 index 000000000000..99ab1d0edc0e --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts @@ -0,0 +1,66 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01\x02" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_fuzz.proto b/test/common/upstream/least_request_load_balancer_fuzz.proto new file mode 100644 index 000000000000..9e699b964b74 --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_fuzz.proto @@ -0,0 +1,16 @@ + +syntax = "proto3"; + +package test.common.upstream; + +import "validate/validate.proto"; +import "envoy/config/cluster/v3/cluster.proto"; +import "test/common/upstream/zone_aware_load_balancer_fuzz.proto"; + +message LeastRequestLoadBalancerTestCase { + test.common.upstream.ZoneAwareLoadBalancerTestCase zone_aware_load_balancer_test_case = 1 + [(validate.rules).message.required = true]; + envoy.config.cluster.v3.Cluster.LeastRequestLbConfig least_request_lb_config = 2; + // This is used to determine the requests for each host - will wrap around if runs out of space + bytes random_bytestring_for_requests = 3 [(validate.rules).bytes = {min_len: 1, max_len: 2048}]; +} diff --git a/test/common/upstream/least_request_load_balancer_fuzz_test.cc b/test/common/upstream/least_request_load_balancer_fuzz_test.cc new file mode 100644 index 000000000000..85b0689f4d1e --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_fuzz_test.cc @@ -0,0 +1,82 @@ +#include + +#include "test/common/upstream/least_request_load_balancer_fuzz.pb.validate.h" +#include "test/common/upstream/zone_aware_load_balancer_fuzz_base.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Upstream { + +// Least Request takes into account both weights (handled in ZoneAwareLoadBalancerFuzzBase), and +// requests active as well +void setRequestsActiveForStaticHosts(NiceMock& priority_set, + const std::string& random_bytestring) { + uint32_t index_of_random_bytestring = 0; + // Iterate through all the current host sets and set requests for each + for (uint32_t priority_level = 0; priority_level < priority_set.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + // Make sure no weights persisted from previous fuzz iterations + ASSERT(host->stats().rq_active_.value() == 0); + host->stats().rq_active_.set( + random_bytestring[index_of_random_bytestring % random_bytestring.length()] % 3); + ++index_of_random_bytestring; + } + } +} + +void removeRequestsActiveForStaticHosts(NiceMock& priority_set) { + // Clear out any set requests active + for (uint32_t priority_level = 0; priority_level < priority_set.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + host->stats().rq_active_.set(0); + } + } +} + +DEFINE_PROTO_FUZZER(const test::common::upstream::LeastRequestLoadBalancerTestCase& input) { + try { + TestUtility::validate(input); + } catch (const ProtoValidationException& e) { + ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); + return; + } + + const test::common::upstream::ZoneAwareLoadBalancerTestCase& zone_aware_load_balancer_test_case = + input.zone_aware_load_balancer_test_case(); + + ZoneAwareLoadBalancerFuzzBase zone_aware_load_balancer_fuzz = ZoneAwareLoadBalancerFuzzBase( + zone_aware_load_balancer_test_case.need_local_priority_set(), + zone_aware_load_balancer_test_case.random_bytestring_for_weights()); + zone_aware_load_balancer_fuzz.initializeLbComponents( + zone_aware_load_balancer_test_case.load_balancer_test_case()); + + setRequestsActiveForStaticHosts(zone_aware_load_balancer_fuzz.priority_set_, + input.random_bytestring_for_requests()); + + try { + zone_aware_load_balancer_fuzz.lb_ = std::make_unique( + zone_aware_load_balancer_fuzz.priority_set_, + zone_aware_load_balancer_fuzz.local_priority_set_.get(), + zone_aware_load_balancer_fuzz.stats_, zone_aware_load_balancer_fuzz.runtime_, + zone_aware_load_balancer_fuzz.random_, + zone_aware_load_balancer_test_case.load_balancer_test_case().common_lb_config(), + input.least_request_lb_config()); + } catch (EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException; {}", e.what()); + removeRequestsActiveForStaticHosts(zone_aware_load_balancer_fuzz.priority_set_); + return; + } + + zone_aware_load_balancer_fuzz.replay( + zone_aware_load_balancer_test_case.load_balancer_test_case().actions()); + + removeRequestsActiveForStaticHosts(zone_aware_load_balancer_fuzz.priority_set_); +} + +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/load_balancer_fuzz_base.cc b/test/common/upstream/load_balancer_fuzz_base.cc index 1bf578b214df..8fce55d4f7cc 100644 --- a/test/common/upstream/load_balancer_fuzz_base.cc +++ b/test/common/upstream/load_balancer_fuzz_base.cc @@ -40,6 +40,8 @@ void LoadBalancerFuzzBase::initializeASingleHostSet( // all priority levels. while (hosts_made < std::min(num_hosts_in_priority_level, MaxNumHostsPerPriorityLevel) && port < 60000) { + // Make sure no health flags persisted from previous fuzz iterations + ASSERT(initialized_hosts_[port]->health() == Host::Health::Healthy); host_set.hosts_.push_back(initialized_hosts_[port]); ++port; ++hosts_made; @@ -248,20 +250,5 @@ void LoadBalancerFuzzBase::replay( } } -void LoadBalancerFuzzBase::clearStaticHostsHealthFlags() { - // The only outstanding health flags set are those that are set from hosts being placed in - // degraded and excluded. Thus, use the priority set pointer to know which flags to clear. - for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); - ++priority_level) { - MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); - for (auto& host : host_set.degraded_hosts_) { - host->healthFlagClear(Host::HealthFlag::DEGRADED_ACTIVE_HC); - } - for (auto& host : host_set.excluded_hosts_) { - host->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); - } - } -} - } // namespace Upstream } // namespace Envoy diff --git a/test/common/upstream/load_balancer_fuzz_base.h b/test/common/upstream/load_balancer_fuzz_base.h index fc9abcffbf0f..210eae065008 100644 --- a/test/common/upstream/load_balancer_fuzz_base.h +++ b/test/common/upstream/load_balancer_fuzz_base.h @@ -22,8 +22,8 @@ class LoadBalancerFuzzBase { // Initializes load balancer components shared amongst every load balancer, random_, and // priority_set_ - void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input); - void + virtual void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input); + virtual void updateHealthFlagsForAHostSet(const uint64_t host_priority, const uint32_t num_healthy_hosts, const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts, const Protobuf::RepeatedField& random_bytestring); @@ -33,11 +33,8 @@ class LoadBalancerFuzzBase { // and lb_->chooseHost(). void prefetch(); void chooseHost(); - ~LoadBalancerFuzzBase() = default; void replay(const Protobuf::RepeatedPtrField& actions); - void clearStaticHostsHealthFlags(); - // These public objects shared amongst all types of load balancers will be used to construct load // balancers in specific load balancer fuzz classes Stats::IsolatedStoreImpl stats_store_; @@ -45,12 +42,29 @@ class LoadBalancerFuzzBase { NiceMock runtime_; Random::PsuedoRandomGenerator64 random_; NiceMock priority_set_; - std::unique_ptr lb_; + std::unique_ptr lb_; -private: + virtual ~LoadBalancerFuzzBase() { + // In an iteration, after this class is destructed, whether through an exception throw or + // finishing an action stream, must clear any state that could persist in static hosts. + // The only outstanding health flags set are those that are set from hosts being placed in + // degraded and excluded. Thus, use the priority set pointer to know which flags to clear. + for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + for (auto& host : host_set.degraded_hosts_) { + host->healthFlagClear(Host::HealthFlag::DEGRADED_ACTIVE_HC); + } + for (auto& host : host_set.excluded_hosts_) { + host->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); + } + } + }; + +protected: // Untrusted upstreams don't have the ability to change the host set size, so keep it constant // over the fuzz iteration. - void + virtual void initializeASingleHostSet(const test::common::upstream::SetupPriorityLevel& setup_priority_level, const uint8_t priority_level, uint16_t& port); @@ -58,6 +72,7 @@ class LoadBalancerFuzzBase { // random uint64 against this number. uint8_t num_priority_levels_ = 0; +private: // This map used when updating health flags - making sure the health flags are updated hosts in // localities Key - index of host within full host list, value - locality level host at index is // in diff --git a/test/common/upstream/load_stats_reporter_test.cc b/test/common/upstream/load_stats_reporter_test.cc index 111e7356a064..46105592c00a 100644 --- a/test/common/upstream/load_stats_reporter_test.cc +++ b/test/common/upstream/load_stats_reporter_test.cc @@ -125,7 +125,8 @@ TEST_F(LoadStatsReporterTest, ExistingClusters) { foo_cluster.info_->load_report_stats_.upstream_rq_dropped_.add(2); foo_cluster.info_->eds_service_name_ = "bar"; NiceMock bar_cluster; - MockClusterManager::ClusterInfoMap cluster_info{{"foo", foo_cluster}, {"bar", bar_cluster}}; + MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", foo_cluster}, {"bar", bar_cluster}}, + {}}; ON_CALL(cm_, clusters()).WillByDefault(Return(cluster_info)); deliverLoadStatsResponse({"foo"}); // Initial stats report for foo on timer tick. diff --git a/test/common/upstream/original_dst_cluster_test.cc b/test/common/upstream/original_dst_cluster_test.cc index 4e95e2303882..643de67a77e5 100644 --- a/test/common/upstream/original_dst_cluster_test.cc +++ b/test/common/upstream/original_dst_cluster_test.cc @@ -25,6 +25,7 @@ #include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -143,7 +144,8 @@ TEST_F(OriginalDstClusterTest, BadConfigWithLoadAssignment) { "ORIGINAL_DST clusters must have no load assignment or hosts configured"); } -TEST_F(OriginalDstClusterTest, BadConfigWithDeprecatedHosts) { +TEST_F(OriginalDstClusterTest, DEPRECATED_FEATURE_TEST(BadConfigWithDeprecatedHosts)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( name: name connect_timeout: 0.25s diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 new file mode 100644 index 000000000000..159fa40953f2 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 @@ -0,0 +1,31 @@ +zone_aware_load_balancer_test_case { + load_balancer_test_case { + common_lb_config { + healthy_panic_threshold { + value: nan + } + locality_weighted_lb_config { + } + update_merge_window { + seconds: -1 + nanos: 292 + } + ignore_new_hosts_until_first_hc: true + close_connections_on_host_set_change: true + } + actions { + update_health_flags { + random_bytestring: 67108864 + } + } + setup_priority_levels { + num_hosts_in_priority_level: 46080 + num_hosts_locality_a: 46080 + random_bytestring: 46080 + random_bytestring: 46080 + } + seed_for_prng: 2816 + } + need_local_priority_set: true + random_bytestring_for_weights: "qk\274\337\204\263!\246" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts new file mode 100644 index 000000000000..c0c794caa55f --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts @@ -0,0 +1,62 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01\x02" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts new file mode 100644 index 000000000000..c3e6091cf7ff --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts @@ -0,0 +1,62 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01\x02" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority new file mode 100644 index 000000000000..f3502762e74a --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority @@ -0,0 +1,48 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts new file mode 100644 index 000000000000..99fe34c09ed0 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts @@ -0,0 +1,48 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 50 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 100 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts new file mode 100644 index 000000000000..41e64fbf19b1 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts @@ -0,0 +1,28 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +seed_for_prng: 2 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal new file mode 100644 index 000000000000..6086e00c5d87 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal @@ -0,0 +1,45 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: "\x01\x02" + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: "\x01\x02" +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_fuzz.proto b/test/common/upstream/round_robin_load_balancer_fuzz.proto new file mode 100644 index 000000000000..a5ecf67ccc1c --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_fuzz.proto @@ -0,0 +1,12 @@ + +syntax = "proto3"; + +package test.common.upstream; + +import "validate/validate.proto"; +import "test/common/upstream/zone_aware_load_balancer_fuzz.proto"; + +message RoundRobinLoadBalancerTestCase { + test.common.upstream.ZoneAwareLoadBalancerTestCase zone_aware_load_balancer_test_case = 1 + [(validate.rules).message.required = true]; +} diff --git a/test/common/upstream/round_robin_load_balancer_fuzz_test.cc b/test/common/upstream/round_robin_load_balancer_fuzz_test.cc new file mode 100644 index 000000000000..4c1809a9a223 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_fuzz_test.cc @@ -0,0 +1,45 @@ +#include + +#include "test/common/upstream/round_robin_load_balancer_fuzz.pb.validate.h" +#include "test/common/upstream/zone_aware_load_balancer_fuzz_base.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Upstream { + +DEFINE_PROTO_FUZZER(const test::common::upstream::RoundRobinLoadBalancerTestCase& input) { + try { + TestUtility::validate(input); + } catch (const ProtoValidationException& e) { + ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); + return; + } + + const test::common::upstream::ZoneAwareLoadBalancerTestCase& zone_aware_load_balancer_test_case = + input.zone_aware_load_balancer_test_case(); + + ZoneAwareLoadBalancerFuzzBase zone_aware_load_balancer_fuzz = ZoneAwareLoadBalancerFuzzBase( + zone_aware_load_balancer_test_case.need_local_priority_set(), + zone_aware_load_balancer_test_case.random_bytestring_for_weights()); + zone_aware_load_balancer_fuzz.initializeLbComponents( + zone_aware_load_balancer_test_case.load_balancer_test_case()); + + try { + zone_aware_load_balancer_fuzz.lb_ = std::make_unique( + zone_aware_load_balancer_fuzz.priority_set_, + zone_aware_load_balancer_fuzz.local_priority_set_.get(), + zone_aware_load_balancer_fuzz.stats_, zone_aware_load_balancer_fuzz.runtime_, + zone_aware_load_balancer_fuzz.random_, + zone_aware_load_balancer_test_case.load_balancer_test_case().common_lb_config()); + } catch (EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException; {}", e.what()); + return; + } + + zone_aware_load_balancer_fuzz.replay( + zone_aware_load_balancer_test_case.load_balancer_test_case().actions()); +} + +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/test_cluster_manager.h b/test/common/upstream/test_cluster_manager.h index d76c5b06b0f1..3cce3f09182d 100644 --- a/test/common/upstream/test_cluster_manager.h +++ b/test/common/upstream/test_cluster_manager.h @@ -199,10 +199,12 @@ class MockedUpdatedClusterManagerImpl : public TestClusterManagerImpl { local_cluster_update_(local_cluster_update), local_hosts_removed_(local_hosts_removed) {} protected: - void postThreadLocalClusterUpdate(const Cluster&, uint32_t priority, - const HostVector& hosts_added, - const HostVector& hosts_removed) override { - local_cluster_update_.post(priority, hosts_added, hosts_removed); + void postThreadLocalClusterUpdate(ClusterManagerCluster&, + ThreadLocalClusterUpdateParams&& params) override { + for (const auto& per_priority : params.per_priority_update_params_) { + local_cluster_update_.post(per_priority.priority_, per_priority.hosts_added_, + per_priority.hosts_removed_); + } } void postThreadLocalDrainConnections(const Cluster&, const HostVector& hosts_removed) override { diff --git a/test/common/upstream/transport_socket_matcher_test.cc b/test/common/upstream/transport_socket_matcher_test.cc index 61e5ab2cec43..b564192f860e 100644 --- a/test/common/upstream/transport_socket_matcher_test.cc +++ b/test/common/upstream/transport_socket_matcher_test.cc @@ -31,9 +31,9 @@ namespace { class FakeTransportSocketFactory : public Network::TransportSocketFactory { public: MOCK_METHOD(bool, implementsSecureTransport, (), (const)); + MOCK_METHOD(bool, usesProxyProtocolOptions, (), (const)); MOCK_METHOD(Network::TransportSocketPtr, createTransportSocket, (Network::TransportSocketOptionsSharedPtr), (const)); - MOCK_METHOD(bool, isReady, (), (const)); FakeTransportSocketFactory(std::string id) : id_(std::move(id)) {} std::string id() const { return id_; } @@ -47,9 +47,9 @@ class FooTransportSocketFactory Logger::Loggable { public: MOCK_METHOD(bool, implementsSecureTransport, (), (const)); + MOCK_METHOD(bool, usesProxyProtocolOptions, (), (const)); MOCK_METHOD(Network::TransportSocketPtr, createTransportSocket, (Network::TransportSocketOptionsSharedPtr), (const)); - MOCK_METHOD(bool, isReady, (), (const)); Network::TransportSocketFactoryPtr createTransportSocketFactory(const Protobuf::Message& proto, @@ -110,7 +110,8 @@ name: "enableFooSocket" hasSidecar: "true" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "abc" )EOF"}); @@ -125,7 +126,8 @@ name: "sidecar_socket" sidecar: "true" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "sidecar")EOF", R"EOF( name: "http_socket" @@ -133,7 +135,8 @@ name: "http_socket" protocol: "http" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "http" )EOF"}); @@ -161,7 +164,8 @@ name: "sidecar_http_socket" protocol: "http" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "sidecar_http" )EOF", R"EOF( @@ -170,7 +174,8 @@ name: "sidecar_socket" sidecar: "true" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "sidecar" )EOF"}); envoy::config::core::v3::Metadata metadata; @@ -188,7 +193,8 @@ name: "match_all" match: {} transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "match_all" )EOF"}); envoy::config::core::v3::Metadata metadata; diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 336f40d86d91..a796379301d8 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -38,6 +38,7 @@ #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/priority_set.h" #include "test/test_common/registry.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -2397,7 +2398,9 @@ TEST_F(ClusterInfoImplTest, TypedExtensionProtocolOptionsForUnknownFilter) { } // This test case can't be converted for V3 API as it is specific for extension_protocol_options -TEST_F(ClusterInfoImplTest, OneofExtensionProtocolOptionsForUnknownFilter) { +TEST_F(ClusterInfoImplTest, + DEPRECATED_FEATURE_TEST(OneofExtensionProtocolOptionsForUnknownFilter)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( name: name connect_timeout: 0.25s diff --git a/test/common/upstream/utility.h b/test/common/upstream/utility.h index ef3dd210adf3..cd92eb9d37a3 100644 --- a/test/common/upstream/utility.h +++ b/test/common/upstream/utility.h @@ -79,48 +79,48 @@ inline envoy::config::cluster::v3::Cluster defaultStaticCluster(const std::strin inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& hostname, const std::string& url, uint32_t weight = 1) { - return HostSharedPtr{ - new HostImpl(cluster, hostname, Network::Utility::resolveUrl(url), nullptr, weight, - envoy::config::core::v3::Locality(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, - envoy::config::core::v3::UNKNOWN)}; + return std::make_shared( + cluster, hostname, Network::Utility::resolveUrl(url), nullptr, weight, + envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN); } inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, uint32_t weight = 1, uint32_t priority = 0) { - return HostSharedPtr{ - new HostImpl(cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, - envoy::config::core::v3::Locality(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), - priority, envoy::config::core::v3::UNKNOWN)}; + return std::make_shared( + cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, + envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), priority, + envoy::config::core::v3::UNKNOWN); } inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, const envoy::config::core::v3::Metadata& metadata, uint32_t weight = 1) { - return HostSharedPtr{ - new HostImpl(cluster, "", Network::Utility::resolveUrl(url), - std::make_shared(metadata), weight, - envoy::config::core::v3::Locality(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, - envoy::config::core::v3::UNKNOWN)}; + return std::make_shared( + cluster, "", Network::Utility::resolveUrl(url), + std::make_shared(metadata), weight, + envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN); } inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config, uint32_t weight = 1) { - return HostSharedPtr{new HostImpl(cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, + return std::make_shared(cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, envoy::config::core::v3::Locality(), health_check_config, 0, - envoy::config::core::v3::UNKNOWN)}; + envoy::config::core::v3::UNKNOWN); } inline HostDescriptionConstSharedPtr makeTestHostDescription(ClusterInfoConstSharedPtr cluster, const std::string& url) { - return HostDescriptionConstSharedPtr{new HostDescriptionImpl( + return std::make_shared( cluster, "", Network::Utility::resolveUrl(url), nullptr, envoy::config::core::v3::Locality().default_instance(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0)}; + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0); } inline HostsPerLocalitySharedPtr makeHostsPerLocality(std::vector&& locality_hosts, diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz.proto b/test/common/upstream/zone_aware_load_balancer_fuzz.proto new file mode 100644 index 000000000000..fa08df063c2e --- /dev/null +++ b/test/common/upstream/zone_aware_load_balancer_fuzz.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package test.common.upstream; + +import "validate/validate.proto"; +import "test/common/upstream/load_balancer_fuzz.proto"; + +message ZoneAwareLoadBalancerTestCase { + test.common.upstream.LoadBalancerTestCase load_balancer_test_case = 1 + [(validate.rules).message.required = true]; + // This determines whether ZoneAwareLoadBalancerFuzzBase will generate a local priority set to + // pass into Zone Aware constructor + bool need_local_priority_set = 2; + // This is used to determine the weights of each host - will wrap around if runs out of space + bytes random_bytestring_for_weights = 3 [(validate.rules).bytes = {min_len: 1, max_len: 2048}]; +} diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc new file mode 100644 index 000000000000..73b3890089f8 --- /dev/null +++ b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc @@ -0,0 +1,72 @@ +#include "zone_aware_load_balancer_fuzz_base.h" + +#include "test/mocks/upstream/host_set.h" + +namespace Envoy { +namespace Upstream { + +void ZoneAwareLoadBalancerFuzzBase::initializeASingleHostSet( + const test::common::upstream::SetupPriorityLevel& setup_priority_level, + const uint8_t priority_level, uint16_t& port) { + LoadBalancerFuzzBase::initializeASingleHostSet(setup_priority_level, priority_level, port); + // Update local priority set if it exists - will mean load balancer is zone aware and has decided + // to construct local priority set + if (priority_level == 0 && local_priority_set_) { + // TODO(zasweq): Perhaps fuzz the local priority set as a distinct host set? rather than + // making it P = 0 of main Priority Set + const MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + const HostVector empty_host_vector; + local_priority_set_->updateHosts(0, HostSetImpl::updateHostsParams(host_set), {}, + empty_host_vector, empty_host_vector, absl::nullopt); + } +} + +void ZoneAwareLoadBalancerFuzzBase::updateHealthFlagsForAHostSet( + const uint64_t host_priority, const uint32_t num_healthy_hosts, + const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts, + const Protobuf::RepeatedField& random_bytestring) { + LoadBalancerFuzzBase::updateHealthFlagsForAHostSet( + host_priority, num_healthy_hosts, num_degraded_hosts, num_excluded_hosts, random_bytestring); + // Update local priority set if it exists - will mean load balancer is zone aware and has decided + // to construct local priority set + const uint8_t priority_of_host_set = host_priority % num_priority_levels_; + if (priority_of_host_set == 0 && local_priority_set_) { + const MockHostSet& host_set = *priority_set_.getMockHostSet(priority_of_host_set); + const HostVector empty_host_vector; + local_priority_set_->updateHosts(0, HostSetImpl::updateHostsParams(host_set), {}, + empty_host_vector, empty_host_vector, absl::nullopt); + } +} + +void ZoneAwareLoadBalancerFuzzBase::initializeLbComponents( + const test::common::upstream::LoadBalancerTestCase& input) { + LoadBalancerFuzzBase::initializeLbComponents(input); + setupZoneAwareLoadBalancingSpecificLogic(); +} + +void ZoneAwareLoadBalancerFuzzBase::setupZoneAwareLoadBalancingSpecificLogic() { + // Having 3 possible weights, 1, 2, and 3 to provide the state space at least some variation + // in regards to weights, which do affect the load balancing algorithm. Cap the amount of + // weights at 3 for simplicity's sake + stats_.max_host_weight_.set(3UL); + addWeightsToHosts(); +} + +// Initialize the host set with weights once at setup +void ZoneAwareLoadBalancerFuzzBase::addWeightsToHosts() { + // Iterate through all the current host sets and update weights for each + for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + // Make sure no weights persisted from previous fuzz iterations + ASSERT(host->weight() == 1); + host->weight( + (random_bytestring_[index_of_random_bytestring_ % random_bytestring_.length()] % 3) + 1); + ++index_of_random_bytestring_; + } + } +} + +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.h b/test/common/upstream/zone_aware_load_balancer_fuzz_base.h new file mode 100644 index 000000000000..cc0359943c09 --- /dev/null +++ b/test/common/upstream/zone_aware_load_balancer_fuzz_base.h @@ -0,0 +1,60 @@ +#include "test/mocks/upstream/priority_set.h" + +#include "load_balancer_fuzz_base.h" + +namespace Envoy { +namespace Upstream { +class ZoneAwareLoadBalancerFuzzBase : public LoadBalancerFuzzBase { +public: + ZoneAwareLoadBalancerFuzzBase(bool need_local_cluster, const std::string& random_bytestring) + : random_bytestring_(random_bytestring) { + if (need_local_cluster) { + local_priority_set_ = std::make_shared(); + local_priority_set_->getOrCreateHostSet(0); + } + } + + ~ZoneAwareLoadBalancerFuzzBase() override { + // Clear out any set weights + for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + host->weight(1); + } + } + // This deletes the load balancer first. If constructed with a local priority set, load balancer + // with reference local priority set on destruction. Since local priority set is in a base + // class, it will be initialized second and thus destructed first. Thus, in order to avoid a use + // after free, you must delete the load balancer before deleting the priority set. + lb_.reset(); + } + + // These extend base class logic in order to handle local_priority_set_ if applicable. + void + initializeASingleHostSet(const test::common::upstream::SetupPriorityLevel& setup_priority_level, + const uint8_t priority_level, uint16_t& port) override; + + void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input) override; + + void updateHealthFlagsForAHostSet( + const uint64_t host_priority, const uint32_t num_healthy_hosts, + const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts, + const Protobuf::RepeatedField& random_bytestring) override; + + void setupZoneAwareLoadBalancingSpecificLogic(); + + void addWeightsToHosts(); + + // If fuzzing Zone Aware Load Balancers, local priority set will get constructed sometimes. If not + // constructed, a local_priority_set_.get() call will return a nullptr. + std::shared_ptr local_priority_set_; + +private: + // This bytestring will be iterated through representing randomness in order to choose + // weights for hosts. + const std::string random_bytestring_; + uint32_t index_of_random_bytestring_ = 0; +}; +} // namespace Upstream +} // namespace Envoy diff --git a/test/config/utility.cc b/test/config/utility.cc index 171651259497..54a8247dfb18 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -109,7 +109,7 @@ std::string ConfigHelper::tcpProxyConfig() { filters: name: tcp typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_stats cluster: cluster_0 )EOF"); @@ -128,7 +128,7 @@ std::string ConfigHelper::httpProxyConfig() { filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test delayed_close_timeout: nanos: 100 @@ -140,7 +140,7 @@ std::string ConfigHelper::httpProxyConfig() { filter: not_health_check_filter: {{}} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: {} route_config: virtual_hosts: @@ -167,7 +167,7 @@ std::string ConfigHelper::quicHttpProxyConfig() { filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: name: envoy.filters.http.router @@ -177,7 +177,7 @@ std::string ConfigHelper::quicHttpProxyConfig() { filter: not_health_check_filter: {{}} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: {} route_config: virtual_hosts: @@ -199,7 +199,7 @@ std::string ConfigHelper::defaultBufferFilter() { return R"EOF( name: buffer typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.buffer.v2.Buffer + "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer max_request_bytes : 5242880 )EOF"; } @@ -208,7 +208,7 @@ std::string ConfigHelper::smallBufferFilter() { return R"EOF( name: buffer typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.buffer.v2.Buffer + "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer max_request_bytes : 1024 )EOF"; } @@ -217,7 +217,7 @@ std::string ConfigHelper::defaultHealthCheckFilter() { return R"EOF( name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false )EOF"; } @@ -226,7 +226,7 @@ std::string ConfigHelper::defaultSquashFilter() { return R"EOF( name: squash typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.squash.v2.Squash + "@type": type.googleapis.com/envoy.extensions.filters.http.squash.v3.Squash cluster: squash attachment_template: spec: @@ -259,6 +259,7 @@ std::string ConfigHelper::discoveredClustersBootstrap(const std::string& api_typ port_value: 0 dynamic_resources: cds_config: + resource_api_version: V3 api_config_source: api_type: {} grpc_services: @@ -288,7 +289,7 @@ std::string ConfigHelper::discoveredClustersBootstrap(const std::string& api_typ filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: name: envoy.filters.http.router @@ -477,7 +478,7 @@ ConfigHelper::buildListener(const std::string& name, const std::string& route_co filters: - name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: {} codec_type: HTTP2 rds: @@ -617,6 +618,11 @@ void ConfigHelper::addRuntimeOverride(const std::string& key, const std::string& (*static_layer->mutable_fields())[std::string(key)] = ValueUtil::stringValue(std::string(value)); } +void ConfigHelper::enableDeprecatedV2Api() { + addRuntimeOverride("envoy.reloadable_features.enable_deprecated_v2_api", "true"); + addRuntimeOverride("envoy.features.enable_all_deprecated_features", "true"); +} + void ConfigHelper::setNewCodecs() { addRuntimeOverride("envoy.reloadable_features.new_codec_behavior", "true"); } diff --git a/test/config/utility.h b/test/config/utility.h index 51b1a8ef152b..0f7fc73b433e 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -242,6 +242,9 @@ class ConfigHelper { // Add this key value pair to the static runtime. void addRuntimeOverride(const std::string& key, const std::string& value); + // Enable deprecated v2 API resources via the runtime. + void enableDeprecatedV2Api(); + // Add filter_metadata to a cluster with the given name void addClusterFilterMetadata(absl::string_view metadata_yaml, absl::string_view cluster_name = "cluster_0"); diff --git a/test/config_test/BUILD b/test/config_test/BUILD index 792ba3d60320..6f17e5bcd689 100644 --- a/test/config_test/BUILD +++ b/test/config_test/BUILD @@ -64,6 +64,7 @@ envoy_cc_test( "//source/common/config:api_version_lib", "//test/test_common:environment_lib", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v2:pkg_cc_proto", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/test/config_test/deprecated_configs_test.cc b/test/config_test/deprecated_configs_test.cc index dbb10707912e..c0c68b9dd1d5 100644 --- a/test/config_test/deprecated_configs_test.cc +++ b/test/config_test/deprecated_configs_test.cc @@ -6,6 +6,7 @@ #include "test/config_test/config_test.h" #include "test/test_common/environment.h" #include "test/test_common/logging.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -28,11 +29,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapTextProtoDepr const std::string filename = TestEnvironment::writeStringToFileForTest("proto.pb_text", bootstrap_text); - // Loading as previous version should work (after upgrade) - API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; - EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", - ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); - EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading as previous version should work (after upgrade) + API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; + EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", + ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); + EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + } // Loading as current version should fail API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v3_from_file; @@ -80,11 +84,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapBinaryProtoDe const std::string filename = TestEnvironment::writeStringToFileForTest("proto.pb", bootstrap_binary_str); - // Loading as previous version should work (after upgrade) - API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; - EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", - ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); - EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading as previous version should work (after upgrade) + API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; + EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", + ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); + EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + } // Loading as current version should fail API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v3_from_file; @@ -111,11 +118,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapBinaryProtoDe HasSubstr("Illegal use of hidden_envoy_deprecated_ V2 field " "'envoy.config.core.v3.Node.hidden_envoy_deprecated_build_version'")); - // Loading binary proto v3 with hidden-deprecated field with boosting will - // succeed as it cannot differentiate between v2 with the deprecated field and - // v3 with hidden_envoy_deprecated field - ConfigTest::loadVersionedBootstrapFile(filename_v3, proto_v3_from_file); - EXPECT_EQ("foo", proto_v3_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading binary proto v3 with hidden-deprecated field with boosting will + // succeed as it cannot differentiate between v2 with the deprecated field and + // v3 with hidden_envoy_deprecated field + ConfigTest::loadVersionedBootstrapFile(filename_v3, proto_v3_from_file); + EXPECT_EQ("foo", proto_v3_from_file.node().hidden_envoy_deprecated_build_version()); + } } // A deprecated field can be used in previous version yaml and upgraded. @@ -131,11 +141,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapYamlDeprecate const std::string filename = TestEnvironment::writeStringToFileForTest( "proto.yaml", MessageUtil::getYamlStringFromMessage(bootstrap, false, false)); - // Loading as previous version should work (after upgrade) - API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; - EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", - ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); - EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading as previous version should work (after upgrade) + API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; + EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", + ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); + EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + } // Loading as current version should fail API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v3_from_file; @@ -171,6 +184,7 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapYamlDeprecate // A deprecated field can be used in previous version json and upgraded. TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapJsonDeprecatedField)) { + TestDeprecatedV2Api _deprecated_v2_api; API_NO_BOOST(envoy::config::bootstrap::v2::Bootstrap) bootstrap = TestUtility::parseYaml(R"EOF( node: diff --git a/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc index b635e7084fd3..a54dcaa7ec7d 100644 --- a/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc +++ b/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc @@ -137,7 +137,6 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { cluster: cluster_name locality: zone: zone_name - build_version: {} user_agent_name: "envoy" log_name: foo http_logs: @@ -156,8 +155,7 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { value: 404 response_code_details: "route_not_found" response_headers_bytes: 54 -)EOF", - VersionInfo::version()))); +)EOF"))); BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( lookupPort("http"), "GET", "/notfound", "", downstream_protocol_, version_); @@ -210,7 +208,6 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { cluster: cluster_name locality: zone: zone_name - build_version: {} user_agent_name: "envoy" log_name: foo http_logs: @@ -229,8 +226,7 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { value: 404 response_code_details: "route_not_found" response_headers_bytes: 54 -)EOF", - VersionInfo::version()))); +)EOF"))); cleanup(); } diff --git a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc index 852f9867c7c2..203b7eb98068 100644 --- a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc +++ b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc @@ -147,15 +147,14 @@ TEST_P(TcpGrpcAccessLogIntegrationTest, BasicAccessLogFlow) { ASSERT_TRUE(waitForAccessLogConnection()); ASSERT_TRUE(waitForAccessLogStream()); - ASSERT_TRUE(waitForAccessLogRequest( - fmt::format(R"EOF( + ASSERT_TRUE( + waitForAccessLogRequest(fmt::format(R"EOF( identifier: node: id: node_name cluster: cluster_name locality: zone: zone_name - build_version: {} user_agent_name: "envoy" log_name: foo tcp_logs: @@ -181,11 +180,11 @@ TEST_P(TcpGrpcAccessLogIntegrationTest, BasicAccessLogFlow) { received_bytes: 3 sent_bytes: 5 )EOF", - VersionInfo::version(), Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion())))); + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion())))); cleanup(); } diff --git a/test/extensions/access_loggers/wasm/BUILD b/test/extensions/access_loggers/wasm/BUILD index 54ab90482a91..fc56bc9b7c39 100644 --- a/test/extensions/access_loggers/wasm/BUILD +++ b/test/extensions/access_loggers/wasm/BUILD @@ -25,6 +25,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/access_loggers/wasm:config", "//test/extensions/access_loggers/wasm/test_data:test_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/access_loggers/wasm/config_test.cc b/test/extensions/access_loggers/wasm/config_test.cc index 02a71c9132b7..fb570453db30 100644 --- a/test/extensions/access_loggers/wasm/config_test.cc +++ b/test/extensions/access_loggers/wasm/config_test.cc @@ -9,6 +9,7 @@ #include "extensions/access_loggers/well_known_names.h" #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" @@ -39,17 +40,8 @@ class TestFactoryContext : public NiceMock {}; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmAccessLogConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmAccessLogConfigTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmAccessLogConfigTest, CreateWasmFromEmpty) { auto factory = diff --git a/test/extensions/bootstrap/wasm/BUILD b/test/extensions/bootstrap/wasm/BUILD index 6a6488e2b63b..b9c8282420c3 100644 --- a/test/extensions/bootstrap/wasm/BUILD +++ b/test/extensions/bootstrap/wasm/BUILD @@ -37,6 +37,7 @@ envoy_extension_cc_test( "//source/extensions/bootstrap/wasm:config", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:stats_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", @@ -58,6 +59,7 @@ envoy_extension_cc_test( "//source/extensions/bootstrap/wasm:config", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:start_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/event:event_mocks", "//test/mocks/server:server_mocks", "//test/mocks/thread_local:thread_local_mocks", @@ -85,6 +87,7 @@ envoy_extension_cc_test_binary( "//source/extensions/bootstrap/wasm:config", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:speed_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", diff --git a/test/extensions/bootstrap/wasm/config_test.cc b/test/extensions/bootstrap/wasm/config_test.cc index 6fb99261a3f8..a0b7274e53fd 100644 --- a/test/extensions/bootstrap/wasm/config_test.cc +++ b/test/extensions/bootstrap/wasm/config_test.cc @@ -6,6 +6,7 @@ #include "extensions/bootstrap/wasm/config.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/event/mocks.h" #include "test/mocks/server/mocks.h" #include "test/mocks/thread_local/mocks.h" @@ -67,17 +68,8 @@ class WasmFactoryTest : public testing::TestWithParam { Server::BootstrapExtensionPtr extension_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFactoryTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFactoryTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmFactoryTest, CreateWasmFromWasm) { auto factory = std::make_unique(); diff --git a/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc b/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc index 70fde8f6ae19..9a5becfab1b3 100644 --- a/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc +++ b/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc @@ -26,10 +26,7 @@ extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_configure(uint32_t, uint32_t c extern "C" PROXY_WASM_KEEPALIVE void proxy_on_context_create(uint32_t, uint32_t) {} -extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_vm_start(uint32_t, uint32_t) { - proxy_set_tick_period_milliseconds(10); - return 1; -} +extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_vm_start(uint32_t, uint32_t) { return 1; } extern "C" PROXY_WASM_KEEPALIVE void proxy_on_tick(uint32_t) { const char* root_id = nullptr; diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 2befcbd97cba..757b086770b6 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -3,6 +3,7 @@ #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" @@ -73,27 +74,14 @@ class WasmTestBase { std::shared_ptr wasm_; }; -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) class WasmTest : public WasmTestBase, public testing::TestWithParam { public: void createWasm() { WasmTestBase::createWasm(GetParam()); } }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8" -#endif -#if defined(ENVOY_WASM_V8) && defined(ENVOY_WASM_WAVM) - , -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm" -#endif -); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmTest, testing_values); -#endif +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmTest, + Envoy::Extensions::Common::Wasm::sandbox_runtime_values); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmTest); class WasmNullTest : public WasmTestBase, public testing::TestWithParam { public: @@ -109,19 +97,8 @@ class WasmNullTest : public WasmTestBase, public testing::TestWithParam> { public: @@ -141,18 +118,9 @@ class WasmTestMatrix : public WasmTestBase, }; INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmTestMatrix, - testing::Combine(testing::Values( -#if defined(ENVOY_WASM_V8) - "v8" -#endif -#if defined(ENVOY_WASM_V8) && defined(ENVOY_WASM_WAVM) - , -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm" -#endif - ), + testing::Combine(Envoy::Extensions::Common::Wasm::sandbox_runtime_values, testing::Values("cpp", "rust"))); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmTestMatrix); TEST_P(WasmTestMatrix, Logging) { plugin_configuration_ = "configure-test"; @@ -186,9 +154,7 @@ TEST_P(WasmTestMatrix, Logging) { dispatcher_->run(Event::Dispatcher::RunType::NonBlock); dispatcher_->clearDeferredDeleteList(); } -#endif -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) TEST_P(WasmTest, BadSignature) { createWasm(); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( @@ -207,7 +173,7 @@ TEST_P(WasmTest, Segv) { auto context = static_cast(wasm_->start(plugin_)); EXPECT_CALL(*context, log_(spdlog::level::err, Eq("before badptr"))); EXPECT_FALSE(wasm_->configure(context, plugin_)); - wasm_->isFailed(); + EXPECT_TRUE(wasm_->isFailed()); } TEST_P(WasmTest, DivByZero) { @@ -219,7 +185,7 @@ TEST_P(WasmTest, DivByZero) { auto context = static_cast(wasm_->start(plugin_)); EXPECT_CALL(*context, log_(spdlog::level::err, Eq("before div by zero"))); context->onLog(); - wasm_->isFailed(); + EXPECT_TRUE(wasm_->isFailed()); } TEST_P(WasmTest, IntrinsicGlobals) { @@ -250,7 +216,6 @@ TEST_P(WasmTest, Asm2Wasm) { EXPECT_CALL(*context, log_(spdlog::level::info, Eq("out 0 0 0"))); EXPECT_TRUE(wasm_->configure(context, plugin_)); } -#endif TEST_P(WasmNullTest, Stats) { createWasm(); diff --git a/test/extensions/clusters/aggregate/cluster_integration_test.cc b/test/extensions/clusters/aggregate/cluster_integration_test.cc index 566392702462..623bc60c8149 100644 --- a/test/extensions/clusters/aggregate/cluster_integration_test.cc +++ b/test/extensions/clusters/aggregate/cluster_integration_test.cc @@ -38,6 +38,7 @@ const std::string& config() { port_value: 0 dynamic_resources: cds_config: + resource_api_version: V3 api_config_source: api_type: GRPC grpc_services: @@ -64,7 +65,7 @@ const std::string& config() { cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - cluster_1 - cluster_2 @@ -78,7 +79,7 @@ const std::string& config() { filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: name: envoy.filters.http.router @@ -227,7 +228,7 @@ TEST_P(AggregateIntegrationTest, TwoClusters) { // Tell Envoy that cluster_1 is gone. EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); sendDiscoveryResponse( - Config::TypeUrl::get().Cluster, {cluster2_}, {}, {FirstClusterName}, "42"); + Config::TypeUrl::get().Cluster, {cluster2_}, {}, {FirstClusterName}, "43"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); @@ -237,7 +238,7 @@ TEST_P(AggregateIntegrationTest, TwoClusters) { ASSERT_TRUE(codec_client_->waitForDisconnect()); // Tell Envoy that cluster_1 is back. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "43", {}, {}, {})); sendDiscoveryResponse( Config::TypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_}, {}, "413"); diff --git a/test/extensions/clusters/aggregate/cluster_test.cc b/test/extensions/clusters/aggregate/cluster_test.cc index e98d79524a9f..c45de45e3778 100644 --- a/test/extensions/clusters/aggregate/cluster_test.cc +++ b/test/extensions/clusters/aggregate/cluster_test.cc @@ -155,7 +155,7 @@ class AggregateClusterTest : public testing::Test { cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - primary - secondary diff --git a/test/extensions/clusters/aggregate/cluster_update_test.cc b/test/extensions/clusters/aggregate/cluster_update_test.cc index 8755ba8a29ef..b706e38dfbfc 100644 --- a/test/extensions/clusters/aggregate/cluster_update_test.cc +++ b/test/extensions/clusters/aggregate/cluster_update_test.cc @@ -67,7 +67,7 @@ class AggregateClusterUpdateTest : public testing::Test { cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - primary - secondary @@ -253,7 +253,7 @@ TEST_F(AggregateClusterUpdateTest, InitializeAggregateClusterAfterOtherClusters) cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - primary - secondary diff --git a/test/extensions/clusters/dynamic_forward_proxy/BUILD b/test/extensions/clusters/dynamic_forward_proxy/BUILD index 9572e4a947d7..116fe24dfc52 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/BUILD +++ b/test/extensions/clusters/dynamic_forward_proxy/BUILD @@ -29,6 +29,7 @@ envoy_extension_cc_test( "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:load_balancer_mocks", "//test/test_common:environment_lib", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc index 6908428c3c10..4bce356d487a 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc +++ b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc @@ -16,6 +16,7 @@ #include "test/mocks/upstream/load_balancer.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/test_common/environment.h" +#include "test/test_common/test_runtime.h" using testing::AtLeast; using testing::DoAll; @@ -137,7 +138,7 @@ connect_timeout: 0.25s cluster_type: name: dynamic_forward_proxy typed_config: - "@type": type.googleapis.com/envoy.config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig dns_cache_config: name: foo dns_lookup_family: AUTO @@ -232,6 +233,7 @@ class ClusterFactoryTest : public testing::Test { // Verify that using 'sni' causes a failure. TEST_F(ClusterFactoryTest, DEPRECATED_FEATURE_TEST(InvalidSNI)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_config = TestEnvironment::substitute(R"EOF( name: name connect_timeout: 0.25s @@ -256,6 +258,7 @@ connect_timeout: 0.25s // Verify that using 'verify_subject_alt_name' causes a failure. TEST_F(ClusterFactoryTest, DEPRECATED_FEATURE_TEST(InvalidVerifySubjectAltName)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_config = TestEnvironment::substitute(R"EOF( name: name connect_timeout: 0.25s @@ -285,7 +288,7 @@ connect_timeout: 0.25s cluster_type: name: dynamic_forward_proxy typed_config: - "@type": type.googleapis.com/envoy.config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig dns_cache_config: name: foo upstream_http_protocol_options: {} diff --git a/test/extensions/clusters/redis/redis_cluster_integration_test.cc b/test/extensions/clusters/redis/redis_cluster_integration_test.cc index 53f4d6454ec9..a45187a2d19a 100644 --- a/test/extensions/clusters/redis/redis_cluster_integration_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_integration_test.cc @@ -36,7 +36,7 @@ const std::string& listenerConfig() { filters: name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats prefix_routes: catch_all_route: @@ -118,7 +118,7 @@ const std::string& testConfigWithAuth() { CONSTRUCT_ON_FIRST_USE(std::string, testConfig() + R"EOF( typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: { inline_string: somepassword } )EOF"); } diff --git a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc index d6bd8e2b698d..a180f9ef16a8 100644 --- a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc +++ b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc @@ -23,12 +23,12 @@ class AwsMetadataIntegrationTestBase : public ::testing::Test, public BaseIntegr filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: metadata_test http_filters: - name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault delay: fixed_delay: seconds: {} diff --git a/test/extensions/common/redis/cluster_refresh_manager_test.cc b/test/extensions/common/redis/cluster_refresh_manager_test.cc index 916a1457da31..ea07e1624384 100644 --- a/test/extensions/common/redis/cluster_refresh_manager_test.cc +++ b/test/extensions/common/redis/cluster_refresh_manager_test.cc @@ -33,8 +33,8 @@ class ClusterRefreshManagerTest : public testing::Test { : cluster_name_("fake_cluster"), refresh_manager_(std::make_shared( dispatcher_, cm_, time_system_)) { time_system_.setMonotonicTime(std::chrono::seconds(1)); - map_.emplace("fake_cluster", mock_cluster_); - ON_CALL(cm_, clusters()).WillByDefault(Return(map_)); + cluster_maps_.active_clusters_.emplace("fake_cluster", mock_cluster_); + ON_CALL(cm_, clusters()).WillByDefault(Return(cluster_maps_)); } ~ClusterRefreshManagerTest() override = default; @@ -104,7 +104,7 @@ class ClusterRefreshManagerTest : public testing::Test { const std::string cluster_name_; NiceMock dispatcher_; NiceMock cm_; - Upstream::ClusterManager::ClusterInfoMap map_; + Upstream::ClusterManager::ClusterInfoMaps cluster_maps_; Upstream::MockClusterMockPrioritySet mock_cluster_; Event::SimulatedTimeSystem time_system_; std::shared_ptr refresh_manager_; diff --git a/test/extensions/common/tap/BUILD b/test/extensions/common/tap/BUILD index c5a459721faf..5a5948d3eee0 100644 --- a/test/extensions/common/tap/BUILD +++ b/test/extensions/common/tap/BUILD @@ -27,6 +27,7 @@ envoy_cc_test( "//source/extensions/common/tap:admin", "//test/mocks/server:admin_mocks", "//test/mocks/server:admin_stream_mocks", + "//test/test_common:logging_lib", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/common/tap/admin_test.cc b/test/extensions/common/tap/admin_test.cc index 6daa71156b0e..020f563600d5 100644 --- a/test/extensions/common/tap/admin_test.cc +++ b/test/extensions/common/tap/admin_test.cc @@ -4,6 +4,7 @@ #include "test/mocks/server/admin.h" #include "test/mocks/server/admin_stream.h" +#include "test/test_common/logging.h" #include "gtest/gtest.h" @@ -28,9 +29,11 @@ class MockExtensionConfig : public ExtensionConfig { class AdminHandlerTest : public testing::Test { public: - AdminHandlerTest() { + void setup(Network::Address::Type socket_type = Network::Address::Type::Ip) { + ON_CALL(admin_.socket_, addressType()).WillByDefault(Return(socket_type)); EXPECT_CALL(admin_, addHandler("/tap", "tap filter control", _, true, true)) .WillOnce(DoAll(SaveArg<2>(&cb_), Return(true))); + EXPECT_CALL(admin_, socket()); handler_ = std::make_unique(admin_, main_thread_dispatcher_); } @@ -58,8 +61,18 @@ config_id: test_config_id )EOF"; }; +// Make sure warn if using a pipe address for the admin handler. +TEST_F(AdminHandlerTest, AdminWithPipeSocket) { + EXPECT_LOG_CONTAINS( + "warn", + "Admin tapping (via /tap) is unreliable when the admin endpoint is a pipe and the connection " + "is HTTP/1. Either use an IP address or connect using HTTP/2.", + setup(Network::Address::Type::Pipe)); +} + // Request with no config body. TEST_F(AdminHandlerTest, NoBody) { + setup(); EXPECT_CALL(admin_stream_, getRequestBody()); EXPECT_EQ(Http::Code::BadRequest, cb_("/tap", response_headers_, response_, admin_stream_)); EXPECT_EQ("/tap requires a JSON/YAML body", response_.toString()); @@ -67,6 +80,7 @@ TEST_F(AdminHandlerTest, NoBody) { // Request with a config body that doesn't parse/verify. TEST_F(AdminHandlerTest, BadBody) { + setup(); Buffer::OwnedImpl bad_body("hello"); EXPECT_CALL(admin_stream_, getRequestBody()).WillRepeatedly(Return(&bad_body)); EXPECT_EQ(Http::Code::BadRequest, cb_("/tap", response_headers_, response_, admin_stream_)); @@ -75,6 +89,7 @@ TEST_F(AdminHandlerTest, BadBody) { // Request that references an unknown config ID. TEST_F(AdminHandlerTest, UnknownConfigId) { + setup(); Buffer::OwnedImpl body(admin_request_yaml_); EXPECT_CALL(admin_stream_, getRequestBody()).WillRepeatedly(Return(&body)); EXPECT_EQ(Http::Code::BadRequest, cb_("/tap", response_headers_, response_, admin_stream_)); @@ -84,6 +99,7 @@ TEST_F(AdminHandlerTest, UnknownConfigId) { // Request while there is already an active tap session. TEST_F(AdminHandlerTest, RequestTapWhileAttached) { + setup(); MockExtensionConfig extension_config; handler_->registerConfig(extension_config, "test_config_id"); diff --git a/test/extensions/common/tap/common.h b/test/extensions/common/tap/common.h index 023e61a82444..772af3c6f546 100644 --- a/test/extensions/common/tap/common.h +++ b/test/extensions/common/tap/common.h @@ -13,7 +13,7 @@ namespace data { namespace tap { namespace v3 { -// TODO(mattklein123): AFAICT gtest has built in printing for proto messages but it doesn't seem +// TODO(mattklein123): `AFAICT` gtest has built in printing for proto messages but it doesn't seem // to work unless this is here. std::ostream& operator<<(std::ostream& os, const TraceWrapper& trace); diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 4e586cb7902f..b26be4cdb7e5 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -2,6 +2,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_cc_test_binary", + "envoy_cc_test_library", "envoy_package", ) load( @@ -21,6 +22,7 @@ envoy_cc_test( ]), deps = [ "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/test_common:environment_lib", "//test/test_common:registry_lib", "//test/test_common:utility_lib", @@ -44,6 +46,7 @@ envoy_cc_test( "//source/common/stats:stats_lib", "//source/extensions/common/crypto:utility_lib", "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/common/wasm/test_data:test_context_cpp_plugin", "//test/extensions/common/wasm/test_data:test_cpp_plugin", "//test/mocks/server:server_mocks", @@ -63,8 +66,21 @@ envoy_cc_test_binary( deps = [ "//source/common/event:dispatcher_lib", "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", ], ) + +envoy_cc_test_library( + name = "wasm_runtime", + srcs = ["wasm_runtime.cc"], + hdrs = ["wasm_runtime.h"], + deps = [ + "//source/extensions/wasm_runtime/null:config", + "//source/extensions/wasm_runtime/v8:config", + "//source/extensions/wasm_runtime/wasmtime:config", + "//source/extensions/wasm_runtime/wavm:config", + ], +) diff --git a/test/extensions/common/wasm/test_data/test_cpp.cc b/test/extensions/common/wasm/test_data/test_cpp.cc index 1d990901846a..f2003072ee8c 100644 --- a/test/extensions/common/wasm/test_data/test_cpp.cc +++ b/test/extensions/common/wasm/test_data/test_cpp.cc @@ -267,6 +267,10 @@ WASM_EXPORT(uint32_t, proxy_on_done, (uint32_t)) { return 0; } +WASM_EXPORT(void, proxy_on_tick, (uint32_t)) { + proxy_done(); +} + WASM_EXPORT(void, proxy_on_delete, (uint32_t)) { std::string message = "on_delete logging"; proxy_log(LogLevel::info, message.c_str(), message.size()); diff --git a/test/extensions/common/wasm/wasm_runtime.cc b/test/extensions/common/wasm/wasm_runtime.cc new file mode 100644 index 000000000000..a8451c70df64 --- /dev/null +++ b/test/extensions/common/wasm/wasm_runtime.cc @@ -0,0 +1,41 @@ +#include "test/extensions/common/wasm/wasm_runtime.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +std::vector runtimes() { + std::vector runtimes = sandboxRuntimes(); + runtimes.push_back("null"); + return runtimes; +} + +std::vector sandboxRuntimes() { + std::vector runtimes; +#if defined(ENVOY_WASM_V8) + runtimes.push_back("v8"); +#endif +#if defined(ENVOY_WASM_WAVM) + runtimes.push_back("wavm"); +#endif +#if defined(ENVOY_WASM_WASMTIME) + runtimes.push_back("wasmtime"); +#endif + return runtimes; +} + +std::vector> runtimesAndLanguages() { + std::vector> values; + for (const auto& runtime : sandboxRuntimes()) { + values.push_back(std::make_tuple(runtime, "cpp")); + values.push_back(std::make_tuple(runtime, "rust")); + } + values.push_back(std::make_tuple("null", "cpp")); + return values; +} + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_runtime.h b/test/extensions/common/wasm/wasm_runtime.h new file mode 100644 index 000000000000..ef248d85310b --- /dev/null +++ b/test/extensions/common/wasm/wasm_runtime.h @@ -0,0 +1,26 @@ +#pragma once + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +// All WASM runtimes. +std::vector runtimes(); + +// All sandboxed WASM runtimes. +std::vector sandboxRuntimes(); + +// Testable runtime and language combinations +std::vector> runtimesAndLanguages(); + +inline auto runtime_values = testing::ValuesIn(runtimes()); +inline auto sandbox_runtime_values = testing::ValuesIn(sandboxRuntimes()); +inline auto runtime_and_language_values = testing::ValuesIn(runtimesAndLanguages()); + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 0c84105cd778..cd56089daf5f 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -6,6 +6,7 @@ #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/mocks.h" @@ -89,17 +90,7 @@ class WasmCommonTest : public testing::TestWithParam { } }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto test_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonTest, test_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonTest, Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmCommonTest, EnvoyWasm) { auto envoy_wasm = std::make_unique(); @@ -234,8 +225,8 @@ TEST_P(WasmCommonTest, Logging) { wasm_handle.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); // This will fault on nullptr if wasm has been deleted. - plugin->plugin_configuration_ = "done"; - wasm_weak.lock()->configure(root_context, plugin); + wasm_weak.lock()->setTimerPeriod(root_context->id(), std::chrono::milliseconds(10)); + wasm_weak.lock()->tickHandler(root_context->id()); dispatcher->run(Event::Dispatcher::RunType::NonBlock); dispatcher->clearDeferredDeleteList(); } @@ -645,7 +636,7 @@ TEST_P(WasmCommonTest, VmCache) { auto root_id = ""; auto vm_id = ""; auto vm_configuration = "vm_cache"; - auto plugin_configuration = "init"; + auto plugin_configuration = "done"; auto plugin = std::make_shared( name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); @@ -689,7 +680,7 @@ TEST_P(WasmCommonTest, VmCache) { EXPECT_NE(wasm_handle2, nullptr); EXPECT_EQ(wasm_handle, wasm_handle2); - auto wasm_handle_local = getOrCreateThreadLocalWasm( + auto plugin_handle_local = getOrCreateThreadLocalPlugin( wasm_handle, plugin, [&dispatcher](const WasmHandleBaseSharedPtr& base_wasm) -> WasmHandleBaseSharedPtr { auto wasm = @@ -698,22 +689,24 @@ TEST_P(WasmCommonTest, VmCache) { nullptr, [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { auto root_context = new TestContext(wasm, plugin); EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_vm_start vm_cache"))); - EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_configuration init"))); EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_done logging"))); EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); return std::make_shared(wasm); + }, + [](const WasmHandleBaseSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(base_wasm), + plugin_key); }); wasm_handle.reset(); wasm_handle2.reset(); - auto wasm = wasm_handle_local->wasm().get(); - wasm_handle_local.reset(); + auto wasm = plugin_handle_local->wasm(); + plugin_handle_local.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); - - plugin->plugin_configuration_ = "done"; wasm->configure(wasm->getContext(1), plugin); plugin.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); @@ -792,7 +785,7 @@ TEST_P(WasmCommonTest, RemoteCode) { EXPECT_NE(wasm_handle, nullptr); - auto wasm_handle_local = getOrCreateThreadLocalWasm( + auto plugin_handle_local = getOrCreateThreadLocalPlugin( wasm_handle, plugin, [&dispatcher](const WasmHandleBaseSharedPtr& base_wasm) -> WasmHandleBaseSharedPtr { auto wasm = @@ -806,11 +799,17 @@ TEST_P(WasmCommonTest, RemoteCode) { return root_context; }); return std::make_shared(wasm); + }, + [](const WasmHandleBaseSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(base_wasm), + plugin_key); }); wasm_handle.reset(); - auto wasm = wasm_handle_local->wasm().get(); - wasm_handle_local.reset(); + auto wasm = plugin_handle_local->wasm(); + plugin_handle_local.reset(); + dispatcher->run(Event::Dispatcher::RunType::NonBlock); wasm->configure(wasm->getContext(1), plugin); plugin.reset(); @@ -902,7 +901,7 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { dispatcher->run(Event::Dispatcher::RunType::NonBlock); EXPECT_NE(wasm_handle, nullptr); - auto wasm_handle_local = getOrCreateThreadLocalWasm( + auto plugin_handle_local = getOrCreateThreadLocalPlugin( wasm_handle, plugin, [&dispatcher](const WasmHandleBaseSharedPtr& base_wasm) -> WasmHandleBaseSharedPtr { auto wasm = @@ -916,11 +915,16 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { return root_context; }); return std::make_shared(wasm); + }, + [](const WasmHandleBaseSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(base_wasm), + plugin_key); }); wasm_handle.reset(); - auto wasm = wasm_handle_local->wasm().get(); - wasm_handle_local.reset(); + auto wasm = plugin_handle_local->wasm(); + plugin_handle_local.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); wasm->configure(wasm->getContext(1), plugin); @@ -953,7 +957,8 @@ class WasmCommonContextTest std::unique_ptr context_; }; -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, test_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmCommonContextTest, OnDnsResolve) { std::string code; diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index 88f4c980d62d..208d5817c853 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -448,6 +448,9 @@ TEST(Context, ConnectionAttributes) { EXPECT_CALL(info, upstreamTransportFailureReason()) .WillRepeatedly(ReturnRef(upstream_transport_failure_reason)); EXPECT_CALL(info, connectionID()).WillRepeatedly(Return(123)); + const absl::optional connection_termination_details = "unauthorized"; + EXPECT_CALL(info, connectionTerminationDetails()) + .WillRepeatedly(ReturnRef(connection_termination_details)); EXPECT_CALL(*downstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address)); @@ -611,6 +614,13 @@ TEST(Context, ConnectionAttributes) { EXPECT_EQ(123, value.value().Uint64OrDie()); } + { + auto value = connection[CelValue::CreateStringView(ConnectionTerminationDetails)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsString()); + EXPECT_EQ(connection_termination_details.value(), value.value().StringOrDie().value()); + } + { auto value = upstream[CelValue::CreateStringView(TLSVersion)]; EXPECT_TRUE(value.has_value()); diff --git a/test/extensions/filters/common/ext_authz/BUILD b/test/extensions/filters/common/ext_authz/BUILD index ebf3ad3eac1f..7c840b57ca09 100644 --- a/test/extensions/filters/common/ext_authz/BUILD +++ b/test/extensions/filters/common/ext_authz/BUILD @@ -32,7 +32,6 @@ envoy_cc_test( deps = [ "//source/extensions/filters/common/ext_authz:ext_authz_grpc_lib", "//test/extensions/filters/common/ext_authz:ext_authz_test_common", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/service/auth/v3:pkg_cc_proto", diff --git a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc index d51a6bd5c104..6f5a4e042e3f 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc @@ -11,7 +11,6 @@ #include "test/extensions/filters/common/ext_authz/test_common.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/test_common/test_runtime.h" @@ -355,65 +354,6 @@ TEST_P(ExtAuthzGrpcClientTest, AuthorizationOkWithDynamicMetadata) { client_->onSuccess(std::move(check_response), span_); } -class AsyncClientCacheTest : public testing::Test { -public: - AsyncClientCacheTest() { - client_cache_ = std::make_unique(async_client_manager_, scope_, tls_); - } - - void expectClientCreation() { - factory_ = new Grpc::MockAsyncClientFactory; - async_client_ = new Grpc::MockAsyncClient; - EXPECT_CALL(async_client_manager_, factoryForGrpcService(_, _, true)) - .WillOnce(Invoke([this](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { - EXPECT_CALL(*factory_, create()).WillOnce(Invoke([this] { - return Grpc::RawAsyncClientPtr{async_client_}; - })); - return Grpc::AsyncClientFactoryPtr{factory_}; - })); - } - - NiceMock tls_; - Grpc::MockAsyncClientManager async_client_manager_; - Grpc::MockAsyncClient* async_client_ = nullptr; - Grpc::MockAsyncClientFactory* factory_ = nullptr; - std::unique_ptr client_cache_; - NiceMock scope_; -}; - -TEST_F(AsyncClientCacheTest, Deduplication) { - Stats::IsolatedStoreImpl scope; - testing::InSequence s; - - envoy::extensions::filters::http::ext_authz::v3::ExtAuthz config; - config.mutable_grpc_service()->mutable_google_grpc()->set_target_uri("dns://test01"); - config.mutable_grpc_service()->mutable_google_grpc()->set_credentials_factory_name( - "test_credential01"); - - expectClientCreation(); - Grpc::RawAsyncClientSharedPtr test_client_01 = client_cache_->getOrCreateAsyncClient(config); - // Fetches the existing client. - EXPECT_EQ(test_client_01, client_cache_->getOrCreateAsyncClient(config)); - - config.mutable_grpc_service()->mutable_google_grpc()->set_credentials_factory_name( - "test_credential02"); - expectClientCreation(); - // Different credentials use different clients. - EXPECT_NE(test_client_01, client_cache_->getOrCreateAsyncClient(config)); - Grpc::RawAsyncClientSharedPtr test_client_02 = client_cache_->getOrCreateAsyncClient(config); - - config.mutable_grpc_service()->mutable_google_grpc()->set_credentials_factory_name( - "test_credential02"); - // No creation, fetching the existing one. - EXPECT_EQ(test_client_02, client_cache_->getOrCreateAsyncClient(config)); - - // Different targets use different clients. - config.mutable_grpc_service()->mutable_google_grpc()->set_target_uri("dns://test02"); - expectClientCreation(); - EXPECT_NE(test_client_01, client_cache_->getOrCreateAsyncClient(config)); - EXPECT_NE(test_client_02, client_cache_->getOrCreateAsyncClient(config)); -} - } // namespace ExtAuthz } // namespace Common } // namespace Filters diff --git a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h index 06d8a457e264..e5460e289d16 100644 --- a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h +++ b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h @@ -10,7 +10,7 @@ const std::string ADAPTIVE_CONCURRENCY_CONFIG = R"EOF( name: envoy.filters.http.adaptive_concurrency typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.adaptive_concurrency.v2alpha.AdaptiveConcurrency + "@type": type.googleapis.com/envoy.extensions.filters.http.adaptive_concurrency.v3.AdaptiveConcurrency gradient_controller_config: sample_aggregate_percentile: value: 50 diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index 2bfc77f46212..847c0c0bb8a9 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -48,7 +48,8 @@ class MockResponseEvaluator : public ResponseEvaluator { class TestConfig : public AdmissionControlFilterConfig { public: TestConfig(const AdmissionControlProto& proto_config, Runtime::Loader& runtime, - Random::RandomGenerator& random, Stats::Scope& scope, ThreadLocal::SlotPtr&& tls, + Random::RandomGenerator& random, Stats::Scope& scope, + ThreadLocal::TypedSlotPtr&& tls, MockThreadLocalController& controller, std::shared_ptr evaluator) : AdmissionControlFilterConfig(proto_config, runtime, random, scope, std::move(tls), std::move(evaluator)), @@ -66,7 +67,8 @@ class AdmissionControlTest : public testing::Test { std::shared_ptr makeConfig(const std::string& yaml) { AdmissionControlProto proto; TestUtility::loadFromYamlAndValidate(yaml, proto); - auto tls = context_.threadLocal().allocateSlot(); + auto tls = + ThreadLocal::TypedSlot::makeUnique(context_.threadLocal()); evaluator_ = std::make_shared(); return std::make_shared(proto, runtime_, random_, scope_, std::move(tls), diff --git a/test/extensions/filters/http/admission_control/config_test.cc b/test/extensions/filters/http/admission_control/config_test.cc index 2fba5f26016f..c5cc6917f8de 100644 --- a/test/extensions/filters/http/admission_control/config_test.cc +++ b/test/extensions/filters/http/admission_control/config_test.cc @@ -34,7 +34,8 @@ class AdmissionControlConfigTest : public testing::Test { std::shared_ptr makeConfig(const std::string& yaml) { AdmissionControlProto proto; TestUtility::loadFromYamlAndValidate(yaml, proto); - auto tls = context_.threadLocal().allocateSlot(); + auto tls = + ThreadLocal::TypedSlot::makeUnique(context_.threadLocal()); auto evaluator = std::make_unique(proto.success_criteria()); return std::make_shared(proto, runtime_, random_, scope_, std::move(tls), std::move(evaluator)); diff --git a/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc b/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc index 8fa6c16085b6..7d5f53f8bbc4 100644 --- a/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc +++ b/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc @@ -17,7 +17,7 @@ using Envoy::Extensions::HttpFilters::CdnLoop::Parser::ParsedCdnInfoList; DEFINE_FUZZER(const uint8_t* buf, size_t len) { absl::string_view input(reinterpret_cast(buf), len); StatusOr list = parseCdnInfoList(ParseContext(input)); - if (list) { + if (list.ok()) { // If we successfully parse input, we should make sure that cdn_ids we find appear in the input // string in order. size_t start = 0; diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/clusterfuzz-testcase-minimized-filter_fuzz_test-6268043707285504 b/test/extensions/filters/http/common/fuzz/filter_corpus/clusterfuzz-testcase-minimized-filter_fuzz_test-6268043707285504 new file mode 100644 index 000000000000..ef35670946f4 --- /dev/null +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/clusterfuzz-testcase-minimized-filter_fuzz_test-6268043707285504 @@ -0,0 +1,7 @@ +config { + name: "envoy.filters.http.adaptive_concurrency" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.http.adaptive_concurrency.v3.AdaptiveConcurrency" + value: "\n\'\022\n\032\010\010\334\340\240\001\020\200~\032\031\n\010\010\334\340\230\001\020\200~\022\002\010\001\032\t\t\000\000\000\000\000\010\000\000" + } +} diff --git a/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc b/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc index 6500bf77b61a..e3890805cdab 100644 --- a/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc +++ b/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc @@ -5,7 +5,7 @@ namespace { const std::string CSRF_ENABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 100 @@ -19,7 +19,7 @@ name: csrf const std::string CSRF_FILTER_ENABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 100 @@ -29,7 +29,7 @@ name: csrf const std::string CSRF_SHADOW_ENABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 0 @@ -43,7 +43,7 @@ name: csrf const std::string CSRF_DISABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 0 diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index 167779f8aeed..bff444e127cb 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -37,7 +37,11 @@ void expectCorrectProtoGrpc(envoy::config::core::v3::ApiVersion api_version) { fmt::format(yaml, TestUtility::getVersionStringFromApiVersion(api_version)), *proto_config); testing::StrictMock context; - EXPECT_CALL(context, singletonManager()).Times(1); + testing::StrictMock server_context; + EXPECT_CALL(context, getServerFactoryContext()) + .Times(1) + .WillOnce(testing::ReturnRef(server_context)); + EXPECT_CALL(server_context, singletonManager()).Times(1); EXPECT_CALL(context, threadLocal()).Times(1); EXPECT_CALL(context, messageValidationVisitor()).Times(1); EXPECT_CALL(context, clusterManager()).Times(1); 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 0d48ac15cfb7..385aee15ff89 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 @@ -813,13 +813,18 @@ TEST_P(ExtAuthzGrpcIntegrationTest, GoogleAsyncClientCreation) { initializeConfig(); setDownstreamProtocol(Http::CodecClient::Type::HTTP2); HttpIntegrationTest::initialize(); - initiateClientConnection(4, Headers{}, Headers{}); - waitForExtAuthzRequest(expectedCheckRequest(Http::CodecClient::Type::HTTP2)); + int expected_grpc_client_creation_count = 0; if (clientType() == Grpc::ClientType::GoogleGrpc) { - // Make sure one Google grpc client is created. - EXPECT_EQ(1, test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); + // Make sure Google grpc client is created before the request coming in. + // Since this is not laziness creation, it should create one client per + // thread before the traffic comes. + expected_grpc_client_creation_count = + test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value(); } + + initiateClientConnection(4, Headers{}, Headers{}); + waitForExtAuthzRequest(expectedCheckRequest(Http::CodecClient::Type::HTTP2)); sendExtAuthzResponse(Headers{}, Headers{}, Headers{}, Http::TestRequestHeaderMapImpl{}, Http::TestRequestHeaderMapImpl{}); @@ -846,8 +851,9 @@ TEST_P(ExtAuthzGrpcIntegrationTest, GoogleAsyncClientCreation) { RELEASE_ASSERT(result, result.message()); if (clientType() == Grpc::ClientType::GoogleGrpc) { - // Make sure one Google grpc client is created. - EXPECT_EQ(1, test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); + // Make sure no more Google grpc client is created no matter how many requests coming in. + EXPECT_EQ(expected_grpc_client_creation_count, + test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); } sendExtAuthzResponse(Headers{}, Headers{}, Headers{}, Http::TestRequestHeaderMapImpl{}, Http::TestRequestHeaderMapImpl{}); @@ -869,6 +875,12 @@ TEST_P(ExtAuthzGrpcIntegrationTest, GoogleAsyncClientCreation) { EXPECT_EQ("200", response_->headers().getStatusValue()); EXPECT_EQ(response_size_, response_->body().size()); + if (clientType() == Grpc::ClientType::GoogleGrpc) { + // Make sure no more Google grpc client is created no matter how many requests coming in. + EXPECT_EQ(expected_grpc_client_creation_count, + test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); + } + cleanup(); } diff --git a/test/extensions/filters/http/fault/fault_filter_integration_test.cc b/test/extensions/filters/http/fault/fault_filter_integration_test.cc index 957667fad1ae..0a1a013b6bfc 100644 --- a/test/extensions/filters/http/fault/fault_filter_integration_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_integration_test.cc @@ -20,7 +20,7 @@ class FaultIntegrationTest : public Event::TestUsingSimulatedTime, R"EOF( name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault response_rate_limit: fixed_limit: limit_kbps: 1 @@ -32,7 +32,7 @@ name: fault R"EOF( name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault abort: header_abort: {} percentage: @@ -72,7 +72,7 @@ TEST_P(FaultIntegrationTestAllProtocols, NoFault) { R"EOF( name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault )EOF"; initializeFilter(filter_config); diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index e5ca23e2094d..f22c9ed88eaf 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -46,7 +46,6 @@ class FaultFilterTest : public testing::Test { public: const std::string fixed_delay_and_abort_nodes_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -62,7 +61,6 @@ class FaultFilterTest : public testing::Test { const std::string fixed_delay_only_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -79,7 +77,6 @@ class FaultFilterTest : public testing::Test { const std::string fixed_delay_and_abort_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -100,7 +97,6 @@ class FaultFilterTest : public testing::Test { const std::string fixed_delay_and_abort_match_headers_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -118,7 +114,6 @@ class FaultFilterTest : public testing::Test { const std::string delay_with_upstream_cluster_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -208,7 +203,6 @@ TEST(FaultFilterBadConfigTest, BadDelayType) { TEST(FaultFilterBadConfigTest, BadDelayDuration) { const std::string yaml = R"EOF( delay: - type: fixed percentage: numerator: 50 denominator: HUNDRED @@ -221,7 +215,6 @@ TEST(FaultFilterBadConfigTest, BadDelayDuration) { TEST(FaultFilterBadConfigTest, MissingDelayDuration) { const std::string yaml = R"EOF( delay: - type: fixed percentage: numerator: 50 denominator: HUNDRED diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 473d9bfa655f..a792f8cea8ec 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -35,7 +35,7 @@ class GrpcJsonTranscoderIntegrationTest R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor : "{}" services : "bookstore.Bookstore" )EOF"; @@ -462,7 +462,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryGetError1) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor : "{}" services : "bookstore.Bookstore" ignore_unknown_query_parameters : true @@ -486,7 +486,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryErrorConvertedToJson) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor: "{}" services: "bookstore.Bookstore" convert_grpc_status: true @@ -511,7 +511,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryErrorInTrailerConvertedToJson) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor: "{}" services: "bookstore.Bookstore" convert_grpc_status: true @@ -536,7 +536,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, StreamingErrorConvertedToJson) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor: "{}" services: "bookstore.Bookstore" convert_grpc_status: true diff --git a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc index 1ec37be541b4..8082a846db89 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc @@ -1270,6 +1270,78 @@ INSTANTIATE_TEST_SUITE_P( })", R"({"id":"101","gender":"MALE","last_name":"Shakespeare"})"})); +struct GrpcJsonTranscoderFilterUnescapeTestParam { + std::string config_json_; + std::string expected_arg_; +}; + +class GrpcJsonTranscoderFilterUnescapeTest + : public testing::TestWithParam, + public GrpcJsonTranscoderFilterTestBase { +protected: + GrpcJsonTranscoderFilterUnescapeTest() { + envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder proto_config; + TestUtility::loadFromJson(TestEnvironment::substitute(GetParam().config_json_), proto_config); + config_ = std::make_unique(proto_config, *api_); + filter_ = std::make_unique(*config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + } + + std::unique_ptr config_; + std::unique_ptr filter_; + NiceMock decoder_callbacks_; + NiceMock encoder_callbacks_; +}; + +TEST_P(GrpcJsonTranscoderFilterUnescapeTest, UnescapeSpec) { + Http::TestRequestHeaderMapImpl request_headers{ + {"content-type", "text/plain"}, {":method", "POST"}, {":path", "/wildcard/%2f%23/%20%2523"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + + Buffer::OwnedImpl request_data{"{}"}; + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(request_data, true)); + + Grpc::Decoder decoder; + std::vector frames; + decoder.decode(request_data, frames); + + EXPECT_EQ(1, frames.size()); + + bookstore::EchoBodyRequest expected_request; + expected_request.set_arg(GetParam().expected_arg_); + + bookstore::EchoBodyRequest request; + request.ParseFromString(frames[0].data_->toString()); + + EXPECT_EQ(expected_request.ByteSize(), frames[0].length_); + EXPECT_TRUE(MessageDifferencer::Equals(expected_request, request)); +} + +INSTANTIATE_TEST_SUITE_P(GrpcJsonTranscoderFilterUnescapeOptions, + GrpcJsonTranscoderFilterUnescapeTest, + ::testing::Values( + GrpcJsonTranscoderFilterUnescapeTestParam{ + R"({ + "proto_descriptor": "{{ test_rundir }}/test/proto/bookstore.descriptor", + "services": ["bookstore.Bookstore"] + })", + "%2f%23/ %23"}, + GrpcJsonTranscoderFilterUnescapeTestParam{ + R"({ + "proto_descriptor": "{{ test_rundir }}/test/proto/bookstore.descriptor", + "services": ["bookstore.Bookstore"], + "url_unescape_spec": "ALL_CHARACTERS_EXCEPT_SLASH" + })", + "%2f#/ %23"}, + GrpcJsonTranscoderFilterUnescapeTestParam{ + R"({ + "proto_descriptor": "{{ test_rundir }}/test/proto/bookstore.descriptor", + "services": ["bookstore.Bookstore"], + "url_unescape_spec": "ALL_CHARACTERS" + })", + "/#/ %23"})); + } // namespace } // namespace GrpcJsonTranscoder } // namespace HttpFilters diff --git a/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc b/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc index 2eaf4ef7fdb1..5a8831476b53 100644 --- a/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc +++ b/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc @@ -110,5 +110,41 @@ TEST_P(GrpcWebFilterIntegrationTest, GrpcWebTrailersNotDuplicated) { } } +TEST_P(GrpcWebFilterIntegrationTest, UpstreamDisconnect) { + const auto downstream_protocol = std::get<1>(GetParam()); + const bool http2_skip_encoding_empty_trailers = std::get<2>(GetParam()); + + if (downstream_protocol == Http::CodecClient::Type::HTTP1) { + config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); + } else { + skipEncodingEmptyTrailers(http2_skip_encoding_empty_trailers); + } + + setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); + + Http::TestRequestTrailerMapImpl request_trailers{{"request1", "trailer1"}, + {"request2", "trailer2"}}; + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto encoder_decoder = codec_client_->startRequest( + Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {"content-type", "application/grpc-web"}, + {":authority", "host"}}); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_client_->sendData(*request_encoder_, 1, false); + codec_client_->sendTrailers(*request_encoder_, request_trailers); + waitForNextUpstreamRequest(); + + ASSERT_TRUE(fake_upstream_connection_->close()); + response->waitForEndStream(); + EXPECT_EQ("503", response->headers().getStatusValue()); + + codec_client_->close(); +} + } // namespace } // namespace Envoy diff --git a/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc b/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc index a7a0d8d00b70..bd0edc6d1d56 100644 --- a/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc +++ b/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc @@ -23,6 +23,7 @@ class GzipIntegrationTest : public testing::TestWithParamdecodeHeaders(headers, true)); EXPECT_FALSE(headers.has("accept-encoding")); } diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 3b1cf0812b09..176ebaa445f8 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -140,6 +140,7 @@ envoy_extension_cc_test( ":mock_lib", ":test_common_lib", "//source/extensions/filters/http/jwt_authn:matchers_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc index 14f6c0d67d12..2afa53ed0c98 100644 --- a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc @@ -29,6 +29,7 @@ constexpr char kConfigTemplate[] = R"( - name: "x-example" value_prefix: "" forward_payload_header: "x-example-payload" + forward: true local_jwks: inline_string: "" other_provider: @@ -37,6 +38,7 @@ constexpr char kConfigTemplate[] = R"( - name: "x-other" value_prefix: "" forward_payload_header: "x-other-payload" + forward: true local_jwks: inline_string: "" rules: @@ -47,20 +49,18 @@ constexpr char kConfigTemplate[] = R"( constexpr char kExampleHeader[] = "x-example"; constexpr char kOtherHeader[] = "x-other"; -// Returns true if the jwt_header is empty, and the jwt_header payload exists. -// Based on the JWT provider setup for this test, this matcher is equivalent to JWT verification -// was success. +// Returns true if jwt_header payload exists. +// Payload is added only after verification was success. MATCHER_P(JwtOutputSuccess, jwt_header, "") { auto payload_header = absl::StrCat(jwt_header, "-payload"); - return !arg.has(std::string(jwt_header)) && arg.has(payload_header); + return arg.has(payload_header); } -// Returns true if the jwt_header exists, and the jwt_header payload is empty. -// Based on the JWT provider setup for this test, this matcher is equivalent to JWT verification -// was failed. +// Returns true if the jwt_header payload is empty. +// Payload is added only after verification was success. MATCHER_P(JwtOutputFailedOrIgnore, jwt_header, "") { auto payload_header = absl::StrCat(jwt_header, "-payload"); - return arg.has(std::string(jwt_header)) && !arg.has(payload_header); + return !arg.has(payload_header); } class AllVerifierTest : public testing::Test { @@ -143,9 +143,7 @@ TEST_F(AllowFailedInSingleRequirementTest, OneGoodJwt) { auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); - // As requirement has nothing except allow_missing_or_failed, it will - // not try to check any token. - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); } TEST_F(AllowFailedInSingleRequirementTest, TwoGoodJwts) { @@ -154,8 +152,8 @@ TEST_F(AllowFailedInSingleRequirementTest, TwoGoodJwts) { Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, OtherGoodToken}}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); } TEST_F(AllowFailedInSingleRequirementTest, GoodAndBadJwts) { @@ -164,7 +162,73 @@ TEST_F(AllowFailedInSingleRequirementTest, GoodAndBadJwts) { Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, ExpiredToken}}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); +} + +// The `allow_missing_or_failed` is defined in an OR-list of requirements by itself. +class SingleAllowMissingInOrListTest : public AllVerifierTest { +protected: + void SetUp() override { + AllVerifierTest::SetUp(); + const char allow_missing_yaml[] = R"( +requires_any: + requirements: + - allow_missing: {} +)"; + modifyRequirement(allow_missing_yaml); + createVerifier(); + } +}; + +TEST_F(SingleAllowMissingInOrListTest, NoJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); +} + +TEST_F(SingleAllowMissingInOrListTest, BadJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::JwtExpired)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ExpiredToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, MissingIssToken) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ES256WithoutIssToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, OneGoodJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, TwoGoodJwts) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = + Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, OtherGoodToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, GoodAndBadJwts) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = + Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, ExpiredToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); } @@ -291,8 +355,7 @@ TEST_F(AllowFailedInAndListTest, TwoGoodJwts) { context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); - // The JWT in x-other is ignored. - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); } class AllowFailedInAndOfOrListTest : public AllVerifierTest { @@ -467,7 +530,7 @@ TEST_F(AllowMissingInAndListTest, TwoGoodJwts) { context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); } class AllowMissingInAndOfOrListTest : public AllVerifierTest { @@ -526,7 +589,7 @@ TEST_F(AllowMissingInAndOfOrListTest, TwoGoodJwts) { } TEST_F(AllowMissingInAndOfOrListTest, GoodAndBadJwts) { - EXPECT_CALL(mock_cb_, onComplete(Status::JwtUnknownIssuer)).Times(1); + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); // Use the token with example.com issuer for x-other. auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, GoodToken}}; diff --git a/test/extensions/filters/http/jwt_authn/authenticator_test.cc b/test/extensions/filters/http/jwt_authn/authenticator_test.cc index e9ceb23cbbbd..fbb732a63298 100644 --- a/test/extensions/filters/http/jwt_authn/authenticator_test.cc +++ b/test/extensions/filters/http/jwt_authn/authenticator_test.cc @@ -303,6 +303,23 @@ TEST_F(AuthenticatorTest, TestExpiredJWT) { expectVerifyStatus(Status::JwtExpired, headers); } +// This test verifies when a JWT is expired but with a big clock skew. +TEST_F(AuthenticatorTest, TestExpiredJWTWithABigClockSkew) { + auto& provider = (*proto_config_.mutable_providers())[std::string(ProviderName)]; + // Token is expired at 1205005587, but add clock skew at another 1205005587. + provider.set_clock_skew_seconds(1205005587); + createAuthenticator(); + + EXPECT_CALL(*raw_fetcher_, fetch(_, _, _)) + .WillOnce(Invoke([this](const envoy::config::core::v3::HttpUri&, Tracing::Span&, + JwksFetcher::JwksReceiver& receiver) { + receiver.onJwksSuccess(std::move(jwks_)); + })); + + Http::TestRequestHeaderMapImpl headers{{"Authorization", "Bearer " + std::string(ExpiredToken)}}; + expectVerifyStatus(Status::Ok, headers); +} + // This test verifies when a JWT is not yet valid, JwtNotYetValid status is returned. TEST_F(AuthenticatorTest, TestNotYetValidJWT) { EXPECT_CALL(*raw_fetcher_, fetch(_, _, _)).Times(0); diff --git a/test/extensions/filters/http/jwt_authn/filter_config_test.cc b/test/extensions/filters/http/jwt_authn/filter_config_test.cc index 45da3fea600f..6aae1a8e1b39 100644 --- a/test/extensions/filters/http/jwt_authn/filter_config_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_config_test.cc @@ -13,6 +13,7 @@ #include "gtest/gtest.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; +using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; using testing::ReturnRef; namespace Envoy { @@ -57,6 +58,100 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByMatch) { filter_state) == nullptr); } +TEST(HttpJwtAuthnFilterConfigTest, FindByMatchDisabled) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks +rules: +- match: + path: /path1 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + + StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::FilterChain); + EXPECT_TRUE(filter_conf->findVerifier( + Http::TestRequestHeaderMapImpl{ + {":path", "/path1"}, + }, + filter_state) == nullptr); +} + +TEST(HttpJwtAuthnFilterConfigTest, FindByMatchWrongRequirementName) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks +rules: +- match: + path: /path1 + requirement_name: rr +requirement_map: + r1: + provider_name: provider1 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + EXPECT_THROW_WITH_MESSAGE(FilterConfigImpl::create(proto_config, "", context), EnvoyException, + "Wrong requirement_name: rr. It should be one of [r1]"); +} + +TEST(HttpJwtAuthnFilterConfigTest, FindByMatchRequirementName) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks + provider2: + issuer: issuer2 + local_jwks: + inline_string: jwks +rules: +- match: + path: /path1 + requirement_name: r1 +- match: + path: /path2 + requirement_name: r2 +requirement_map: + r1: + provider_name: provider1 + r2: + provider_name: provider2 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::FilterChain); + + EXPECT_TRUE(filter_conf->findVerifier( + Http::TestRequestHeaderMapImpl{ + {":path", "/path1"}, + }, + filter_state) != nullptr); + EXPECT_TRUE(filter_conf->findVerifier( + Http::TestRequestHeaderMapImpl{ + {":path", "/path2"}, + }, + filter_state) != nullptr); +} + TEST(HttpJwtAuthnFilterConfigTest, VerifyTLSLifetime) { const char config[] = R"( providers: @@ -159,6 +254,63 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByFilterState) { nullptr); } +TEST(HttpJwtAuthnFilterConfigTest, FindByRequiremenMap) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks + provider2: + issuer: issuer2 + local_jwks: + inline_string: jwks +requirement_map: + r1: + provider_name: provider1 + r2: + provider_name: provider2 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + + PerRouteConfig per_route; + const Verifier* verifier; + std::string error_msg; + + per_route.Clear(); + per_route.set_disabled(true); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_EQ(verifier, nullptr); + EXPECT_EQ(error_msg, EMPTY_STRING); + + per_route.Clear(); + per_route.set_requirement_name("r1"); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_NE(verifier, nullptr); + EXPECT_EQ(error_msg, EMPTY_STRING); + + per_route.Clear(); + per_route.set_requirement_name("r2"); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_NE(verifier, nullptr); + EXPECT_EQ(error_msg, EMPTY_STRING); + + per_route.Clear(); + per_route.set_requirement_name("wrong-name"); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_EQ(verifier, nullptr); + EXPECT_EQ(error_msg, "Wrong requirement_name: wrong-name. It should be one of [r1,r2]"); +} + } // namespace } // namespace JwtAuthn } // namespace HttpFilters diff --git a/test/extensions/filters/http/jwt_authn/filter_factory_test.cc b/test/extensions/filters/http/jwt_authn/filter_factory_test.cc index ea89ea616225..f58adfe62226 100644 --- a/test/extensions/filters/http/jwt_authn/filter_factory_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_factory_test.cc @@ -1,6 +1,7 @@ #include "envoy/extensions/filters/http/jwt_authn/v3/config.pb.h" #include "envoy/extensions/filters/http/jwt_authn/v3/config.pb.validate.h" +#include "extensions/filters/http/jwt_authn/filter_config.h" #include "extensions/filters/http/jwt_authn/filter_factory.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" @@ -10,6 +11,7 @@ #include "gtest/gtest.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; +using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; using testing::_; namespace Envoy { @@ -56,6 +58,53 @@ TEST(HttpJwtAuthnFilterFactoryTest, BadLocalJwks) { EXPECT_THROW(factory.createFilterFactoryFromProto(proto_config, "stats", context), EnvoyException); } + +TEST(HttpJwtAuthnFilterFactoryTest, EmptyPerRouteConfig) { + PerRouteConfig per_route; + NiceMock context; + FilterFactory factory; + EXPECT_THROW(factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()), + EnvoyException); +} + +TEST(HttpJwtAuthnFilterFactoryTest, WrongPerRouteConfigType) { + JwtAuthentication per_route; + NiceMock context; + FilterFactory factory; + EXPECT_THROW(factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()), + std::bad_cast); +} + +TEST(HttpJwtAuthnFilterFactoryTest, DisabledPerRouteConfig) { + PerRouteConfig per_route; + per_route.set_disabled(true); + + NiceMock context; + FilterFactory factory; + auto base_ptr = factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()); + EXPECT_NE(base_ptr, nullptr); + const PerRouteFilterConfig* typed_ptr = dynamic_cast(base_ptr.get()); + EXPECT_NE(typed_ptr, nullptr); + EXPECT_TRUE(typed_ptr->config().disabled()); +} + +TEST(HttpJwtAuthnFilterFactoryTest, GoodPerRouteConfig) { + PerRouteConfig per_route; + per_route.set_requirement_name("name"); + + NiceMock context; + FilterFactory factory; + auto base_ptr = factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()); + EXPECT_NE(base_ptr, nullptr); + const PerRouteFilterConfig* typed_ptr = dynamic_cast(base_ptr.get()); + EXPECT_NE(typed_ptr, nullptr); + EXPECT_EQ(typed_ptr->config().requirement_name(), "name"); +} + } // namespace } // namespace JwtAuthn } // namespace HttpFilters diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index 4c71a7bef275..bf0af7934ab3 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -13,6 +13,7 @@ #include "test/test_common/registry.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; +using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; using envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter; namespace Envoy { @@ -447,6 +448,141 @@ TEST_P(RemoteJwksIntegrationTest, FetchFailedMissingCluster) { cleanup(); } +class PerRouteIntegrationTest : public HttpProtocolIntegrationTest { +public: + void setup(const std::string& filter_config, const PerRouteConfig& per_route) { + config_helper_.addFilter(getAuthFilterConfig(filter_config, true)); + + config_helper_.addConfigModifier( + [per_route]( + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + auto* virtual_host = hcm.mutable_route_config()->mutable_virtual_hosts(0); + auto& per_route_any = + (*virtual_host->mutable_routes(0) + ->mutable_typed_per_filter_config())[HttpFilterNames::get().JwtAuthn]; + per_route_any.PackFrom(per_route); + }); + + initialize(); + } +}; + +INSTANTIATE_TEST_SUITE_P(Protocols, PerRouteIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +// This test verifies per-route config disabled. +TEST_P(PerRouteIntegrationTest, PerRouteConfigDisabled) { + // per-route config has disabled flag. + PerRouteConfig per_route; + per_route.set_disabled(true); + // Use a normal filter config that requires jwt_auth. + setup(ExampleConfig, per_route); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // So the request without a JWT token is OK. + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + }); + + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + +// This test verifies per-route config with wrong requirement_name +TEST_P(PerRouteIntegrationTest, PerRouteConfigWrongRequireName) { + // A config with a requirement_map + const std::string filter_conf = R"( + providers: + example_provider: + issuer: https://example.com + audiences: + - example_service + requirement_map: + abc: + provider_name: "example_provider" +)"; + + // Per-route config has a wrong requirement_name. + PerRouteConfig per_route; + per_route.set_requirement_name("wrong-requirement-name"); + setup(filter_conf, per_route); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // So the request with a good Jwt token is rejected. + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Bearer " + std::string(GoodToken)}, + }); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("403", response->headers().getStatusValue()); +} + +// This test verifies per-route config with correct requirement_name +TEST_P(PerRouteIntegrationTest, PerRouteConfigOK) { + // A config with a requirement_map + const std::string filter_conf = R"( + providers: + example_provider: + issuer: https://example.com + audiences: + - example_service + requirement_map: + abc: + provider_name: "example_provider" +)"; + + // Per-route config with correct requirement_name + PerRouteConfig per_route; + per_route.set_requirement_name("abc"); + setup(filter_conf, per_route); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // So the request with a JWT token is OK. + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Bearer " + std::string(GoodToken)}, + }); + + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + // A request with missing token is rejected. + auto response1 = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + }); + + response1->waitForEndStream(); + ASSERT_TRUE(response1->complete()); + EXPECT_EQ("401", response1->headers().getStatusValue()); +} + } // namespace } // namespace JwtAuthn } // namespace HttpFilters diff --git a/test/extensions/filters/http/jwt_authn/filter_test.cc b/test/extensions/filters/http/jwt_authn/filter_test.cc index 059f9f6656b8..8983d94aafd2 100644 --- a/test/extensions/filters/http/jwt_authn/filter_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_test.cc @@ -42,6 +42,8 @@ class MockFilterConfig : public FilterConfig { MOCK_METHOD(const Verifier*, findVerifier, (const Http::RequestHeaderMap& headers, const StreamInfo::FilterState& filter_state), (const)); + MOCK_METHOD((std::pair), findPerRouteVerifier, + (const PerRouteFilterConfig& per_route), (const)); MOCK_METHOD(bool, bypassCorsPreflightRequest, (), (const)); MOCK_METHOD(JwtAuthnFilterStats&, stats, ()); @@ -57,6 +59,10 @@ class FilterTest : public testing::Test { mock_verifier_ = std::make_unique(); filter_ = std::make_unique(mock_config_); filter_->setDecoderFilterCallbacks(filter_callbacks_); + + mock_route_ = std::make_shared>(); + envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig per_route; + per_route_config_ = std::make_shared(per_route); } void setupMockConfig() { @@ -69,6 +75,8 @@ class FilterTest : public testing::Test { std::unique_ptr mock_verifier_; NiceMock verifier_callback_; Http::TestRequestTrailerMapImpl trailers_; + std::shared_ptr> mock_route_; + std::shared_ptr per_route_config_; }; // This test verifies Verifier::Callback is called inline with OK status. @@ -179,8 +187,8 @@ TEST_F(FilterTest, InlineUnauthorizedFailure) { Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); - EXPECT_EQ(filter_callbacks_.details(), "jwt_authn_access_denied{Jwt is not in the form of " - "Header.Payload.Signature with two dots and 3 sections}"); + EXPECT_EQ(filter_callbacks_.details(), "jwt_authn_access_denied{Jwt_is_not_in_the_form_of_" + "Header.Payload.Signature_with_two_dots_and_3_sections}"); } // This test verifies Verifier::Callback is called inline with a failure(403 Forbidden) status. @@ -202,7 +210,7 @@ TEST_F(FilterTest, InlineForbiddenFailure) { EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); EXPECT_EQ(filter_callbacks_.details(), - "jwt_authn_access_denied{Audiences in Jwt are not allowed}"); + "jwt_authn_access_denied{Audiences_in_Jwt_are_not_allowed}"); } // This test verifies Verifier::Callback is called with OK status after verify(). @@ -288,8 +296,139 @@ TEST_F(FilterTest, OutBoundForbiddenFailure) { } // Test verifies that if no route matched requirement, then request is allowed. -TEST_F(FilterTest, TestNoRouteMatched) { +TEST_F(FilterTest, TestNoRequirementMatched) { + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test if route() return null, fallback to call config config. +TEST_F(FilterTest, TestNoRoute) { + // route() call return nullptr + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(nullptr)); + + // Calling the findVerifier from filter config. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + // findPerRouteVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)).Times(0); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test if routeEntry() return null, fallback to call config config. +TEST_F(FilterTest, TestNoRouteEnty) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + // routeEntry() call return nullptr + EXPECT_CALL(*mock_route_, routeEntry()).WillOnce(Return(nullptr)); + + // Calling the findVerifier from filter config. EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + // findPerRouteVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)).Times(0); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test if no per-route config, fallback to call config config. +TEST_F(FilterTest, TestNoPerRouteConfig) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + // perFilterConfig return nullptr. + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(nullptr)); + + // Calling the findVerifier from filter config. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + // findPerRouteVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)).Times(0); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test bypass requirement from per-route config +TEST_F(FilterTest, TestPerRouteBypass) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(per_route_config_.get())); + + // findVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).Times(0); + // If findPerRouteVerifier is called, and return nullptr, it means bypass + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)) + .WillOnce(Return(std::make_pair(nullptr, EMPTY_STRING))); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test per-route config with wrong requirement_name +TEST_F(FilterTest, TestPerRouteWrongRequirementName) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(per_route_config_.get())); + + // findVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).Times(0); + // If findPerRouteVerifier is called, and return error message. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)) + .WillOnce( + Return(std::make_pair(nullptr, "Wrong requirement_name: abc. Correct names are: r1,r2"))); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().denied_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); + EXPECT_EQ(filter_callbacks_.details(), + "jwt_authn_access_denied{Wrong_requirement_name:_abc._Correct_names_are:_r1,r2}"); +} + +// Test verifier from per-route config +TEST_F(FilterTest, TestPerRouteVerifierOK) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(per_route_config_.get())); + + // findVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).Times(0); + // If findPerRouteVerifier is called, and return the mock_verifier_. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)) + .WillOnce(Return(std::make_pair(mock_verifier_.get(), EMPTY_STRING))); + + // A successful authentication + EXPECT_CALL(*mock_verifier_, verify(_)).WillOnce(Invoke([](ContextSharedPtr context) { + context->callback()->onComplete(Status::Ok); + })); auto headers = Http::TestRequestHeaderMapImpl{}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); diff --git a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc index bd5d5d5ffa93..d9315ce42c70 100644 --- a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc @@ -544,8 +544,8 @@ TEST_F(GroupVerifierTest, TestAllInAnyBothRequireAllAreOk) { callbacks_["provider_4"](Status::Ok); } -// Test require any with additional allow all -TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowAll) { +// Test RequiresAny with two providers and allow_failed +TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowFailed) { TestUtility::loadFromYaml(RequiresAnyConfig, proto_config_); proto_config_.mutable_rules(0) ->mutable_requires() @@ -554,21 +554,51 @@ TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowAll) { ->mutable_allow_missing_or_failed(); createAsyncMockAuthsAndVerifier(std::vector{"example_provider", "other_provider"}); - auto mock_auth = std::make_unique(); - EXPECT_CALL(*mock_auth, doVerify(_, _, _, _, _)) - .WillOnce(Invoke([&](Http::HeaderMap&, Tracing::Span&, std::vector*, - SetPayloadCallback, AuthenticatorCallback callback) { - callbacks_[allowfailed] = std::move(callback); - })); - EXPECT_CALL(*mock_auth, onDestroy()).Times(1); - mock_auths_[allowfailed] = std::move(mock_auth); EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); auto headers = Http::TestRequestHeaderMapImpl{}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); - callbacks_[allowfailed](Status::Ok); - // with requires any, if any inner verifier returns OK the whole any verifier should return OK. + callbacks_["example_provider"](Status::JwtMissed); + callbacks_["other_provider"](Status::JwtExpired); +} + +// Test RequiresAny with two providers and allow_missing, failed +TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButFailed) { + TestUtility::loadFromYaml(RequiresAnyConfig, proto_config_); + proto_config_.mutable_rules(0) + ->mutable_requires() + ->mutable_requires_any() + ->add_requirements() + ->mutable_allow_missing(); + + createAsyncMockAuthsAndVerifier(std::vector{"example_provider", "other_provider"}); + EXPECT_CALL(mock_cb_, onComplete(Status::JwtExpired)).Times(1); + + auto headers = Http::TestRequestHeaderMapImpl{}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + callbacks_["example_provider"](Status::JwtMissed); + callbacks_["other_provider"](Status::JwtExpired); +} + +// Test RequiresAny with two providers and allow_missing, but OK +TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButOk) { + TestUtility::loadFromYaml(RequiresAnyConfig, proto_config_); + proto_config_.mutable_rules(0) + ->mutable_requires() + ->mutable_requires_any() + ->add_requirements() + ->mutable_allow_missing(); + + createAsyncMockAuthsAndVerifier(std::vector{"example_provider", "other_provider"}); + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + + auto headers = Http::TestRequestHeaderMapImpl{}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + callbacks_["example_provider"](Status::JwtMissed); + callbacks_["other_provider"](Status::JwtUnknownIssuer); } } // namespace diff --git a/test/extensions/filters/http/jwt_authn/matcher_test.cc b/test/extensions/filters/http/jwt_authn/matcher_test.cc index e64245b6cfdb..78dfabecd733 100644 --- a/test/extensions/filters/http/jwt_authn/matcher_test.cc +++ b/test/extensions/filters/http/jwt_authn/matcher_test.cc @@ -6,6 +6,7 @@ #include "test/extensions/filters/http/jwt_authn/mock.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" using envoy::extensions::filters::http::jwt_authn::v3::RequirementRule; @@ -40,6 +41,7 @@ TEST_F(MatcherTest, TestMatchPrefix) { } TEST_F(MatcherTest, TestMatchRegex) { + TestDeprecatedV2Api _deprecated_v2_api; const char config[] = R"(match: regex: "/[^c][au]t")"; RequirementRule rule; @@ -105,7 +107,8 @@ TEST_F(MatcherTest, TestMatchQuery) { prefix: "/" query_parameters: - name: foo - value: bar)"; + string_match: + exact: bar)"; RequirementRule rule; TestUtility::loadFromYaml(config, rule); MatcherConstPtr matcher = Matcher::create(rule); @@ -146,7 +149,8 @@ TEST_F(MatcherTest, TestMatchPathAndHeader) { path: "/boo" query_parameters: - name: foo - value: bar)"; + string_match: + exact: bar)"; RequirementRule rule; TestUtility::loadFromYaml(config, rule); MatcherConstPtr matcher = Matcher::create(rule); diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index 92f8e7e141a6..60ca43871e71 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -115,6 +115,8 @@ class LuaIntegrationTest : public testing::TestWithParamset_route_config_name(route_config_name); + hcm.mutable_rds()->mutable_config_source()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); envoy::config::core::v3::ApiConfigSource* rds_api_config_source = hcm.mutable_rds()->mutable_config_source()->mutable_api_config_source(); rds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); @@ -215,7 +217,7 @@ TEST_P(LuaIntegrationTest, CallMetadataDuringLocalReply) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_response(response_handle) local metadata = response_handle:metadata():get("foo.bar") @@ -240,7 +242,7 @@ TEST_P(LuaIntegrationTest, RequestAndResponse) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) request_handle:logTrace("log test") @@ -379,7 +381,7 @@ TEST_P(LuaIntegrationTest, UpstreamHttpCall) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) local headers, body = request_handle:httpCall( @@ -437,7 +439,7 @@ TEST_P(LuaIntegrationTest, UpstreamCallAndRespond) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) local headers, body = request_handle:httpCall( @@ -487,7 +489,7 @@ TEST_P(LuaIntegrationTest, UpstreamAsyncHttpCall) { R"EOF( name: envoy.filters.http.lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) local headers, body = request_handle:httpCall( @@ -537,7 +539,7 @@ TEST_P(LuaIntegrationTest, ChangeRoute) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) request_handle:headers():remove(":path") @@ -571,7 +573,7 @@ TEST_P(LuaIntegrationTest, SurviveMultipleCalls) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) request_handle:streamInfo():dynamicMetadata() @@ -607,7 +609,7 @@ TEST_P(LuaIntegrationTest, SignatureVerification) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function string.fromhex(str) return (str:gsub('..', function (cc) @@ -956,7 +958,7 @@ TEST_P(LuaIntegrationTest, RewriteResponseBuffer) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_response(response_handle) local content_length = response_handle:body():setBytes("ok") @@ -975,7 +977,7 @@ TEST_P(LuaIntegrationTest, RewriteChunkedBody) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_response(response_handle) response_handle:headers():replace("content-length", 2) diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index eed8d311c1a0..83f5e7f08833 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -205,7 +205,7 @@ TEST_F(OAuth2Test, OAuthOkPass) { // Sanitized return reference mocking std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(mock_request_headers, false)); @@ -414,7 +414,7 @@ TEST_F(OAuth2Test, OAuthTestInvalidUrlInStateQueryParam) { EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), false)); EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, @@ -436,7 +436,7 @@ TEST_F(OAuth2Test, OAuthTestCallbackUrlInStateQueryParam) { "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, }; - Http::TestRequestHeaderMapImpl expected_headers{ + Http::TestRequestHeaderMapImpl expected_response_headers{ {Http::Headers::get().Status.get(), "401"}, {Http::Headers::get().ContentLength.get(), "18"}, {Http::Headers::get().ContentType.get(), "text/plain"}, @@ -447,11 +447,28 @@ TEST_F(OAuth2Test, OAuthTestCallbackUrlInStateQueryParam) { EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), false)); + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&expected_response_headers), false)); EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, filter_->decodeHeaders(request_headers, false)); + + Http::TestRequestHeaderMapImpl final_request_headers{ + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Path.get(), "/_oauth?code=abcdefxyz123&scope=user&" + "state=https%3A%2F%2Ftraffic.example.com%2F_oauth"}, + {Http::Headers::get().Cookie.get(), "OauthExpires=123;version=test"}, + {Http::Headers::get().Cookie.get(), "BearerToken=legit_token;version=test"}, + {Http::Headers::get().Cookie.get(), + "OauthHMAC=" + "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" + "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, + {Http::CustomHeaders::get().Authorization.get(), "Bearer legit_token"}, + }; + + EXPECT_EQ(request_headers, final_request_headers); } /** @@ -475,7 +492,7 @@ TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, }; - Http::TestRequestHeaderMapImpl expected_headers{ + Http::TestRequestHeaderMapImpl expected_response_headers{ {Http::Headers::get().Status.get(), "302"}, {Http::Headers::get().Location.get(), "https://traffic.example.com/original_path"}, }; @@ -485,10 +502,27 @@ TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), true)); + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&expected_response_headers), true)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + + Http::TestRequestHeaderMapImpl final_request_headers{ + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Path.get(), "/_oauth?code=abcdefxyz123&scope=user&" + "state=https%3A%2F%2Ftraffic.example.com%2Foriginal_path"}, + {Http::Headers::get().Cookie.get(), "OauthExpires=123;version=test"}, + {Http::Headers::get().Cookie.get(), "BearerToken=legit_token;version=test"}, + {Http::Headers::get().Cookie.get(), + "OauthHMAC=" + "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" + "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, + {Http::CustomHeaders::get().Authorization.get(), "Bearer legit_token"}, + }; + + EXPECT_EQ(request_headers, final_request_headers); } /** 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 2df0f265d24e..d2978064b2d9 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 @@ -91,14 +91,14 @@ TEST_F(OnDemandFilterTest, TestOnRouteConfigUpdateCompletionContinuesDecodingWit TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionContinuesDecodingIfRedirectFails) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); - EXPECT_CALL(decoder_callbacks_, recreateStream()).WillOnce(Return(false)); + EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(false)); filter_->onRouteConfigUpdateCompletion(true); } // tests onRouteConfigUpdateCompletion() when route was resolved TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionRestartsActiveStream) { EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); - EXPECT_CALL(decoder_callbacks_, recreateStream()).WillOnce(Return(true)); + EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(true)); filter_->onRouteConfigUpdateCompletion(true); } diff --git a/test/extensions/filters/http/ratelimit/BUILD b/test/extensions/filters/http/ratelimit/BUILD index 09550058b265..f5bd1b2b7201 100644 --- a/test/extensions/filters/http/ratelimit/BUILD +++ b/test/extensions/filters/http/ratelimit/BUILD @@ -51,6 +51,7 @@ envoy_extension_cc_test( name = "ratelimit_integration_test", srcs = ["ratelimit_integration_test.cc"], extension_name = "envoy.filters.http.ratelimit", + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ "//source/common/buffer:zero_copy_input_stream_lib", diff --git a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc index 89b73bbef081..6c2f397dbd20 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc @@ -13,7 +13,7 @@ namespace { const std::string RBAC_CONFIG = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: @@ -26,7 +26,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_DENY_ACTION = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: action: DENY policies: @@ -40,7 +40,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_PREFIX_MATCH = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: @@ -53,7 +53,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_PATH_EXACT_MATCH = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: @@ -67,7 +67,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_PATH_IGNORE_CASE_MATCH = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: 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 ee6bf2f75484..6edcaa3f254e 100644 --- a/test/extensions/filters/http/tap/tap_filter_integration_test.cc +++ b/test/extensions/filters/http/tap/tap_filter_integration_test.cc @@ -140,7 +140,7 @@ class TapIntegrationTest : public testing::TestWithParam { protected: @@ -65,20 +65,9 @@ class WasmFilterConfigTest : public Event::TestUsingSimulatedTime, Event::TimerCb retry_timer_cb_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8" -#endif -#if defined(ENVOY_WASM_V8) && defined(ENVOY_WASM_WAVM) - , -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm" -#endif -); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterConfigTest, + Envoy::Extensions::Common::Wasm::sandbox_runtime_values); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmFilterConfigTest); TEST_P(WasmFilterConfigTest, JsonLoadFromFileWasm) { const std::string json = TestEnvironment::substitute(absl::StrCat(R"EOF( @@ -825,7 +814,6 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcodeFailOpen) { // The filter is not registered. cb(filter_callback); } -#endif } // namespace Wasm } // namespace HttpFilters diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index 34fbbba8e3ab..f35b19e114b7 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -45,6 +45,15 @@ wasm_rust_binary( ], ) +wasm_rust_binary( + name = "resume_call_rust.wasm", + srcs = ["resume_call_rust.rs"], + deps = [ + "//bazel/external/cargo:log", + "//bazel/external/cargo:proxy_wasm", + ], +) + wasm_rust_binary( name = "shared_data_rust.wasm", srcs = ["shared_data_rust.rs"], @@ -72,6 +81,7 @@ envoy_cc_library( "test_cpp_null_plugin.cc", "test_grpc_call_cpp.cc", "test_grpc_stream_cpp.cc", + "test_resume_call_cpp.cc", "test_shared_data_cpp.cc", "test_shared_queue_cpp.cc", ], @@ -97,6 +107,7 @@ envoy_wasm_cc_binary( "test_cpp.cc", "test_grpc_call_cpp.cc", "test_grpc_stream_cpp.cc", + "test_resume_call_cpp.cc", "test_shared_data_cpp.cc", "test_shared_queue_cpp.cc", ], diff --git a/test/extensions/filters/http/wasm/test_data/resume_call_rust.rs b/test/extensions/filters/http/wasm/test_data/resume_call_rust.rs new file mode 100644 index 000000000000..d9eb08b1fa33 --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/resume_call_rust.rs @@ -0,0 +1,39 @@ +use log::info; +use proxy_wasm::traits::{Context, HttpContext}; +use proxy_wasm::types::*; +use std::time::Duration; + +#[no_mangle] +pub fn _start() { + proxy_wasm::set_log_level(LogLevel::Trace); + proxy_wasm::set_http_context(|_, _| -> Box { Box::new(TestStream) }); +} + +struct TestStream; + +impl HttpContext for TestStream { + fn on_http_request_headers(&mut self, _: usize) -> Action { + self.dispatch_http_call( + "cluster", + vec![(":method", "POST"), (":path", "/"), (":authority", "foo")], + Some(b"resume"), + vec![], + Duration::from_secs(1), + ) + .unwrap(); + info!("onRequestHeaders"); + Action::Pause + } + + fn on_http_request_body(&mut self, _: usize, _: bool) -> Action { + info!("onRequestBody"); + Action::Continue + } +} + +impl Context for TestStream { + fn on_http_call_response(&mut self, _: u32, _: usize, _: usize, _: usize) { + info!("continueRequest"); + self.resume_http_request(); + } +} diff --git a/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc new file mode 100644 index 000000000000..f557eb143859 --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc @@ -0,0 +1,53 @@ +// NOLINT(namespace-envoy) +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics_lite.h" +#else +#include "extensions/common/wasm/ext/envoy_null_plugin.h" +#endif + +START_WASM_PLUGIN(HttpWasmTestCpp) + +class ResumeCallContext : public Context { +public: + explicit ResumeCallContext(uint32_t id, RootContext* root) : Context(id, root) {} + + FilterHeadersStatus onRequestHeaders(uint32_t, bool) override; + FilterDataStatus onRequestBody(size_t, bool) override; +}; + +class ResumeCallRootContext : public RootContext { +public: + explicit ResumeCallRootContext(uint32_t id, std::string_view root_id) + : RootContext(id, root_id) {} +}; + +static RegisterContextFactory register_ResumeCallContext(CONTEXT_FACTORY(ResumeCallContext), + ROOT_FACTORY(ResumeCallRootContext), + "resume_call"); + +FilterHeadersStatus ResumeCallContext::onRequestHeaders(uint32_t, bool) { + auto context_id = id(); + auto resume_callback = [context_id](uint32_t, size_t, uint32_t) { + getContext(context_id)->setEffectiveContext(); + logInfo("continueRequest"); + continueRequest(); + }; + if (root()->httpCall("cluster", {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, + "resume", {}, 1000, resume_callback) != WasmResult::Ok) { + logError("unexpected failure"); + return FilterHeadersStatus::StopIteration; + } + logInfo("onRequestHeaders"); + return FilterHeadersStatus::StopIteration; +} + +FilterDataStatus ResumeCallContext::onRequestBody(size_t, bool) { + logInfo("onRequestBody"); + return FilterDataStatus::Continue; +} + +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 538481f36f6c..fddde58db40b 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -2,11 +2,13 @@ #include "extensions/filters/http/wasm/wasm_filter.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/network/connection.h" #include "test/mocks/router/mocks.h" #include "test/test_common/wasm_base.h" using testing::Eq; +using testing::InSequence; using testing::Invoke; using testing::Return; using testing::ReturnRef; @@ -82,7 +84,7 @@ class WasmHttpFilterTest : public Common::Wasm::WasmHttpFilterTestBase< } setupBase(std::get<0>(GetParam()), code, createContextFn(), root_id, vm_configuration); } - void setupFilter(const std::string root_id = "") { setupFilterBase(root_id); } + void setupFilter() { setupFilterBase(); } void setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks); @@ -94,17 +96,8 @@ class WasmHttpFilterTest : public Common::Wasm::WasmHttpFilterTestBase< Grpc::MockAsyncClientManager async_client_manager_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - std::make_tuple("v8", "cpp"), std::make_tuple("v8", "rust"), -#endif -#if defined(ENVOY_WASM_WAVM) - std::make_tuple("wavm", "cpp"), std::make_tuple("wavm", "rust"), -#endif - std::make_tuple("null", "cpp")); -INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, testing_values); +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, + Envoy::Extensions::Common::Wasm::runtime_and_language_values); // Bad code in initial config. TEST_P(WasmHttpFilterTest, BadCode) { @@ -211,7 +204,7 @@ TEST_P(WasmHttpFilterTest, HeadersStopAndContinue) { EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy-wasm-pause"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, true)); root_context_->onTick(0); filter().clearRouteCache(); @@ -291,7 +284,7 @@ TEST_P(WasmHttpFilterTest, HeadersStopAndWatermark) { // Script that reads the body. TEST_P(WasmHttpFilterTest, BodyRequestReadBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "ReadBody"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); @@ -303,7 +296,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestReadBody) { // Script that prepends and appends to the body. TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); EXPECT_CALL(filter(), log_(spdlog::level::err, @@ -327,7 +320,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBody) { // Script that replaces the body. TEST_P(WasmHttpFilterTest, BodyRequestReplaceBody) { setupTest("body"); - setupFilter("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", "ReplaceBody"}}; @@ -348,7 +341,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestReplaceBody) { // Script that removes the body. TEST_P(WasmHttpFilterTest, BodyRequestRemoveBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "RemoveBody"}}; @@ -361,7 +354,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestRemoveBody) { // Script that buffers the body. TEST_P(WasmHttpFilterTest, BodyRequestBufferBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "BufferBody"}}; @@ -404,7 +397,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestBufferBody) { // Script that prepends and appends to the buffered body. TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBufferedBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); Http::TestRequestHeaderMapImpl request_headers{ @@ -418,7 +411,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBufferedBody) { // Script that replaces the buffered body. TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "ReplaceBufferedBody"}}; @@ -431,7 +424,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { // Script that removes the buffered body. TEST_P(WasmHttpFilterTest, BodyRequestRemoveBufferedBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "RemoveBufferedBody"}}; @@ -444,7 +437,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestRemoveBufferedBody) { // Script that buffers the first part of the body and streams the rest TEST_P(WasmHttpFilterTest, BodyRequestBufferThenStreamBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); @@ -494,7 +487,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestBufferThenStreamBody) { // Script that buffers the first part of the body and streams the rest TEST_P(WasmHttpFilterTest, BodyResponseBufferThenStreamBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); @@ -577,7 +570,7 @@ TEST_P(WasmHttpFilterTest, AccessLogCreate) { TEST_P(WasmHttpFilterTest, AsyncCall) { setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -612,7 +605,56 @@ TEST_P(WasmHttpFilterTest, AsyncCall) { callbacks->onSuccess(request, std::move(response_message)); return proxy_wasm::WasmResult::Ok; })); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + EXPECT_NE(callbacks, nullptr); +} + +TEST_P(WasmHttpFilterTest, StopAndResumeViaAsyncCall) { + setupTest("resume_call"); + setupFilter(); + + InSequence s; + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); + Http::AsyncClient::Callbacks* callbacks = nullptr; + EXPECT_CALL(cluster_manager_, get(Eq("cluster"))); + EXPECT_CALL(cluster_manager_, httpAsyncClientForCluster("cluster")); + EXPECT_CALL(cluster_manager_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/"}, + {":authority", "foo"}, + {"content-length", "6"}}), + message->headers()); + callbacks = &cb; + return &request; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) + .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { + Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + NiceMock span; + Http::TestResponseHeaderMapImpl response_header{{":status", "200"}}; + callbacks->onBeforeFinalizeUpstreamSpan(span, &response_header); + callbacks->onSuccess(request, std::move(response_message)); + return proxy_wasm::WasmResult::Ok; + })); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("continueRequest"))); + + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks, continueDecoding()).WillOnce(Invoke([&]() { + // Verify that we're not resuming processing from within Wasm callback. + EXPECT_EQ(proxy_wasm::current_context_, nullptr); + })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_NE(callbacks, nullptr); @@ -624,7 +666,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { return; } setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -644,7 +686,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { TEST_P(WasmHttpFilterTest, AsyncCallFailure) { setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -677,7 +719,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallFailure) { } else { EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq("async_call failed"))); } - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_NE(callbacks, nullptr); @@ -685,7 +727,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallFailure) { TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -708,7 +750,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { })); EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); @@ -716,6 +758,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { // Destroy the Context, Plugin and VM. context_.reset(); plugin_.reset(); + plugin_handle_.reset(); wasm_.reset(); Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( @@ -735,7 +778,7 @@ TEST_P(WasmHttpFilterTest, GrpcCall) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -768,7 +811,7 @@ TEST_P(WasmHttpFilterTest, GrpcCall) { })); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -790,7 +833,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallBadCall) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); Grpc::MockAsyncClientManager client_manager; auto client_factory = std::make_unique(); auto async_client = std::make_unique(); @@ -819,7 +862,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallFailure) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -852,7 +895,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallFailure) { })); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("failure bad"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); // Test some additional error paths. @@ -881,7 +924,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallCancel) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -913,7 +956,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallCancel) { return std::move(client_factory); })); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); rootContext().onQueueReady(0); @@ -925,7 +968,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallClose) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -957,7 +1000,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallClose) { return std::move(client_factory); })); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); rootContext().onQueueReady(1); @@ -969,7 +1012,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); Grpc::MockAsyncRequest request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -1002,7 +1045,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { })); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); @@ -1010,6 +1053,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { // Destroy the Context, Plugin and VM. context_.reset(); plugin_.reset(); + plugin_handle_.reset(); wasm_.reset(); ProtobufWkt::Value value; @@ -1026,7 +1070,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { void WasmHttpFilterTest::setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks) { setupTest("grpc_stream"); - setupFilter("grpc_stream"); + setupFilter(); EXPECT_CALL(async_client_manager_, factoryForGrpcService(_, _, _)) .WillRepeatedly( @@ -1066,7 +1110,7 @@ TEST_P(WasmHttpFilterTest, GrpcStream) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close done"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1097,7 +1141,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamCloseLocal) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response close"))); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close ok"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1127,7 +1171,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamCloseRemote) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close close"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1154,7 +1198,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamCancel) { setupGrpcStreamTest(callbacks); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1182,7 +1226,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1203,13 +1247,16 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { // Destroy the Context, Plugin and VM. context_.reset(); plugin_.reset(); + plugin_handle_.reset(); wasm_.reset(); } // Test metadata access including CEL expressions. -// TODO: re-enable this on Windows if and when the CEL `Antlr` parser compiles on Windows. -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) TEST_P(WasmHttpFilterTest, Metadata) { +#ifdef WIN32 + // TODO: re-enable this on Windows if and when the CEL `Antlr` parser compiles on Windows. + GTEST_SKIP() << "Skipping on Windows"; +#endif setupTest("", "metadata"); setupFilter(); envoy::config::core::v3::Node node_data; @@ -1263,7 +1310,6 @@ TEST_P(WasmHttpFilterTest, Metadata) { filter().onDestroy(); filter().onDestroy(); // Does nothing. } -#endif TEST_P(WasmHttpFilterTest, Property) { if (std::get<1>(GetParam()) == "rust") { @@ -1381,7 +1427,7 @@ TEST_P(WasmHttpFilterTest, SharedData) { TEST_P(WasmHttpFilterTest, SharedQueue) { setupTest("shared_queue"); - setupFilter("shared_queue"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onRequestHeaders enqueue Ok")))); EXPECT_CALL(filter(), log_(spdlog::level::warn, @@ -1420,7 +1466,7 @@ TEST_P(WasmHttpFilterTest, RootId1) { return; } setupTest("context1"); - setupFilter("context1"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders1 2")))); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); @@ -1433,10 +1479,11 @@ TEST_P(WasmHttpFilterTest, RootId2) { return; } setupTest("context2"); - setupFilter("context2"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders2 2")))); Http::TestRequestHeaderMapImpl request_headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, true)); } } // namespace Wasm diff --git a/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc b/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc index cea84965b576..6f5d6387af07 100644 --- a/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc @@ -174,6 +174,30 @@ TEST(HessianUtilsTest, peekLong) { } // Single octet longs + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xeb'})); + size_t size; + EXPECT_EQ(11, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(1, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xec'})); + size_t size; + EXPECT_EQ(12, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(1, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xed'})); + size_t size; + EXPECT_EQ(13, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(1, size); + } + { Buffer::OwnedImpl buffer; buffer.add(std::string({'\xef'})); @@ -223,6 +247,38 @@ TEST(HessianUtilsTest, peekLong) { EXPECT_EQ(2, size); } + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfc', 0x00})); + size_t size; + EXPECT_EQ(1024, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfc', '\xff'})); + size_t size; + EXPECT_EQ(1279, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfd', 0x00})); + size_t size; + EXPECT_EQ(1280, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfd', '\xff'})); + size_t size; + EXPECT_EQ(1535, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + { Buffer::OwnedImpl buffer; buffer.add(std::string({'\xff', '\xff'})); @@ -409,6 +465,22 @@ TEST(HessianUtilsTest, peekInt) { EXPECT_EQ(2, size); } + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xcc', 0x00})); + size_t size; + EXPECT_EQ(1024, HessianUtils::peekInt(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xcc', '\xff'})); + size_t size; + EXPECT_EQ(1279, HessianUtils::peekInt(buffer, &size)); + EXPECT_EQ(2, size); + } + { Buffer::OwnedImpl buffer; buffer.add(std::string({'\xcf', '\xff'})); diff --git a/test/extensions/filters/network/http_connection_manager/BUILD b/test/extensions/filters/network/http_connection_manager/BUILD index 9ebb1b5e6cc7..673c3f11e327 100644 --- a/test/extensions/filters/network/http_connection_manager/BUILD +++ b/test/extensions/filters/network/http_connection_manager/BUILD @@ -35,6 +35,7 @@ envoy_extension_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", "//test/test_common:registry_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 66e90035326b..4b6dbb368a7f 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -23,6 +23,7 @@ #include "test/mocks/server/factory_context.h" #include "test/test_common/printers.h" #include "test/test_common/registry.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -128,7 +129,7 @@ stat_prefix: router - name: envoy.filters.http.router - name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false )EOF"; @@ -156,7 +157,7 @@ stat_prefix: router http_filters: - name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false )EOF"; @@ -169,6 +170,7 @@ stat_prefix: router // When deprecating v2, remove the old style "operation_name: egress" config // but retain the rest of the test. TEST_F(HttpConnectionManagerConfigTest, DEPRECATED_FEATURE_TEST(MiscConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_string = R"EOF( codec_type: http1 server_name: foo @@ -367,7 +369,6 @@ stat_prefix: router route: cluster: cluster tracing: - operation_name: ingress max_path_tag_length: 128 provider: # notice inlined tracing provider configuration name: zipkin @@ -447,6 +448,7 @@ stat_prefix: router } TEST_F(HttpConnectionManagerConfigTest, DEPRECATED_FEATURE_TEST(RequestHeaderForTagsConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_string = R"EOF( stat_prefix: router route_config: @@ -469,6 +471,7 @@ stat_prefix: router TEST_F(HttpConnectionManagerConfigTest, DEPRECATED_FEATURE_TEST(ListenerDirectionOutboundOverride)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_string = R"EOF( stat_prefix: router route_config: @@ -496,6 +499,7 @@ stat_prefix: router } TEST_F(HttpConnectionManagerConfigTest, DEPRECATED_FEATURE_TEST(ListenerDirectionInboundOverride)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_string = R"EOF( stat_prefix: router route_config: @@ -529,8 +533,7 @@ TEST_F(HttpConnectionManagerConfigTest, SamplingDefault) { unix_sockets: true route_config: name: local_route - tracing: - operation_name: ingress + tracing: {} http_filters: - name: envoy.filters.http.router )EOF"; @@ -560,7 +563,6 @@ TEST_F(HttpConnectionManagerConfigTest, SamplingConfigured) { route_config: name: local_route tracing: - operation_name: ingress client_sampling: value: 1 random_sampling: @@ -595,7 +597,6 @@ TEST_F(HttpConnectionManagerConfigTest, FractionalSamplingConfigured) { route_config: name: local_route tracing: - operation_name: ingress client_sampling: value: 0.1 random_sampling: @@ -715,6 +716,7 @@ TEST_F(HttpConnectionManagerConfigTest, DisabledStreamIdleTimeout) { // Validate that deprecated idle_timeout is still ingested. TEST_F(HttpConnectionManagerConfigTest, DEPRECATED_FEATURE_TEST(IdleTimeout)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_string = R"EOF( stat_prefix: ingress_http idle_timeout: 1s @@ -1257,7 +1259,7 @@ stat_prefix: my_stat_prefix access_log: - name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/dev/null" filter: [] )EOF"; @@ -1286,7 +1288,7 @@ stat_prefix: my_stat_prefix access_log: - name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/dev/null" filter: bad_type: {} @@ -1316,7 +1318,7 @@ stat_prefix: my_stat_prefix access_log: - name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/dev/null" filter: and_filter: @@ -1819,9 +1821,9 @@ stat_prefix: router config_discovery: config_source: { ads: {} } default_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck type_urls: - - type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + - type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck )EOF"; EXPECT_THROW_WITH_MESSAGE( @@ -1882,14 +1884,14 @@ stat_prefix: router "@type": type.googleapis.com/udpa.type.v1.TypedStruct type_url: type.googleapis.com/envoy.extensions.filters.http.router.v3.Router type_urls: - - type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + - type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck - name: envoy.filters.http.router )EOF"; EXPECT_THROW_WITH_MESSAGE( createHttpConnectionManagerConfig(yaml_string), EnvoyException, "Error: filter config has type URL envoy.extensions.filters.http.router.v3.Router but " - "expect envoy.config.filter.http.health_check.v2.HealthCheck."); + "expect envoy.extensions.filters.http.health_check.v3.HealthCheck."); } TEST_F(HttpConnectionManagerConfigTest, DynamicFilterRequireTypeUrlMissingFactory) { @@ -1938,10 +1940,10 @@ stat_prefix: router config_discovery: config_source: { ads: {} } default_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false type_urls: - - type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + - type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck apply_default_config_without_warming: true - name: envoy.filters.http.router )EOF"; @@ -2003,12 +2005,12 @@ stat_prefix: router config_discovery: config_source: { ads: {} } type_urls: - - type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + - type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck - name: bar config_discovery: config_source: { ads: {} } type_urls: - - type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + - type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck - name: envoy.filters.http.router )EOF"; HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, diff --git a/test/extensions/filters/network/local_ratelimit/local_ratelimit_integration_test.cc b/test/extensions/filters/network/local_ratelimit/local_ratelimit_integration_test.cc index 1eaa8c66ff74..964ea9561ddc 100644 --- a/test/extensions/filters/network/local_ratelimit/local_ratelimit_integration_test.cc +++ b/test/extensions/filters/network/local_ratelimit/local_ratelimit_integration_test.cc @@ -25,7 +25,7 @@ TEST_P(LocalRateLimitIntegrationTest, NoRateLimiting) { setup(R"EOF( name: ratelimit typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.local_rate_limit.v2alpha.LocalRateLimit + "@type": type.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit stat_prefix: local_rate_limit_stats token_bucket: max_tokens: 1 diff --git a/test/extensions/filters/network/rbac/integration_test.cc b/test/extensions/filters/network/rbac/integration_test.cc index 9b2f70bab6ba..ba77509e45aa 100644 --- a/test/extensions/filters/network/rbac/integration_test.cc +++ b/test/extensions/filters/network/rbac/integration_test.cc @@ -33,7 +33,7 @@ class RoleBasedAccessControlNetworkFilterIntegrationTest filters: - name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC stat_prefix: tcp. rules: policies: @@ -70,7 +70,7 @@ TEST_P(RoleBasedAccessControlNetworkFilterIntegrationTest, Allowed) { initializeFilter(R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC stat_prefix: tcp. rules: policies: @@ -103,7 +103,7 @@ TEST_P(RoleBasedAccessControlNetworkFilterIntegrationTest, Denied) { initializeFilter(R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC stat_prefix: tcp. rules: policies: @@ -136,7 +136,7 @@ TEST_P(RoleBasedAccessControlNetworkFilterIntegrationTest, DeniedWithDenyAction) initializeFilter(R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC stat_prefix: tcp. rules: action: DENY diff --git a/test/extensions/filters/network/redis_proxy/config_test.cc b/test/extensions/filters/network/redis_proxy/config_test.cc index 155b72689284..ce0276dfae1a 100644 --- a/test/extensions/filters/network/redis_proxy/config_test.cc +++ b/test/extensions/filters/network/redis_proxy/config_test.cc @@ -69,7 +69,7 @@ settings: {} TEST(RedisProxyFilterConfigFactoryTest, DEPRECATED_FEATURE_TEST(RedisProxyCorrectProtoLegacyCluster)) { - TestScopedRuntime scoped_runtime; + TestDeprecatedV2Api _deprecated_v2_api; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.deprecated_features:envoy.config.filter.network.redis_proxy.v2.RedisProxy.cluster", "true"}, @@ -97,7 +97,7 @@ stat_prefix: foo TEST(RedisProxyFilterConfigFactoryTest, DEPRECATED_FEATURE_TEST(RedisProxyCorrectProtoLegacyCatchAllCluster)) { - TestScopedRuntime scoped_runtime; + TestDeprecatedV2Api _deprecated_v2_api; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.deprecated_features:envoy.config.filter.network.redis_proxy.v2.RedisProxy." "PrefixRoutes.catch_all_cluster", diff --git a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc index d7345dbac788..821f2497d7f5 100644 --- a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc +++ b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc @@ -151,7 +151,7 @@ const std::string CONFIG_WITH_ROUTES_BASE = fmt::format(R"EOF( filters: name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats settings: op_timeout: 5s @@ -210,7 +210,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( type: STATIC typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: {{ inline_string: cluster_0_password }} lb_policy: RANDOM load_assignment: @@ -227,7 +227,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( lb_policy: RANDOM typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: {{ inline_string: cluster_1_password }} load_assignment: cluster_name: cluster_1 @@ -242,7 +242,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( type: STATIC typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: {{ inline_string: cluster_2_password }} lb_policy: RANDOM load_assignment: @@ -264,7 +264,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( filters: name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats settings: op_timeout: 5s diff --git a/test/extensions/filters/network/tcp_proxy/BUILD b/test/extensions/filters/network/tcp_proxy/BUILD index ad332adc27ac..e8df13157fa0 100644 --- a/test/extensions/filters/network/tcp_proxy/BUILD +++ b/test/extensions/filters/network/tcp_proxy/BUILD @@ -18,6 +18,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/network/tcp_proxy:config", "//test/mocks/server:factory_context_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/network/tcp_proxy/config_test.cc b/test/extensions/filters/network/tcp_proxy/config_test.cc index ff74cf1cb0f8..5dc16e512be8 100644 --- a/test/extensions/filters/network/tcp_proxy/config_test.cc +++ b/test/extensions/filters/network/tcp_proxy/config_test.cc @@ -6,6 +6,7 @@ #include "extensions/filters/network/tcp_proxy/config.h" #include "test/mocks/server/factory_context.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -71,6 +72,7 @@ INSTANTIATE_TEST_SUITE_P(IpList, RouteIpListConfigTest, ],)EOF")); TEST_P(RouteIpListConfigTest, DEPRECATED_FEATURE_TEST(TcpProxy)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string json_string = R"EOF( { "stat_prefix": "my_stat_prefix", diff --git a/test/extensions/filters/network/thrift_proxy/integration_test.cc b/test/extensions/filters/network/thrift_proxy/integration_test.cc index 0d520bb20da4..2766bf61d540 100644 --- a/test/extensions/filters/network/thrift_proxy/integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/integration_test.cc @@ -28,7 +28,7 @@ class ThriftConnManagerIntegrationTest filters: - name: thrift typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.thrift_proxy.v2alpha1.ThriftProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.thrift_proxy.v3.ThriftProxy stat_prefix: thrift_stats route_config: name: "routes" diff --git a/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc b/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc index 7b07ad7ab623..0c50426b0fef 100644 --- a/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc @@ -29,7 +29,7 @@ class ThriftTranslationIntegrationTest filters: - name: thrift typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.thrift_proxy.v2alpha1.ThriftProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.thrift_proxy.v3.ThriftProxy stat_prefix: thrift_stats route_config: name: "routes" diff --git a/test/extensions/filters/network/wasm/BUILD b/test/extensions/filters/network/wasm/BUILD index d21eba6c0853..bfbd34124d5f 100644 --- a/test/extensions/filters/network/wasm/BUILD +++ b/test/extensions/filters/network/wasm/BUILD @@ -29,6 +29,7 @@ envoy_extension_cc_test( "//source/extensions/common/crypto:utility_lib", "//source/extensions/common/wasm:wasm_lib", "//source/extensions/filters/network/wasm:config", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/filters/network/wasm/test_data:test_cpp_plugin", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", @@ -46,6 +47,7 @@ envoy_extension_cc_test( extension_name = "envoy.filters.network.wasm", deps = [ "//source/extensions/filters/network/wasm:wasm_filter_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/filters/network/wasm/test_data:test_cpp_plugin", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index 58d17c177fb7..6d93a167f674 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -8,6 +8,7 @@ #include "extensions/filters/network/wasm/config.h" #include "extensions/filters/network/wasm/wasm_filter.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" @@ -56,17 +57,8 @@ class WasmNetworkFilterConfigTest : public testing::TestWithParam { Event::TimerCb retry_timer_cb_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNetworkFilterConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNetworkFilterConfigTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmNetworkFilterConfigTest, YamlLoadFromFileWasm) { if (GetParam() == "null") { @@ -180,7 +172,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailOpen) { envoy::extensions::filters::network::wasm::v3::Wasm proto_config; TestUtility::loadFromYaml(yaml, proto_config); NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); - filter_config.wasm()->fail(proxy_wasm::FailState::RuntimeError, ""); + filter_config.wasmForTest()->fail(proxy_wasm::FailState::RuntimeError, ""); EXPECT_EQ(filter_config.createFilter(), nullptr); } diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 6bf1ca8151e6..0b602428083c 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -3,6 +3,7 @@ #include "extensions/common/wasm/wasm.h" #include "extensions/filters/network/wasm/wasm_filter.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/wasm_base.h" @@ -58,7 +59,7 @@ class WasmNetworkFilterTest : public Common::Wasm::WasmNetworkFilterTestBase< "" /* root_id */, "" /* vm_configuration */, fail_open); } - void setupFilter() { setupFilterBase(""); } + void setupFilter() { setupFilterBase(); } TestFilter& filter() { return *static_cast(context_.get()); } @@ -82,17 +83,8 @@ class WasmNetworkFilterTest : public Common::Wasm::WasmNetworkFilterTestBase< std::string code_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - std::make_tuple("v8", "cpp"), std::make_tuple("v8", "rust"), -#endif -#if defined(ENVOY_WASM_WAVM) - std::make_tuple("wavm", "cpp"), std::make_tuple("wavm", "rust"), -#endif - std::make_tuple("null", "cpp")); -INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmNetworkFilterTest, testing_values); +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmNetworkFilterTest, + Envoy::Extensions::Common::Wasm::runtime_and_language_values); // Bad code in initial config. TEST_P(WasmNetworkFilterTest, BadCode) { diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc index 905b464d977f..0c018a3c4353 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc @@ -232,7 +232,7 @@ TEST_P(DnsFilterIntegrationTest, ExternalLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(1, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ExternalLookupTestIPv6) { @@ -250,7 +250,7 @@ TEST_P(DnsFilterIntegrationTest, ExternalLookupTestIPv6) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(1, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, LocalLookupTest) { @@ -268,7 +268,7 @@ TEST_P(DnsFilterIntegrationTest, LocalLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(4, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ClusterLookupTest) { @@ -292,7 +292,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(2, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ClusterEndpointLookupTest) { @@ -317,7 +317,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterEndpointLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(2, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ClusterEndpointWithPortServiceRecordLookupTest) { @@ -335,7 +335,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterEndpointWithPortServiceRecordLookupTest) EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(2, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); for (const auto& answer : query_ctx_->answers_) { EXPECT_EQ(answer.second->type_, DNS_RECORD_TYPE_SRV); @@ -370,7 +370,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterEndpointWithoutPortServiceRecordLookupTe EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(endpoints, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); std::set ports; for (const auto& answer : query_ctx_->answers_) { diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc index d07e2e955a11..dbb385a2eeb4 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc @@ -400,7 +400,7 @@ TEST_F(DnsFilterTest, InvalidQuery) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -408,9 +408,6 @@ TEST_F(DnsFilterTest, InvalidQuery) { EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); EXPECT_TRUE(config_->stats().downstream_rx_bytes_.used()); EXPECT_TRUE(config_->stats().downstream_tx_bytes_.used()); - - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); - EXPECT_EQ(0, query_ctx_->answers_.size()); } TEST_F(DnsFilterTest, MaxQueryAndResponseSizeTest) { @@ -429,7 +426,7 @@ TEST_F(DnsFilterTest, MaxQueryAndResponseSizeTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); // There are 8 addresses, however, since the domain is part of the answer record, each // serialized answer is over 100 bytes in size, there is room for 3 before the next // serialized answer puts the buffer over the 512 byte limit. The query itself is also @@ -460,7 +457,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTooLongTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -469,7 +466,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTooLongTest) { EXPECT_TRUE(config_->stats().downstream_rx_bytes_.used()); EXPECT_TRUE(config_->stats().downstream_tx_bytes_.used()); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); } @@ -488,7 +485,7 @@ TEST_F(DnsFilterTest, InvalidLabelNameTooLongTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -497,7 +494,7 @@ TEST_F(DnsFilterTest, InvalidLabelNameTooLongTest) { EXPECT_TRUE(config_->stats().downstream_rx_bytes_.used()); EXPECT_TRUE(config_->stats().downstream_tx_bytes_.used()); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); } @@ -516,7 +513,7 @@ TEST_F(DnsFilterTest, SingleTypeAQuery) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); // Verify that we have an answer record for the queried domain @@ -543,24 +540,36 @@ TEST_F(DnsFilterTest, RepeatedTypeAQuerySuccess) { setup(forward_query_off_config); constexpr size_t loopCount = 5; const std::string domain("www.foo3.com"); - size_t total_query_bytes = 0; + std::list query_id_list{}; + + query_id_list.resize(loopCount); for (size_t i = 0; i < loopCount; i++) { + + // Generate a changing, non-zero query ID for each lookup + const uint16_t query_id = (random_.random() + i) & 0xFFFF; const std::string query = - Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN); - total_query_bytes += query.size(); + Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN, query_id); ASSERT_FALSE(query.empty()); sendQueryFromClient("10.0.0.1:1000", query); query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); + + const uint16_t response_id = query_ctx_->header_.id; + auto iter = std::find(query_id_list.begin(), query_id_list.end(), query_id); + EXPECT_EQ(iter, query_id_list.end()); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); // Verify that we have an answer record for the queried domain const DnsAnswerRecordPtr& answer = query_ctx_->answers_.find(domain)->second; + // Verify that the Query ID matches the Response ID + EXPECT_EQ(query_id, response_id); + query_id_list.emplace_back(query_id); + // Verify the address returned std::list expected{"10.0.3.1"}; Utils::verifyAddress(expected, answer); @@ -585,7 +594,7 @@ TEST_F(DnsFilterTest, LocalTypeAQueryFail) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -610,7 +619,7 @@ TEST_F(DnsFilterTest, LocalTypeAAAAQuerySuccess) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(expected.size(), query_ctx_->answers_.size()); // Verify the address returned @@ -658,7 +667,7 @@ TEST_F(DnsFilterTest, ExternalResolutionReturnSingleAddress) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); std::list expected{expected_address}; @@ -711,7 +720,7 @@ TEST_F(DnsFilterTest, ExternalResolutionIpv6SingleAddress) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); std::list expected{expected_address}; @@ -764,7 +773,7 @@ TEST_F(DnsFilterTest, ExternalResolutionReturnMultipleAddresses) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(expected_address.size(), query_ctx_->answers_.size()); EXPECT_LT(udp_response_.buffer_->length(), Utils::MAX_UDP_DNS_SIZE); @@ -814,7 +823,7 @@ TEST_F(DnsFilterTest, ExternalResolutionReturnNoAddresses) { // parse the result query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -853,7 +862,7 @@ TEST_F(DnsFilterTest, ExternalResolutionTimeout) { // parse the result query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -901,7 +910,7 @@ TEST_F(DnsFilterTest, ExternalResolutionTimeout2) { // parse the result query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -952,7 +961,7 @@ TEST_F(DnsFilterTest, ExternalResolutionExceedMaxPendingLookups) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(0, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); // Validate stats EXPECT_EQ(3, config_->stats().downstream_rx_queries_.value()); @@ -980,7 +989,7 @@ TEST_F(DnsFilterTest, ConsumeExternalJsonTableTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(2, query_ctx_->answers_.size()); // Verify the address returned @@ -1014,7 +1023,7 @@ TEST_F(DnsFilterTest, ConsumeExternalJsonTableTestNoIpv6Answer) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -1043,7 +1052,7 @@ TEST_F(DnsFilterTest, ConsumeExternalYamlTableTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(2, query_ctx_->answers_.size()); // Verify the address returned @@ -1086,7 +1095,7 @@ TEST_F(DnsFilterTest, RawBufferTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); // Verify that we have an answer record for the queried domain @@ -1124,7 +1133,7 @@ TEST_F(DnsFilterTest, InvalidAnswersInQueryTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); } @@ -1155,7 +1164,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); } @@ -1525,7 +1534,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTest2) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); // TODO(abaptiste): underflow/overflow stats EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1563,7 +1572,7 @@ TEST_F(DnsFilterTest, MultipleQueryCountTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); @@ -1595,7 +1604,7 @@ TEST_F(DnsFilterTest, InvalidQueryCountTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1628,7 +1637,7 @@ TEST_F(DnsFilterTest, InvalidNameLabelTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1661,7 +1670,7 @@ TEST_F(DnsFilterTest, NotImplementedQueryTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(0, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1693,7 +1702,7 @@ TEST_F(DnsFilterTest, NotImplementedAuthorityRRTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, query_ctx_->getQueryResponseCode()); } TEST_F(DnsFilterTest, NoTransactionIdTest) { @@ -1722,7 +1731,7 @@ TEST_F(DnsFilterTest, NoTransactionIdTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); } TEST_F(DnsFilterTest, InvalidShortBufferTest) { @@ -1736,7 +1745,7 @@ TEST_F(DnsFilterTest, InvalidShortBufferTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1755,7 +1764,7 @@ TEST_F(DnsFilterTest, RandomizeFirstAnswerTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); // Although 16 addresses are defined, only 8 are returned EXPECT_EQ(8, query_ctx_->answers_.size()); @@ -1790,7 +1799,7 @@ TEST_F(DnsFilterTest, ConsumeExternalTableWithServicesTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); std::map validation_weight_map = { {10, "backup.voip.subzero.com"}, @@ -1888,7 +1897,7 @@ TEST_F(DnsFilterTest, SrvTargetResolution) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); const DnsAnswerRecordPtr& answer = query_ctx_->answers_.find(domain)->second; @@ -1921,7 +1930,7 @@ TEST_F(DnsFilterTest, NonExistentClusterServiceLookup) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -1959,7 +1968,7 @@ TEST_F(DnsFilterTest, SrvRecordQuery) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->queries_.size()); const auto& parsed_query = query_ctx_->queries_.front(); @@ -1990,7 +1999,7 @@ TEST_F(DnsFilterTest, SrvQueryMaxRecords) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); // We can only serialize 7 records before reaching the 512 byte limit EXPECT_LT(query_ctx_->answers_.size(), MAX_RETURNED_RECORDS); diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc index 364ee8b3094c..b6a50496a7f3 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc @@ -18,10 +18,11 @@ std::string buildQueryFromBytes(const char* bytes, const size_t count) { return query; } -std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class) { +std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class, + const uint16_t query_id) { Random::RandomGeneratorImpl random_; - struct DnsMessageParser::DnsHeader query {}; - uint16_t id = random_.random() & 0xFFFF; + struct DnsHeader query {}; + uint16_t id = (query_id ? query_id : random_.random() & 0xFFFF); // Generate a random query ID query.id = id; diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h index 4c500091ea29..378abbe43186 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h @@ -12,7 +12,8 @@ namespace Utils { static constexpr uint64_t MAX_UDP_DNS_SIZE{512}; std::string buildQueryFromBytes(const char* bytes, const size_t count); -std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class); +std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class, + const uint16_t query_id = 0); void verifyAddress(const std::list& addresses, const DnsAnswerRecordPtr& answer); size_t getResponseQueryCount(DnsMessageParser& parser); diff --git a/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc b/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc index 6001a7bfdacd..6a5852bb9a1a 100644 --- a/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc +++ b/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc @@ -69,14 +69,14 @@ class GrpcAwsIamClientIntegrationTest : public GrpcSslClientIntegrationTest { ABSL_FALLTHROUGH_INTENDED; case RegionLocation::NotProvided: config_yaml = fmt::format(R"EOF( - "@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.AwsIamConfig + "@type": type.googleapis.com/envoy.config.grpc_credential.v3.AwsIamConfig service_name: {} )EOF", service_name_); break; case RegionLocation::InConfig: config_yaml = fmt::format(R"EOF( - "@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.AwsIamConfig + "@type": type.googleapis.com/envoy.config.grpc_credential.v3.AwsIamConfig service_name: {} region: {} )EOF", diff --git a/test/extensions/grpc_credentials/file_based_metadata/BUILD b/test/extensions/grpc_credentials/file_based_metadata/BUILD index a7c18341b47b..8ccb3fe5b848 100644 --- a/test/extensions/grpc_credentials/file_based_metadata/BUILD +++ b/test/extensions/grpc_credentials/file_based_metadata/BUILD @@ -10,10 +10,9 @@ licenses(["notice"]) # Apache 2 envoy_package() envoy_cc_test( - name = "file_based_metadata_grpc_credentials_test", - srcs = ["file_based_metadata_grpc_credentials_test.cc"], + name = "integration_test", + srcs = ["integration_test.cc"], data = ["//test/config/integration/certs"], - tags = ["flaky_on_windows"], deps = [ "//source/extensions/grpc_credentials:well_known_names", "//source/extensions/grpc_credentials/file_based_metadata:config", diff --git a/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc similarity index 97% rename from test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc rename to test/extensions/grpc_credentials/file_based_metadata/integration_test.cc index f9be83ec5b6e..416059bdb7e6 100644 --- a/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc +++ b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc @@ -41,7 +41,7 @@ class GrpcFileBasedMetadataClientIntegrationTest : public GrpcSslClientIntegrati TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); if (!header_value_1_.empty()) { const std::string yaml1 = fmt::format(R"EOF( -"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig +"@type": type.googleapis.com/envoy.config.grpc_credential.v3.FileBasedMetadataConfig secret_data: inline_string: {} header_key: {} @@ -56,7 +56,7 @@ header_prefix: {} if (!header_value_2_.empty()) { // uses default key/prefix const std::string yaml2 = fmt::format(R"EOF( -"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig +"@type": type.googleapis.com/envoy.config.grpc_credential.v3.FileBasedMetadataConfig secret_data: inline_string: {} )EOF", diff --git a/test/extensions/health_checkers/redis/BUILD b/test/extensions/health_checkers/redis/BUILD index 5e91cd4f9cd8..6a134aebe2ba 100644 --- a/test/extensions/health_checkers/redis/BUILD +++ b/test/extensions/health_checkers/redis/BUILD @@ -30,6 +30,7 @@ envoy_extension_cc_test( "//test/mocks/upstream:host_mocks", "//test/mocks/upstream:host_set_mocks", "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/redis_proxy/v3:pkg_cc_proto", ], @@ -49,6 +50,7 @@ envoy_extension_cc_test( "//test/mocks/server:health_checker_factory_context_mocks", "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/network/redis_proxy/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/health_checkers/redis/config_test.cc b/test/extensions/health_checkers/redis/config_test.cc index b54620d614ab..5f4caf4a4f66 100644 --- a/test/extensions/health_checkers/redis/config_test.cc +++ b/test/extensions/health_checkers/redis/config_test.cc @@ -12,6 +12,7 @@ #include "test/mocks/server/health_checker_factory_context.h" #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/priority_set.h" +#include "test/test_common/test_runtime.h" namespace Envoy { namespace Extensions { @@ -22,6 +23,7 @@ namespace { using CustomRedisHealthChecker = Extensions::HealthCheckers::RedisHealthChecker::RedisHealthChecker; TEST(HealthCheckerFactoryTest, DEPRECATED_FEATURE_TEST(CreateRedisDeprecated)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s @@ -71,6 +73,7 @@ TEST(HealthCheckerFactoryTest, CreateRedis) { } TEST(HealthCheckerFactoryTest, DEPRECATED_FEATURE_TEST(CreateRedisWithoutKeyDeprecated)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s diff --git a/test/extensions/health_checkers/redis/redis_test.cc b/test/extensions/health_checkers/redis/redis_test.cc index a751b8be6428..47e37851b7fe 100644 --- a/test/extensions/health_checkers/redis/redis_test.cc +++ b/test/extensions/health_checkers/redis/redis_test.cc @@ -18,6 +18,7 @@ #include "test/mocks/upstream/host.h" #include "test/mocks/upstream/host_set.h" #include "test/mocks/upstream/priority_set.h" +#include "test/test_common/test_runtime.h" using testing::_; using testing::DoAll; @@ -573,6 +574,7 @@ TEST_F(RedisHealthCheckerTest, LogInitialFailure) { } TEST_F(RedisHealthCheckerTest, DEPRECATED_FEATURE_TEST(ExistsDeprecated)) { + TestDeprecatedV2Api _deprecated_v2_api; InSequence s; setupExistsHealthcheckDeprecated(false); diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 92aad3fe9a2e..4375b705ca7d 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -84,6 +84,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/test_common:utility_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + "@com_googlesource_quiche//:quic_test_tools_qpack_qpack_test_utils_lib", "@com_googlesource_quiche//:quic_test_tools_session_peer_lib", ], ) @@ -105,6 +106,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/test_common:utility_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + "@com_googlesource_quiche//:quic_test_tools_qpack_qpack_test_utils_lib", ], ) @@ -290,5 +292,6 @@ envoy_cc_test_library( "//test/test_common:environment_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", "@com_googlesource_quiche//:quic_test_tools_first_flight_lib", + "@com_googlesource_quiche//:quic_test_tools_qpack_qpack_encoder_test_utils_lib", ], ) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc index e2d90d916469..e03c6ab3ba64 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc @@ -49,9 +49,9 @@ class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection { Network::ConnectionSocketPtr&& connection_socket) : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, &writer, false, supported_versions, dispatcher, std::move(connection_socket)) { - SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); SetEncrypter(quic::ENCRYPTION_FORWARD_SECURE, std::make_unique(quic::Perspective::IS_CLIENT)); + SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); } MOCK_METHOD(void, SendConnectionClosePacket, (quic::QuicErrorCode, const std::string&)); @@ -152,7 +152,8 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam { std::string host("www.abc.com"); Http::TestRequestHeaderMapImpl request_headers{ {":authority", host}, {":method", "GET"}, {":path", "/"}}; - stream.encodeHeaders(request_headers, true); + const auto result = stream.encodeHeaders(request_headers, true); + ASSERT(result.ok()); return stream; } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index ac82239db0bb..01871dcfff99 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -48,11 +48,11 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(testing::Return(false)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillRepeatedly(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillRepeatedly( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, const quic::QuicSocketAddress&, quic::PerPacketOptions*) { @@ -69,6 +69,7 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { response_headers_.OnHeader(":status", "200"); response_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_response_headers_[":status"] = "200"; trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); @@ -77,6 +78,7 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { trailers_.OnHeader(":final-offset", absl::StrCat("", response_body_.length())); } trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_trailers_["key1"] = "value1"; } void TearDown() override { @@ -87,6 +89,41 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { } } + std::string bodyToStreamPayload(const std::string& body) { + if (!quic::VersionUsesHttp3(quic_version_.transport_version)) { + return body; + } + return bodyToHttp3StreamPayload(body); + } + + size_t receiveResponse(const std::string& payload, bool fin) { + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { + EXPECT_EQ("200", headers->getStatusValue()); + })); + + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(payload, buffer.toString()); + EXPECT_EQ(fin, finished_reading); + })); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_response_headers_), + bodyToStreamPayload(payload)); + quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return data.length(); + } + quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), + response_headers_); + + quic::QuicStreamFrame frame(stream_id_, fin, 0, payload); + quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return payload.length(); + } + protected: Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -107,7 +144,9 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { Http::TestRequestHeaderMapImpl request_headers_; Http::TestRequestTrailerMapImpl request_trailers_; quic::QuicHeaderList response_headers_; + spdy::SpdyHeaderBlock spdy_response_headers_; quic::QuicHeaderList trailers_; + spdy::SpdyHeaderBlock spdy_trailers_; Buffer::OwnedImpl request_body_{"Hello world"}; std::string response_body_{"OK\n"}; }; @@ -115,43 +154,35 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { INSTANTIATE_TEST_SUITE_P(EnvoyQuicClientStreamTests, EnvoyQuicClientStreamTest, testing::ValuesIn({true, false})); -TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { - EXPECT_EQ(absl::nullopt, quic_stream_->http1StreamEncoderOptions()); - quic_stream_->encodeHeaders(request_headers_, false); - quic_stream_->encodeData(request_body_, false); - quic_stream_->encodeTrailers(request_trailers_); +TEST_P(EnvoyQuicClientStreamTest, GetRequestAndHeaderOnlyResponse) { + const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/true); + EXPECT_TRUE(result.ok()); - EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/!quic::VersionUsesHttp3( + quic_version_.transport_version))) .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { EXPECT_EQ("200", headers->getStatusValue()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), - response_headers_); - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); - - EXPECT_CALL(stream_decoder_, decodeData(_, _)) - .Times(testing::AtMost(2)) - .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_EQ(response_body_, buffer.toString()); - EXPECT_FALSE(finished_reading); - })) - // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But - // since there is trailers, finished_reading should always be false. - .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_FALSE(finished_reading); - EXPECT_EQ(0, buffer.length()); - })); - std::string data = response_body_; if (quic::VersionUsesHttp3(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::QuicByteCount data_frame_header_length = - quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); - quiche::QuicheStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, response_body_); + EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(""), /*end_stream=*/true)); + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_response_headers_); + quic::QuicStreamFrame frame(stream_id_, true, 0, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/true, response_headers_.uncompressed_header_bytes(), + response_headers_); } - quic::QuicStreamFrame frame(stream_id_, false, 0, data); - quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); +} +TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { + EXPECT_EQ(absl::nullopt, quic_stream_->http1StreamEncoderOptions()); + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); + quic_stream_->encodeData(request_body_, false); + quic_stream_->encodeTrailers(request_trailers_); + + size_t offset = receiveResponse(response_body_, false); EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::ResponseTrailerMapPtr& headers) { Http::LowerCaseString key1("key1"); @@ -159,7 +190,22 @@ TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { EXPECT_EQ("value1", headers->get(key1)[0]->value().getStringView()); EXPECT_TRUE(headers->get(key2).empty()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string more_response_body{"bbb"}; + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(more_response_body, buffer.toString()); + EXPECT_EQ(false, finished_reading); + })); + std::string payload = absl::StrCat(bodyToStreamPayload(more_response_body), + spdyHeaderToHttp3StreamPayload(spdy_trailers_)); + quic::QuicStreamFrame frame(stream_id_, true, offset, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList( + /*fin=*/!quic::VersionUsesHttp3(quic_version_.transport_version), + trailers_.uncompressed_header_bytes(), trailers_); + } } TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { @@ -167,7 +213,8 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); return; } - quic_stream_->encodeHeaders(request_headers_, true); + const auto result = quic_stream_->encodeHeaders(request_headers_, true); + EXPECT_TRUE(result.ok()); EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { EXPECT_EQ("200", headers->getStatusValue()); @@ -184,7 +231,7 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { std::unique_ptr data_buffer; quic::QuicByteCount data_frame_header_length = quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); - quiche::QuicheStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); data = absl::StrCat(data_frame_header, response_body_); } quic::QuicStreamFrame frame(stream_id_, false, 0, data); @@ -220,7 +267,8 @@ TEST_P(EnvoyQuicClientStreamTest, WatermarkSendBuffer) { quic_session_.OnWindowUpdateFrame(window_update); request_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte - quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + EXPECT_TRUE(result.ok()); // Encode 32kB request body. first 16KB should be written out right away. The // rest should be buffered. The high watermark is 16KB, so this call should // make the send buffer reach its high watermark. @@ -283,12 +331,13 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { // Make the stream blocked by congestion control. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); - quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); + const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + EXPECT_TRUE(result.ok()); // Encode 16kB -10 bytes request body. Because the high watermark is 16KB, with previously // buffered headers, this call should make the send buffers reach their high watermark. @@ -301,11 +350,11 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { // Unblock writing now, and this will write out 16kB data and cause stream to // be blocked by the flow control limit. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()); quic_session_.OnCanWrite(); EXPECT_TRUE(quic_stream_->IsFlowControlBlocked()); @@ -315,20 +364,20 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { 32 * 1024); quic_stream_->OnWindowUpdateFrame(window_update1); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); quic_session_.OnCanWrite(); // No data should be buffered at this point. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); // Send more data. If watermark bytes counting were not cleared in previous // OnCanWrite, this write would have caused the stream to exceed its high watermark. std::string request1(16 * 1024 - 3, 'a'); @@ -341,5 +390,18 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } +TEST_P(EnvoyQuicClientStreamTest, ResetStream) { + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::LocalReset, _)); + quic_stream_->resetStream(Http::StreamResetReason::LocalReset); + EXPECT_TRUE(quic_stream_->rst_sent()); +} + +TEST_P(EnvoyQuicClientStreamTest, ReceiveResetStream) { + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::RemoteReset, _)); + quic_stream_->OnStreamReset(quic::QuicRstStreamFrame( + quic::kInvalidControlFrameId, quic_stream_->id(), quic::QUIC_STREAM_NO_ERROR, 0)); + EXPECT_TRUE(quic_stream_->rst_received()); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index cbf66f511f50..8a493a8e8954 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -25,7 +25,7 @@ namespace Quic { class TestGetProofCallback : public quic::ProofSource::Callback { public: TestGetProofCallback(bool& called, bool should_succeed, const std::string& server_config, - quic::QuicTransportVersion& version, quiche::QuicheStringPiece chlo_hash, + quic::QuicTransportVersion& version, absl::string_view chlo_hash, Network::FilterChain& filter_chain) : called_(called), should_succeed_(should_succeed), server_config_(server_config), version_(version), chlo_hash_(chlo_hash), expected_filter_chain_(filter_chain) { @@ -100,7 +100,7 @@ class TestGetProofCallback : public quic::ProofSource::Callback { bool should_succeed_; const std::string& server_config_; const quic::QuicTransportVersion& version_; - quiche::QuicheStringPiece chlo_hash_; + absl::string_view chlo_hash_; Network::FilterChain& expected_filter_chain_; NiceMock store_; Event::GlobalTimeSystem time_system_; @@ -178,7 +178,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { quic::QuicSocketAddress server_address_; quic::QuicSocketAddress client_address_; quic::QuicTransportVersion version_{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash_{"aaaaa"}; + absl::string_view chlo_hash_{"aaaaa"}; std::string server_config_{"Server Config"}; std::string expected_certs_{quic::test::kTestCertificateChainPem}; std::string pkey_{quic::test::kTestCertificatePrivateKeyPem}; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 4a1dfe144dd3..9cdc169cd6f5 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -163,7 +163,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureEmptyCertChain) { std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; @@ -181,7 +181,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidLeafCert) { std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; @@ -197,7 +197,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidLeafCert) { TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureUnsupportedECKey) { configCertVerificationDetails(true); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; @@ -236,7 +236,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidSignature) { std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 05307c6b9b7c..4fc37685788a 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -61,6 +61,7 @@ class TestEnvoyQuicServerConnection : public EnvoyQuicServerConnection { const quic::ParsedQuicVersionVector& supported_versions, Network::Socket& listen_socket) : EnvoyQuicServerConnection(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any4(), 12345), quic::QuicSocketAddress(quic::QuicIpAddress::Loopback4(), 12345), helper, alarm_factory, &writer, /*owns_writer=*/false, supported_versions, listen_socket) {} @@ -201,10 +202,10 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { crypto_stream_ = test_crypto_stream; } quic::test::QuicServerSessionBasePeer::SetCryptoStream(&envoy_quic_session_, crypto_stream); - quic_connection_->SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); quic_connection_->SetEncrypter( quic::ENCRYPTION_FORWARD_SECURE, std::make_unique(quic::Perspective::IS_SERVER)); + quic_connection_->SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); } bool installReadFilter() { diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 42ba39344f4b..3e37ba3e1d39 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -51,6 +51,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), quic_connection_(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 123), quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, alarm_factory_, &writer_, /*owns_writer=*/false, {quic_version_}, *listener_config_.socket_), @@ -66,11 +67,11 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(testing::Return(false)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillRepeatedly(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillRepeatedly( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, const quic::QuicSocketAddress&, quic::PerPacketOptions*) { @@ -88,14 +89,16 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { request_headers_.OnHeader(":path", "/"); request_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_request_headers_[":authority"] = host_; + spdy_request_headers_[":method"] = "POST"; + spdy_request_headers_[":path"] = "/"; trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); - if (!quic::VersionUsesHttp3(quic_version_.transport_version)) { - // ":final-offset" is required and stripped off by quic. - trailers_.OnHeader(":final-offset", absl::StrCat("", request_body_.length())); - } + // ":final-offset" is required and stripped off by quic. + trailers_.OnHeader(":final-offset", absl::StrCat("", request_body_.length())); trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_trailers_["key1"] = "value1"; } void TearDown() override { @@ -105,27 +108,20 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { } std::string bodyToStreamPayload(const std::string& body) { - std::string data = body; - if (quic::VersionUsesHttp3(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::QuicByteCount data_frame_header_length = - quic::HttpEncoder::SerializeDataFrameHeader(body.length(), &data_buffer); - quiche::QuicheStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, body); + if (!quic::VersionUsesHttp3(quic_version_.transport_version)) { + return body; } - return data; + return bodyToHttp3StreamPayload(body); } - size_t sendRequest(const std::string& payload, bool fin, size_t decoder_buffer_high_watermark) { + size_t receiveRequest(const std::string& payload, bool fin, + size_t decoder_buffer_high_watermark) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::RequestHeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->getHostValue()); EXPECT_EQ("/", headers->getPathValue()); EXPECT_EQ(Http::Headers::get().MethodValues.Post, headers->getMethodValue()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { @@ -135,10 +131,21 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_stream_->readDisable(true); } })); - std::string data = bodyToStreamPayload(payload); - quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_request_headers_), + bodyToStreamPayload(payload)); + quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return data.length(); + } + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + + quic::QuicStreamFrame frame(stream_id_, fin, 0, payload); quic_stream_->OnStreamFrame(frame); - return data.length(); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return payload.length(); } protected: @@ -158,9 +165,11 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { Http::MockRequestDecoder stream_decoder_; Http::MockStreamCallbacks stream_callbacks_; quic::QuicHeaderList request_headers_; + spdy::SpdyHeaderBlock spdy_request_headers_; Http::TestResponseHeaderMapImpl response_headers_; Http::TestResponseTrailerMapImpl response_trailers_; quic::QuicHeaderList trailers_; + spdy::SpdyHeaderBlock spdy_trailers_; std::string host_{"www.abc.com"}; std::string request_body_{"Hello world"}; }; @@ -177,27 +186,40 @@ TEST_P(EnvoyQuicServerStreamTest, GetRequestAndResponse) { request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); - EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/true)) + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/!quic::VersionUsesHttp3( + quic_version_.transport_version))) .WillOnce(Invoke([this](const Http::RequestHeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->getHostValue()); EXPECT_EQ("/", headers->getPathValue()); EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->getMethodValue()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), - request_headers); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(""), /*end_stream=*/true)); + spdy::SpdyHeaderBlock spdy_headers; + spdy_headers[":authority"] = host_; + spdy_headers[":method"] = "GET"; + spdy_headers[":path"] = "/"; + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_headers); + quic::QuicStreamFrame frame(stream_id_, true, 0, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), + request_headers); + } EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); } TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { EXPECT_EQ(absl::nullopt, quic_stream_->http1StreamEncoderOptions()); - sendRequest(request_body_, true, request_body_.size() * 2); + receiveRequest(request_body_, true, request_body_.size() * 2); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); quic_stream_->encodeTrailers(response_trailers_); } TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { - sendRequest(request_body_, false, request_body_.size() * 2); + size_t offset = receiveRequest(request_body_, false, request_body_.size() * 2); + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::RequestTrailerMapPtr& headers) { Http::LowerCaseString key1("key1"); @@ -205,7 +227,14 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ("value1", headers->get(key1)[0]->value().getStringView()); EXPECT_TRUE(headers->get(key2).empty()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_trailers_); + quic::QuicStreamFrame frame(stream_id_, true, offset, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), + trailers_); + } EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } @@ -227,8 +256,7 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { // Trailer should be delivered to HCM later after body arrives. quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); - std::string data = bodyToStreamPayload(request_body_); - quic::QuicStreamFrame frame(stream_id_, false, 0, data); + quic::QuicStreamFrame frame(stream_id_, false, 0, request_body_); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { EXPECT_EQ(request_body_, buffer.toString()); @@ -245,10 +273,29 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { quic_stream_->OnStreamFrame(frame); } +TEST_P(EnvoyQuicServerStreamTest, ResetStreamByHCM) { + receiveRequest(request_body_, false, request_body_.size() * 2); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); + quic_stream_->resetStream(Http::StreamResetReason::LocalReset); + EXPECT_TRUE(quic_stream_->rst_sent()); +} + +TEST_P(EnvoyQuicServerStreamTest, EarlyResponseWithReset) { + receiveRequest(request_body_, false, request_body_.size() * 2); + // Write response headers with FIN before finish receiving request. + quic_stream_->encodeHeaders(response_headers_, true); + // Resetting the stream now means stop reading and sending QUIC_STREAM_NO_ERROR. + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); + quic_stream_->resetStream(Http::StreamResetReason::LocalReset); + EXPECT_TRUE(quic_stream_->rst_sent()); + EXPECT_TRUE(quic_stream_->reading_stopped()); + EXPECT_EQ(quic::QUIC_STREAM_NO_ERROR, quic_stream_->stream_error()); +} + TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { std::string large_request(1024, 'a'); // Sending such large request will cause read to be disabled. - size_t payload_offset = sendRequest(large_request, false, 512); + size_t payload_offset = receiveRequest(large_request, false, 512); EXPECT_FALSE(quic_stream_->HasBytesToRead()); // Disable reading one more time. quic_stream_->readDisable(true); @@ -324,7 +371,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { // Tests that the stream with a send buffer whose high limit is 16k and low // limit is 8k sends over 32kB response. TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { - sendRequest(request_body_, true, request_body_.size() * 2); + receiveRequest(request_body_, true, request_body_.size() * 2); // Bump connection flow control window large enough not to cause connection // level flow control blocked. @@ -386,7 +433,7 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { return; } - sendRequest(request_body_, true, request_body_.size() * 2); + receiveRequest(request_body_, true, request_body_.size() * 2); // Bump connection flow control window large enough not to cause connection level flow control // blocked @@ -397,11 +444,11 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { // Make the stream blocked by congestion control. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); // Encode 16kB -10 bytes request body. Because the high watermark is 16KB, with previously @@ -415,11 +462,11 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { // Unblock writing now, and this will write out 16kB data and cause stream to // be blocked by the flow control limit. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()); quic_session_.OnCanWrite(); EXPECT_TRUE(quic_stream_->IsFlowControlBlocked()); @@ -429,20 +476,20 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { 32 * 1024); quic_stream_->OnWindowUpdateFrame(window_update1); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); quic_session_.OnCanWrite(); // No data should be buffered at this point. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillRepeatedly(Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); + .WillRepeatedly( + Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); // Send more data. If watermark bytes counting were not cleared in previous // OnCanWrite, this write would have caused the stream to exceed its high watermark. std::string response1(16 * 1024 - 3, 'a'); diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD index dbef14c2195f..998625c54e72 100644 --- a/test/extensions/quic_listeners/quiche/integration/BUILD +++ b/test/extensions/quic_listeners/quiche/integration/BUILD @@ -13,7 +13,11 @@ envoy_cc_test( size = "medium", srcs = ["quic_http_integration_test.cc"], data = ["//test/config/integration/certs"], - # TODO(envoyproxy/windows-dev): Diagnose msvc-cl opt build gcp rbe CI flake (passes locally) + # TODO(envoyproxy/windows-dev): Diagnose why opt build test under Windows GCP RBE + # takes 10x as long as on linux (>300s vs ~30s). Shards = 2 solves for windows, see: + # https://github.com/envoyproxy/envoy/pull/13713/files#r512160087 + # Each of these tests exceeds 20s; + # QuicHttpIntegrationTests/QuicHttpIntegrationTest.MultipleQuicConnections[With|No]BPF* tags = [ "flaky_on_windows", "nofips", diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 11cd3959edad..4c6309620f3d 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -249,6 +249,8 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers // create connections with the first 4 bytes of connection id different from each // other so they should be evenly distributed. designated_connection_ids_.push_back(quic::test::TestConnectionId(i << 32)); + // TODO(sunjayBhatia,wrowe): deserialize this, establishing all connections in parallel + // (Expected to save ~14s each across 6 tests on Windows) codec_clients.push_back(makeHttpConnection(lookupPort("http"))); } constexpr auto timeout_first = std::chrono::seconds(15); @@ -391,9 +393,13 @@ TEST_P(QuicHttpIntegrationTest, TestDelayedConnectionTeardownTimeoutTrigger) { 1); } -TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsWithBPF) { testMultipleQuicConnections(); } +// Ensure multiple quic connections work, regardless of platform BPF support +TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsDefaultMode) { + testMultipleQuicConnections(); +} TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsNoBPF) { + // Note: This runtime override is a no-op on platforms without BPF config_helper_.addRuntimeOverride( "envoy.reloadable_features.prefer_quic_kernel_bpf_packet_routing", "false"); @@ -525,5 +531,11 @@ TEST_P(QuicHttpIntegrationTest, CertVerificationFailure) { EXPECT_EQ(failure_reason, codec_client_->connection()->transportFailureReason()); } +TEST_P(QuicHttpIntegrationTest, RequestResponseWithTrailers) { + config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); + testTrailers(/*request_size=*/10, /*response_size=*/10, /*request_trailers_present=*/true, + /*response_trailers_present=*/true); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/platform/BUILD b/test/extensions/quic_listeners/quiche/platform/BUILD index 420e812b85a7..7dbb08d821cc 100644 --- a/test/extensions/quic_listeners/quiche/platform/BUILD +++ b/test/extensions/quic_listeners/quiche/platform/BUILD @@ -9,16 +9,6 @@ licenses(["notice"]) # Apache 2 envoy_package() -envoy_cc_test( - name = "quiche_platform_test", - srcs = ["quiche_platform_test.cc"], - external_deps = ["quiche_common_platform"], - deps = [ - "@com_googlesource_quiche//:quiche_common_platform", - "@com_googlesource_quiche//:quiche_common_platform_endian", - ], -) - envoy_cc_test( name = "http2_platform_test", srcs = ["http2_platform_test.cc"], @@ -63,7 +53,6 @@ envoy_cc_test( "@com_googlesource_quiche//:quic_platform_mem_slice_span", "@com_googlesource_quiche//:quic_platform_mem_slice_storage", "@com_googlesource_quiche//:quic_platform_mock_log", - "@com_googlesource_quiche//:quic_platform_port_utils", "@com_googlesource_quiche//:quic_platform_sleep", "@com_googlesource_quiche//:quic_platform_system_event_loop", "@com_googlesource_quiche//:quic_platform_test", @@ -150,17 +139,6 @@ envoy_cc_test_library( deps = ["@com_googlesource_quiche//:quic_platform_base"], ) -envoy_cc_test_library( - name = "quic_platform_port_utils_impl_lib", - srcs = ["quic_port_utils_impl.cc"], - hdrs = ["quic_port_utils_impl.h"], - tags = ["nofips"], - deps = [ - "//source/common/network:utility_lib", - "//test/test_common:environment_lib", - ], -) - envoy_cc_test_library( name = "quic_platform_test_mem_slice_vector_impl_lib", hdrs = ["quic_test_mem_slice_vector_impl.h"], diff --git a/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc index 069a79eab0ef..35aee5d27373 100644 --- a/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc +++ b/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc @@ -72,20 +72,14 @@ TEST(Http2PlatformTest, Http2Log) { HTTP2_DLOG_EVERY_N(ERROR, 2) << "DLOG_EVERY_N(ERROR, 2)"; } -TEST(Http2PlatformTest, Http2StringPiece) { - std::string s = "bar"; - quiche::QuicheStringPiece sp(s); - EXPECT_EQ('b', sp[0]); -} - TEST(Http2PlatformTest, Http2Macro) { EXPECT_DEBUG_DEATH(HTTP2_UNREACHABLE(), ""); EXPECT_DEATH(HTTP2_DIE_IF_NULL(nullptr), ""); } TEST(Http2PlatformTest, Http2Flags) { - auto& flag_registry = quiche::FlagRegistry::GetInstance(); - flag_registry.ResetFlags(); + auto& flag_registry = quiche::FlagRegistry::getInstance(); + flag_registry.resetFlags(); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); SetHttp2ReloadableFlag(http2_testonly_default_false, true); EXPECT_TRUE(GetHttp2ReloadableFlag(http2_testonly_default_false)); @@ -93,22 +87,22 @@ TEST(Http2PlatformTest, Http2Flags) { for (std::string s : {"1", "t", "true", "TRUE", "y", "yes", "Yes"}) { SetHttp2ReloadableFlag(http2_testonly_default_false, false); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); - EXPECT_TRUE(flag_registry.FindFlag("http2_reloadable_flag_http2_testonly_default_false") - ->SetValueFromString(s)); + EXPECT_TRUE(flag_registry.findFlag("FLAGS_quic_reloadable_flag_http2_testonly_default_false") + ->setValueFromString(s)); EXPECT_TRUE(GetHttp2ReloadableFlag(http2_testonly_default_false)); } for (std::string s : {"0", "f", "false", "FALSE", "n", "no", "No"}) { SetHttp2ReloadableFlag(http2_testonly_default_false, true); EXPECT_TRUE(GetHttp2ReloadableFlag(http2_testonly_default_false)); - EXPECT_TRUE(flag_registry.FindFlag("http2_reloadable_flag_http2_testonly_default_false") - ->SetValueFromString(s)); + EXPECT_TRUE(flag_registry.findFlag("FLAGS_quic_reloadable_flag_http2_testonly_default_false") + ->setValueFromString(s)); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); } for (std::string s : {"some", "invalid", "values", ""}) { SetHttp2ReloadableFlag(http2_testonly_default_false, false); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); - EXPECT_FALSE(flag_registry.FindFlag("http2_reloadable_flag_http2_testonly_default_false") - ->SetValueFromString(s)); + EXPECT_FALSE(flag_registry.findFlag("FLAGS_quic_reloadable_flag_http2_testonly_default_false") + ->setValueFromString(s)); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); } } diff --git a/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc index 68141aa94039..902ad1a9ea0f 100644 --- a/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc +++ b/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc @@ -30,7 +30,6 @@ #include "gtest/gtest.h" #include "quiche/common/platform/api/quiche_string_piece.h" #include "quiche/epoll_server/fake_simple_epoll_server.h" -#include "quiche/quic/platform/api/quic_aligned.h" #include "quiche/quic/platform/api/quic_bug_tracker.h" #include "quiche/quic/platform/api/quic_cert_utils.h" #include "quiche/quic/platform/api/quic_client_stats.h" @@ -42,7 +41,6 @@ #include "quiche/quic/platform/api/quic_flags.h" #include "quiche/quic/platform/api/quic_hostname_utils.h" #include "quiche/quic/platform/api/quic_logging.h" -#include "quiche/quic/platform/api/quic_macros.h" #include "quiche/quic/platform/api/quic_map_util.h" #include "quiche/quic/platform/api/quic_mem_slice.h" #include "quiche/quic/platform/api/quic_mem_slice_span.h" @@ -50,7 +48,6 @@ #include "quiche/quic/platform/api/quic_mock_log.h" #include "quiche/quic/platform/api/quic_mutex.h" #include "quiche/quic/platform/api/quic_pcc_sender.h" -#include "quiche/quic/platform/api/quic_port_utils.h" #include "quiche/quic/platform/api/quic_ptr_util.h" #include "quiche/quic/platform/api/quic_server_stats.h" #include "quiche/quic/platform/api/quic_sleep.h" @@ -92,8 +89,6 @@ class QuicPlatformTest : public testing::Test { const int verbosity_log_threshold_; }; -TEST_F(QuicPlatformTest, QuicAlignOf) { EXPECT_LT(0, QUIC_ALIGN_OF(int)); } - enum class TestEnum { ZERO = 0, ONE, TWO, COUNT }; TEST_F(QuicPlatformTest, QuicBugTracker) { @@ -468,9 +463,9 @@ TEST_F(QuicPlatformTest, QuicCertUtils) { unsigned char* der = nullptr; int len = i2d_X509(x509_cert.get(), &der); ASSERT_GT(len, 0); - quiche::QuicheStringPiece out; + absl::string_view out; QuicCertUtils::ExtractSubjectNameFromDERCert( - quiche::QuicheStringPiece(reinterpret_cast(der), len), &out); + absl::string_view(reinterpret_cast(der), len), &out); EXPECT_EQ("0z1\v0\t\x6\x3U\x4\x6\x13\x2US1\x13" "0\x11\x6\x3U\x4\b\f\nCalifornia1\x16" "0\x14\x6\x3U\x4\a\f\rSan Francisco1\r" @@ -566,8 +561,8 @@ TEST_F(QuicPlatformTest, MonotonicityWithFakeEpollClock) { } TEST_F(QuicPlatformTest, QuicFlags) { - auto& flag_registry = quiche::FlagRegistry::GetInstance(); - flag_registry.ResetFlags(); + auto& flag_registry = quiche::FlagRegistry::getInstance(); + flag_registry.resetFlags(); EXPECT_FALSE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_TRUE(GetQuicReloadableFlag(quic_testonly_default_true)); @@ -583,14 +578,15 @@ TEST_F(QuicPlatformTest, QuicFlags) { SetQuicFlag(FLAGS_quic_time_wait_list_seconds, 100); EXPECT_EQ(100, GetQuicFlag(FLAGS_quic_time_wait_list_seconds)); - flag_registry.ResetFlags(); + flag_registry.resetFlags(); EXPECT_FALSE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_TRUE(GetQuicRestartFlag(quic_testonly_default_true)); EXPECT_EQ(200, GetQuicFlag(FLAGS_quic_time_wait_list_seconds)); - flag_registry.FindFlag("quic_reloadable_flag_quic_testonly_default_false") - ->SetValueFromString("true"); - flag_registry.FindFlag("quic_restart_flag_quic_testonly_default_true")->SetValueFromString("0"); - flag_registry.FindFlag("quic_time_wait_list_seconds")->SetValueFromString("100"); + flag_registry.findFlag("FLAGS_quic_reloadable_flag_quic_testonly_default_false") + ->setValueFromString("true"); + flag_registry.findFlag("FLAGS_quic_restart_flag_quic_testonly_default_true") + ->setValueFromString("0"); + flag_registry.findFlag("FLAGS_quic_time_wait_list_seconds")->setValueFromString("100"); EXPECT_TRUE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_FALSE(GetQuicRestartFlag(quic_testonly_default_true)); EXPECT_EQ(100, GetQuicFlag(FLAGS_quic_time_wait_list_seconds)); @@ -661,35 +657,6 @@ TEST_F(FileUtilsTest, ReadFileContents) { EXPECT_EQ(data, output); } -TEST_F(QuicPlatformTest, PickUnsedPort) { - int port = QuicPickServerPortForTestsOrDie(); - std::vector supported_versions = - Envoy::TestEnvironment::getIpVersionsForTest(); - for (auto ip_version : supported_versions) { - Envoy::Network::Address::InstanceConstSharedPtr addr = - Envoy::Network::Test::getCanonicalLoopbackAddress(ip_version); - Envoy::Network::Address::InstanceConstSharedPtr addr_with_port = - Envoy::Network::Utility::getAddressWithPort(*addr, port); - Envoy::Network::SocketImpl sock(Envoy::Network::Socket::Type::Datagram, addr_with_port); - // binding of given port should success. - EXPECT_EQ(0, sock.bind(addr_with_port).rc_); - } -} - -TEST_F(QuicPlatformTest, FailToPickUnsedPort) { - Envoy::Api::MockOsSysCalls os_sys_calls; - Envoy::TestThreadsafeSingletonInjector os_calls(&os_sys_calls); - // Actually create sockets. - EXPECT_CALL(os_sys_calls, socket(_, _, _)).WillRepeatedly([](int domain, int type, int protocol) { - os_fd_t fd = ::socket(domain, type, protocol); - return Envoy::Api::SysCallSocketResult{fd, errno}; - }); - // Fail bind call's to mimic port exhaustion. - EXPECT_CALL(os_sys_calls, bind(_, _, _)) - .WillRepeatedly(Return(Envoy::Api::SysCallIntResult{-1, SOCKET_ERROR_ADDR_IN_USE})); - EXPECT_DEATH(QuicPickServerPortForTestsOrDie(), "Failed to pick a port for test."); -} - TEST_F(QuicPlatformTest, TestEnvoyQuicBufferAllocator) { QuicStreamBufferAllocator allocator; Envoy::Stats::TestUtil::MemoryTest memory_test; @@ -711,14 +678,6 @@ TEST_F(QuicPlatformTest, TestSystemEventLoop) { QuicSystemEventLoop("dummy"); } -QUIC_MUST_USE_RESULT bool dummyTestFunction() { return false; } - -TEST_F(QuicPlatformTest, TestQuicMacros) { - // Just make sure it compiles. - EXPECT_FALSE(dummyTestFunction()); - int a QUIC_UNUSED; -} - TEST(EnvoyQuicMemSliceTest, ConstructMemSliceFromBuffer) { std::string str(512, 'b'); // Fragment needs to out-live buffer. diff --git a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc index 556f6cd3e18a..9eaf8532aa49 100644 --- a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc +++ b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc @@ -19,7 +19,7 @@ namespace quic { namespace { -void QuicRecordTestOutputToFile(const std::string& filename, quiche::QuicheStringPiece data) { +void quicRecordTestOutputToFile(const std::string& filename, absl::string_view data) { const char* output_dir_env = std::getenv("QUIC_TEST_OUTPUT_DIR"); if (output_dir_env == nullptr) { QUIC_LOG(WARNING) << "Could not save test output since QUIC_TEST_OUTPUT_DIR is not set"; @@ -64,11 +64,13 @@ void QuicRecordTestOutputToFile(const std::string& filename, quiche::QuicheStrin } } // namespace -void QuicSaveTestOutputImpl(quiche::QuicheStringPiece filename, quiche::QuicheStringPiece data) { - QuicRecordTestOutputToFile(filename.data(), data); +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicSaveTestOutputImpl(absl::string_view filename, absl::string_view data) { + quicRecordTestOutputToFile(filename.data(), data); } -bool QuicLoadTestOutputImpl(quiche::QuicheStringPiece filename, std::string* data) { +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicLoadTestOutputImpl(absl::string_view filename, std::string* data) { const char* read_dir_env = std::getenv("QUIC_TEST_OUTPUT_DIR"); if (read_dir_env == nullptr) { QUIC_LOG(WARNING) << "Could not load test output since QUIC_TEST_OUTPUT_DIR is not set"; @@ -96,7 +98,8 @@ bool QuicLoadTestOutputImpl(quiche::QuicheStringPiece filename, std::string* dat return true; } -void QuicRecordTraceImpl(quiche::QuicheStringPiece identifier, quiche::QuicheStringPiece data) { +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicRecordTraceImpl(absl::string_view identifier, absl::string_view data) { const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); std::string timestamp = absl::FormatTime("%Y%m%d%H%M%S", absl::Now(), absl::LocalTimeZone()); @@ -104,7 +107,7 @@ void QuicRecordTraceImpl(quiche::QuicheStringPiece identifier, quiche::QuicheStr std::string filename = fmt::sprintf("%s.%s.%s.%s.qtr", test_info->name(), test_info->test_case_name(), identifier.data(), timestamp); - QuicRecordTestOutputToFile(filename, data); + quicRecordTestOutputToFile(filename, data); } } // namespace quic diff --git a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h index a1c6c7305d48..fcf0c47b3a75 100644 --- a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h +++ b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h @@ -6,14 +6,16 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" namespace quic { +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicSaveTestOutputImpl(absl::string_view filename, absl::string_view data); -void QuicSaveTestOutputImpl(quiche::QuicheStringPiece filename, quiche::QuicheStringPiece data); +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicLoadTestOutputImpl(absl::string_view filename, std::string* data); -bool QuicLoadTestOutputImpl(quiche::QuicheStringPiece filename, std::string* data); - -void QuicRecordTraceImpl(quiche::QuicheStringPiece identifier, quiche::QuicheStringPiece data); +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicRecordTraceImpl(absl::string_view identifier, absl::string_view data); } // namespace quic diff --git a/test/extensions/quic_listeners/quiche/platform/quiche_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/quiche_platform_test.cc deleted file mode 100644 index a733894b5505..000000000000 --- a/test/extensions/quic_listeners/quiche/platform/quiche_platform_test.cc +++ /dev/null @@ -1,39 +0,0 @@ -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#include "gtest/gtest.h" -#include "quiche/common/platform/api/quiche_arraysize.h" -#include "quiche/common/platform/api/quiche_endian.h" -#include "quiche/common/platform/api/quiche_optional.h" -#include "quiche/common/platform/api/quiche_ptr_util.h" -#include "quiche/common/platform/api/quiche_string_piece.h" - -namespace quiche { - -TEST(QuichePlatformTest, Arraysize) { - int array[] = {0, 1, 2, 3, 4}; - EXPECT_EQ(5, QUICHE_ARRAYSIZE(array)); -} - -TEST(QuichePlatformTest, StringPiece) { - std::string s = "bar"; - QuicheStringPiece sp(s); - EXPECT_EQ('b', sp[0]); -} - -TEST(QuichePlatformTest, WrapUnique) { - auto p = QuicheWrapUnique(new int(6)); - EXPECT_EQ(6, *p); -} - -TEST(QuichePlatformTest, TestQuicheOptional) { - QuicheOptional maybe_a; - EXPECT_FALSE(maybe_a.has_value()); - maybe_a = 1; - EXPECT_EQ(1, *maybe_a); -} - -} // namespace quiche diff --git a/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc index 56453e232c1a..eeae58c0ab26 100644 --- a/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc +++ b/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc @@ -8,7 +8,6 @@ #include "gtest/gtest.h" #include "quiche/spdy/platform/api/spdy_bug_tracker.h" #include "quiche/spdy/platform/api/spdy_containers.h" -#include "quiche/spdy/platform/api/spdy_endianness_util.h" #include "quiche/spdy/platform/api/spdy_estimate_memory_usage.h" #include "quiche/spdy/platform/api/spdy_flags.h" #include "quiche/spdy/platform/api/spdy_logging.h" @@ -47,11 +46,6 @@ TEST(SpdyPlatformTest, SpdyHashSet) { EXPECT_EQ(0, hset.count("qux")); } -TEST(SpdyPlatformTest, SpdyEndianness) { - EXPECT_EQ(0x1234, spdy::SpdyNetToHost16(spdy::SpdyHostToNet16(0x1234))); - EXPECT_EQ(0x12345678, spdy::SpdyNetToHost32(spdy::SpdyHostToNet32(0x12345678))); -} - TEST(SpdyPlatformTest, SpdyEstimateMemoryUsage) { std::string s = "foo"; // Stubbed out to always return 0. @@ -92,19 +86,19 @@ TEST(SpdyPlatformTest, SpdyTestHelpers) { } TEST(SpdyPlatformTest, SpdyFlags) { - auto& flag_registry = quiche::FlagRegistry::GetInstance(); - flag_registry.ResetFlags(); + auto& flag_registry = quiche::FlagRegistry::getInstance(); + flag_registry.resetFlags(); EXPECT_FALSE(GetSpdyReloadableFlag(spdy_testonly_default_false)); EXPECT_FALSE(GetSpdyRestartFlag(spdy_testonly_default_false)); - flag_registry.FindFlag("spdy_reloadable_flag_spdy_testonly_default_false") - ->SetValueFromString("true"); + flag_registry.findFlag("FLAGS_quic_reloadable_flag_spdy_testonly_default_false") + ->setValueFromString("true"); EXPECT_TRUE(GetSpdyReloadableFlag(spdy_testonly_default_false)); EXPECT_FALSE(GetSpdyRestartFlag(spdy_testonly_default_false)); - flag_registry.ResetFlags(); - flag_registry.FindFlag("spdy_restart_flag_spdy_testonly_default_false") - ->SetValueFromString("yes"); + flag_registry.resetFlags(); + flag_registry.findFlag("FLAGS_quic_restart_flag_spdy_testonly_default_false") + ->setValueFromString("yes"); EXPECT_FALSE(GetSpdyReloadableFlag(spdy_testonly_default_false)); EXPECT_TRUE(GetSpdyRestartFlag(spdy_testonly_default_false)); } diff --git a/test/extensions/quic_listeners/quiche/test_proof_source.h b/test/extensions/quic_listeners/quiche/test_proof_source.h index a249b43144fd..bbedfd6c7b00 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_source.h +++ b/test/extensions/quic_listeners/quiche/test_proof_source.h @@ -36,7 +36,7 @@ class TestProofSource : public EnvoyQuicProofSourceBase { void signPayload(const quic::QuicSocketAddress& /*server_address*/, const quic::QuicSocketAddress& /*client_address*/, const std::string& /*hostname*/, uint16_t /*signature_algorithm*/, - quiche::QuicheStringPiece in, + absl::string_view in, std::unique_ptr callback) override { callback->Run(true, absl::StrCat("Fake signature for { ", in, " }"), std::make_unique(filter_chain_)); diff --git a/test/extensions/quic_listeners/quiche/test_utils.h b/test/extensions/quic_listeners/quiche/test_utils.h index 102f7608e50b..f59720130c70 100644 --- a/test/extensions/quic_listeners/quiche/test_utils.h +++ b/test/extensions/quic_listeners/quiche/test_utils.h @@ -13,6 +13,8 @@ #include "quiche/quic/core/quic_utils.h" #include "quiche/quic/test_tools/crypto_test_utils.h" #include "quiche/quic/test_tools/quic_config_peer.h" +#include "quiche/quic/test_tools/qpack/qpack_test_utils.h" +#include "quiche/quic/test_tools/qpack/qpack_encoder_test_utils.h" #if defined(__GNUC__) #pragma GCC diagnostic pop @@ -46,7 +48,7 @@ class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterMana MOCK_METHOD(quic::QuicConsumedData, WritevData, (quic::QuicStreamId id, size_t write_length, quic::QuicStreamOffset offset, quic::StreamSendingState state, quic::TransmissionType type, - quiche::QuicheOptional level)); + absl::optional level)); MOCK_METHOD(bool, ShouldYield, (quic::QuicStreamId id)); absl::string_view requestedServerName() const override { @@ -90,7 +92,7 @@ class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, MOCK_METHOD(quic::QuicConsumedData, WritevData, (quic::QuicStreamId id, size_t write_length, quic::QuicStreamOffset offset, quic::StreamSendingState state, quic::TransmissionType type, - quiche::QuicheOptional level)); + absl::optional level)); MOCK_METHOD(bool, ShouldYield, (quic::QuicStreamId id)); absl::string_view requestedServerName() const override { @@ -167,6 +169,29 @@ enum class QuicVersionType { Iquic, }; +std::string spdyHeaderToHttp3StreamPayload(const spdy::SpdyHeaderBlock& header) { + quic::test::NoopQpackStreamSenderDelegate encoder_stream_sender_delegate; + quic::test::NoopDecoderStreamErrorDelegate decoder_stream_error_delegate; + auto qpack_encoder = std::make_unique(&decoder_stream_error_delegate); + qpack_encoder->set_qpack_stream_sender_delegate(&encoder_stream_sender_delegate); + // QpackEncoder does not use the dynamic table by default, + // therefore the value of |stream_id| does not matter. + std::string payload = qpack_encoder->EncodeHeaderList(/* stream_id = */ 0, header, nullptr); + std::unique_ptr headers_buffer; + quic::QuicByteCount headers_frame_header_length = + quic::HttpEncoder::SerializeHeadersFrameHeader(payload.length(), &headers_buffer); + absl::string_view headers_frame_header(headers_buffer.get(), headers_frame_header_length); + return absl::StrCat(headers_frame_header, payload); +} + +std::string bodyToHttp3StreamPayload(const std::string& body) { + std::unique_ptr data_buffer; + quic::QuicByteCount data_frame_header_length = + quic::HttpEncoder::SerializeDataFrameHeader(body.length(), &data_buffer); + absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); + return absl::StrCat(data_frame_header, body); +} + // A test suite with variation of ip version and a knob to turn on/off IETF QUIC implementation. class QuicMultiVersionTest : public testing::TestWithParam> {}; diff --git a/test/extensions/stats_sinks/hystrix/hystrix_test.cc b/test/extensions/stats_sinks/hystrix/hystrix_test.cc index 29e7c79d02da..38f88019602c 100644 --- a/test/extensions/stats_sinks/hystrix/hystrix_test.cc +++ b/test/extensions/stats_sinks/hystrix/hystrix_test.cc @@ -128,9 +128,9 @@ class HystrixSinkTest : public testing::Test { void createClusterAndCallbacks() { // Set cluster. - cluster_map_.emplace(cluster1_name_, cluster1_.cluster_); + cluster_maps_.active_clusters_.emplace(cluster1_name_, cluster1_.cluster_); ON_CALL(server_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); - ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_map_)); + ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_maps_)); ON_CALL(callbacks_, encodeData(_, _)).WillByDefault(Invoke([&](Buffer::Instance& data, bool) { // Set callbacks to send data to buffer. This will append to the end of the buffer, so @@ -141,15 +141,15 @@ class HystrixSinkTest : public testing::Test { void addClusterToMap(const std::string& cluster_name, NiceMock& cluster) { - cluster_map_.emplace(cluster_name, cluster); - // Redefining since cluster_map_ is returned by value. - ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_map_)); + cluster_maps_.active_clusters_.emplace(cluster_name, cluster); + // Redefining since cluster_maps_ is returned by value. + ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_maps_)); } void removeClusterFromMap(const std::string& cluster_name) { - cluster_map_.erase(cluster_name); - // Redefining since cluster_map_ is returned by value. - ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_map_)); + cluster_maps_.active_clusters_.erase(cluster_name); + // Redefining since cluster_maps_ is returned by value. + ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_maps_)); } void addSecondClusterHelper(Buffer::OwnedImpl& buffer) { @@ -245,7 +245,7 @@ class HystrixSinkTest : public testing::Test { NiceMock callbacks_; NiceMock server_; - Upstream::ClusterManager::ClusterInfoMap cluster_map_; + Upstream::ClusterManager::ClusterInfoMaps cluster_maps_; Buffer::OwnedImpl cluster_stats_buffer_; std::unique_ptr sink_; diff --git a/test/extensions/stats_sinks/metrics_service/BUILD b/test/extensions/stats_sinks/metrics_service/BUILD index 71a859379b27..cbc6820bf66e 100644 --- a/test/extensions/stats_sinks/metrics_service/BUILD +++ b/test/extensions/stats_sinks/metrics_service/BUILD @@ -45,6 +45,7 @@ envoy_extension_cc_test( name = "metrics_service_integration_test", srcs = ["metrics_service_integration_test.cc"], extension_name = "envoy.stat_sinks.metrics_service", + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ "//source/common/buffer:zero_copy_input_stream_lib", 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 ce940b650136..927b710ac756 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 @@ -96,12 +96,11 @@ class MetricsServiceSinkTest : public testing::Test { MetricsServiceSinkTest() = default; NiceMock snapshot_; - Event::SimulatedTimeSystem time_system_; std::shared_ptr streamer_{new MockGrpcMetricsStreamer()}; }; TEST_F(MetricsServiceSinkTest, CheckSendCall) { - MetricsServiceSink sink(streamer_, time_system_, false); + MetricsServiceSink sink(streamer_, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -125,7 +124,7 @@ TEST_F(MetricsServiceSinkTest, CheckSendCall) { } TEST_F(MetricsServiceSinkTest, CheckStatsCount) { - MetricsServiceSink sink(streamer_, time_system_, false); + MetricsServiceSink sink(streamer_, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -156,7 +155,7 @@ TEST_F(MetricsServiceSinkTest, CheckStatsCount) { // Test that verifies counters are correctly reported as current value when configured to do so. TEST_F(MetricsServiceSinkTest, ReportCountersValues) { - MetricsServiceSink sink(streamer_, time_system_, false); + MetricsServiceSink sink(streamer_, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -174,7 +173,7 @@ TEST_F(MetricsServiceSinkTest, ReportCountersValues) { // Test that verifies counters are reported as the delta between flushes when configured to do so. TEST_F(MetricsServiceSinkTest, ReportCountersAsDeltas) { - MetricsServiceSink sink(streamer_, time_system_, true); + MetricsServiceSink sink(streamer_, true); auto counter = std::make_shared>(); counter->name_ = "test_counter"; diff --git a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc index dcdf47945e94..ff46886b4cd7 100644 --- a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc +++ b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc @@ -87,6 +87,7 @@ class MetricsServiceIntegrationTest : public Grpc::VersionedGrpcClientIntegratio const Protobuf::RepeatedPtrField<::io::prometheus::client::MetricFamily>& envoy_metrics = request_msg.envoy_metrics(); + int64_t previous_time_stamp = 0; for (const ::io::prometheus::client::MetricFamily& metrics_family : envoy_metrics) { if (metrics_family.name() == "cluster.cluster_0.membership_change" && metrics_family.type() == ::io::prometheus::client::MetricType::COUNTER) { @@ -112,6 +113,11 @@ class MetricsServiceIntegrationTest : public Grpc::VersionedGrpcClientIntegratio Stats::HistogramSettingsImpl::defaultBuckets().size()); } ASSERT(metrics_family.metric(0).has_timestamp_ms()); + // Validate that all metrics have the same timestamp. + if (previous_time_stamp > 0) { + EXPECT_EQ(previous_time_stamp, metrics_family.metric(0).timestamp_ms()); + } + previous_time_stamp = metrics_family.metric(0).timestamp_ms(); if (known_counter_exists && known_gauge_exists && known_histogram_exists) { break; } diff --git a/test/extensions/stats_sinks/wasm/BUILD b/test/extensions/stats_sinks/wasm/BUILD index 6135c8cfcf0a..b0911d7aaa9f 100644 --- a/test/extensions/stats_sinks/wasm/BUILD +++ b/test/extensions/stats_sinks/wasm/BUILD @@ -24,6 +24,7 @@ envoy_extension_cc_test( extension_name = "envoy.stat_sinks.wasm", deps = [ "//source/extensions/stat_sinks/wasm:config", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/stats_sinks/wasm/test_data:test_context_cpp_plugin", "//test/mocks/server:server_mocks", "@envoy_api//envoy/extensions/stat_sinks/wasm/v3:pkg_cc_proto", @@ -41,6 +42,7 @@ envoy_extension_cc_test( deps = [ "//source/common/stats:stats_lib", "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/stats_sinks/wasm/test_data:test_context_cpp_plugin", "//test/mocks/stats:stats_mocks", "//test/test_common:wasm_lib", diff --git a/test/extensions/stats_sinks/wasm/config_test.cc b/test/extensions/stats_sinks/wasm/config_test.cc index 1e115dd2f946..012f4ecc2c98 100644 --- a/test/extensions/stats_sinks/wasm/config_test.cc +++ b/test/extensions/stats_sinks/wasm/config_test.cc @@ -8,6 +8,7 @@ #include "extensions/stat_sinks/wasm/wasm_stat_sink_impl.h" #include "extensions/stat_sinks/well_known_names.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" @@ -65,17 +66,8 @@ class WasmStatSinkConfigTest : public testing::TestWithParam { Stats::SinkPtr sink_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmStatSinkConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmStatSinkConfigTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmStatSinkConfigTest, CreateWasmFromEmpty) { envoy::extensions::stat_sinks::wasm::v3::Wasm config; diff --git a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc index acd4df85dbde..db9f4108aedd 100644 --- a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc +++ b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc @@ -2,6 +2,7 @@ #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/wasm_base.h" @@ -54,17 +55,8 @@ class WasmCommonContextTest std::unique_ptr context_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmCommonContextTest, OnStat) { std::string code; diff --git a/test/extensions/tracers/datadog/config_test.cc b/test/extensions/tracers/datadog/config_test.cc index 52a44719367c..8c84b6c3f475 100644 --- a/test/extensions/tracers/datadog/config_test.cc +++ b/test/extensions/tracers/datadog/config_test.cc @@ -33,7 +33,7 @@ TEST(DatadogTracerConfigTest, DatadogHttpTracer) { http: name: datadog typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.DatadogConfig + "@type": type.googleapis.com/envoy.config.trace.v3.DatadogConfig collector_cluster: fake_cluster service_name: fake_file )EOF"; diff --git a/test/extensions/tracers/dynamic_ot/config_test.cc b/test/extensions/tracers/dynamic_ot/config_test.cc index a655b23e5f6c..e16cd0a05687 100644 --- a/test/extensions/tracers/dynamic_ot/config_test.cc +++ b/test/extensions/tracers/dynamic_ot/config_test.cc @@ -35,7 +35,8 @@ TEST(DynamicOtTracerConfigTest, DynamicOpentracingHttpTracer) { R"EOF( http: name: envoy.tracers.dynamic_ot - config: + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.DynamicOtConfig library: %s config: output_file: fake_file diff --git a/test/extensions/tracers/lightstep/config_test.cc b/test/extensions/tracers/lightstep/config_test.cc index e56ff7b0c507..711e8455c7da 100644 --- a/test/extensions/tracers/lightstep/config_test.cc +++ b/test/extensions/tracers/lightstep/config_test.cc @@ -33,7 +33,7 @@ TEST(LightstepTracerConfigTest, LightstepHttpTracer) { http: name: lightstep typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.LightstepConfig + "@type": type.googleapis.com/envoy.config.trace.v3.LightstepConfig collector_cluster: fake_cluster access_token_file: fake_file )EOF"; diff --git a/test/extensions/tracers/opencensus/config_test.cc b/test/extensions/tracers/opencensus/config_test.cc index 227ed0d353c8..9e2698fee03d 100644 --- a/test/extensions/tracers/opencensus/config_test.cc +++ b/test/extensions/tracers/opencensus/config_test.cc @@ -26,7 +26,7 @@ TEST(OpenCensusTracerConfigTest, InvalidStackdriverConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig stackdriver_exporter_enabled: true stackdriver_grpc_service: envoy_grpc: @@ -49,7 +49,7 @@ TEST(OpenCensusTracerConfigTest, InvalidOcagentConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig ocagent_exporter_enabled: true ocagent_grpc_service: envoy_grpc: @@ -87,7 +87,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerWithTypedConfig) { http: name: opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -129,7 +129,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerGrpc) { http: name: opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -177,7 +177,7 @@ TEST(OpenCensusTracerConfigTest, ShouldCreateAtMostOneOpenCensusTracer) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -230,7 +230,7 @@ TEST(OpenCensusTracerConfigTest, ShouldNotCacheInvalidConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig ocagent_exporter_enabled: true ocagent_grpc_service: envoy_grpc: @@ -248,7 +248,7 @@ TEST(OpenCensusTracerConfigTest, ShouldNotCacheInvalidConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig ocagent_exporter_enabled: true ocagent_grpc_service: google_grpc: @@ -279,7 +279,7 @@ TEST(OpenCensusTracerConfigTest, ShouldRejectSubsequentCreateAttemptsWithDiffere http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -296,7 +296,7 @@ TEST(OpenCensusTracerConfigTest, ShouldRejectSubsequentCreateAttemptsWithDiffere http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 321 @@ -317,7 +317,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerStackdriverGrpc) { http: name: opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig stackdriver_exporter_enabled: true stackdriver_grpc_service: google_grpc: diff --git a/test/extensions/tracers/skywalking/BUILD b/test/extensions/tracers/skywalking/BUILD new file mode 100644 index 000000000000..b18f8cfe91dc --- /dev/null +++ b/test/extensions/tracers/skywalking/BUILD @@ -0,0 +1,113 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + "//source/extensions/tracers/skywalking:config", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/server:tracer_factory_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "skywalking_client_config_test", + srcs = ["skywalking_client_config_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + "//source/extensions/tracers/skywalking:skywalking_client_config_lib", + "//test/mocks:common_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "skywalking_types_test", + srcs = ["skywalking_types_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:skywalking_types_lib", + "//test/mocks:common_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "trace_segment_reporter_test", + srcs = ["trace_segment_reporter_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:trace_segment_reporter_lib", + "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "skywalking_test_helper", + srcs = ["skywalking_test_helper.h"], + extension_name = "envoy.tracers.skywalking", + deps = [ + "//source/common/common:base64_lib", + "//source/common/common:hex_lib", + "//source/extensions/tracers/skywalking:skywalking_types_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "tracer_test", + srcs = ["tracer_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:skywalking_tracer_lib", + "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/stats:stats_mocks", + "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "skywalking_tracer_impl_test", + srcs = ["skywalking_tracer_impl_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:skywalking_tracer_lib", + "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/tracers/skywalking/config_test.cc b/test/extensions/tracers/skywalking/config_test.cc new file mode 100644 index 000000000000..19c966cf7cb7 --- /dev/null +++ b/test/extensions/tracers/skywalking/config_test.cc @@ -0,0 +1,88 @@ +#include "envoy/config/trace/v3/http_tracer.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.validate.h" + +#include "extensions/tracers/skywalking/config.h" + +#include "test/mocks/server/tracer_factory.h" +#include "test/mocks/server/tracer_factory_context.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Eq; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +TEST(SkyWalkingTracerConfigTest, SkyWalkingHttpTracer) { + NiceMock context; + EXPECT_CALL(context.server_factory_context_.cluster_manager_, get(Eq("fake_cluster"))) + .WillRepeatedly( + Return(&context.server_factory_context_.cluster_manager_.thread_local_cluster_)); + ON_CALL(*context.server_factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_, + features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + http: + name: skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + envoy::config::trace::v3::Tracing configuration; + TestUtility::loadFromYaml(yaml_string, configuration); + + SkyWalkingTracerFactory factory; + auto message = Config::Utility::translateToFactoryConfig( + configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); + Tracing::HttpTracerSharedPtr skywalking_tracer = factory.createHttpTracer(*message, context); + EXPECT_NE(nullptr, skywalking_tracer); +} + +TEST(SkyWalkingTracerConfigTest, SkyWalkingHttpTracerWithClientConfig) { + NiceMock context; + EXPECT_CALL(context.server_factory_context_.cluster_manager_, get(Eq("fake_cluster"))) + .WillRepeatedly( + Return(&context.server_factory_context_.cluster_manager_.thread_local_cluster_)); + ON_CALL(*context.server_factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_, + features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + http: + name: skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + client_config: + backend_token: "A fake auth string for SkyWalking test" + service_name: "Test Service" + instance_name: "Test Instance" + max_cache_size: 2333 + )EOF"; + envoy::config::trace::v3::Tracing configuration; + TestUtility::loadFromYaml(yaml_string, configuration); + + SkyWalkingTracerFactory factory; + auto message = Config::Utility::translateToFactoryConfig( + configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); + Tracing::HttpTracerSharedPtr skywalking_tracer = factory.createHttpTracer(*message, context); + EXPECT_NE(nullptr, skywalking_tracer); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_client_config_test.cc b/test/extensions/tracers/skywalking/skywalking_client_config_test.cc new file mode 100644 index 000000000000..c0d4131a8e9d --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_client_config_test.cc @@ -0,0 +1,100 @@ +#include "extensions/tracers/skywalking/skywalking_client_config.h" + +#include "test/mocks/common.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class SkyWalkingClientConfigTest : public testing::Test { +public: + void setupSkyWalkingClientConfig(const std::string& yaml_string) { + auto& local_info = context_.server_factory_context_.local_info_; + + ON_CALL(local_info, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(local_info, nodeName()).WillByDefault(ReturnRef(test_string)); + + envoy::config::trace::v3::SkyWalkingConfig proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + + client_config_ = + std::make_unique(context_, proto_config.client_config()); + } + +protected: + NiceMock context_; + + std::string test_string = "ABCDEFGHIJKLMN"; + + SkyWalkingClientConfigPtr client_config_; +}; + +// Test whether the default value can be set correctly when there is no proto client config +// provided. +TEST_F(SkyWalkingClientConfigTest, NoProtoClientConfigTest) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + + setupSkyWalkingClientConfig(yaml_string); + + EXPECT_EQ(client_config_->service(), test_string); + EXPECT_EQ(client_config_->serviceInstance(), test_string); + EXPECT_EQ(client_config_->maxCacheSize(), 1024); + EXPECT_EQ(client_config_->backendToken(), ""); +} + +// Test whether the client config can work correctly when the proto client config is provided. +TEST_F(SkyWalkingClientConfigTest, WithProtoClientConfigTest) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + client_config: + backend_token: "FAKE_FAKE_FAKE_FAKE_FAKE_FAKE" + service_name: "FAKE_FAKE_FAKE" + instance_name: "FAKE_FAKE_FAKE" + max_cache_size: 2333 + )EOF"; + + setupSkyWalkingClientConfig(yaml_string); + + EXPECT_EQ(client_config_->service(), "FAKE_FAKE_FAKE"); + EXPECT_EQ(client_config_->serviceInstance(), "FAKE_FAKE_FAKE"); + EXPECT_EQ(client_config_->maxCacheSize(), 2333); + EXPECT_EQ(client_config_->backendToken(), "FAKE_FAKE_FAKE_FAKE_FAKE_FAKE"); +} + +// Test whether the client config can get default value for service name and instance name. +TEST_F(SkyWalkingClientConfigTest, BothLocalInfoAndClientConfigEmptyTest) { + test_string = ""; + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + + setupSkyWalkingClientConfig(yaml_string); + + EXPECT_EQ(client_config_->service(), "EnvoyProxy"); + EXPECT_EQ(client_config_->serviceInstance(), "EnvoyProxy"); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_test_helper.h b/test/extensions/tracers/skywalking/skywalking_test_helper.h new file mode 100644 index 000000000000..e158bb535da8 --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_test_helper.h @@ -0,0 +1,77 @@ +#pragma once + +#include "common/common/base64.h" +#include "common/common/hex.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +/* + * A simple helper class for auxiliary testing. Contains some simple static functions, such as + * encoding, generating random id, creating SpanContext, etc. + */ +class SkyWalkingTestHelper { +public: + static std::string generateId(Random::RandomGenerator& random) { + return absl::StrCat(Hex::uint64ToHex(random.random()), Hex::uint64ToHex(random.random())); + } + + static std::string base64Encode(absl::string_view input) { + return Base64::encode(input.data(), input.length()); + } + + static SegmentContextSharedPtr createSegmentContext(bool sampled, std::string seed, + std::string prev_seed, + Random::RandomGenerator& random) { + SpanContextPtr previous_span_context; + if (!prev_seed.empty()) { + std::string header_value = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", sampled ? 1 : 0, base64Encode(generateId(random)), + base64Encode(generateId(random)), random.random(), + base64Encode(prev_seed + "#SERVICE"), base64Encode(prev_seed + "#INSTANCE"), + base64Encode(prev_seed + "#ENDPOINT"), base64Encode(prev_seed + "#ADDRESS")); + + Http::TestRequestHeaderMapImpl request_headers{{"sw8", header_value}}; + previous_span_context = SpanContext::spanContextFromRequest(request_headers); + ASSERT(previous_span_context); + } + Tracing::Decision decision; + decision.traced = sampled; + decision.reason = Tracing::Reason::Sampling; + + auto segment_context = + std::make_shared(std::move(previous_span_context), decision, random); + + segment_context->setService(seed + "#SERVICE"); + segment_context->setServiceInstance(seed + "#INSTANCE"); + + return segment_context; + } + + static SpanStore* createSpanStore(SegmentContext* segment_context, SpanStore* parent_span_store, + std::string seed) { + SpanStore* span_store = segment_context->createSpanStore(parent_span_store); + + span_store->setAsError(false); + span_store->setOperation(seed + "#OPERATION"); + span_store->setPeerAddress("0.0.0.0"); + span_store->setStartTime(22222222); + span_store->setEndTime(33333333); + + span_store->addTag(seed + "#TAG_KEY_A", seed + "#TAG_VALUE_A"); + span_store->addTag(seed + "#TAG_KEY_B", seed + "#TAG_VALUE_B"); + span_store->addTag(seed + "#TAG_KEY_C", seed + "#TAG_VALUE_C"); + return span_store; + } +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc b/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc new file mode 100644 index 000000000000..cb5075665dcc --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc @@ -0,0 +1,179 @@ +#include "extensions/tracers/skywalking/skywalking_tracer_impl.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/tracing/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class SkyWalkingDriverTest : public testing::Test { +public: + void setupSkyWalkingDriver(const std::string& yaml_string) { + auto mock_client_factory = std::make_unique>(); + auto mock_client = std::make_unique>(); + mock_stream_ptr_ = std::make_unique>(); + + EXPECT_CALL(*mock_client, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + EXPECT_CALL(*mock_client_factory, create()).WillOnce(Return(ByMove(std::move(mock_client)))); + + auto& factory_context = context_.server_factory_context_; + + EXPECT_CALL(factory_context.cluster_manager_.async_client_manager_, + factoryForGrpcService(_, _, _)) + .WillOnce(Return(ByMove(std::move(mock_client_factory)))); + + EXPECT_CALL(factory_context.thread_local_.dispatcher_, createTimer_(_)) + .WillOnce(Invoke([](Event::TimerCb) { return new NiceMock(); })); + + ON_CALL(factory_context.local_info_, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(factory_context.local_info_, nodeName()).WillByDefault(ReturnRef(test_string)); + + TestUtility::loadFromYaml(yaml_string, config_); + driver_ = std::make_unique(config_, context_); + } + +protected: + NiceMock context_; + NiceMock mock_tracing_config_; + Event::SimulatedTimeSystem time_system_; + + std::unique_ptr> mock_stream_ptr_{nullptr}; + + envoy::config::trace::v3::SkyWalkingConfig config_; + std::string test_string = "ABCDEFGHIJKLMN"; + + DriverPtr driver_; +}; + +TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestWithClientConfig) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + client_config: + backend_token: "FAKE_FAKE_FAKE_FAKE_FAKE_FAKE" + service_name: "FAKE_FAKE_FAKE" + instance_name: "FAKE_FAKE_FAKE" + max_cache_size: 2333 + )EOF"; + setupSkyWalkingDriver(yaml_string); + + std::string trace_id = + SkyWalkingTestHelper::generateId(context_.server_factory_context_.api_.random_); + std::string segment_id = + SkyWalkingTestHelper::generateId(context_.server_factory_context_.api_.random_); + + // Create new span segment with previous span context. + std::string previous_header_value = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode("SERVICE"), + SkyWalkingTestHelper::base64Encode("INSTATNCE"), + SkyWalkingTestHelper::base64Encode("ENDPOINT"), + SkyWalkingTestHelper::base64Encode("ADDRESS")); + + Http::TestRequestHeaderMapImpl request_headers{{"sw8", previous_header_value}, + {":path", "/path"}, + {":method", "GET"}, + {":authority", "test.com"}}; + + ON_CALL(mock_tracing_config_, operationName()) + .WillByDefault(Return(Tracing::OperationName::Ingress)); + + Tracing::Decision decision; + decision.traced = true; + + Tracing::SpanPtr org_span = driver_->startSpan(mock_tracing_config_, request_headers, "TEST_OP", + time_system_.systemTime(), decision); + EXPECT_NE(nullptr, org_span.get()); + + Span* span = dynamic_cast(org_span.get()); + ASSERT(span); + + EXPECT_NE(nullptr, span->segmentContext()->previousSpanContext()); + + EXPECT_EQ("FAKE_FAKE_FAKE", span->segmentContext()->service()); + EXPECT_EQ("FAKE_FAKE_FAKE", span->segmentContext()->serviceInstance()); + + // Tracing decision will be overwrite by sampling flag in propagation headers. + EXPECT_EQ(0, span->segmentContext()->sampled()); + + // Since the sampling flag is false, no segment data is reported. + span->finishSpan(); + + auto& factory_context = context_.server_factory_context_; + EXPECT_EQ(0U, factory_context.scope_.counter("tracing.skywalking.segments_sent").value()); + + // Create new span segment with no previous span context. + Http::TestRequestHeaderMapImpl new_request_headers{ + {":path", "/path"}, {":method", "GET"}, {":authority", "test.com"}}; + + Tracing::SpanPtr org_new_span = driver_->startSpan(mock_tracing_config_, new_request_headers, "", + time_system_.systemTime(), decision); + + Span* new_span = dynamic_cast(org_new_span.get()); + ASSERT(new_span); + + EXPECT_EQ(nullptr, new_span->segmentContext()->previousSpanContext()); + + EXPECT_EQ(true, new_span->segmentContext()->sampled()); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); + new_span->finishSpan(); + EXPECT_EQ(1U, factory_context.scope_.counter("tracing.skywalking.segments_sent").value()); + + // Create new span segment with error propagation header. + Http::TestRequestHeaderMapImpl error_request_headers{{":path", "/path"}, + {":method", "GET"}, + {":authority", "test.com"}, + {"sw8", "xxxxxx-error-propagation-header"}}; + Tracing::SpanPtr org_null_span = driver_->startSpan( + mock_tracing_config_, error_request_headers, "TEST_OP", time_system_.systemTime(), decision); + + EXPECT_EQ(nullptr, dynamic_cast(org_null_span.get())); + + auto& null_span = *org_null_span; + EXPECT_EQ(typeid(null_span).name(), typeid(Tracing::NullSpan).name()); +} + +TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestNoClientConfig) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + + setupSkyWalkingDriver(yaml_string); + + Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/path"}, {":method", "GET"}, {":authority", "test.com"}}; + + Tracing::SpanPtr org_span = driver_->startSpan(mock_tracing_config_, request_headers, "TEST_OP", + time_system_.systemTime(), Tracing::Decision()); + EXPECT_NE(nullptr, org_span.get()); + + Span* span = dynamic_cast(org_span.get()); + ASSERT(span); + + EXPECT_EQ(test_string, span->segmentContext()->service()); + EXPECT_EQ(test_string, span->segmentContext()->serviceInstance()); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_types_test.cc b/test/extensions/tracers/skywalking/skywalking_types_test.cc new file mode 100644 index 000000000000..eb1d3147558f --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_types_test.cc @@ -0,0 +1,343 @@ +#include "common/common/base64.h" +#include "common/common/hex.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +// Some constant strings for testing. +constexpr absl::string_view TEST_SERVICE = "EnvoyIngressForTest"; +constexpr absl::string_view TEST_INSTANCE = "node-2.3.4.5~ingress"; +constexpr absl::string_view TEST_ADDRESS = "255.255.255.255"; +constexpr absl::string_view TEST_ENDPOINT = "/POST/path/for/test"; + +// Test whether SpanContext can correctly parse data from propagation headers and throw exceptions +// when errors occur. +TEST(SpanContextTest, SpanContextCommonTest) { + NiceMock mock_random_generator; + ON_CALL(mock_random_generator, random()).WillByDefault(Return(uint64_t(23333))); + + std::string trace_id = SkyWalkingTestHelper::generateId(mock_random_generator); + std::string segment_id = SkyWalkingTestHelper::generateId(mock_random_generator); + + // No propagation header then previous span context will be null. + Http::TestRequestHeaderMapImpl headers_no_propagation; + auto null_span_context = SpanContext::spanContextFromRequest(headers_no_propagation); + EXPECT_EQ(nullptr, null_span_context.get()); + + // Create properly formatted propagation headers and test whether the propagation headers can be + // parsed correctly. + std::string header_value_with_right_format = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + + Http::TestRequestHeaderMapImpl headers_with_right_format{{"sw8", header_value_with_right_format}}; + + auto previous_span_context = SpanContext::spanContextFromRequest(headers_with_right_format); + EXPECT_NE(nullptr, previous_span_context.get()); + + // Verify that each field parsed from the propagation headers is correct. + EXPECT_EQ(previous_span_context->sampled_, 0); + EXPECT_EQ(previous_span_context->trace_id_, trace_id); + EXPECT_EQ(previous_span_context->trace_segment_id_, segment_id); + EXPECT_EQ(previous_span_context->span_id_, 233333); + EXPECT_EQ(previous_span_context->service_, TEST_SERVICE); + EXPECT_EQ(previous_span_context->service_instance_, TEST_INSTANCE); + EXPECT_EQ(previous_span_context->endpoint_, TEST_ENDPOINT); + EXPECT_EQ(previous_span_context->target_address_, TEST_ADDRESS); + + std::string header_value_with_sampled = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + + Http::TestRequestHeaderMapImpl headers_with_sampled{{"sw8", header_value_with_sampled}}; + + auto previous_span_context_with_sampled = + SpanContext::spanContextFromRequest(headers_with_sampled); + EXPECT_EQ(previous_span_context_with_sampled->sampled_, 1); + + // Test whether an exception can be correctly thrown when some fields are missing. + std::string header_value_lost_some_parts = + fmt::format("{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 3, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE)); + + Http::TestRequestHeaderMapImpl headers_lost_some_parts{{"sw8", header_value_lost_some_parts}}; + + EXPECT_THROW_WITH_MESSAGE( + SpanContext::spanContextFromRequest(headers_lost_some_parts), EnvoyException, + fmt::format("Invalid propagation header for SkyWalking: {}", header_value_lost_some_parts)); + + // Test whether an exception can be correctly thrown when the sampling flag is wrong. + Http::TestRequestHeaderMapImpl headers_with_error_sampled{ + {"sw8", + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 3, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 3, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS))}}; + + EXPECT_THROW_WITH_MESSAGE(SpanContext::spanContextFromRequest(headers_with_error_sampled), + EnvoyException, + "Invalid propagation header for SkyWalking: sampling flag can only be " + "'0' or '1' but '3' was provided"); + + // Test whether an exception can be correctly thrown when the span id format is wrong. + Http::TestRequestHeaderMapImpl headers_with_error_span_id{ + {"sw8", + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), "abc", + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS))}}; + + EXPECT_THROW_WITH_MESSAGE( + SpanContext::spanContextFromRequest(headers_with_error_span_id), EnvoyException, + "Invalid propagation header for SkyWalking: connot convert 'abc' to valid span id"); + + // Test whether an exception can be correctly thrown when a field is empty. + std::string header_value_with_empty_field = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 4, "", + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + Http::TestRequestHeaderMapImpl headers_with_empty_field{{"sw8", header_value_with_empty_field}}; + + EXPECT_THROW_WITH_MESSAGE( + SpanContext::spanContextFromRequest(headers_with_empty_field), EnvoyException, + fmt::format("Invalid propagation header for SkyWalking: {}", header_value_with_empty_field)); + + // Test whether an exception can be correctly thrown when a string is not properly encoded. + Http::TestRequestHeaderMapImpl headers_with_error_field{ + {"sw8", + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 4, "hhhhhhh", + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS))}}; + + EXPECT_THROW_WITH_MESSAGE(SpanContext::spanContextFromRequest(headers_with_error_field), + EnvoyException, + "Invalid propagation header for SkyWalking: parse error"); +} + +// Test whether the SegmentContext works normally when Envoy is the root node (Propagation headers +// does not exist). +TEST(SegmentContextTest, SegmentContextTestWithEmptyPreviousSpanContext) { + NiceMock mock_random_generator; + + ON_CALL(mock_random_generator, random()).WillByDefault(Return(233333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "", mock_random_generator); + + // When previous span context is null, the value of the sampling flag depends on the tracing + // decision + EXPECT_EQ(segment_context->sampled(), 1); + // The SegmentContext will use random generator to create new trace id and new trace segment id. + EXPECT_EQ(segment_context->traceId(), SkyWalkingTestHelper::generateId(mock_random_generator)); + EXPECT_EQ(segment_context->traceSegmentId(), + SkyWalkingTestHelper::generateId(mock_random_generator)); + + EXPECT_EQ(segment_context->previousSpanContext(), nullptr); + + // Test whether the value of the fields can be set correctly and the value of the fields can be + // obtained correctly. + EXPECT_EQ(segment_context->service(), "NEW#SERVICE"); + segment_context->setService(std::string(TEST_SERVICE)); + EXPECT_EQ(segment_context->service(), TEST_SERVICE); + + EXPECT_EQ(segment_context->serviceInstance(), "NEW#INSTANCE"); + segment_context->setServiceInstance(std::string(TEST_INSTANCE)); + EXPECT_EQ(segment_context->serviceInstance(), TEST_INSTANCE); + + EXPECT_EQ(segment_context->rootSpanStore(), nullptr); + + // Test whether SegmentContext can correctly create SpanStore object with null parent SpanStore. + SpanStore* root_span = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + EXPECT_NE(nullptr, root_span); + + // The span id of the first SpanStore in each SegmentContext is 0. Its parent span id is -1. + EXPECT_EQ(root_span->spanId(), 0); + EXPECT_EQ(root_span->parentSpanId(), -1); + + // Root span of current segment should be Entry Span. + EXPECT_EQ(root_span->isEntrySpan(), true); + + // Verify that the SpanStore object is correctly stored in the SegmentContext. + EXPECT_EQ(segment_context->spanList().size(), 1); + EXPECT_EQ(segment_context->spanList()[0].get(), root_span); + + // Test whether SegmentContext can correctly create SpanStore object with a parent SpanStore. + SpanStore* child_span = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), root_span, "CHILD"); + + EXPECT_NE(nullptr, child_span); + + EXPECT_EQ(child_span->spanId(), 1); + EXPECT_EQ(child_span->parentSpanId(), 0); + + // All child spans of current segment should be Exit Span. + EXPECT_EQ(child_span->isEntrySpan(), false); + + EXPECT_EQ(segment_context->spanList().size(), 2); + EXPECT_EQ(segment_context->spanList()[1].get(), child_span); +} + +// Test whether the SegmentContext can work normally when a previous span context exists. +TEST(SegmentContextTest, SegmentContextTestWithPreviousSpanContext) { + NiceMock mock_random_generator; + + ON_CALL(mock_random_generator, random()).WillByDefault(Return(23333)); + + std::string trace_id = SkyWalkingTestHelper::generateId(mock_random_generator); + std::string segment_id = SkyWalkingTestHelper::generateId(mock_random_generator); + + std::string header_value_with_right_format = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + + Http::TestRequestHeaderMapImpl headers_with_right_format{{"sw8", header_value_with_right_format}}; + + auto previous_span_context = SpanContext::spanContextFromRequest(headers_with_right_format); + SpanContext* previous_span_context_bk = previous_span_context.get(); + + Tracing::Decision decision; + decision.traced = true; + + EXPECT_CALL(mock_random_generator, random()).WillRepeatedly(Return(666666)); + + SegmentContext segment_context(std::move(previous_span_context), decision, mock_random_generator); + + // When a previous span context exists, the sampling flag of the SegmentContext depends on + // previous span context rather than tracing decision. + EXPECT_EQ(segment_context.sampled(), 0); + + // When previous span context exists, the trace id of SegmentContext remains the same as that of + // previous span context. + EXPECT_EQ(segment_context.traceId(), trace_id); + // SegmentContext will always create a new trace segment id. + EXPECT_NE(segment_context.traceSegmentId(), segment_id); + + EXPECT_EQ(segment_context.previousSpanContext(), previous_span_context_bk); +} + +// Test whether SpanStore can work properly. +TEST(SpanStoreTest, SpanStoreCommonTest) { + NiceMock mock_random_generator; + + Event::SimulatedTimeSystem time_system; + Envoy::SystemTime now = time_system.systemTime(); + + ON_CALL(mock_random_generator, random()).WillByDefault(Return(23333)); + + // Create segment context and first span store. + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "CURR", "PREV", mock_random_generator); + SpanStore* root_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "ROOT"); + EXPECT_NE(nullptr, root_store); + EXPECT_EQ(3, root_store->tags().size()); + + root_store->addLog(now, "TestLogStringAndNeverBeStored"); + EXPECT_EQ(0, root_store->logs().size()); + + // The span id of the first SpanStore in each SegmentContext is 0. Its parent span id is -1. + EXPECT_EQ(0, root_store->spanId()); + EXPECT_EQ(-1, root_store->parentSpanId()); + + root_store->setSpanId(123); + EXPECT_EQ(123, root_store->spanId()); + root_store->setParentSpanId(234); + EXPECT_EQ(234, root_store->parentSpanId()); + + EXPECT_EQ(1, root_store->sampled()); + root_store->setSampled(0); + EXPECT_EQ(0, root_store->sampled()); + + // Test whether the value of the fields can be set correctly and the value of the fields can be + // obtained correctly. + EXPECT_EQ(true, root_store->isEntrySpan()); + root_store->setAsEntrySpan(false); + EXPECT_EQ(false, root_store->isEntrySpan()); + + EXPECT_EQ(false, root_store->isError()); + root_store->setAsError(true); + EXPECT_EQ(true, root_store->isError()); + + EXPECT_EQ("ROOT#OPERATION", root_store->operation()); + root_store->setOperation(""); + EXPECT_EQ("", root_store->operation()); + root_store->setOperation("oooooop"); + EXPECT_EQ("oooooop", root_store->operation()); + + EXPECT_EQ("0.0.0.0", root_store->peerAddress()); + root_store->setPeerAddress(std::string(TEST_ADDRESS)); + EXPECT_EQ(TEST_ADDRESS, root_store->peerAddress()); + + EXPECT_EQ(22222222, root_store->startTime()); + root_store->setStartTime(23333); + EXPECT_EQ(23333, root_store->startTime()); + + EXPECT_EQ(33333333, root_store->endTime()); + root_store->setEndTime(25555); + EXPECT_EQ(25555, root_store->endTime()); + + SpanStore* child_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), root_store, "CHILD"); + + // Test whether SpanStore can correctly inject propagation headers to request headers. + Http::TestRequestHeaderMapImpl request_headers_no_upstream{{":authority", "test.com"}}; + // Only child span (Exit Span) can inject context header to request headers. + child_store->injectContext(request_headers_no_upstream); + std::string expected_header_value = fmt::format( + "{}-{}-{}-{}-{}-{}-{}-{}", child_store->sampled(), + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator)), + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator)), + child_store->spanId(), SkyWalkingTestHelper::base64Encode("CURR#SERVICE"), + SkyWalkingTestHelper::base64Encode("CURR#INSTANCE"), + SkyWalkingTestHelper::base64Encode("oooooop"), + SkyWalkingTestHelper::base64Encode("test.com")); + + EXPECT_EQ(child_store->peerAddress(), "test.com"); + + EXPECT_EQ(request_headers_no_upstream.get_("sw8"), expected_header_value); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc new file mode 100644 index 000000000000..fa3f59effdb5 --- /dev/null +++ b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc @@ -0,0 +1,246 @@ +#include "extensions/tracers/skywalking/trace_segment_reporter.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/grpc/mocks.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class TraceSegmentReporterTest : public testing::Test { +public: + void setupTraceSegmentReporter(const std::string& yaml_string) { + EXPECT_CALL(mock_dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb timer_cb) { + timer_cb_ = timer_cb; + return timer_; + })); + timer_ = new NiceMock(); + + auto mock_client_factory = std::make_unique>(); + + auto mock_client = std::make_unique>(); + mock_client_ptr_ = mock_client.get(); + + mock_stream_ptr_ = std::make_unique>(); + + EXPECT_CALL(*mock_client_factory, create()).WillOnce(Return(ByMove(std::move(mock_client)))); + EXPECT_CALL(*mock_client_ptr_, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + + auto& local_info = context_.server_factory_context_.local_info_; + + ON_CALL(local_info, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(local_info, nodeName()).WillByDefault(ReturnRef(test_string)); + + envoy::config::trace::v3::ClientConfig proto_client_config; + TestUtility::loadFromYaml(yaml_string, proto_client_config); + client_config_ = std::make_unique(context_, proto_client_config); + + reporter_ = std::make_unique(std::move(mock_client_factory), + mock_dispatcher_, mock_random_generator_, + tracing_stats_, *client_config_); + } + +protected: + NiceMock context_; + + NiceMock& mock_dispatcher_ = context_.server_factory_context_.dispatcher_; + NiceMock& mock_random_generator_ = + context_.server_factory_context_.api_.random_; + Event::GlobalTimeSystem& mock_time_source_ = context_.server_factory_context_.time_system_; + + NiceMock& mock_scope_ = context_.server_factory_context_.scope_; + + NiceMock* mock_client_ptr_{nullptr}; + + std::unique_ptr> mock_stream_ptr_{nullptr}; + + NiceMock* timer_; + Event::TimerCb timer_cb_; + + std::string test_string = "ABCDEFGHIJKLMN"; + + SkyWalkingClientConfigPtr client_config_; + + SkyWalkingTracerStats tracing_stats_{ + SKYWALKING_TRACER_STATS(POOL_COUNTER_PREFIX(mock_scope_, "tracing.skywalking."))}; + TraceSegmentReporterPtr reporter_; +}; + +// Test whether the reporter can correctly add metadata according to the configuration. +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterInitialMetadata) { + const std::string yaml_string = R"EOF( + backend_token: "FakeStringForAuthenticaion" + )EOF"; + + setupTraceSegmentReporter(yaml_string); + Http::TestRequestHeaderMapImpl metadata; + reporter_->onCreateInitialMetadata(metadata); + + EXPECT_EQ("FakeStringForAuthenticaion", metadata.get_("authentication")); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterNoMetadata) { + setupTraceSegmentReporter("{}"); + Http::TestRequestHeaderMapImpl metadata; + reporter_->onCreateInitialMetadata(metadata); + + EXPECT_EQ("", metadata.get_("authentication")); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportTraceSegment) { + setupTraceSegmentReporter("{}"); + ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE", mock_random_generator_); + SpanStore* parent_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + // Parent span store has peer address. + parent_store->setPeerAddress("0.0.0.0"); + + SpanStore* first_child_sptore = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + // Skip reporting the first child span. + first_child_sptore->setSampled(0); + + // Create second child span. + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); + + reporter_->report(*segment_context); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, 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()); + + // Create a segment context with no previous span context. + SegmentContextSharedPtr second_segment_context = SkyWalkingTestHelper::createSegmentContext( + true, "SECOND_SEGMENT", "", mock_random_generator_); + SkyWalkingTestHelper::createSpanStore(second_segment_context.get(), nullptr, "PARENT"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); + reporter_->report(*second_segment_context); + + EXPECT_EQ(2U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, 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()); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportWithDefaultCache) { + setupTraceSegmentReporter("{}"); + ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE", mock_random_generator_); + SpanStore* parent_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)).Times(1025); + + reporter_->report(*segment_context); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, 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()); + + // Simulates a disconnected connection. + EXPECT_CALL(*timer_, enableTimer(_, _)); + reporter_->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Unknown, ""); + + // Try to report 10 segments. Due to the disconnection, the cache size is only 3. So 7 of the + // segments will be discarded. + for (int i = 0; i < 2048; i++) { + reporter_->report(*segment_context); + } + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(1024U, 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()); + + // Simulate the situation where the connection is re-established. The remaining segments in the + // cache will be reported. + EXPECT_CALL(*mock_client_ptr_, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + timer_cb_(); + + EXPECT_EQ(1025U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(1024U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(1024U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportWithCacheConfig) { + const std::string yaml_string = R"EOF( + max_cache_size: 3 + )EOF"; + + setupTraceSegmentReporter(yaml_string); + + ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE", mock_random_generator_); + SpanStore* parent_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)).Times(4); + + reporter_->report(*segment_context); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, 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()); + + // Simulates a disconnected connection. + EXPECT_CALL(*timer_, enableTimer(_, _)); + reporter_->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Unknown, ""); + + // Try to report 10 segments. Due to the disconnection, the cache size is only 3. So 7 of the + // segments will be discarded. + for (int i = 0; i < 10; i++) { + reporter_->report(*segment_context); + } + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(7U, 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()); + + // Simulate the situation where the connection is re-established. The remaining segments in the + // cache will be reported. + EXPECT_CALL(*mock_client_ptr_, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + timer_cb_(); + + EXPECT_EQ(4U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(7U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(3U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc new file mode 100644 index 000000000000..c0d9356f1d50 --- /dev/null +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -0,0 +1,193 @@ +#include "extensions/tracers/skywalking/skywalking_client_config.h" +#include "extensions/tracers/skywalking/tracer.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/stats/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 "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class TracerTest : public testing::Test { +public: + void setupTracer(const std::string& yaml_string) { + EXPECT_CALL(mock_dispatcher_, createTimer_(_)).WillOnce(Invoke([](Event::TimerCb) { + return new NiceMock(); + })); + + auto mock_client_factory = std::make_unique>(); + + auto mock_client = std::make_unique>(); + + mock_stream_ptr_ = std::make_unique>(); + + EXPECT_CALL(*mock_client, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + EXPECT_CALL(*mock_client_factory, create()).WillOnce(Return(ByMove(std::move(mock_client)))); + + auto& local_info = context_.server_factory_context_.local_info_; + + ON_CALL(local_info, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(local_info, nodeName()).WillByDefault(ReturnRef(test_string)); + + envoy::config::trace::v3::ClientConfig proto_client_config; + TestUtility::loadFromYaml(yaml_string, proto_client_config); + client_config_ = std::make_unique(context_, proto_client_config); + + tracer_ = std::make_unique(mock_time_source_); + tracer_->setReporter(std::make_unique( + std::move(mock_client_factory), mock_dispatcher_, mock_random_generator_, tracing_stats_, + *client_config_)); + } + +protected: + NiceMock mock_tracing_config_; + + NiceMock context_; + + NiceMock& mock_dispatcher_ = context_.server_factory_context_.dispatcher_; + NiceMock& mock_random_generator_ = + context_.server_factory_context_.api_.random_; + Event::GlobalTimeSystem& mock_time_source_ = context_.server_factory_context_.time_system_; + + NiceMock& mock_scope_ = context_.server_factory_context_.scope_; + + std::unique_ptr> mock_stream_ptr_{nullptr}; + + std::string test_string = "ABCDEFGHIJKLMN"; + + SkyWalkingClientConfigPtr client_config_; + + SkyWalkingTracerStats tracing_stats_{ + SKYWALKING_TRACER_STATS(POOL_COUNTER_PREFIX(mock_scope_, "tracing.skywalking."))}; + + TracerPtr tracer_; +}; + +// Test that the basic functionality of Tracer is working, including creating Span, using Span to +// create new child Spans. +TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { + setupTracer("{}"); + EXPECT_CALL(mock_random_generator_, random()).WillRepeatedly(Return(666666)); + + // Create a new SegmentContext. + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "CURR", "", mock_random_generator_); + + Envoy::Tracing::SpanPtr org_span = tracer_->startSpan( + mock_tracing_config_, mock_time_source_.systemTime(), "TEST_OP", segment_context, nullptr); + Span* span = dynamic_cast(org_span.get()); + + EXPECT_EQ(true, span->spanStore()->isEntrySpan()); + + EXPECT_EQ("", span->getBaggage("FakeStringAndNothingToDo")); + span->setBaggage("FakeStringAndNothingToDo", "FakeStringAndNothingToDo"); + + // Test whether the basic functions of Span are normal. + + span->setSampled(false); + EXPECT_EQ(false, span->spanStore()->sampled()); + + // The initial operation name is consistent with the 'operation' parameter in the 'startSpan' + // method call. + EXPECT_EQ("TEST_OP", span->spanStore()->operation()); + span->setOperation("op"); + EXPECT_EQ("op", span->spanStore()->operation()); + + // Test whether the tag can be set correctly. + span->setTag("TestTagKeyA", "TestTagValueA"); + span->setTag("TestTagKeyB", "TestTagValueB"); + EXPECT_EQ("TestTagValueA", span->spanStore()->tags().at(0).second); + EXPECT_EQ("TestTagValueB", span->spanStore()->tags().at(1).second); + + // When setting the status code tag, the corresponding tag name will be rewritten as + // 'status_code'. + span->setTag(Tracing::Tags::get().HttpStatusCode, "200"); + EXPECT_EQ("status_code", span->spanStore()->tags().at(2).first); + EXPECT_EQ("200", span->spanStore()->tags().at(2).second); + + // When setting the error tag, the SpanStore object will also mark itself as an error. + span->setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); + EXPECT_EQ(Tracing::Tags::get().Error, span->spanStore()->tags().at(3).first); + EXPECT_EQ(Tracing::Tags::get().True, span->spanStore()->tags().at(3).second); + EXPECT_EQ(true, span->spanStore()->isError()); + + // When setting http url tag, the corresponding tag name will be rewritten as 'url'. + span->setTag(Tracing::Tags::get().HttpUrl, "http://test.com/test/path"); + EXPECT_EQ("url", span->spanStore()->tags().at(4).first); + + Envoy::Tracing::SpanPtr org_first_child_span = + span->spawnChild(mock_tracing_config_, "TestChild", mock_time_source_.systemTime()); + Span* first_child_span = dynamic_cast(org_first_child_span.get()); + + EXPECT_EQ(false, first_child_span->spanStore()->isEntrySpan()); + + EXPECT_EQ(0, first_child_span->spanStore()->sampled()); + EXPECT_EQ(1, first_child_span->spanStore()->spanId()); + EXPECT_EQ(0, first_child_span->spanStore()->parentSpanId()); + + EXPECT_EQ("TestChild", first_child_span->spanStore()->operation()); + + Http::TestRequestHeaderMapImpl first_child_headers{{":authority", "test.com"}}; + std::string expected_header_value = fmt::format( + "{}-{}-{}-{}-{}-{}-{}-{}", 0, + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator_)), + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator_)), + 1, SkyWalkingTestHelper::base64Encode("CURR#SERVICE"), + SkyWalkingTestHelper::base64Encode("CURR#INSTANCE"), SkyWalkingTestHelper::base64Encode("op"), + SkyWalkingTestHelper::base64Encode("test.com")); + + first_child_span->injectContext(first_child_headers); + EXPECT_EQ(expected_header_value, first_child_headers.get_("sw8")); + + // Reset sampling flag to true. + span->setSampled(true); + Envoy::Tracing::SpanPtr org_second_child_span = + span->spawnChild(mock_tracing_config_, "TestChild", mock_time_source_.systemTime()); + Span* second_child_span = dynamic_cast(org_second_child_span.get()); + + EXPECT_EQ(1, second_child_span->spanStore()->sampled()); + EXPECT_EQ(2, second_child_span->spanStore()->spanId()); + EXPECT_EQ(0, second_child_span->spanStore()->parentSpanId()); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)).Times(1); + + // When the child span ends, the data is not reported immediately, but the end time is set. + first_child_span->finishSpan(); + second_child_span->finishSpan(); + EXPECT_NE(0, first_child_span->spanStore()->endTime()); + EXPECT_NE(0, second_child_span->spanStore()->endTime()); + + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, 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()); + + // When the first span in the current segment ends, the entire segment is reported. + span->finishSpan(); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, 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 +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/xray/config_test.cc b/test/extensions/tracers/xray/config_test.cc index 9bf6a24c65bb..7008ef0a0c54 100644 --- a/test/extensions/tracers/xray/config_test.cc +++ b/test/extensions/tracers/xray/config_test.cc @@ -28,7 +28,7 @@ TEST(XRayTracerConfigTest, XRayHttpTracerWithTypedConfig) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: UDP address: 127.0.0.1 @@ -62,7 +62,7 @@ TEST(XRayTracerConfigTest, XRayHttpTracerWithInvalidFileName) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: UDP address: 127.0.0.1 @@ -88,7 +88,7 @@ TEST(XRayTracerConfigTest, ProtocolNotUDPThrows) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: TCP address: 127.0.0.1 @@ -113,7 +113,7 @@ TEST(XRayTracerConfigTest, UsingNamedPortThrows) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: UDP address: 127.0.0.1 diff --git a/test/extensions/tracers/zipkin/config_test.cc b/test/extensions/tracers/zipkin/config_test.cc index 0f62b8f7fd7f..a862017d9e74 100644 --- a/test/extensions/tracers/zipkin/config_test.cc +++ b/test/extensions/tracers/zipkin/config_test.cc @@ -30,7 +30,7 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracer) { http: name: zipkin typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.ZipkinConfig + "@type": type.googleapis.com/envoy.config.trace.v3.ZipkinConfig collector_cluster: fake_cluster collector_endpoint: /api/v1/spans collector_endpoint_version: HTTP_JSON @@ -57,7 +57,7 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracerWithTypedConfig) { http: name: zipkin typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.ZipkinConfig + "@type": type.googleapis.com/envoy.config.trace.v3.ZipkinConfig collector_cluster: fake_cluster collector_endpoint: /api/v2/spans collector_endpoint_version: HTTP_PROTO diff --git a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc index 5e605999b671..84f335c3c211 100644 --- a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc +++ b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc @@ -205,14 +205,6 @@ TEST_F(ZipkinDriverTest, AllowCollectorClusterToBeAddedViaApi) { setup(zipkin_config, true); } -TEST_F(ZipkinDriverTest, FlushSeveralSpans) { - expectValidFlushSeveralSpans("HTTP_JSON_V1", "application/json"); -} - -TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpJsonV1) { - expectValidFlushSeveralSpans("HTTP_JSON_V1", "application/json"); -} - TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpJson) { expectValidFlushSeveralSpans("HTTP_JSON", "application/json"); } @@ -222,7 +214,7 @@ TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpProto) { } TEST_F(ZipkinDriverTest, FlushOneSpanReportFailure) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); Http::MockAsyncClientRequest request(&cm_.async_client_); Http::AsyncClient::Callbacks* callback; @@ -268,7 +260,7 @@ TEST_F(ZipkinDriverTest, SkipReportIfCollectorClusterHasBeenRemoved) { EXPECT_CALL(cm_, addThreadLocalClusterUpdateCallbacks_(_)) .WillOnce(DoAll(SaveArgAddress(&cluster_update_callbacks), Return(nullptr))); - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.min_flush_spans", 5)) .WillRepeatedly(Return(1)); @@ -386,7 +378,7 @@ TEST_F(ZipkinDriverTest, SkipReportIfCollectorClusterHasBeenRemoved) { } TEST_F(ZipkinDriverTest, CancelInflightRequestsOnDestruction) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); StrictMock request1(&cm_.async_client_), request2(&cm_.async_client_), request3(&cm_.async_client_), request4(&cm_.async_client_); @@ -446,7 +438,7 @@ TEST_F(ZipkinDriverTest, CancelInflightRequestsOnDestruction) { } TEST_F(ZipkinDriverTest, FlushSpansTimer) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); const absl::optional timeout(std::chrono::seconds(5)); EXPECT_CALL(cm_.async_client_, @@ -473,7 +465,7 @@ TEST_F(ZipkinDriverTest, FlushSpansTimer) { } TEST_F(ZipkinDriverTest, NoB3ContextSampledTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -487,7 +479,7 @@ TEST_F(ZipkinDriverTest, NoB3ContextSampledTrue) { } TEST_F(ZipkinDriverTest, NoB3ContextSampledFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -501,7 +493,7 @@ TEST_F(ZipkinDriverTest, NoB3ContextSampledFalse) { } TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -517,7 +509,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { } TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -533,7 +525,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleFalse) { } TEST_F(ZipkinDriverTest, PropagateB3NotSampled) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -554,7 +546,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NotSampled) { } TEST_F(ZipkinDriverTest, PropagateB3NotSampledWithFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -576,7 +568,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NotSampledWithFalse) { } TEST_F(ZipkinDriverTest, PropagateB3SampledWithTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -598,7 +590,7 @@ TEST_F(ZipkinDriverTest, PropagateB3SampledWithTrue) { } TEST_F(ZipkinDriverTest, PropagateB3SampleFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -614,7 +606,7 @@ TEST_F(ZipkinDriverTest, PropagateB3SampleFalse) { } TEST_F(ZipkinDriverTest, ZipkinSpanTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); // ==== // Test effective setTag() @@ -701,7 +693,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); const std::string trace_id = Hex::uint64ToHex(generateRandom64()); const std::string span_id = Hex::uint64ToHex(generateRandom64()); @@ -725,7 +717,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersEmptyParentSpanTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); // Root span so have same trace and span id const std::string id = Hex::uint64ToHex(generateRandom64()); @@ -745,7 +737,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersEmptyParentSpanTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); const uint64_t trace_id_high = generateRandom64(); const uint64_t trace_id_low = generateRandom64(); @@ -773,7 +765,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, std::string("xyz")); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_SPAN_ID, @@ -787,7 +779,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -801,7 +793,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidParentIdB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -816,7 +808,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidParentIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ExplicitlySetSampledFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, true}); @@ -833,7 +825,7 @@ TEST_F(ZipkinDriverTest, ExplicitlySetSampledFalse) { } TEST_F(ZipkinDriverTest, ExplicitlySetSampledTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, false}); @@ -850,7 +842,7 @@ TEST_F(ZipkinDriverTest, ExplicitlySetSampledTrue) { } TEST_F(ZipkinDriverTest, DuplicatedHeader) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_SPAN_ID, diff --git a/test/extensions/transport_sockets/alts/BUILD b/test/extensions/transport_sockets/alts/BUILD index 1002501c3ef1..145711ab4720 100644 --- a/test/extensions/transport_sockets/alts/BUILD +++ b/test/extensions/transport_sockets/alts/BUILD @@ -39,8 +39,6 @@ envoy_extension_cc_test( name = "tsi_handshaker_test", srcs = ["tsi_handshaker_test.cc"], extension_name = "envoy.transport_sockets.alts", - # Fails intermittantly on local build - tags = ["flaky_on_windows"], deps = [ "//include/envoy/event:dispatcher_interface", "//source/extensions/transport_sockets/alts:tsi_handshaker", diff --git a/test/extensions/transport_sockets/alts/tsi_socket_test.cc b/test/extensions/transport_sockets/alts/tsi_socket_test.cc index 3fd39b520020..85f40ab3fcd8 100644 --- a/test/extensions/transport_sockets/alts/tsi_socket_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_socket_test.cc @@ -55,7 +55,6 @@ class TsiSocketTest : public testing::Test { client_.tsi_socket_ = std::make_unique(client_.handshaker_factory_, client_validator, Network::TransportSocketPtr{client_.raw_socket_}); - ON_CALL(client_.callbacks_.connection_, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); ON_CALL(server_.callbacks_.connection_, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); @@ -194,6 +193,7 @@ static const std::string ClientToServerData = "hello from client"; TEST_F(TsiSocketTest, DoesNotHaveSsl) { initialize(nullptr, nullptr); EXPECT_EQ(nullptr, client_.tsi_socket_->ssl()); + EXPECT_FALSE(client_.tsi_socket_->canFlushClose()); const auto& socket_ = *client_.tsi_socket_; EXPECT_EQ(nullptr, socket_.ssl()); @@ -406,6 +406,10 @@ TEST_F(TsiSocketFactoryTest, ImplementsSecureTransport) { EXPECT_TRUE(socket_factory_->implementsSecureTransport()); } +TEST_F(TsiSocketFactoryTest, UsesProxyProtocolOptions) { + EXPECT_FALSE(socket_factory_->usesProxyProtocolOptions()); +} + } // namespace } // namespace Alts } // namespace TransportSockets 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 99e04a348746..5747ec73d125 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 @@ -104,6 +104,36 @@ TEST_P(ProxyProtocolIntegrationTest, TestV1ProxyProtocol) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } +TEST_P(ProxyProtocolIntegrationTest, TestV1ProxyProtocolMultipleConnections) { + if (GetParam() != Network::Address::IpVersion::v4) { + return; + } + + setup(envoy::config::core::v3::ProxyProtocolConfig::V1, false, + "envoy.transport_sockets.raw_buffer"); + initialize(); + auto listener_port = lookupPort("listener_0"); + + auto loopback2 = Network::Utility::resolveUrl("tcp://127.0.0.2:0"); + auto tcp_client2 = makeTcpConnection(listener_port, nullptr, loopback2); + + auto tcp_client = makeTcpConnection(listener_port); + + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); + FakeRawConnectionPtr conn2; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(conn2)); + + std::string data1, data2; + ASSERT_TRUE( + fake_upstream_connection_->waitForData(FakeRawConnection::waitForAtLeastBytes(32), &data1)); + ASSERT_TRUE(conn2->waitForData(FakeRawConnection::waitForAtLeastBytes(32), &data2)); + + EXPECT_NE(data1, data2); + + tcp_client->close(); + tcp_client2->close(); +} + // Test header is sent unencrypted using a TLS inner socket TEST_P(ProxyProtocolIntegrationTest, TestTLSSocket) { setup(envoy::config::core::v3::ProxyProtocolConfig::V1, false, "envoy.transport_sockets.tls"); @@ -201,4 +231,4 @@ TEST_P(ProxyProtocolIntegrationTest, TestV2ProxyProtocol) { } } // namespace -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 32a2281cf0af..16f56b4f9489 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -256,7 +256,7 @@ TEST_F(ProxyProtocolTest, V1IPV4DownstreamAddresses) { new Network::Address::Ipv4Instance("174.2.2.222", 80)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -288,7 +288,7 @@ TEST_F(ProxyProtocolTest, V1IPV6DownstreamAddresses) { Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("a:b:c:d::", 80)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -365,7 +365,7 @@ TEST_F(ProxyProtocolTest, V2IPV4DownstreamAddresses) { Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -397,7 +397,7 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddresses) { new Network::Address::Ipv6Instance("1:100:200:3::", 2)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -429,7 +429,7 @@ TEST_F(ProxyProtocolTest, OnConnectedCallsInnerOnConnected) { new Network::Address::Ipv6Instance("1:100:200:3::", 2)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -469,15 +469,8 @@ TEST_F(ProxyProtocolSocketFactoryTest, ImplementsSecureTransportCallInnerFactory ASSERT_TRUE(factory_->implementsSecureTransport()); } -// Test isReady calls inner factory -TEST_F(ProxyProtocolSocketFactoryTest, IsReadyCallInnerFactory) { - initialize(); - EXPECT_CALL(*inner_factory_, isReady()).WillOnce(Return(true)); - ASSERT_TRUE(factory_->isReady()); -} - } // namespace } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tap/tap_config_impl_test.cc b/test/extensions/transport_sockets/tap/tap_config_impl_test.cc index c8bcefbf623d..99f10e518e8d 100644 --- a/test/extensions/transport_sockets/tap/tap_config_impl_test.cc +++ b/test/extensions/transport_sockets/tap/tap_config_impl_test.cc @@ -58,7 +58,11 @@ class PerSocketTapperImplTest : public testing::Test { EXPECT_CALL(matcher_, onNewStream(_)) .WillOnce(Invoke([this](TapCommon::Matcher::MatchStatusVector& statuses) { statuses_ = &statuses; - statuses[0].matches_ = true; + if (fail_match_) { + statuses[0].matches_ = false; + } else { + statuses[0].matches_ = true; + } statuses[0].might_change_status_ = false; })); EXPECT_CALL(*config_, streaming()).WillRepeatedly(Return(streaming)); @@ -79,6 +83,7 @@ class PerSocketTapperImplTest : public testing::Test { TapCommon::Matcher::MatchStatusVector* statuses_; NiceMock connection_; Event::SimulatedTimeSystem time_system_; + bool fail_match_{}; }; // Verify the full streaming flow. @@ -139,6 +144,15 @@ TEST_F(PerSocketTapperImplTest, StreamingFlow) { tapper_->closeSocket(Network::ConnectionEvent::RemoteClose); } +TEST_F(PerSocketTapperImplTest, NonMatchingFlow) { + fail_match_ = true; + setup(true); + + EXPECT_CALL(*sink_manager_, submitTrace_(_)).Times(0); + time_system_.setSystemTime(std::chrono::seconds(2)); + tapper_->closeSocket(Network::ConnectionEvent::RemoteClose); +} + } // namespace } // namespace Tap } // namespace TransportSockets diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 575689e4b2ea..49445e4ec616 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -1247,7 +1247,7 @@ TEST_F(ClientContextConfigImplTest, RSA1024Cert) { std::string error_msg( "Failed to load certificate chain from .*selfsigned_rsa_1024_cert.pem, only RSA certificates " #ifdef BORINGSSL_FIPS - "with 2048-bit or 3072-bit keys are supported in FIPS mode" + "with 2048-bit, 3072-bit or 4096-bit keys are supported in FIPS mode" #else "with 2048-bit or larger keys are supported" #endif @@ -1274,8 +1274,7 @@ TEST_F(ClientContextConfigImplTest, RSA3072Cert) { manager.createSslClientContext(store, client_context_config, nullptr); } -// Validate that 4096-bit RSA certificates load successfully in non-FIPS builds, but are rejected -// in FIPS builds. +// Validate that 4096-bit RSA certificates load successfully. TEST_F(ClientContextConfigImplTest, RSA4096Cert) { envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; const std::string tls_certificate_yaml = R"EOF( @@ -1290,14 +1289,7 @@ TEST_F(ClientContextConfigImplTest, RSA4096Cert) { Event::SimulatedTimeSystem time_system; ContextManagerImpl manager(time_system); Stats::IsolatedStoreImpl store; -#ifdef BORINGSSL_FIPS - EXPECT_THROW_WITH_REGEX( - manager.createSslClientContext(store, client_context_config, nullptr), EnvoyException, - "Failed to load certificate chain from .*selfsigned_rsa_4096_cert.pem, only RSA certificates " - "with 2048-bit or 3072-bit keys are supported in FIPS mode"); -#else manager.createSslClientContext(store, client_context_config, nullptr); -#endif } // Validate that P256 ECDSA certs load. @@ -1656,62 +1648,6 @@ TEST_F(ClientContextConfigImplTest, MissingStaticCertificateValidationContext) { "Unknown static certificate validation context: missing"); } -TEST_F(ClientContextConfigImplTest, ValidationContextEntityNotExist) { - envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; - auto* validation_context_sds_secret_config = - tls_context.mutable_common_tls_context()->mutable_validation_context_sds_secret_config(); - validation_context_sds_secret_config->set_name("sds_validation_context"); - auto* config_source = validation_context_sds_secret_config->mutable_sds_config(); - auto* api_config_source = config_source->mutable_api_config_source(); - api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); - - NiceMock local_info; - Stats::IsolatedStoreImpl stats; - NiceMock init_manager; - NiceMock dispatcher; - EXPECT_CALL(factory_context_, localInfo()).WillOnce(ReturnRef(local_info)); - EXPECT_CALL(factory_context_, stats()).WillOnce(ReturnRef(stats)); - EXPECT_CALL(factory_context_, initManager()).WillRepeatedly(ReturnRef(init_manager)); - EXPECT_CALL(factory_context_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher)); - - ClientContextConfigImpl client_context_config(tls_context, factory_context_); - EXPECT_FALSE(client_context_config.isSecretReady()); - - NiceMock secret_callback; - client_context_config.setSecretUpdateCallback( - [&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); - client_context_config.setSecretUpdateCallback([]() {}); -} - -TEST_F(ClientContextConfigImplTest, TlsCertificateEntityNotExist) { - envoy::extensions::transport_sockets::tls::v3::SdsSecretConfig secret_config; - secret_config.set_name("sds_tls_certificate"); - auto* config_source = secret_config.mutable_sds_config(); - auto* api_config_source = config_source->mutable_api_config_source(); - api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); - envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; - auto tls_certificate_sds_secret_configs = - tls_context.mutable_common_tls_context()->mutable_tls_certificate_sds_secret_configs(); - *tls_certificate_sds_secret_configs->Add() = secret_config; - - NiceMock local_info; - Stats::IsolatedStoreImpl stats; - NiceMock init_manager; - NiceMock dispatcher; - EXPECT_CALL(factory_context_, localInfo()).WillOnce(ReturnRef(local_info)); - EXPECT_CALL(factory_context_, stats()).WillOnce(ReturnRef(stats)); - EXPECT_CALL(factory_context_, initManager()).WillRepeatedly(ReturnRef(init_manager)); - EXPECT_CALL(factory_context_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher)); - - ClientContextConfigImpl client_context_config(tls_context, factory_context_); - EXPECT_FALSE(client_context_config.isSecretReady()); - - NiceMock secret_callback; - client_context_config.setSecretUpdateCallback( - [&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); - client_context_config.setSecretUpdateCallback([]() {}); -} - class ServerContextConfigImplTest : public SslCertsTest {}; // Multiple TLS certificates are supported. 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 f9e76ee3024b..14a9c49370f6 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -192,12 +192,12 @@ class RawWriteSslIntegrationTest : public SslIntegrationTest { initialize(); // write_request_cb will write each of the items in request_chunks as a separate SSL_write. - auto write_request_cb = [&request_chunks](Network::ClientConnection& client) { + auto write_request_cb = [&request_chunks](Buffer::Instance& buffer) { if (!request_chunks.empty()) { - Buffer::OwnedImpl buffer(request_chunks.front()); - client.write(buffer, false); + buffer.add(request_chunks.front()); request_chunks.pop_front(); } + return false; }; auto client_transport_socket_factory_ptr = @@ -363,7 +363,7 @@ TEST_P(SslCertficateIntegrationTest, ServerEcdsa) { checkStats(); } -// Server with RSA/ECDSAs certificates and a client with RSA/ECDSA cipher suites works. +// Server with RSA/`ECDSAs` certificates and a client with RSA/ECDSA cipher suites works. TEST_P(SslCertficateIntegrationTest, ServerRsaEcdsa) { server_rsa_cert_ = true; server_ecdsa_cert_ = true; diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index d7718e17997b..403a08b61c3a 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -1015,7 +1015,8 @@ TEST_P(SslSocketTest, GetUriWithUriSan) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" - verify_subject_alt_name: "spiffe://lyft.com/test-team" + match_subject_alt_names: + exact: "spiffe://lyft.com/test-team" )EOF"; TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); @@ -1030,7 +1031,8 @@ TEST_P(SslSocketTest, Ipv4San) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem" - verify_subject_alt_name: "127.0.0.1" + match_subject_alt_names: + exact: "127.0.0.1" )EOF"; const std::string server_ctx_yaml = R"EOF( @@ -1053,7 +1055,8 @@ TEST_P(SslSocketTest, Ipv6San) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem" - verify_subject_alt_name: "::1" + match_subject_alt_names: + exact: "::1" )EOF"; const std::string server_ctx_yaml = R"EOF( @@ -1455,7 +1458,8 @@ TEST_P(SslSocketTest, FailedClientAuthSanVerificationNoClientCert) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" - verify_subject_alt_name: "example.com" + match_subject_alt_names: + exact: "example.com" )EOF"; TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); @@ -1482,7 +1486,8 @@ TEST_P(SslSocketTest, FailedClientAuthSanVerification) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" - verify_subject_alt_name: "example.com" + match_subject_alt_names: + exact: "example.com" )EOF"; TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); @@ -2540,6 +2545,200 @@ TEST_P(SslSocketTest, HalfClose) { dispatcher_->run(Event::Dispatcher::RunType::Block); } +TEST_P(SslSocketTest, ShutdownWithCloseNotify) { + 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/unittest_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_certificates.pem" +)EOF"; + + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context); + auto server_cfg = std::make_unique(server_tls_context, factory_context_); + ContextManagerImpl manager(time_system_); + Stats::TestUtil::TestStore server_stats_store; + ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, + server_stats_store, std::vector{}); + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); + Network::MockTcpListenerCallbacks listener_callbacks; + Network::MockConnectionHandler connection_handler; + Network::ListenerPtr listener = + dispatcher_->createListener(socket, listener_callbacks, true, ENVOY_TCP_BACKLOG_SIZE); + std::shared_ptr server_read_filter(new Network::MockReadFilter()); + std::shared_ptr client_read_filter(new Network::MockReadFilter()); + + const std::string client_ctx_yaml = R"EOF( + common_tls_context: + )EOF"; + + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), tls_context); + auto client_cfg = std::make_unique(tls_context, factory_context_); + Stats::TestUtil::TestStore client_stats_store; + ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, + client_stats_store); + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( + socket->localAddress(), Network::Address::InstanceConstSharedPtr(), + client_ssl_socket_factory.createTransportSocket(nullptr), nullptr); + Network::MockConnectionCallbacks client_connection_callbacks; + client_connection->enableHalfClose(true); + client_connection->addReadFilter(client_read_filter); + client_connection->addConnectionCallbacks(client_connection_callbacks); + client_connection->connect(); + + Network::ConnectionPtr server_connection; + Network::MockConnectionCallbacks server_connection_callbacks; + EXPECT_CALL(listener_callbacks, onAccept_(_)) + .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void { + server_connection = dispatcher_->createServerConnection( + std::move(socket), server_ssl_socket_factory.createTransportSocket(nullptr), + stream_info_); + server_connection->enableHalfClose(true); + server_connection->addReadFilter(server_read_filter); + server_connection->addConnectionCallbacks(server_connection_callbacks); + })); + EXPECT_CALL(*server_read_filter, onNewConnection()); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { + Buffer::OwnedImpl data("hello"); + server_connection->write(data, true); + EXPECT_EQ(data.length(), 0); + })); + + EXPECT_CALL(*client_read_filter, onNewConnection()) + .WillOnce(Return(Network::FilterStatus::Continue)); + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); + EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), true)) + .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus { + read_buffer.drain(read_buffer.length()); + client_connection->close(Network::ConnectionCloseType::NoFlush); + return Network::FilterStatus::StopIteration; + })); + EXPECT_CALL(*server_read_filter, onData(_, true)); + + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { + server_connection->close(Network::ConnectionCloseType::NoFlush); + dispatcher_->exit(); + })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_P(SslSocketTest, ShutdownWithoutCloseNotify) { + 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/unittest_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_certificates.pem" +)EOF"; + + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context); + auto server_cfg = std::make_unique(server_tls_context, factory_context_); + ContextManagerImpl manager(time_system_); + Stats::TestUtil::TestStore server_stats_store; + ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, + server_stats_store, std::vector{}); + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); + Network::MockTcpListenerCallbacks listener_callbacks; + Network::MockConnectionHandler connection_handler; + Network::ListenerPtr listener = + dispatcher_->createListener(socket, listener_callbacks, true, ENVOY_TCP_BACKLOG_SIZE); + std::shared_ptr server_read_filter(new Network::MockReadFilter()); + std::shared_ptr client_read_filter(new Network::MockReadFilter()); + + const std::string client_ctx_yaml = R"EOF( + common_tls_context: + )EOF"; + + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), tls_context); + auto client_cfg = std::make_unique(tls_context, factory_context_); + Stats::TestUtil::TestStore client_stats_store; + ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, + client_stats_store); + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( + socket->localAddress(), Network::Address::InstanceConstSharedPtr(), + client_ssl_socket_factory.createTransportSocket(nullptr), nullptr); + Network::MockConnectionCallbacks client_connection_callbacks; + client_connection->enableHalfClose(true); + client_connection->addReadFilter(client_read_filter); + client_connection->addConnectionCallbacks(client_connection_callbacks); + client_connection->connect(); + + Network::ConnectionPtr server_connection; + Network::MockConnectionCallbacks server_connection_callbacks; + EXPECT_CALL(listener_callbacks, onAccept_(_)) + .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void { + server_connection = dispatcher_->createServerConnection( + std::move(socket), server_ssl_socket_factory.createTransportSocket(nullptr), + stream_info_); + server_connection->enableHalfClose(true); + server_connection->addReadFilter(server_read_filter); + server_connection->addConnectionCallbacks(server_connection_callbacks); + })); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { + Buffer::OwnedImpl data("hello"); + server_connection->write(data, false); + EXPECT_EQ(data.length(), 0); + // Calling close(FlushWrite) in onEvent() callback results in PostIoAction::Close, + // after which the connection is closed without write ready event being delivered, + // and with all outstanding data (here, "hello") being lost. + })); + + EXPECT_CALL(*client_read_filter, onNewConnection()) + .WillOnce(Return(Network::FilterStatus::Continue)); + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); + EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), false)) + .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus { + read_buffer.drain(read_buffer.length()); + // Close without sending close_notify alert. + const SslHandshakerImpl* ssl_socket = + dynamic_cast(client_connection->ssl().get()); + EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete); + SSL_set_quiet_shutdown(ssl_socket->ssl(), 1); + client_connection->close(Network::ConnectionCloseType::FlushWrite); + return Network::FilterStatus::StopIteration; + })); + + EXPECT_CALL(*server_read_filter, onNewConnection()) + .WillOnce(Return(Network::FilterStatus::Continue)); + EXPECT_CALL(*server_read_filter, onData(BufferStringEqual(""), true)) + .WillOnce(Invoke([&](Buffer::Instance&, bool) -> Network::FilterStatus { + // Close without sending close_notify alert. + const SslHandshakerImpl* ssl_socket = + dynamic_cast(server_connection->ssl().get()); + EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete); + SSL_set_quiet_shutdown(ssl_socket->ssl(), 1); + server_connection->close(Network::ConnectionCloseType::NoFlush); + return Network::FilterStatus::StopIteration; + })); + + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + TEST_P(SslSocketTest, ClientAuthMultipleCAs) { const std::string server_ctx_yaml = R"EOF( common_tls_context: @@ -3684,6 +3883,11 @@ TEST_P(SslSocketTest, ProtocolVersions) { envoy::extensions::transport_sockets::tls::v3::TlsParameters* client_params = client.mutable_common_tls_context()->mutable_tls_params(); + // Note: There aren't any valid TLSv1.0 or TLSv1.1 cipher suites enabled by default, + // so enable them to avoid false positives. + client_params->add_cipher_suites("ECDHE-RSA-AES128-SHA"); + server_params->add_cipher_suites("ECDHE-RSA-AES128-SHA"); + // Connection using defaults (client & server) succeeds, negotiating TLSv1.2. TestUtilOptionsV2 tls_v1_2_test_options = createProtocolTestOptions(listener, client, GetParam(), "TLSv1.2"); @@ -3983,6 +4187,12 @@ TEST_P(SslSocketTest, CipherSuites) { client_params->clear_cipher_suites(); server_params->clear_cipher_suites(); + // Client connects to a server offering only deprecated cipher suites, connection fails. + server_params->add_cipher_suites("ECDHE-RSA-AES128-SHA"); + error_test_options.setExpectedServerStats("ssl.connection_error"); + testUtilV2(error_test_options); + server_params->clear_cipher_suites(); + // Verify that ECDHE-RSA-CHACHA20-POLY1305 is not offered by default in FIPS builds. client_params->add_cipher_suites(common_cipher_suite); #ifdef BORINGSSL_FIPS @@ -4458,7 +4668,22 @@ TEST_P(SslSocketTest, OverrideApplicationProtocols) { // in the config. server_ctx->add_alpn_protocols("test"); transport_socket_options = std::make_shared( - "", std::vector{}, std::vector{}, "test"); + "", std::vector{}, std::vector{}, std::vector{"test"}); + testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions( + transport_socket_options)); + + // With multiple fallbacks specified, a single match will match. + transport_socket_options = std::make_shared( + "", std::vector{}, std::vector{}, + std::vector{"foo", "test"}); + testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions( + transport_socket_options)); + + // With multiple matching fallbacks specified, a single match will match. + server_ctx->add_alpn_protocols("foo"); + transport_socket_options = std::make_shared( + "", std::vector{}, std::vector{}, + std::vector{"foo", "test"}); testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions( transport_socket_options)); @@ -4530,7 +4755,6 @@ TEST_P(SslSocketTest, UpstreamNotReadySslSocket) { EXPECT_FALSE(client_cfg->isReady()); ContextManagerImpl manager(time_system_); - ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store); auto transport_socket = client_ssl_socket_factory.createTransportSocket(nullptr); EXPECT_EQ(EMPTY_STRING, transport_socket->protocol()); @@ -5740,15 +5964,6 @@ TEST_P(SslSocketTest, TestConnectionFailsOnMultipleCertificatesNonePassOcspPolic testUtil(test_options.setExpectedServerStats("ssl.ocsp_staple_failed").enableOcspStapling()); } -TEST_P(SslSocketTest, ClientSocketFactoryIsReadyTest) { - ContextManagerImpl manager(time_system_); - Stats::TestUtil::TestStore stats_store; - auto client_cfg = std::make_unique>(); - EXPECT_CALL(*client_cfg, isSecretReady()).WillOnce(Return(true)); - ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store); - EXPECT_TRUE(client_ssl_socket_factory.isReady()); -} - } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/test/extensions/upstreams/http/tcp/upstream_request_test.cc b/test/extensions/upstreams/http/tcp/upstream_request_test.cc index ef3d08ca908a..d835151eab82 100644 --- a/test/extensions/upstreams/http/tcp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/tcp/upstream_request_test.cc @@ -117,7 +117,7 @@ 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)); - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Proxy the data. EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); @@ -154,7 +154,7 @@ TEST_F(TcpUpstreamTest, V1Header) { // encodeHeaders now results in the proxy proto header being sent. EXPECT_CALL(connection_, write(BufferEqual(&expected_data), false)); - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Data is proxied as usual. EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); @@ -177,7 +177,7 @@ TEST_F(TcpUpstreamTest, V2Header) { // encodeHeaders now results in the proxy proto header being sent. EXPECT_CALL(connection_, write(BufferEqual(&expected_data), false)); - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Data is proxied as usual. EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); @@ -187,7 +187,7 @@ TEST_F(TcpUpstreamTest, V2Header) { TEST_F(TcpUpstreamTest, TrailersEndStream) { // Swallow the headers. - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); EXPECT_CALL(connection_, write(BufferStringEqual(""), true)); Envoy::Http::TestRequestTrailerMapImpl trailers{{"foo", "bar"}}; @@ -196,7 +196,7 @@ TEST_F(TcpUpstreamTest, TrailersEndStream) { TEST_F(TcpUpstreamTest, HeaderEndStreamHalfClose) { EXPECT_CALL(connection_, write(BufferStringEqual(""), true)); - tcp_upstream_->encodeHeaders(request_, true); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, true).ok()); } TEST_F(TcpUpstreamTest, ReadDisable) { diff --git a/test/extensions/upstreams/tcp/generic/BUILD b/test/extensions/upstreams/tcp/generic/BUILD new file mode 100644 index 000000000000..e14b000d6056 --- /dev/null +++ b/test/extensions/upstreams/tcp/generic/BUILD @@ -0,0 +1,18 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + "//source/extensions/upstreams/tcp/generic:config", + "//test/mocks/upstream:upstream_mocks", + ], +) diff --git a/test/extensions/upstreams/tcp/generic/config_test.cc b/test/extensions/upstreams/tcp/generic/config_test.cc new file mode 100644 index 000000000000..9136d888ea72 --- /dev/null +++ b/test/extensions/upstreams/tcp/generic/config_test.cc @@ -0,0 +1,39 @@ +#include "extensions/upstreams/tcp/generic/config.h" + +#include "test/mocks/tcp/mocks.h" +#include "test/mocks/upstream/cluster_manager.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Tcp { +namespace Generic { + +class TcpConnPoolTest : public ::testing::Test { +public: + NiceMock cluster_manager_; + const std::string cluster_name_{"cluster_name"}; + GenericConnPoolFactory factory_; + NiceMock callbacks_; +}; + +TEST_F(TcpConnPoolTest, TestNoMatchingClusterName) { + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config; + config.set_hostname("host"); + EXPECT_CALL(cluster_manager_, get(_)).WillOnce(Return(nullptr)); + EXPECT_EQ(nullptr, factory_.createGenericConnPool(cluster_name_, cluster_manager_, config, + nullptr, callbacks_)); +} + +} // namespace Generic +} // namespace Tcp +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/test/integration/BUILD b/test/integration/BUILD index dfeba31c4d25..43dfa373074d 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -135,6 +135,7 @@ envoy_cc_test( envoy_cc_test( name = "eds_integration_test", srcs = ["eds_integration_test.cc"], + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in opt build test tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -318,7 +319,6 @@ envoy_cc_test( "http2_flood_integration_test.cc", ], shard_count = 4, - tags = ["flaky_on_windows"], deps = [ ":autonomous_upstream_lib", ":http_integration_lib", @@ -343,6 +343,7 @@ envoy_cc_test( "http2_integration_test.h", ], shard_count = 4, + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -372,7 +373,7 @@ envoy_cc_test( srcs = [ "http_subset_lb_integration_test.cc", ], - # Consistently times out in CI on Windows, but observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -434,6 +435,9 @@ 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 = 5, + # TODO(envoyproxy/windows-dev): Largely resolved by Level Events PR #13787 - diagose remaining rare failure; + # Protocols/DownstreamProtocolIntegrationTest.LargeRequestUrlRejected/IPv6_HttpDownstream_HttpUpstream + # test/integration/http_integration.cc(1035): error: Value of: response->complete() Actual: false Expected: true tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", @@ -443,6 +447,7 @@ envoy_cc_test( "//test/common/http/http2:http2_frame", "//test/integration/filters:continue_headers_only_inject_body", "//test/integration/filters:encoder_decoder_buffer_filter_lib", + "//test/integration/filters:invalid_header_filter_lib", "//test/integration/filters:local_reply_during_encoding_filter_lib", "//test/integration/filters:random_pause_filter_lib", "//test/test_common:utility_lib", @@ -458,6 +463,8 @@ envoy_cc_test( "http2_upstream_integration_test.cc", "http2_upstream_integration_test.h", ], + # TODO(envoyproxy/windows-dev): Diagnose test timeout observed in msvc-cl opt build + # in Http2UpstreamIntegrationTest.RouterUpstreamDisconnectBeforeResponseComplete/IPv4 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -565,7 +572,7 @@ envoy_cc_test( # 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 = 2, - # Consistently fails in CI on Windows, but observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", @@ -874,7 +881,7 @@ envoy_cc_test( "integration_test.h", ], shard_count = 2, - # Times out on Windows + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in clang-cl opt build test tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -885,6 +892,7 @@ envoy_cc_test( "//source/extensions/filters/http/health_check:config", "//test/integration/filters:clear_route_cache_filter_lib", "//test/integration/filters:encoder_decoder_buffer_filter_lib", + "//test/integration/filters:invalid_header_filter_lib", "//test/integration/filters:process_context_lib", "//test/integration/filters:stop_iteration_and_continue", "//test/mocks/http:http_mocks", @@ -921,7 +929,7 @@ envoy_cc_test( "websocket_integration_test.cc", "websocket_integration_test.h", ], - # Consistently fails in CI on Windows, but observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", @@ -984,7 +992,8 @@ envoy_cc_test( envoy_cc_test( name = "load_stats_integration_test", srcs = ["load_stats_integration_test.cc"], - # Consistently timing out on Windows, observed to pass locally + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in opt build test, hangs at + # IpVersionsClientType/LoadStatsIntegrationTest.NoLocalLocality/3 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -1007,7 +1016,7 @@ envoy_cc_test( "//test/config/integration/certs", ], shard_count = 2, - # Alternately timing out and failing in CI on windows; observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -1042,7 +1051,6 @@ envoy_cc_test( envoy_cc_test( name = "overload_integration_test", srcs = ["overload_integration_test.cc"], - tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", "//test/common/config:dummy_config_proto_cc_proto", @@ -1131,8 +1139,13 @@ envoy_cc_test( "sds_dynamic_integration_test.cc", ], data = [ + "sds_dynamic_key_rotation_setup.sh", "//test/config/integration/certs", ], + # TODO(envoyproxy/windows-dev): The key rotation in SdsDynamicKeyRotationIntegrationTest via + # TestEnvironment::renameFile() fails on Windows. The renameFile() implementation does not + # correctly handle symlinks. + tags = ["fails_on_windows"], deps = [ ":http_integration_lib", "//source/common/config:api_version_lib", @@ -1189,7 +1202,7 @@ envoy_cc_test( "//test/config/integration/certs", ], shard_count = 2, - # Fails intermittantly on local build + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in opt build test tags = ["flaky_on_windows"], deps = [ ":integration_lib", @@ -1225,13 +1238,14 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", ":http_protocol_integration_lib", "//source/extensions/filters/network/tcp_proxy:config", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/config/filter/network/tcp_proxy/v2:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], ) @@ -1305,6 +1319,7 @@ envoy_cc_test( "//test/config/integration:server_xds_files", "//test/config/integration/certs", ], + # TODO(envoyproxy/windows-dev): Diagnose test failure observed in opt build tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", diff --git a/test/integration/ads_integration.cc b/test/integration/ads_integration.cc index 7d81b1de0a1b..fc3d044807dd 100644 --- a/test/integration/ads_integration.cc +++ b/test/integration/ads_integration.cc @@ -34,8 +34,9 @@ AdsIntegrationTest::AdsIntegrationTest(const envoy::config::core::v3::ApiVersion void AdsIntegrationTest::TearDown() { cleanUpXdsConnection(); } -envoy::config::cluster::v3::Cluster AdsIntegrationTest::buildCluster(const std::string& name) { - return ConfigHelper::buildCluster(name, "ROUND_ROBIN", api_version_); +envoy::config::cluster::v3::Cluster AdsIntegrationTest::buildCluster(const std::string& name, + const std::string& lb_policy) { + return ConfigHelper::buildCluster(name, lb_policy, api_version_); } envoy::config::cluster::v3::Cluster AdsIntegrationTest::buildTlsCluster(const std::string& name) { @@ -74,7 +75,7 @@ AdsIntegrationTest::buildRedisListener(const std::string& name, const std::strin filters: - name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy settings: op_timeout: 1s stat_prefix: {} @@ -127,6 +128,9 @@ void AdsIntegrationTest::initializeAds(const bool rate_limiting) { ads_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(context); }); setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); + if (api_version_ == envoy::config::core::v3::ApiVersion::V2 && !fatal_by_default_v2_override_) { + config_helper_.enableDeprecatedV2Api(); + } HttpIntegrationTest::initialize(); if (xds_stream_ == nullptr) { createXdsConnection(); diff --git a/test/integration/ads_integration.h b/test/integration/ads_integration.h index 0da99aea566a..804b3b3f315e 100644 --- a/test/integration/ads_integration.h +++ b/test/integration/ads_integration.h @@ -18,11 +18,12 @@ namespace Envoy { class AdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public HttpIntegrationTest { public: AdsIntegrationTest(const envoy::config::core::v3::ApiVersion api_version); - AdsIntegrationTest() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V2) {} + AdsIntegrationTest() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V3) {} void TearDown() override; - envoy::config::cluster::v3::Cluster buildCluster(const std::string& name); + envoy::config::cluster::v3::Cluster buildCluster(const std::string& name, + const std::string& lb_policy = "ROUND_ROBIN"); envoy::config::cluster::v3::Cluster buildTlsCluster(const std::string& name); @@ -55,7 +56,10 @@ class AdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public Ht envoy::admin::v3::ListenersConfigDump getListenersConfigDump(); envoy::admin::v3::RoutesConfigDump getRoutesConfigDump(); + // If API version is v2, fatal-by-default is disabled unless fatal_by_default_v2_override_ is set. envoy::config::core::v3::ApiVersion api_version_; + // Set to force fatal-by-default v2 even if API version is v2. + bool fatal_by_default_v2_override_{false}; }; } // namespace Envoy diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 01aae9dc9f73..24468b45f20f 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -34,6 +34,140 @@ TEST_P(AdsIntegrationTest, Basic) { testBasicFlow(); } +// Basic CDS/EDS update that warms and makes active a single cluster. +TEST_P(AdsIntegrationTest, BasicClusterInitialWarming) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + eds_type_url, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Update the only warming cluster. Verify that the new cluster is still warming and the cluster +// manager as a whole is not initialized. +TEST_P(AdsIntegrationTest, ClusterInitializationUpdateTheOnlyWarmingCluster) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0", "MAGLEV")}, {buildCluster("cluster_0", "MAGLEV")}, + {}, "2", false); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + eds_type_url, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Primary cluster is warming during cluster initialization. Update the cluster with immediate ready +// config and verify that all the clusters are initialized. +TEST_P(AdsIntegrationTest, TestPrimaryClusterWarmClusterInitialization) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + auto loopback = Network::Test::getLoopbackAddressString(ipVersion()); + addFakeUpstream(FakeHttpConnection::Type::HTTP2); + auto port = fake_upstreams_.back()->localAddress()->ip()->port(); + + // This cluster will be blocked since endpoint name cannot be resolved. + auto warming_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); + // Below endpoint accepts request but never return. The health check hangs 1 hour which covers the + // test running. + auto blocking_health_check = TestUtility::parseYaml(R"EOF( + timeout: 3600s + interval: 3600s + unhealthy_threshold: 2 + healthy_threshold: 2 + tcp_health_check: + send: + text: '01' + receive: + - text: '02' + )EOF"); + *warming_cluster.add_health_checks() = blocking_health_check; + + // Active cluster has the same name with warming cluster but has no blocking health check. + auto active_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse(cds_type_url, {warming_cluster}, + {warming_cluster}, {}, "1", false); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_.back()->waitForRawConnection(fake_upstream_connection)); + + // fake_cluster is in warming. + test_server_->waitForGaugeGe("cluster_manager.warming_clusters", 1); + + // Now replace the warming cluster by the config which will turn ready immediately. + sendDiscoveryResponse(cds_type_url, {active_cluster}, + {active_cluster}, {}, "2", false); + + // All clusters are ready. + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Two cluster warming, update one of them. Verify that the clusters are eventually initialized. +TEST_P(AdsIntegrationTest, ClusterInitializationUpdateOneOfThe2Warming) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0"), buildCluster("cluster_1")}, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0"), buildCluster("cluster_1")}, + {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); + + // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. + sendDiscoveryResponse( + cds_type_url, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, + {}, "2", false); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0", "cluster_1"}, + {"cluster_0", "cluster_1"}, {})); + sendDiscoveryResponse( + eds_type_url, + {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, + {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, {}, "1", + false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); +} // Validate basic config delivery and upgrade with RateLimiting. TEST_P(AdsIntegrationTest, BasicWithRateLimiting) { initializeAds(true); @@ -419,8 +553,9 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { // Send the second warming cluster. sendDiscoveryResponse( - Config::TypeUrl::get().Cluster, {buildCluster("warming_cluster_2")}, - {buildCluster("warming_cluster_2")}, {}, "3"); + Config::TypeUrl::get().Cluster, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3"); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. @@ -452,6 +587,87 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { {"warming_cluster_2", "warming_cluster_1"}, {}, {})); } +TEST_P(AdsIntegrationTest, RemoveWarmingCluster) { + initialize(); + + // Send initial configuration, validate we can process a request. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("cluster_0")}, + {buildCluster("cluster_0")}, {}, "1"); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "", + {"cluster_0"}, {"cluster_0"}, {})); + + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "", {}, {}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildListener("listener_0", "route_config_0")}, + {buildListener("listener_0", "route_config_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"cluster_0"}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().RouteConfiguration, "", + {"route_config_0"}, {"route_config_0"}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, + {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "1", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().RouteConfiguration, "1", + {"route_config_0"}, {}, {})); + + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + makeSingleRequest(); + + // Send the first warming cluster. + sendDiscoveryResponse( + Config::TypeUrl::get().Cluster, {buildCluster("warming_cluster_1")}, + {buildCluster("warming_cluster_1")}, {"cluster_0"}, "2"); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"warming_cluster_1"}, {"warming_cluster_1"}, {"cluster_0"})); + + // Send the second warming cluster and remove the first cluster. + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_2")}, + // Delta: remove warming_cluster_1. + {"warming_cluster_1"}, "3"); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"warming_cluster_2"}, {"warming_cluster_2"}, + {"warming_cluster_1"})); + + // Finish warming the clusters. Note that the first warming cluster is not included in the + // response. + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, + {buildClusterLoadAssignment("warming_cluster_2")}, + {buildClusterLoadAssignment("warming_cluster_2")}, {"cluster_0"}, "2"); + + // Validate that all clusters are warmed. + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + + // CDS is resumed and EDS response was acknowledged. + if (sotw_or_delta_ == Grpc::SotwOrDelta::Delta) { + // Envoy will ACK both Cluster messages. Since they arrived while CDS was paused, they aren't + // sent until CDS is unpaused. Since version 3 has already arrived by the time the version 2 + // ACK goes out, they're both acknowledging version 3. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "3", {}, {}, {})); + } + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "3", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "2", + {"warming_cluster_2"}, {}, {})); +} // Validate that warming listeners are removed when left out of SOTW update. TEST_P(AdsIntegrationTest, RemoveWarmingListener) { initialize(); @@ -562,8 +778,9 @@ TEST_P(AdsIntegrationTest, ClusterWarmingOnNamedResponse) { // Send the second warming cluster. sendDiscoveryResponse( - Config::TypeUrl::get().Cluster, {buildCluster("warming_cluster_2")}, - {buildCluster("warming_cluster_2")}, {}, "3"); + Config::TypeUrl::get().Cluster, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3"); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", @@ -736,7 +953,7 @@ class AdsFailIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, : HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion(), ConfigHelper::adsBootstrap( sotwOrDelta() == Grpc::SotwOrDelta::Sotw ? "GRPC" : "DELTA_GRPC", - envoy::config::core::v3::ApiVersion::V2)) { + envoy::config::core::v3::ApiVersion::V3)) { create_xds_upstream_ = true; use_lds_ = false; sotw_or_delta_ = sotwOrDelta(); @@ -777,7 +994,7 @@ class AdsConfigIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, : HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion(), ConfigHelper::adsBootstrap( sotwOrDelta() == Grpc::SotwOrDelta::Sotw ? "GRPC" : "DELTA_GRPC", - envoy::config::core::v3::ApiVersion::V2)) { + envoy::config::core::v3::ApiVersion::V3)) { create_xds_upstream_ = true; use_lds_ = false; sotw_or_delta_ = sotwOrDelta(); @@ -939,7 +1156,7 @@ class AdsClusterFromFileIntegrationTest : public Grpc::DeltaSotwIntegrationParam : HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion(), ConfigHelper::adsBootstrap( sotwOrDelta() == Grpc::SotwOrDelta::Sotw ? "GRPC" : "DELTA_GRPC", - envoy::config::core::v3::ApiVersion::V2)) { + envoy::config::core::v3::ApiVersion::V3)) { create_xds_upstream_ = true; use_lds_ = false; sotw_or_delta_ = sotwOrDelta(); @@ -973,6 +1190,7 @@ class AdsClusterFromFileIntegrationTest : public Grpc::DeltaSotwIntegrationParam ads_eds_cluster->set_type(envoy::config::cluster::v3::Cluster::EDS); auto* eds_cluster_config = ads_eds_cluster->mutable_eds_cluster_config(); auto* eds_config = eds_cluster_config->mutable_eds_config(); + eds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); eds_config->mutable_ads(); }); setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); @@ -1033,6 +1251,7 @@ class AdsIntegrationTestWithRtds : public AdsIntegrationTest { rtds_layer->set_name("ads_rtds_layer"); auto* rtds_config = rtds_layer->mutable_rtds_config(); rtds_config->mutable_ads(); + rtds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); auto* ads_config = bootstrap.mutable_dynamic_resources()->mutable_ads_config(); ads_config->set_set_node_on_first_message_only(true); @@ -1081,6 +1300,8 @@ class AdsIntegrationTestWithRtdsAndSecondaryClusters : public AdsIntegrationTest eds_cluster->set_type(envoy::config::cluster::v3::Cluster::EDS); auto* eds_cluster_config = eds_cluster->mutable_eds_cluster_config(); eds_cluster_config->mutable_eds_config()->mutable_ads(); + eds_cluster_config->mutable_eds_config()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); }); AdsIntegrationTestWithRtds::initialize(); } @@ -1123,70 +1344,85 @@ TEST_P(AdsIntegrationTestWithRtdsAndSecondaryClusters, Basic) { testBasicFlow(); } -// Check if EDS cluster defined in file is loaded before ADS request and used as xDS server -class AdsClusterV3Test : public AdsIntegrationTest { +// Some v2 ADS integration tests, these validate basic v2 support but are not complete, they reflect +// tests that have historically been worth validating on both v2 and v3. They will be removed in Q1. +class AdsClusterV2Test : public AdsIntegrationTest { public: - AdsClusterV3Test() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V3) {} + AdsClusterV2Test() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V2) {} }; -INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, AdsClusterV3Test, +INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, AdsClusterV2Test, DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); -TEST_P(AdsClusterV3Test, BasicClusterInitialWarming) { +// Basic CDS/EDS update that warms and makes active a single cluster (v2 API). +TEST_P(AdsClusterV2Test, BasicClusterInitialWarming) { initialize(); const auto cds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse( - cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); sendDiscoveryResponse( eds_type_url, {buildClusterLoadAssignment("cluster_0")}, - {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + {buildClusterLoadAssignment("cluster_0")}, {}, "1", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); } +// If we attempt to use v2 APIs by default, the configuration should be rejected. +TEST_P(AdsClusterV2Test, RejectV2ConfigByDefault) { + fatal_by_default_v2_override_ = true; + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V2); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", true); + test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); +} + // Verify CDS is paused during cluster warming. -TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { +TEST_P(AdsClusterV2Test, CdsPausedDuringWarming) { initialize(); const auto cds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto lds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto rds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); // Send initial configuration, validate we can process a request. EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse( - cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); sendDiscoveryResponse( eds_type_url, {buildClusterLoadAssignment("cluster_0")}, - {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + {buildClusterLoadAssignment("cluster_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "1", {}, {}, {})); EXPECT_TRUE(compareDiscoveryRequest(lds_type_url, "", {}, {}, {})); sendDiscoveryResponse( lds_type_url, {buildListener("listener_0", "route_config_0")}, - {buildListener("listener_0", "route_config_0")}, {}, "1", false); + {buildListener("listener_0", "route_config_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "1", {"cluster_0"}, {}, {})); EXPECT_TRUE( compareDiscoveryRequest(rds_type_url, "", {"route_config_0"}, {"route_config_0"}, {})); sendDiscoveryResponse( rds_type_url, {buildRouteConfig("route_config_0", "cluster_0")}, - {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1", false); + {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(lds_type_url, "1", {}, {}, {})); EXPECT_TRUE(compareDiscoveryRequest(rds_type_url, "1", {"route_config_0"}, {}, {})); @@ -1197,7 +1433,7 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { // Send the first warming cluster. sendDiscoveryResponse( cds_type_url, {buildCluster("warming_cluster_1")}, {buildCluster("warming_cluster_1")}, - {"cluster_0"}, "2", false); + {"cluster_0"}, "2", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); @@ -1206,8 +1442,8 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { // Send the second warming cluster. sendDiscoveryResponse( - cds_type_url, {buildCluster("warming_cluster_2")}, {buildCluster("warming_cluster_2")}, {}, - "3", false); + cds_type_url, {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. @@ -1221,7 +1457,7 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { buildClusterLoadAssignment("warming_cluster_2")}, {buildClusterLoadAssignment("warming_cluster_1"), buildClusterLoadAssignment("warming_cluster_2")}, - {"cluster_0"}, "2", false); + {"cluster_0"}, "2", true); // Validate that clusters are warmed. test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); @@ -1239,7 +1475,7 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { } // Validates that the initial xDS request batches all resources referred to in static config -TEST_P(AdsClusterV3Test, XdsBatching) { +TEST_P(AdsClusterV2Test, XdsBatching) { config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { bootstrap.mutable_dynamic_resources()->clear_cds_config(); bootstrap.mutable_dynamic_resources()->clear_lds_config(); @@ -1259,9 +1495,9 @@ TEST_P(AdsClusterV3Test, XdsBatching) { const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto rds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"eds_cluster2", "eds_cluster"}, {"eds_cluster2", "eds_cluster"}, {}, true)); @@ -1269,7 +1505,7 @@ TEST_P(AdsClusterV3Test, XdsBatching) { eds_type_url, {buildClusterLoadAssignment("eds_cluster"), buildClusterLoadAssignment("eds_cluster2")}, {buildClusterLoadAssignment("eds_cluster"), buildClusterLoadAssignment("eds_cluster2")}, {}, - "1", false); + "1", true); EXPECT_TRUE(compareDiscoveryRequest(rds_type_url, "", {"route_config2", "route_config"}, {"route_config2", "route_config"}, {})); @@ -1279,10 +1515,27 @@ TEST_P(AdsClusterV3Test, XdsBatching) { buildRouteConfig("route_config", "dummy_cluster")}, {buildRouteConfig("route_config2", "eds_cluster2"), buildRouteConfig("route_config", "dummy_cluster")}, - {}, "1", false); + {}, "1", true); }; initialize(); } +// Regression test for https://github.com/envoyproxy/envoy/issues/13681. +TEST_P(AdsClusterV2Test, TypeUrlAnnotationRegression) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V2); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + auto cluster = buildCluster("cluster_0"); + auto* bias = cluster.mutable_least_request_lb_config()->mutable_active_request_bias(); + bias->set_default_value(1.1); + bias->set_runtime_key("foo"); + sendDiscoveryResponse(cds_type_url, {cluster}, {cluster}, {}, + "1", true); + + test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); +} + } // namespace Envoy diff --git a/test/integration/alpn_selection_integration_test.cc b/test/integration/alpn_selection_integration_test.cc index a576f51d3e16..3ca3964a049e 100644 --- a/test/integration/alpn_selection_integration_test.cc +++ b/test/integration/alpn_selection_integration_test.cc @@ -37,7 +37,7 @@ class AlpnSelectionIntegrationTest : public testing::Test, public HttpIntegratio R"EOF( name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: alpn_protocols: [ %s ] tls_certificates: diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 4a2623f43ae4..b5f8ddb1cf84 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -190,9 +190,10 @@ void BaseIntegrationTest::setUpstreamProtocol(FakeHttpConnection::Type protocol) IntegrationTcpClientPtr BaseIntegrationTest::makeTcpConnection(uint32_t port, - const Network::ConnectionSocket::OptionsSharedPtr& options) { + const Network::ConnectionSocket::OptionsSharedPtr& options, + Network::Address::InstanceConstSharedPtr source_address) { return std::make_unique(*dispatcher_, *mock_buffer_factory_, port, version_, - enable_half_close_, options); + enable_half_close_, options, source_address); } void BaseIntegrationTest::registerPort(const std::string& key, uint32_t port) { @@ -258,7 +259,7 @@ void BaseIntegrationTest::createGeneratedApiTestServer( test_server_ = IntegrationTestServer::create( bootstrap_path, version_, on_server_ready_function_, on_server_init_function_, deterministic_, timeSystem(), *api_, defer_listener_finalization_, process_object_, validator_config, - concurrency_, drain_time_, drain_strategy_, use_real_stats_); + concurrency_, drain_time_, drain_strategy_, use_real_stats_, v2_bootstrap_); if (config_helper_.bootstrap().static_resources().listeners_size() > 0 && !defer_listener_finalization_) { @@ -415,6 +416,22 @@ AssertionResult BaseIntegrationTest::compareDiscoveryRequest( } } +AssertionResult compareSets(const std::set& set1, const std::set& set2, + absl::string_view name) { + if (set1 == set2) { + return AssertionSuccess(); + } + auto failure = AssertionFailure() << name << " field not as expected.\nExpected: {"; + for (const auto& x : set1) { + failure << x << ", "; + } + failure << "}\nActual: {"; + for (const auto& x : set2) { + failure << x << ", "; + } + return failure << "}"; +} + AssertionResult BaseIntegrationTest::compareSotwDiscoveryRequest( const std::string& expected_type_url, const std::string& expected_version, const std::vector& expected_resource_names, bool expect_node, @@ -441,12 +458,12 @@ AssertionResult BaseIntegrationTest::compareSotwDiscoveryRequest( } EXPECT_TRUE( IsSubstring("", "", expected_error_substring, discovery_request.error_detail().message())); - const std::vector resource_names(discovery_request.resource_names().cbegin(), - discovery_request.resource_names().cend()); - if (expected_resource_names != resource_names) { - return AssertionFailure() << fmt::format( - "resources {} do not match expected {} in {}", absl::StrJoin(resource_names, ","), - absl::StrJoin(expected_resource_names, ","), discovery_request.DebugString()); + const std::set resource_names_in_request(discovery_request.resource_names().cbegin(), + discovery_request.resource_names().cend()); + if (auto resource_name_result = compareSets( + std::set(expected_resource_names.cbegin(), expected_resource_names.cend()), + resource_names_in_request, "Sotw resource names")) { + return resource_name_result; } if (expected_version != discovery_request.version_info()) { return AssertionFailure() << fmt::format("version {} does not match expected {} in {}", @@ -456,22 +473,6 @@ AssertionResult BaseIntegrationTest::compareSotwDiscoveryRequest( return AssertionSuccess(); } -AssertionResult compareSets(const std::set& set1, const std::set& set2, - absl::string_view name) { - if (set1 == set2) { - return AssertionSuccess(); - } - auto failure = AssertionFailure() << name << " field not as expected.\nExpected: {"; - for (const auto& x : set1) { - failure << x << ", "; - } - failure << "}\nActual: {"; - for (const auto& x : set2) { - failure << x << ", "; - } - return failure << "}"; -} - AssertionResult BaseIntegrationTest::waitForPortAvailable(uint32_t port, std::chrono::milliseconds timeout) { Event::TestTimeSystem::RealTimeBound bound(timeout); diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index be940cb85fa2..3fc85792b86d 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -86,7 +86,9 @@ class BaseIntegrationTest : protected Logger::Loggable { IntegrationTcpClientPtr makeTcpConnection(uint32_t port, - const Network::ConnectionSocket::OptionsSharedPtr& options = nullptr); + const Network::ConnectionSocket::OptionsSharedPtr& options = nullptr, + Network::Address::InstanceConstSharedPtr source_address = + Network::Address::InstanceConstSharedPtr()); // Test-wide port map. void registerPort(const std::string& key, uint32_t port); @@ -151,7 +153,7 @@ class BaseIntegrationTest : protected Logger::Loggable { void sendDiscoveryResponse(const std::string& type_url, const std::vector& state_of_the_world, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, - const bool api_downgrade = true) { + const bool api_downgrade = false) { if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw) { sendSotwDiscoveryResponse(type_url, state_of_the_world, version, api_downgrade); } else { @@ -187,7 +189,7 @@ class BaseIntegrationTest : protected Logger::Loggable { template void sendSotwDiscoveryResponse(const std::string& type_url, const std::vector& messages, - const std::string& version, const bool api_downgrade = true) { + const std::string& version, const bool api_downgrade = false) { API_NO_BOOST(envoy::api::v2::DiscoveryResponse) discovery_response; discovery_response.set_version_info(version); discovery_response.set_type_url(type_url); @@ -207,7 +209,7 @@ class BaseIntegrationTest : protected Logger::Loggable { void sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, - const std::string& version, const bool api_downgrade = true) { + const std::string& version, const bool api_downgrade = false) { sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, xds_stream_, {}, api_downgrade); } @@ -216,7 +218,7 @@ class BaseIntegrationTest : protected Logger::Loggable { sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, FakeStreamPtr& stream, const std::vector& aliases = {}, - const bool api_downgrade = true) { + const bool api_downgrade = false) { auto response = createDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, aliases, api_downgrade); stream->sendGrpcMessage(response); @@ -227,7 +229,7 @@ class BaseIntegrationTest : protected Logger::Loggable { createDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, const std::vector& aliases, - const bool api_downgrade = true) { + const bool api_downgrade = false) { API_NO_BOOST(envoy::api::v2::DeltaDiscoveryResponse) response; response.set_system_version_info("system_version_info_this_is_a_test"); @@ -242,7 +244,7 @@ class BaseIntegrationTest : protected Logger::Loggable { temp_any.PackFrom(message); resource->mutable_resource()->PackFrom(message); } - resource->set_name(TestUtility::xdsResourceName(temp_any)); + resource->set_name(intResourceName(message)); resource->set_version(version); for (const auto& alias : aliases) { resource->add_aliases(alias); @@ -255,6 +257,17 @@ class BaseIntegrationTest : protected Logger::Loggable { } private: + std::string intResourceName(const envoy::config::listener::v3::Listener& m) { return m.name(); } + std::string intResourceName(const envoy::config::route::v3::RouteConfiguration& m) { + return m.name(); + } + std::string intResourceName(const envoy::config::cluster::v3::Cluster& m) { return m.name(); } + std::string intResourceName(const envoy::config::endpoint::v3::ClusterLoadAssignment& m) { + return m.cluster_name(); + } + std::string intResourceName(const envoy::config::route::v3::VirtualHost& m) { return m.name(); } + std::string intResourceName(const envoy::service::runtime::v3::Runtime& m) { return m.name(); } + Event::GlobalTimeSystem time_system_; public: @@ -436,6 +449,9 @@ class BaseIntegrationTest : protected Logger::Loggable { // This override exists for tests measuring stats memory. bool use_real_stats_{}; + // Use a v2 bootstrap. + bool v2_bootstrap_{false}; + private: // The type for the Envoy-to-backend connection FakeHttpConnection::Type upstream_protocol_{FakeHttpConnection::Type::HTTP1}; diff --git a/test/integration/cds_integration_test.cc b/test/integration/cds_integration_test.cc index 2fd76b992274..08e32d2a392c 100644 --- a/test/integration/cds_integration_test.cc +++ b/test/integration/cds_integration_test.cc @@ -196,7 +196,7 @@ TEST_P(CdsIntegrationTest, TwoClusters) { // Tell Envoy that cluster_1 is gone. EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); sendDiscoveryResponse(Config::TypeUrl::get().Cluster, - {cluster2_}, {}, {ClusterName1}, "42"); + {cluster2_}, {}, {ClusterName1}, "43"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); @@ -207,7 +207,7 @@ TEST_P(CdsIntegrationTest, TwoClusters) { ASSERT_TRUE(codec_client_->waitForDisconnect()); // Tell Envoy that cluster_1 is back. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "43", {}, {}, {})); sendDiscoveryResponse( Config::TypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_}, {}, "413"); diff --git a/test/integration/custom_cluster_integration_test.cc b/test/integration/custom_cluster_integration_test.cc index 62d5cac5ab9f..84c936e006c5 100644 --- a/test/integration/custom_cluster_integration_test.cc +++ b/test/integration/custom_cluster_integration_test.cc @@ -69,10 +69,10 @@ TEST_P(CustomClusterIntegrationTest, TestCustomConfig) { initialize(); // Verify the cluster is correctly setup with the custom priority - const auto& cluster_map = test_server_->server().clusterManager().clusters(); - EXPECT_EQ(1, cluster_map.size()); - EXPECT_EQ(1, cluster_map.count("cluster_0")); - const auto& cluster_ref = cluster_map.find("cluster_0")->second; + const auto& cluster_maps = test_server_->server().clusterManager().clusters(); + EXPECT_EQ(1, cluster_maps.active_clusters_.size()); + EXPECT_EQ(1, cluster_maps.active_clusters_.count("cluster_0")); + const auto& cluster_ref = cluster_maps.active_clusters_.find("cluster_0")->second; const auto& hostset_per_priority = cluster_ref.get().prioritySet().hostSetsPerPriority(); EXPECT_EQ(11, hostset_per_priority.size()); const Envoy::Upstream::HostSetPtr& host_set = hostset_per_priority[10]; diff --git a/test/integration/eds_integration_test.cc b/test/integration/eds_integration_test.cc index 3e1c237e7c59..b8ca3c6f07c2 100644 --- a/test/integration/eds_integration_test.cc +++ b/test/integration/eds_integration_test.cc @@ -316,9 +316,9 @@ TEST_P(EdsIntegrationTest, OverprovisioningFactorUpdate) { setEndpoints(4, 4, 0); auto get_and_compare = [this](const uint32_t expected_factor) { const auto& cluster_map = test_server_->server().clusterManager().clusters(); - EXPECT_EQ(1, cluster_map.size()); - EXPECT_EQ(1, cluster_map.count("cluster_0")); - const auto& cluster_ref = cluster_map.find("cluster_0")->second; + EXPECT_EQ(1, cluster_map.active_clusters_.size()); + EXPECT_EQ(1, cluster_map.active_clusters_.count("cluster_0")); + const auto& cluster_ref = cluster_map.active_clusters_.find("cluster_0")->second; const auto& hostset_per_priority = cluster_ref.get().prioritySet().hostSetsPerPriority(); EXPECT_EQ(1, hostset_per_priority.size()); const Envoy::Upstream::HostSetPtr& host_set = hostset_per_priority[0]; @@ -340,7 +340,7 @@ TEST_P(EdsIntegrationTest, BatchMemberUpdateCb) { auto& priority_set = test_server_->server() .clusterManager() .clusters() - .find("cluster_0") + .active_clusters_.find("cluster_0") ->second.get() .prioritySet(); diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index a4e33d1a7fc8..091edf70478d 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -513,6 +513,12 @@ class FakeRawConnection : public FakeConnectionBase { }; } + // Creates a ValidatorFunction which returns true when data_to_wait_for is + // contains at least bytes_read bytes. + static ValidatorFunction waitForAtLeastBytes(uint32_t bytes) { + return [bytes](const std::string& data) -> bool { return data.size() >= bytes; }; + } + private: struct ReadFilter : public Network::ReadFilterBaseImpl { ReadFilter(FakeRawConnection& parent) : parent_(parent) {} diff --git a/test/integration/filters/BUILD b/test/integration/filters/BUILD index 372a4c0c359e..8e25cc2c1622 100644 --- a/test/integration/filters/BUILD +++ b/test/integration/filters/BUILD @@ -373,3 +373,19 @@ envoy_cc_test_library( "@envoy_api//envoy/extensions/network/socket_interface/v3:pkg_cc_proto", ], ) + +envoy_cc_test_library( + name = "invalid_header_filter_lib", + srcs = [ + "invalid_header_filter.cc", + ], + deps = [ + ":common_lib", + "//include/envoy/http:filter_interface", + "//include/envoy/registry", + "//include/envoy/server:filter_config_interface", + "//source/common/http:header_utility_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "//test/extensions/filters/http/common:empty_http_filter_config_lib", + ], +) diff --git a/test/integration/filters/common.h b/test/integration/filters/common.h index 7021ad09139b..9c33c034ec6b 100644 --- a/test/integration/filters/common.h +++ b/test/integration/filters/common.h @@ -9,7 +9,7 @@ namespace Envoy { -// DRYs up the creation of a simple filter config for a filter that requires no config. +// `DRYs` up the creation of a simple filter config for a filter that requires no config. template class SimpleFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { public: diff --git a/test/integration/filters/invalid_header_filter.cc b/test/integration/filters/invalid_header_filter.cc new file mode 100644 index 000000000000..7c1120356662 --- /dev/null +++ b/test/integration/filters/invalid_header_filter.cc @@ -0,0 +1,46 @@ +#include + +#include "envoy/http/filter.h" +#include "envoy/registry/registry.h" +#include "envoy/server/filter_config.h" + +#include "common/http/header_utility.h" + +#include "extensions/filters/http/common/pass_through_filter.h" + +#include "test/extensions/filters/http/common/empty_http_filter_config.h" +#include "test/integration/filters/common.h" + +namespace Envoy { + +// Faulty filter that may remove critical headers. +class InvalidHeaderFilter : public Http::PassThroughFilter { +public: + constexpr static char name[] = "invalid-header-filter"; + + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override { + // Remove method when there is a "remove-method" header. + if (!headers.get(Http::LowerCaseString("remove-method")).empty()) { + headers.removeMethod(); + } + if (!headers.get(Http::LowerCaseString("remove-path")).empty()) { + headers.removePath(); + } + if (Http::HeaderUtility::isConnect(headers)) { + headers.removeHost(); + } + if (!headers.get(Http::LowerCaseString("send-reply")).empty()) { + decoder_callbacks_->sendLocalReply(Envoy::Http::Code::OK, "", nullptr, absl::nullopt, + "InvalidHeaderFilter ready"); + return Http::FilterHeadersStatus::StopIteration; + } + return Http::FilterHeadersStatus::Continue; + } +}; + +constexpr char InvalidHeaderFilter::name[]; +static Registry::RegisterFactory, + Server::Configuration::NamedHttpFilterConfigFactory> + decoder_register_; + +} // namespace Envoy diff --git a/test/integration/filters/set_response_code_filter.cc b/test/integration/filters/set_response_code_filter.cc index 701ef857bbf3..e9964b8a58e5 100644 --- a/test/integration/filters/set_response_code_filter.cc +++ b/test/integration/filters/set_response_code_filter.cc @@ -18,14 +18,13 @@ class SetResponseCodeFilterConfig { public: SetResponseCodeFilterConfig(const std::string& prefix, uint32_t code, const std::string& body, Server::Configuration::FactoryContext& context) - : prefix_(prefix), code_(code), body_(body), tls_slot_(context.threadLocal().allocateSlot()) { - } + : prefix_(prefix), code_(code), body_(body), tls_slot_(context.threadLocal()) {} const std::string prefix_; const uint32_t code_; const std::string body_; // Allocate a slot to validate that it is destroyed on a main thread only. - ThreadLocal::SlotPtr tls_slot_; + ThreadLocal::TypedSlot<> tls_slot_; }; class SetResponseCodeFilter : public Http::PassThroughFilter { diff --git a/test/integration/h1_corpus/alloc_headers b/test/integration/h1_corpus/alloc_headers new file mode 100644 index 000000000000..3e8a5e62f1a8 --- /dev/null +++ b/test/integration/h1_corpus/alloc_headers @@ -0,0 +1,12 @@ +events { + downstream_send_bytes: "POST /test/long/urlaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaattttttaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa448aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HTTP/1.1\r\nhost: host\r\nx-lyft-user-id: 0\r\nx-forwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarded-for: -1113144117.0.0.1\r\ntransfer-encoding: chunked\r\n\r\n400\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\344aaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} +events { + upstream_send_bytes: "HTTP/1.1 454 \002\002\002\002\002\002\002\002\002\002\002OK\r\ntransfer-encoding: chunked\r\n\r\n200\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} +events { + upstream_send_bytes: "HTTP/1.1 654 \002\002\002\002\002\002\002\002\002\002\002OK\r\ntransfer-encoding: chunked\r\n\r\n200\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} +events { + upstream_send_bytes: "HTTP/1.1 454 \002\002\002\002\002\002\002\002\002\002\002OK\r\ntransfer-encoding: chunked\r\n\r\n200\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} diff --git a/test/integration/h2_capture_fuzz.proto b/test/integration/h2_capture_fuzz.proto index 3943c3f7293e..d22d02ca0b85 100644 --- a/test/integration/h2_capture_fuzz.proto +++ b/test/integration/h2_capture_fuzz.proto @@ -132,6 +132,12 @@ message H2FrameMetadata { Metadata metadata = 3; } +// A header that contains an arbitrary status +message H2FrameStatus { + bytes status = 1; + uint32 stream_index = 2; +} + message H2TestFrame { // These values map to the frame creation methods in: // test/common/http/http2/http2_frame.h @@ -151,6 +157,7 @@ message H2TestFrame { H2FrameRequest request = 13; H2FramePostRequest post_request = 14; H2FrameGeneric generic = 15; + H2FrameStatus status = 16; H2FrameMetadata metadata = 77; } } diff --git a/test/integration/h2_corpus/status_test_overflowed_status b/test/integration/h2_corpus/status_test_overflowed_status new file mode 100644 index 000000000000..24bd81262da0 --- /dev/null +++ b/test/integration/h2_corpus/status_test_overflowed_status @@ -0,0 +1,45 @@ +events { + downstream_send_event { + h2_frames { + settings { + flags: NONE + } + } + h2_frames { + settings { + flags: ACK + } + } + h2_frames { + request { + stream_index: 1 + host: "host" + path: "/path/to/long/url" + } + } + } +} +events { + upstream_send_event { + h2_frames { + settings { + flags: NONE + } + } + h2_frames { + settings { + flags: ACK + } + } + } +} +events { + upstream_send_event { + h2_frames { + status { + value: 11111111111111111111111111111111111 + } + stream_index: 0 + } + } +} diff --git a/test/integration/h2_fuzz.cc b/test/integration/h2_fuzz.cc index cbaefd5e1617..ba3c39adfc31 100644 --- a/test/integration/h2_fuzz.cc +++ b/test/integration/h2_fuzz.cc @@ -146,6 +146,13 @@ void H2FuzzIntegrationTest::sendFrame(const test::integration::H2TestFrame& prot Http2Frame::makeMetadataFrameFromMetadataMap(stream_idx, metadata_map, metadata_flags); break; } + case test::integration::H2TestFrame::kStatus: { + const std::string status = proto_frame.status().status(); + const uint32_t stream_idx = proto_frame.status().stream_index(); + ENVOY_LOG_MISC(trace, "Sending status frame"); + h2_frame = Http2Frame::makeHeadersFrameWithStatus(status, stream_idx); + break; + } case test::integration::H2TestFrame::kGeneric: { const absl::string_view frame_bytes = proto_frame.generic().frame_bytes(); ENVOY_LOG_MISC(trace, "Sending generic frame"); diff --git a/test/integration/hds_integration_test.cc b/test/integration/hds_integration_test.cc index 9f49ed87f0b2..cd8a5027c4c1 100644 --- a/test/integration/hds_integration_test.cc +++ b/test/integration/hds_integration_test.cc @@ -171,7 +171,7 @@ class HdsIntegrationTest : public Grpc::VersionedGrpcClientIntegrationParamTest, transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "%s" } diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index a7a837fe7e5e..40437b8ab8c1 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -217,6 +217,7 @@ class HeaderIntegrationTest type: EDS eds_cluster_config: eds_config: + resource_api_version: V3 api_config_source: api_type: GRPC grpc_services: @@ -382,7 +383,7 @@ class HeaderIntegrationTest discovery_response.set_type_url(Config::TypeUrl::get().ClusterLoadAssignment); auto cluster_load_assignment = - TestUtility::parseYaml(fmt::format( + TestUtility::parseYaml(fmt::format( R"EOF( cluster_name: cluster_0 endpoints: diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index f54735c5f45e..d43643e122c1 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -100,11 +100,13 @@ class Http2FloodMitigationTest : public SocketInterfaceSwap, } protected: + bool initializeUpstreamFloodTest(); std::vector serializeFrames(const Http2Frame& frame, uint32_t num_frames); void floodServer(const Http2Frame& frame, const std::string& flood_stat, uint32_t num_frames); void floodServer(absl::string_view host, absl::string_view path, Http2Frame::ResponseStatus expected_http_status, const std::string& flood_stat, uint32_t num_frames); + void floodClient(const Http2Frame& frame, uint32_t num_frames, const std::string& flood_stat); void setNetworkConnectionBufferSize(); void beginSession() override; @@ -116,6 +118,21 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FloodMitigationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); +bool Http2FloodMitigationTest::initializeUpstreamFloodTest() { + if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { + // Upstream flood checks are not implemented in the old codec based on exceptions + return false; + } + config_helper_.addRuntimeOverride("envoy.reloadable_features.upstream_http2_flood_checks", + "true"); + setDownstreamProtocol(Http::CodecClient::Type::HTTP2); + setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); + // set lower upstream outbound frame limits to make tests run faster + config_helper_.setUpstreamOutboundFramesLimits(AllFrameFloodLimit, ControlFrameFloodLimit); + initialize(); + return true; +} + void Http2FloodMitigationTest::setNetworkConnectionBufferSize() { // nghttp2 library has its own internal mitigation for outbound control frames (see // NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM). The default nghttp2 mitigation threshold of 1K is modified @@ -176,6 +193,34 @@ void Http2FloodMitigationTest::floodServer(const Http2Frame& frame, const std::s test_server_->waitForCounterGe("http.config_test.downstream_cx_delayed_close_timeout", 1); } +// Send header only request, flood client, and verify that the upstream is disconnected and client +// receives 503. +void Http2FloodMitigationTest::floodClient(const Http2Frame& frame, uint32_t num_frames, + const std::string& flood_stat) { + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(); + + // Make Envoy's writes into the upstream connection to return EAGAIN + writev_matcher_->setSourcePort( + fake_upstream_connection_->connection().remoteAddress()->ip()->port()); + + auto buf = serializeFrames(frame, num_frames); + + writev_matcher_->setWritevReturnsEgain(); + auto* upstream = fake_upstreams_.front().get(); + ASSERT_TRUE(upstream->rawWriteConnection(0, std::string(buf.begin(), buf.end()))); + + // Upstream connection should be disconnected + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + // Downstream client should receive 503 since upstream did not send response headers yet + response->waitForEndStream(); + EXPECT_EQ("503", response->headers().getStatusValue()); + if (!flood_stat.empty()) { + EXPECT_EQ(1, test_server_->counter(flood_stat)->value()); + } +} + // Verify that the server detects the flood using specified request parameters. void Http2FloodMitigationTest::floodServer(absl::string_view host, absl::string_view path, Http2Frame::ResponseStatus expected_http_status, @@ -1064,38 +1109,57 @@ TEST_P(Http2FloodMitigationTest, ZerolenHeaderAllowed) { } TEST_P(Http2FloodMitigationTest, UpstreamPingFlood) { - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - // Upstream flood checks are not implemented in the old codec based on exceptions + if (!initializeUpstreamFloodTest()) { + return; + } + + floodClient(Http2Frame::makePingFrame(), ControlFrameFloodLimit + 1, + "cluster.cluster_0.http2.outbound_control_flood"); +} + +TEST_P(Http2FloodMitigationTest, UpstreamSettings) { + if (!initializeUpstreamFloodTest()) { + return; + } + + floodClient(Http2Frame::makeEmptySettingsFrame(), ControlFrameFloodLimit + 1, + "cluster.cluster_0.http2.outbound_control_flood"); +} + +TEST_P(Http2FloodMitigationTest, UpstreamWindowUpdate) { + if (!initializeUpstreamFloodTest()) { + return; + } + + floodClient(Http2Frame::makeWindowUpdateFrame(0, 1), 4, + "cluster.cluster_0.http2.inbound_window_update_frames_flood"); +} + +TEST_P(Http2FloodMitigationTest, UpstreamEmptyHeaders) { + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + cluster->mutable_http2_protocol_options() + ->mutable_max_consecutive_inbound_frames_with_empty_payload() + ->set_value(0); + }); + if (!initializeUpstreamFloodTest()) { return; } - config_helper_.addRuntimeOverride("envoy.reloadable_features.upstream_http2_flood_checks", - "true"); - setDownstreamProtocol(Http::CodecClient::Type::HTTP2); - setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); - // set lower upstream outbound frame limits to make tests run faster - config_helper_.setUpstreamOutboundFramesLimits(AllFrameFloodLimit, ControlFrameFloodLimit); - initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); waitForNextUpstreamRequest(); auto* upstream = fake_upstreams_.front().get(); - // Make Envoy's writes into the upstream connection to return EAGAIN - writev_matcher_->setSourcePort( - fake_upstream_connection_->connection().remoteAddress()->ip()->port()); - - auto buf = serializeFrames(Http2Frame::makePingFrame(), ControlFrameFloodLimit + 1); - - writev_matcher_->setWritevReturnsEgain(); + auto buf = Http2Frame::makeEmptyHeadersFrame(Http2Frame::makeClientStreamId(0), + Http2Frame::HeadersFlags::None); ASSERT_TRUE(upstream->rawWriteConnection(0, std::string(buf.begin(), buf.end()))); - // Upstream connection should be disconnected - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - // Downstream client should receive 503 since upstream did not send response headers yet response->waitForEndStream(); EXPECT_EQ("503", response->headers().getStatusValue()); - EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.http2.outbound_control_flood")->value()); + EXPECT_EQ(1, + test_server_->counter("cluster.cluster_0.http2.inbound_empty_frames_flood")->value()); } } // namespace Envoy diff --git a/test/integration/http2_upstream_integration_test.cc b/test/integration/http2_upstream_integration_test.cc index 30a193b83bc3..7079efb41834 100644 --- a/test/integration/http2_upstream_integration_test.cc +++ b/test/integration/http2_upstream_integration_test.cc @@ -333,13 +333,13 @@ TEST_P(Http2UpstreamIntegrationTest, HittingEncoderFilterLimitForGrpc) { const std::string yaml_string = fmt::format(R"EOF( name: router typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.router.v2.Router + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router upstream_log: name: accesslog filter: not_health_check_filter: {{}} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: {} )EOF", Platform::null_device_path); @@ -458,13 +458,13 @@ TEST_P(Http2UpstreamIntegrationTest, ConfigureHttpOverGrpcLogs) { const std::string yaml_string = R"EOF( name: router typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.router.v2.Router + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router upstream_log: name: grpc_accesslog filter: not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.HttpGrpcAccessLogConfig + "@type": type.googleapis.com/envoy.extensions.access_loggers.grpc.v3.HttpGrpcAccessLogConfig common_config: log_name: foo grpc_service: diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 6ba9e29461bf..198ca37278d1 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -87,7 +87,7 @@ IntegrationCodecClient::makeHeaderOnlyRequest(const Http::RequestHeaderMap& head auto response = std::make_unique(dispatcher_); Http::RequestEncoder& encoder = newStream(*response); encoder.getStream().addCallbacks(*response); - encoder.encodeHeaders(headers, true); + encoder.encodeHeaders(headers, true).IgnoreError(); flushWrite(); return response; } @@ -104,7 +104,7 @@ IntegrationCodecClient::makeRequestWithBody(const Http::RequestHeaderMap& header auto response = std::make_unique(dispatcher_); Http::RequestEncoder& encoder = newStream(*response); encoder.getStream().addCallbacks(*response); - encoder.encodeHeaders(headers, false); + encoder.encodeHeaders(headers, false).IgnoreError(); Buffer::OwnedImpl data(body); encoder.encodeData(data, true); flushWrite(); @@ -155,7 +155,7 @@ IntegrationCodecClient::startRequest(const Http::RequestHeaderMap& headers) { auto response = std::make_unique(dispatcher_); Http::RequestEncoder& encoder = newStream(*response); encoder.getStream().addCallbacks(*response); - encoder.encodeHeaders(headers, false); + encoder.encodeHeaders(headers, false).IgnoreError(); flushWrite(); return {encoder, std::move(response)}; } @@ -829,7 +829,9 @@ void HttpIntegrationTest::testGrpcRetry() { } void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_from_upstream, - const std::string& via) { + const std::string& via, + bool disconnect_after_100) { + useAccessLog("%RESPONSE_CODE%"); initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -865,6 +867,15 @@ void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_ upstream_request_->encode100ContinueHeaders( Http::TestResponseHeaderMapImpl{{":status", "100"}}); } + + if (disconnect_after_100) { + response->waitForContinueHeaders(); + codec_client_->close(); + ASSERT_TRUE(fake_upstream_connection_->close()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("100")); + return; + } + upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(12, true); @@ -879,6 +890,7 @@ void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_ } else { EXPECT_EQ(via.c_str(), response->headers().getViaValue()); } + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("200")); } void HttpIntegrationTest::testEnvoyProxying1xx(bool continue_before_upstream_complete, diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index ae7652d59107..cf8afa9cfa6d 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -216,7 +216,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testGrpcRetry(); void testEnvoyHandling100Continue(bool additional_continue_from_upstream = false, - const std::string& via = ""); + const std::string& via = "", bool disconnect_after_100 = false); void testEnvoyProxying1xx(bool continue_before_upstream_complete = false, bool with_encoder_filter = false, bool with_multiple_1xx_headers = false); diff --git a/test/integration/http_timeout_integration_test.cc b/test/integration/http_timeout_integration_test.cc index 77ea916b467a..c2df233dd2ca 100644 --- a/test/integration/http_timeout_integration_test.cc +++ b/test/integration/http_timeout_integration_test.cc @@ -4,6 +4,8 @@ namespace Envoy { +using testing::HasSubstr; + INSTANTIATE_TEST_SUITE_P(IpVersions, HttpTimeoutIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); @@ -499,4 +501,46 @@ void HttpTimeoutIntegrationTest::testRouterRequestAndResponseWithHedgedPerTryTim EXPECT_EQ("200", response->headers().getStatusValue()); } +// Starts a request with a header timeout specified, sleeps for longer than the +// timeout, and ensures that a timeout is received. +TEST_P(HttpTimeoutIntegrationTest, RequestHeaderTimeout) { + if (downstreamProtocol() != Http::CodecClient::Type::HTTP1) { + // This test requires that the downstream be using HTTP1. + return; + } + + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + auto* request_headers_timeout = hcm.mutable_request_headers_timeout(); + request_headers_timeout->set_seconds(1); + request_headers_timeout->set_nanos(0); + }); + initialize(); + + const std::string input_request = ("GET / HTTP/1.1\r\n" + // Omit trailing \r\n that would indicate the end of headers. + "Host: localhost\r\n"); + std::string response; + + auto connection_driver = createConnectionDriver( + lookupPort("http"), input_request, + [&response](Network::ClientConnection&, const Buffer::Instance& data) -> void { + response.append(data.toString()); + }); + + while (!connection_driver->allBytesSent()) { + connection_driver->run(Event::Dispatcher::RunType::NonBlock); + } + test_server_->waitForGaugeGe("http.config_test.downstream_rq_active", 1); + ASSERT_FALSE(connection_driver->closed()); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(1001)); + connection_driver->run(); + + // The upstream should send a 40x response and send a local reply. + EXPECT_TRUE(connection_driver->closed()); + EXPECT_THAT(response, AllOf(HasSubstr("408"), HasSubstr("header"))); +} + } // namespace Envoy diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index c01716d33c24..6e12c3cf8f2f 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -562,6 +562,7 @@ TEST_P(StatsMatcherIntegrationTest, ExcludePrefixServerDot) { } TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeRequests)) { + v2_bootstrap_ = true; stats_matcher_.mutable_exclusion_list()->add_patterns()->set_hidden_envoy_deprecated_regex( ".*requests.*"); initialize(); @@ -577,6 +578,7 @@ TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeExact)) { } TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeMultipleExact)) { + v2_bootstrap_ = true; stats_matcher_.mutable_exclusion_list()->add_patterns()->set_exact("server.concurrency"); stats_matcher_.mutable_exclusion_list()->add_patterns()->set_hidden_envoy_deprecated_regex( ".*live"); diff --git a/test/integration/integration_tcp_client.cc b/test/integration/integration_tcp_client.cc index 590b5c92fcf4..39c040d4b3b0 100644 --- a/test/integration/integration_tcp_client.cc +++ b/test/integration/integration_tcp_client.cc @@ -37,7 +37,8 @@ using ::testing::NiceMock; IntegrationTcpClient::IntegrationTcpClient( Event::Dispatcher& dispatcher, MockBufferFactory& factory, uint32_t port, Network::Address::IpVersion version, bool enable_half_close, - const Network::ConnectionSocket::OptionsSharedPtr& options) + const Network::ConnectionSocket::OptionsSharedPtr& options, + Network::Address::InstanceConstSharedPtr source_address) : payload_reader_(new WaitForPayloadReader(dispatcher)), callbacks_(new ConnectionCallbacks(*this)) { EXPECT_CALL(factory, create_(_, _, _)) @@ -51,7 +52,7 @@ IntegrationTcpClient::IntegrationTcpClient( connection_ = dispatcher.createClientConnection( Network::Utility::resolveUrl( fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version), port)), - Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), options); + source_address, Network::Test::createRawBufferSocket(), options); ON_CALL(*client_write_buffer_, drain(_)) .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); diff --git a/test/integration/integration_tcp_client.h b/test/integration/integration_tcp_client.h index f353c8a3188c..af75b84de08c 100644 --- a/test/integration/integration_tcp_client.h +++ b/test/integration/integration_tcp_client.h @@ -28,7 +28,8 @@ class IntegrationTcpClient { public: IntegrationTcpClient(Event::Dispatcher& dispatcher, MockBufferFactory& factory, uint32_t port, Network::Address::IpVersion version, bool enable_half_close, - const Network::ConnectionSocket::OptionsSharedPtr& options); + const Network::ConnectionSocket::OptionsSharedPtr& options, + Network::Address::InstanceConstSharedPtr source_address = nullptr); void close(); void waitForData(const std::string& data, bool exact_match = true); diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index f76ad6f889e2..944ccfe81343 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -483,6 +483,38 @@ TEST_P(IntegrationTest, TestSmuggling) { } } +TEST_P(IntegrationTest, TestPipelinedResponses) { + initialize(); + auto tcp_client = makeTcpConnection(lookupPort("http")); + + ASSERT_TRUE(tcp_client->write( + "POST /test/long/url HTTP/1.1\r\nHost: host\r\ntransfer-encoding: chunked\r\n\r\n")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + std::string data; + ASSERT_TRUE(fake_upstream_connection->waitForData( + FakeRawConnection::waitForInexactMatch("\r\n\r\n"), &data)); + ASSERT_THAT(data, HasSubstr("POST")); + + ASSERT_TRUE(fake_upstream_connection->write( + "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n" + "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n" + "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n")); + + tcp_client->waitForData("0\r\n\r\n", false); + std::string response = tcp_client->data(); + + EXPECT_THAT(response, HasSubstr("HTTP/1.1 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("transfer-encoding: chunked\r\n")); + EXPECT_THAT(response, EndsWith("0\r\n\r\n")); + + ASSERT_TRUE(fake_upstream_connection->close()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + tcp_client->close(); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_cx_protocol_error")->value(), 1); +} + TEST_P(IntegrationTest, TestServerAllowChunkedLength) { config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index 183956c12eae..2871798c7bf0 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -60,6 +60,8 @@ class ListenerIntegrationTest : public HttpIntegrationTest, http_connection_manager) { auto* rds_config = http_connection_manager.mutable_rds(); rds_config->set_route_config_name(route_table_name_); + rds_config->mutable_config_source()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); envoy::config::core::v3::ApiConfigSource* rds_api_config_source = rds_config->mutable_config_source()->mutable_api_config_source(); rds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); @@ -78,8 +80,9 @@ class ListenerIntegrationTest : public HttpIntegrationTest, listener_config_.set_name(listener_name_); ENVOY_LOG_MISC(error, "listener config: {}", listener_config_.DebugString()); bootstrap.mutable_static_resources()->mutable_listeners()->Clear(); - auto* lds_api_config_source = - bootstrap.mutable_dynamic_resources()->mutable_lds_config()->mutable_api_config_source(); + 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); envoy::config::core::v3::GrpcService* grpc_service = lds_api_config_source->add_grpc_services(); @@ -151,7 +154,7 @@ class ListenerIntegrationTest : public HttpIntegrationTest, for (const auto& listener_blob : listener_configs) { const auto listener_config = TestUtility::parseYaml(listener_blob); - response.add_resources()->PackFrom(API_DOWNGRADE(listener_config)); + response.add_resources()->PackFrom(listener_config); } ASSERT(lds_upstream_info_.stream_by_resource_name_[listener_name_] != nullptr); lds_upstream_info_.stream_by_resource_name_[listener_name_]->sendGrpcMessage(response); @@ -163,7 +166,7 @@ class ListenerIntegrationTest : public HttpIntegrationTest, response.set_type_url(Config::TypeUrl::get().RouteConfiguration); const auto route_configuration = TestUtility::parseYaml(route_config); - response.add_resources()->PackFrom(API_DOWNGRADE(route_configuration)); + response.add_resources()->PackFrom(route_configuration); ASSERT(rds_upstream_info_.stream_by_resource_name_[route_configuration.name()] != nullptr); rds_upstream_info_.stream_by_resource_name_[route_configuration.name()]->sendGrpcMessage( response); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index cb72353e1ad2..acaf97b0ce64 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -175,7 +175,7 @@ TEST_P(ProtocolIntegrationTest, ComputedHealthCheck) { config_helper_.addFilter(R"EOF( name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false cluster_min_healthy_percentages: example_cluster_name: { value: 75 } @@ -196,7 +196,7 @@ TEST_P(ProtocolIntegrationTest, ModifyBuffer) { config_helper_.addFilter(R"EOF( name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false cluster_min_healthy_percentages: example_cluster_name: { value: 75 } @@ -334,6 +334,88 @@ TEST_P(ProtocolIntegrationTest, ResponseWithHostHeader) { response->headers().get(Http::LowerCaseString("host"))[0]->value().getStringView()); } +// Tests missing headers needed for H/1 codec first line. +TEST_P(ProtocolIntegrationTest, DownstreamRequestWithFaultyFilter) { + useAccessLog("%RESPONSE_CODE_DETAILS%"); + config_helper_.addFilter("{ name: invalid-header-filter, typed_config: { \"@type\": " + "type.googleapis.com/google.protobuf.Empty } }"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Missing method + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"remove-method", "yes"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("missing_required_header")); + + // Missing path for non-CONNECT + response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"remove-path", "yes"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("missing_required_header")); +} + +TEST_P(ProtocolIntegrationTest, FaultyFilterWithConnect) { + // Faulty filter that removed host in a CONNECT request. + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { ConfigHelper::setConnectConfig(hcm, false); }); + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + // Clone the whole listener. + auto static_resources = bootstrap.mutable_static_resources(); + auto* old_listener = static_resources->mutable_listeners(0); + auto* cloned_listener = static_resources->add_listeners(); + cloned_listener->CopyFrom(*old_listener); + old_listener->set_name("http_forward"); + }); + useAccessLog("%RESPONSE_CODE_DETAILS%"); + config_helper_.addFilter("{ name: invalid-header-filter, typed_config: { \"@type\": " + "type.googleapis.com/google.protobuf.Empty } }"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Missing host for CONNECT + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "CONNECT"}, {":scheme", "http"}, {":authority", "www.host.com:80"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("missing_required_header")); +} + +TEST_P(ProtocolIntegrationTest, MissingHeadersLocalReply) { + useAccessLog("%RESPONSE_CODE_DETAILS%"); + config_helper_.addFilter("{ name: invalid-header-filter, typed_config: { \"@type\": " + "type.googleapis.com/google.protobuf.Empty } }"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Missing method + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"remove-method", "yes"}, + {"send-reply", "yes"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("InvalidHeaderFilter_ready\n")); +} + // Regression test for https://github.com/envoyproxy/envoy/issues/10270 TEST_P(ProtocolIntegrationTest, LongHeaderValueWithSpaces) { // Header with at least 20kb of spaces surrounded by non-whitespace characters to ensure that @@ -911,6 +993,14 @@ TEST_P(ProtocolIntegrationTest, HittingEncoderFilterLimit) { test_server_->waitForCounterEq("http.config_test.downstream_rq_5xx", 1); } +// The downstream connection is closed when it is read disabled, and on OSX the +// connection error is not detected under these circumstances. +#if !defined(__APPLE__) +TEST_P(ProtocolIntegrationTest, 100ContinueAndClose) { + testEnvoyHandling100Continue(false, "", true); +} +#endif + TEST_P(ProtocolIntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } TEST_P(ProtocolIntegrationTest, EnvoyHandlingDuplicate100Continue) { diff --git a/test/integration/redirect_integration_test.cc b/test/integration/redirect_integration_test.cc index 0504e33ad338..c0c9d1f1b307 100644 --- a/test/integration/redirect_integration_test.cc +++ b/test/integration/redirect_integration_test.cc @@ -8,12 +8,13 @@ namespace Envoy { -using testing::HasSubstr; +using ::testing::HasSubstr; namespace { constexpr char HandleThreeHopLocationFormat[] = "http://handle.internal.redirect.max.three.hop/path{}"; -} +constexpr char kTestHeaderKey[] = "test-header"; +} // namespace class RedirectIntegrationTest : public HttpProtocolIntegrationTest { public: @@ -81,14 +82,20 @@ class RedirectIntegrationTest : public HttpProtocolIntegrationTest { return new_stream; } - Http::TestResponseHeaderMapImpl redirect_response_{ - {":status", "302"}, {"content-length", "0"}, {"location", "http://authority2/new/url"}}; - + Http::TestResponseHeaderMapImpl redirect_response_{{":status", "302"}, + {"content-length", "0"}, + {"location", "http://authority2/new/url"}, + // Test header added to confirm that response + // headers are populated for internal redirects + {kTestHeaderKey, "test-header-value"}}; + Envoy::Http::LowerCaseString test_header_key_{kTestHeaderKey}; std::vector upstream_connections_; }; // By default if internal redirects are not configured, redirects are proxied. TEST_P(RedirectIntegrationTest, RedirectNotConfigured) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); + // Use base class initialize. HttpProtocolIntegrationTest::initialize(); @@ -96,10 +103,16 @@ TEST_P(RedirectIntegrationTest, RedirectNotConfigured) { auto response = sendRequestAndWaitForResponse(default_request_headers_, 0, redirect_response_, 0); EXPECT_TRUE(response->complete()); EXPECT_EQ("302", response->headers().getStatusValue()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } // Now test a route with redirects configured on in pass-through mode. TEST_P(RedirectIntegrationTest, InternalRedirectPassedThrough) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -109,10 +122,15 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPassedThrough) { EXPECT_EQ( 0, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_failed_total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, BasicInternalRedirect) { - useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); + useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); // Validate that header sanitization is only called once. config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -143,10 +161,17 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirect) { EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_succeeded_total") ->value()); - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("internal_redirect")); + // 302 was never returned downstream + EXPECT_EQ(0, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_2xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + // No test header + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), HasSubstr("200 via_upstream -\n")); } TEST_P(RedirectIntegrationTest, InternalRedirectWithThreeHopLimit) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); // Validate that header sanitization is only called once. config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -184,9 +209,21 @@ TEST_P(RedirectIntegrationTest, InternalRedirectWithThreeHopLimit) { EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_too_many_redirects") ->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 3), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InternalRedirectToDestinationWithBody) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); // Validate that header sanitization is only called once. config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -225,9 +262,17 @@ TEST_P(RedirectIntegrationTest, InternalRedirectToDestinationWithBody) { EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_succeeded_total") ->value()); + // 302 was never returned downstream + EXPECT_EQ(0, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_2xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + // No test header + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), HasSubstr("200 via_upstream -\n")); } TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByPreviousRoutesPredicate) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); auto handle_prevent_repeated_target = config_helper_.createVirtualHost("handle.internal.redirect.no.repeated.target"); auto* internal_redirect_policy = handle_prevent_repeated_target.mutable_routes(0) @@ -278,9 +323,19 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByPreviousRoutesPredica EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_predicate")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByAllowListedRoutesPredicate) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); auto handle_allow_listed_redirect_route = config_helper_.createVirtualHost("handle.internal.redirect.only.allow.listed.target"); auto* internal_redirect_policy = handle_allow_listed_redirect_route.mutable_routes(0) @@ -336,9 +391,19 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByAllowListedRoutesPred EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_predicate")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InternalRedirectPreventedBySafeCrossSchemePredicate) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); auto handle_safe_cross_scheme_route = config_helper_.createVirtualHost( "handle.internal.redirect.only.allow.safe.cross.scheme.redirect"); auto* internal_redirect_policy = handle_safe_cross_scheme_route.mutable_routes(0) @@ -396,9 +461,19 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPreventedBySafeCrossSchemePredic EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_predicate")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InvalidRedirect) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); initialize(); redirect_response_.setLocation("invalid_url"); @@ -412,6 +487,11 @@ TEST_P(RedirectIntegrationTest, InvalidRedirect) { EXPECT_EQ( 1, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_failed_total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } INSTANTIATE_TEST_SUITE_P(Protocols, RedirectIntegrationTest, diff --git a/test/integration/rtds_integration_test.cc b/test/integration/rtds_integration_test.cc index cbcda0cfae79..925cdf42b261 100644 --- a/test/integration/rtds_integration_test.cc +++ b/test/integration/rtds_integration_test.cc @@ -47,6 +47,7 @@ std::string tdsBootstrapConfig(absl::string_view api_type) { rtds_layer: name: some_rtds_layer rtds_config: + resource_api_version: V3 api_config_source: api_type: {} grpc_services: diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index 4e5bc1ddebf5..f686a0946fb4 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -64,6 +64,7 @@ class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTes const std::string& secret_name) { secret_config->set_name(secret_name); auto* config_source = secret_config->mutable_sds_config(); + config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); auto* api_config_source = config_source->mutable_api_config_source(); api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); auto* grpc_service = api_config_source->add_grpc_services(); @@ -123,7 +124,7 @@ class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTes API_NO_BOOST(envoy::api::v2::DiscoveryResponse) discovery_response; discovery_response.set_version_info("1"); discovery_response.set_type_url(Config::TypeUrl::get().Secret); - discovery_response.add_resources()->PackFrom(API_DOWNGRADE(secret)); + discovery_response.add_resources()->PackFrom(secret); xds_stream_->sendGrpcMessage(discovery_response); } @@ -138,6 +139,7 @@ class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTes const std::string server_cert_; const std::string validation_secret_; const std::string client_cert_; + bool v3_resource_api_{false}; }; // Downstream SDS integration test: static Listener with ssl cert from SDS @@ -200,6 +202,103 @@ class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, SdsDynamicDownstreamIntegrationTest, GRPC_CLIENT_INTEGRATION_PARAMS); +class SdsDynamicKeyRotationIntegrationTest : public SdsDynamicDownstreamIntegrationTest { +protected: + envoy::extensions::transport_sockets::tls::v3::Secret getCurrentServerSecret() { + envoy::extensions::transport_sockets::tls::v3::Secret secret; + secret.set_name(server_cert_); + auto* tls_certificate = secret.mutable_tls_certificate(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::temporaryPath("root/current/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::temporaryPath("root/current/serverkey.pem")); + auto* watched_directory = tls_certificate->mutable_watched_directory(); + watched_directory->set_path(TestEnvironment::temporaryPath("root")); + return secret; + } +}; + +// We don't care about multiple gRPC types here, Envoy gRPC is fine, the +// interest is on the filesystem. +INSTANTIATE_TEST_SUITE_P( + IpVersionsClientType, SdsDynamicKeyRotationIntegrationTest, + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + testing::Values(Grpc::ClientType::EnvoyGrpc))); + +// Validate that a basic key-cert rotation works via symlink rename. +TEST_P(SdsDynamicKeyRotationIntegrationTest, BasicRotation) { + v3_resource_api_ = true; + TestEnvironment::exec( + {TestEnvironment::runfilesPath("test/integration/sds_dynamic_key_rotation_setup.sh")}); + + on_server_init_function_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getCurrentServerSecret()); + }; + initialize(); + + // Initial update from filesystem. + test_server_->waitForCounterGe( + listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + // First request with server{cert,key}.pem. + testRouterHeaderOnlyRequestAndResponse(&creator); + cleanupUpstreamAndDownstream(); + // Rotate. + TestEnvironment::renameFile(TestEnvironment::temporaryPath("root/new"), + TestEnvironment::temporaryPath("root/current")); + test_server_->waitForCounterGe( + listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 2); + // The rotation is not a SDS attempt, so no change to these stats. + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); + + // First request with server_ecdsa{cert,key}.pem. + testRouterHeaderOnlyRequestAndResponse(&creator); +} + +// Validate that rotating to a directory with missing certs is handled. +TEST_P(SdsDynamicKeyRotationIntegrationTest, EmptyRotation) { + v3_resource_api_ = true; + TestEnvironment::exec( + {TestEnvironment::runfilesPath("test/integration/sds_dynamic_key_rotation_setup.sh")}); + + on_server_init_function_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getCurrentServerSecret()); + }; + initialize(); + + // Initial update from filesystem. + test_server_->waitForCounterGe( + listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + // First request with server{cert,key}.pem. + testRouterHeaderOnlyRequestAndResponse(&creator); + cleanupUpstreamAndDownstream(); + + // Rotate to an empty directory, this should fail. + TestEnvironment::renameFile(TestEnvironment::temporaryPath("root/empty"), + TestEnvironment::temporaryPath("root/current")); + test_server_->waitForCounterEq("sds.server_cert.key_rotation_failed", 1); + EXPECT_EQ(1, + test_server_ + ->counter(listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds")) + ->value()); + // The rotation is not a SDS attempt, so no change to these stats. + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); + + // Requests continue to work with key/cert pair. + testRouterHeaderOnlyRequestAndResponse(&creator); +} + // A test that SDS server send a good server secret for a static listener. // The first ssl request should be OK. TEST_P(SdsDynamicDownstreamIntegrationTest, BasicSuccess) { @@ -213,6 +312,10 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, BasicSuccess) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); } // A test that SDS server send a bad secret for a static listener, @@ -230,6 +333,10 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { EXPECT_FALSE(codec_client_->connected()); codec_client_->connection()->close(Network::ConnectionCloseType::NoFlush); + // Failure + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_rejected")->value()); + sendSdsResponse(getServerSecret()); // Wait for ssl_context_updated_by_sds counter. @@ -240,6 +347,10 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_rejected")->value()); } class SdsDynamicDownstreamCertValidationContextTest : public SdsDynamicDownstreamIntegrationTest { @@ -368,6 +479,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, BasicSuccess) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // A test that SDS server sends a certificate validation context for a static listener. @@ -385,6 +500,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, CombinedCertValidationCont return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // A test that verifies that both: static cluster and LDS listener are updated when using @@ -408,6 +527,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, BasicWithSharedSecret) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // A test that verifies that both: static cluster and LDS listener are updated when using @@ -432,6 +555,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, CombinedValidationContextW return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // Upstream SDS integration test: a static cluster has ssl cert from SDS. @@ -503,6 +630,10 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, BasicSuccess) { "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); testRouterHeaderOnlyRequestAndResponse(); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.client_cert.update_rejected")->value()); } // To test a static cluster with sds. SDS send a bad client secret first. @@ -526,11 +657,19 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, WrongSecretFirst) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + // Failure + EXPECT_EQ(0, test_server_->counter("sds.client_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_rejected")->value()); + sendSdsResponse(getClientSecret()); test_server_->waitForCounterGe( "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); testRouterHeaderOnlyRequestAndResponse(); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_rejected")->value()); } } // namespace Ssl diff --git a/test/integration/sds_dynamic_key_rotation_setup.sh b/test/integration/sds_dynamic_key_rotation_setup.sh new file mode 100755 index 000000000000..2e5f30d88751 --- /dev/null +++ b/test/integration/sds_dynamic_key_rotation_setup.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +TEST_CERTS=test/config/integration/certs + +ROOT="${TEST_TMPDIR}"/root +SERVER_KEYCERT="${ROOT}"/server +SERVER_ECDSA_KEYCERT="${ROOT}"/server_ecdsa +EMPTY_KEYCERT="${ROOT}"/empty_keycert + +rm -rf "${ROOT}" +mkdir -p "${SERVER_KEYCERT}" +mkdir -p "${SERVER_ECDSA_KEYCERT}" +mkdir -p "${EMPTY_KEYCERT}" + +cp -f "${TEST_CERTS}"/server{cert,key}.pem "${SERVER_KEYCERT}" +cp -f "${TEST_CERTS}"/server_ecdsacert.pem "${SERVER_ECDSA_KEYCERT}"/servercert.pem +cp -f "${TEST_CERTS}"/server_ecdsakey.pem "${SERVER_ECDSA_KEYCERT}"/serverkey.pem + +ln -sf "${SERVER_KEYCERT}" "${ROOT}"/current +ln -sf "${SERVER_ECDSA_KEYCERT}" "${ROOT}"/new +ln -sf "${EMPTY_KEYCERT}" "${ROOT}"/empty diff --git a/test/integration/sds_generic_secret_integration_test.cc b/test/integration/sds_generic_secret_integration_test.cc index ac466c6a40ec..b2b9f8d085ab 100644 --- a/test/integration/sds_generic_secret_integration_test.cc +++ b/test/integration/sds_generic_secret_integration_test.cc @@ -58,6 +58,7 @@ class SdsGenericSecretTestFilterConfig public: SdsGenericSecretTestFilterConfig() : Extensions::HttpFilters::Common::EmptyHttpFilterConfig("sds-generic-secret-test") { + config_source_.set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); auto* api_config_source = config_source_.mutable_api_config_source(); api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); auto* grpc_service = api_config_source->add_grpc_services(); @@ -121,7 +122,7 @@ class SdsGenericSecretIntegrationTest : public Grpc::GrpcClientIntegrationParamT API_NO_BOOST(envoy::api::v2::DiscoveryResponse) discovery_response; discovery_response.set_version_info("0"); discovery_response.set_type_url(Config::TypeUrl::get().Secret); - discovery_response.add_resources()->PackFrom(API_DOWNGRADE(secret)); + discovery_response.add_resources()->PackFrom(secret); xds_stream_->sendGrpcMessage(discovery_response); } diff --git a/test/integration/server.cc b/test/integration/server.cc index e6991ec0a71a..c957caf59b06 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -32,7 +32,7 @@ OptionsImpl createTestOptionsImpl(const std::string& config_path, const std::str Network::Address::IpVersion ip_version, FieldValidationConfig validation_config, uint32_t concurrency, std::chrono::seconds drain_time, - Server::DrainStrategy drain_strategy) { + Server::DrainStrategy drain_strategy, bool v2_bootstrap) { OptionsImpl test_options("cluster_name", "node_name", "zone_name", spdlog::level::info); test_options.setConfigPath(config_path); @@ -47,6 +47,9 @@ OptionsImpl createTestOptionsImpl(const std::string& config_path, const std::str test_options.setIgnoreUnknownFieldsDynamic(validation_config.ignore_unknown_dynamic_fields); test_options.setConcurrency(concurrency); test_options.setHotRestartDisabled(true); + if (v2_bootstrap) { + test_options.setBootstrapVersion(2); + } return test_options; } @@ -60,14 +63,15 @@ IntegrationTestServerPtr IntegrationTestServer::create( Event::TestTimeSystem& time_system, Api::Api& api, bool defer_listener_finalization, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy, - bool use_real_stats) { + bool use_real_stats, bool v2_bootstrap) { IntegrationTestServerPtr server{ std::make_unique(time_system, api, config_path, use_real_stats)}; if (server_ready_function != nullptr) { server->setOnServerReadyCb(server_ready_function); } server->start(version, on_server_init_function, deterministic, defer_listener_finalization, - process_object, validation_config, concurrency, drain_time, drain_strategy); + process_object, validation_config, concurrency, drain_time, drain_strategy, + v2_bootstrap); return server; } @@ -87,15 +91,15 @@ void IntegrationTestServer::start(const Network::Address::IpVersion version, ProcessObjectOptRef process_object, Server::FieldValidationConfig validator_config, uint32_t concurrency, std::chrono::seconds drain_time, - Server::DrainStrategy drain_strategy) { + Server::DrainStrategy drain_strategy, bool v2_bootstrap) { ENVOY_LOG(info, "starting integration test server"); ASSERT(!thread_); - thread_ = - api_.threadFactory().createThread([version, deterministic, process_object, validator_config, - concurrency, drain_time, drain_strategy, this]() -> void { - threadRoutine(version, deterministic, process_object, validator_config, concurrency, - drain_time, drain_strategy); - }); + thread_ = api_.threadFactory().createThread([version, deterministic, process_object, + validator_config, concurrency, drain_time, + drain_strategy, v2_bootstrap, this]() -> void { + threadRoutine(version, deterministic, process_object, validator_config, concurrency, drain_time, + drain_strategy, v2_bootstrap); + }); // If any steps need to be done prior to workers starting, do them now. E.g., xDS pre-init. // Note that there is no synchronization guaranteeing this happens either @@ -171,9 +175,10 @@ void IntegrationTestServer::threadRoutine(const Network::Address::IpVersion vers bool deterministic, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, std::chrono::seconds drain_time, - Server::DrainStrategy drain_strategy) { + Server::DrainStrategy drain_strategy, bool v2_bootstrap) { OptionsImpl options(Server::createTestOptionsImpl(config_path_, "", version, validation_config, - concurrency, drain_time, drain_strategy)); + concurrency, drain_time, drain_strategy, + v2_bootstrap)); Thread::MutexBasicLockable lock; Random::RandomGeneratorPtr random_generator; diff --git a/test/integration/server.h b/test/integration/server.h index 48995e8cb0b7..9d70d735bbb4 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -46,7 +46,8 @@ createTestOptionsImpl(const std::string& config_path, const std::string& config_ FieldValidationConfig validation_config = FieldValidationConfig(), uint32_t concurrency = 1, std::chrono::seconds drain_time = std::chrono::seconds(1), - Server::DrainStrategy drain_strategy = Server::DrainStrategy::Gradual); + Server::DrainStrategy drain_strategy = Server::DrainStrategy::Gradual, + bool v2_bootstrap = false); class TestComponentFactory : public ComponentFactory { public: @@ -396,7 +397,7 @@ class IntegrationTestServer : public Logger::Loggable, Server::FieldValidationConfig validation_config = Server::FieldValidationConfig(), uint32_t concurrency = 1, std::chrono::seconds drain_time = std::chrono::seconds(1), Server::DrainStrategy drain_strategy = Server::DrainStrategy::Gradual, - bool use_real_stats = false); + bool use_real_stats = false, bool v2_bootstrap = false); // Note that the derived class is responsible for tearing down the server in its // destructor. ~IntegrationTestServer() override; @@ -419,7 +420,8 @@ class IntegrationTestServer : public Logger::Loggable, std::function on_server_init_function, bool deterministic, bool defer_listener_finalization, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, - std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy); + std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy, + bool v2_bootstrap); void waitForCounterEq(const std::string& name, uint64_t value, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(), @@ -517,7 +519,8 @@ class IntegrationTestServer : public Logger::Loggable, void threadRoutine(const Network::Address::IpVersion version, bool deterministic, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, - std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy); + std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy, + bool v2_bootstrap); Event::TestTimeSystem& time_system_; Api::Api& api_; diff --git a/test/integration/tcp_conn_pool_integration_test.cc b/test/integration/tcp_conn_pool_integration_test.cc index 592747627de2..159b083a3937 100644 --- a/test/integration/tcp_conn_pool_integration_test.cc +++ b/test/integration/tcp_conn_pool_integration_test.cc @@ -117,7 +117,7 @@ class TcpConnPoolIntegrationTest : public testing::TestWithParammutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); auto* access_log = tcp_proxy_config.add_access_log(); access_log->set_name("accesslog"); @@ -304,8 +305,8 @@ TEST_P(TcpProxyIntegrationTest, AccessLog) { runtime_filter->set_runtime_key("unused-key"); auto* percent_sampled = runtime_filter->mutable_percent_sampled(); percent_sampled->set_numerator(100); - percent_sampled->set_denominator( - envoy::type::FractionalPercent::DenominatorType::FractionalPercent_DenominatorType_HUNDRED); + percent_sampled->set_denominator(envoy::type::v3::FractionalPercent::DenominatorType:: + FractionalPercent_DenominatorType_HUNDRED); config_blob->PackFrom(tcp_proxy_config); }); initialize(); @@ -390,9 +391,10 @@ TEST_P(TcpProxyIntegrationTest, TestIdletimeoutWithNoData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); tcp_proxy_config.mutable_idle_timeout()->set_nanos( std::chrono::duration_cast(std::chrono::milliseconds(100)) .count()); @@ -413,9 +415,10 @@ TEST_P(TcpProxyIntegrationTest, TestIdletimeoutWithLargeOutstandingData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); tcp_proxy_config.mutable_idle_timeout()->set_nanos( std::chrono::duration_cast(std::chrono::milliseconds(500)) .count()); @@ -445,7 +448,8 @@ TEST_P(TcpProxyIntegrationTest, TestMaxDownstreamConnectionDurationWithNoData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert( *config_blob); @@ -469,7 +473,8 @@ TEST_P(TcpProxyIntegrationTest, TestMaxDownstreamConnectionDurationWithLargeOuts auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert( *config_blob); diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index efc7222c90cb..d5056a28720c 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -1,7 +1,7 @@ #include #include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.pb.h" +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" #include "test/integration/http_integration.h" #include "test/integration/http_protocol_integration.h" @@ -324,7 +324,7 @@ class TcpTunnelingIntegrationTest : public testing::TestWithParam void { - envoy::config::filter::network::tcp_proxy::v2::TcpProxy proxy_config; + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy proxy_config; proxy_config.set_stat_prefix("tcp_stats"); proxy_config.set_cluster("cluster_0"); proxy_config.mutable_tunneling_config()->set_hostname("host.com"); @@ -467,9 +467,10 @@ TEST_P(TcpTunnelingIntegrationTest, TestIdletimeoutWithLargeOutstandingData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); tcp_proxy_config.mutable_idle_timeout()->set_nanos( std::chrono::duration_cast(std::chrono::milliseconds(500)) .count()); diff --git a/test/integration/transport_socket_match_integration_test.cc b/test/integration/transport_socket_match_integration_test.cc index 75d101beb702..3487d546c592 100644 --- a/test/integration/transport_socket_match_integration_test.cc +++ b/test/integration/transport_socket_match_integration_test.cc @@ -34,7 +34,7 @@ name: "tls_socket" transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "%s" } diff --git a/test/integration/utility.cc b/test/integration/utility.cc index ddbd0816f53e..f6d369e6ce6a 100644 --- a/test/integration/utility.cc +++ b/test/integration/utility.cc @@ -33,11 +33,12 @@ namespace { RawConnectionDriver::DoWriteCallback writeBufferCallback(Buffer::Instance& data) { auto shared_data = std::make_shared(); shared_data->move(data); - return [shared_data](Network::ClientConnection& client) { + return [shared_data](Buffer::Instance& dest) { if (shared_data->length() > 0) { - client.write(*shared_data, false); + dest.add(*shared_data); shared_data->drain(shared_data->length()); } + return false; }; } @@ -110,7 +111,8 @@ IntegrationUtil::makeSingleRequest(const Network::Address::InstanceConstSharedPt if (!content_type.empty()) { headers.setContentType(content_type); } - encoder.encodeHeaders(headers, body.empty()); + const auto status = encoder.encodeHeaders(headers, body.empty()); + ASSERT(status.ok()); if (!body.empty()) { Buffer::OwnedImpl body_buffer(body); encoder.encodeData(body_buffer, true); @@ -143,11 +145,15 @@ RawConnectionDriver::RawConnectionDriver(uint32_t port, DoWriteCallback write_re Network::Address::IpVersion version, Event::Dispatcher& dispatcher, Network::TransportSocketPtr transport_socket) - : dispatcher_(dispatcher) { + : dispatcher_(dispatcher), remaining_bytes_to_send_(0) { api_ = Api::createApiForTest(stats_store_); Event::GlobalTimeSystem time_system; - callbacks_ = std::make_unique( - [this, write_request_callback]() { write_request_callback(*client_); }); + callbacks_ = std::make_unique([this, write_request_callback]() { + Buffer::OwnedImpl buffer; + const bool close_after = write_request_callback(buffer); + remaining_bytes_to_send_ += buffer.length(); + client_->write(buffer, close_after); + }); if (transport_socket == nullptr) { transport_socket = Network::Test::createRawBufferSocket(); @@ -164,6 +170,7 @@ RawConnectionDriver::RawConnectionDriver(uint32_t port, DoWriteCallback write_re client_->addConnectionCallbacks(*callbacks_); client_->addReadFilter( Network::ReadFilterSharedPtr{new ForwardingFilter(*this, response_data_callback)}); + client_->addBytesSentCallback([&](uint64_t bytes) { remaining_bytes_to_send_ -= bytes; }); client_->connect(); } @@ -182,6 +189,8 @@ void RawConnectionDriver::run(Event::Dispatcher::RunType run_type) { dispatcher_ void RawConnectionDriver::close() { client_->close(Network::ConnectionCloseType::FlushWrite); } +bool RawConnectionDriver::allBytesSent() const { return remaining_bytes_to_send_ == 0; } + WaitForPayloadReader::WaitForPayloadReader(Event::Dispatcher& dispatcher) : dispatcher_(dispatcher) {} diff --git a/test/integration/utility.h b/test/integration/utility.h index 497fe872472b..cad8b153363d 100644 --- a/test/integration/utility.h +++ b/test/integration/utility.h @@ -63,7 +63,10 @@ using BufferingStreamDecoderPtr = std::unique_ptr; */ class RawConnectionDriver { public: - using DoWriteCallback = std::function; + // Callback that is executed to write data to connection. The provided buffer + // should be populated with the data to write. If the callback returns true, + // the connection will be closed after writing. + using DoWriteCallback = std::function; using ReadCallback = std::function; RawConnectionDriver(uint32_t port, DoWriteCallback write_request_callback, @@ -86,6 +89,7 @@ class RawConnectionDriver { void waitForConnection(); bool closed() { return callbacks_->closed(); } + bool allBytesSent() const; private: struct ForwardingFilter : public Network::ReadFilterBaseImpl { @@ -137,6 +141,7 @@ class RawConnectionDriver { Event::Dispatcher& dispatcher_; std::unique_ptr callbacks_; Network::ClientConnectionPtr client_; + uint64_t remaining_bytes_to_send_; }; /** diff --git a/test/integration/version_integration_test.cc b/test/integration/version_integration_test.cc index b07d2c881abb..4888f5c7ceef 100644 --- a/test/integration/version_integration_test.cc +++ b/test/integration/version_integration_test.cc @@ -28,7 +28,7 @@ const char ExampleIpTaggingConfig[] = R"EOF( TEST_P(VersionIntegrationTest, DEPRECATED_FEATURE_TEST(IpTaggingV2StaticStructConfig)) { config_helper_.addFilter(absl::StrCat(R"EOF( name: envoy.filters.http.ip_tagging - config: + hidden_envoy_deprecated_config: )EOF", ExampleIpTaggingConfig)); @@ -41,6 +41,7 @@ TEST_P(VersionIntegrationTest, DEPRECATED_FEATURE_TEST(IpTaggingV2StaticStructCo // envoy.filters.http.ip_tagging from v2 TypedStruct config. TEST_P(VersionIntegrationTest, IpTaggingV2StaticTypedStructConfig) { + config_helper_.enableDeprecatedV2Api(); config_helper_.addFilter(absl::StrCat(R"EOF( name: ip_tagging typed_config: @@ -67,6 +68,7 @@ name: ip_tagging // envoy.filters.http.ip_tagging from v2 typed Any config. TEST_P(VersionIntegrationTest, IpTaggingV2StaticTypedConfig) { + config_helper_.enableDeprecatedV2Api(); config_helper_.addFilter(absl::StrCat(R"EOF( name: ip_tagging typed_config: diff --git a/test/integration/vhds_integration_test.cc b/test/integration/vhds_integration_test.cc index 9e9847804e58..b0f5c4207dc1 100644 --- a/test/integration/vhds_integration_test.cc +++ b/test/integration/vhds_integration_test.cc @@ -67,7 +67,7 @@ const std::string& config() { - filters: - name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: - name: envoy.filters.http.on_demand @@ -76,6 +76,7 @@ const std::string& config() { rds: route_config_name: my_route config_source: + resource_api_version: V3 api_config_source: api_type: GRPC grpc_services: @@ -100,6 +101,7 @@ const char RdsConfig[] = R"EOF( name: my_route vhds: config_source: + resource_api_version: V3 api_config_source: api_type: DELTA_GRPC grpc_services: @@ -117,6 +119,7 @@ name: my_route route: { cluster: my_service } vhds: config_source: + resource_api_version: V3 api_config_source: api_type: DELTA_GRPC grpc_services: @@ -367,8 +370,8 @@ class VhdsIntegrationTest : public HttpIntegrationTest, resource->set_name("my_route/vhost_1"); resource->set_version("4"); resource->mutable_resource()->PackFrom( - API_DOWNGRADE(TestUtility::parseYaml( - virtualHostYaml("my_route/vhost_1", "vhost_1, vhost.first")))); + TestUtility::parseYaml( + virtualHostYaml("my_route/vhost_1", "vhost_1, vhost.first"))); resource->add_aliases("my_route/vhost.first"); ret.set_nonce("test-nonce-0"); diff --git a/test/integration/xds_integration_test.cc b/test/integration/xds_integration_test.cc index 4e0dcaf73c07..5d62e6521d12 100644 --- a/test/integration/xds_integration_test.cc +++ b/test/integration/xds_integration_test.cc @@ -17,7 +17,7 @@ namespace { using testing::HasSubstr; -// This is a minimal litmus test for the v2 xDS APIs. +// This is a minimal litmus test for the v3 xDS APIs. class XdsIntegrationTest : public testing::TestWithParam, public HttpIntegrationTest { public: @@ -113,7 +113,7 @@ class LdsInplaceUpdateTcpProxyIntegrationTest filters: - name: envoy.filters.network.tcp_proxy typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_stats cluster: cluster_0 - filter_chain_match: @@ -121,7 +121,7 @@ class LdsInplaceUpdateTcpProxyIntegrationTest filters: - name: envoy.filters.network.tcp_proxy typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_stats cluster: cluster_1 )EOF") {} @@ -471,17 +471,17 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigUpdatingDefaultFilterCha -> void { auto default_filter_chain = bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_default_filter_chain(); - default_filter_chain->set_name("default_filter_chain_v2"); + default_filter_chain->set_name("default_filter_chain_v3"); }); new_config_helper.setLds("1"); test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); - auto codec_client_default_v2 = createHttpCodec("alpndefaultv2"); + auto codec_client_default_v3 = createHttpCodec("alpndefaultv3"); - Cleanup cleanup2([c_default_v2 = codec_client_default_v2.get()]() { c_default_v2->close(); }); + Cleanup cleanup2([c_default_v3 = codec_client_default_v3.get()]() { c_default_v3->close(); }); expectResponseHeaderConnectionClose(*codec_client_default, true); - expectResponseHeaderConnectionClose(*codec_client_default_v2, false); + expectResponseHeaderConnectionClose(*codec_client_default_v3, false); expectConnenctionServed(); } diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 7fa5dee96696..3b299de13c51 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -46,7 +46,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createClientSslContext(b validation_context: trusted_ca: filename: {{ test_rundir }}/test/config/integration/certs/cacert.pem - match_subject_alt_names: + match_subject_alt_names: exact: "spiffe://lyft.com/backend-team" exact: "lyft.com" exact: "www.lyft.com" @@ -57,7 +57,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createClientSslContext(b validation_context: trusted_ca: filename: {{ test_rundir }}/test/config/integration/certs/cacert.pem - match_subject_alt_names: + match_subject_alt_names: exact: "spiffe://lyft.com/backend-team" exact: "lyft.com" exact: "www.lyft.com" diff --git a/test/mocks/api/mocks.h b/test/mocks/api/mocks.h index 5446fc61e1e6..c6a8222c4298 100644 --- a/test/mocks/api/mocks.h +++ b/test/mocks/api/mocks.h @@ -41,7 +41,7 @@ class MockApi : public Api { Event::TimeSystem&)); MOCK_METHOD(Filesystem::Instance&, fileSystem, ()); MOCK_METHOD(Thread::ThreadFactory&, threadFactory, ()); - MOCK_METHOD(const Stats::Scope&, rootScope, ()); + MOCK_METHOD(Stats::Scope&, rootScope, ()); MOCK_METHOD(Random::RandomGenerator&, randomGenerator, ()); MOCK_METHOD(ProcessContextOptRef, processContext, ()); diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index a89d3147ebf9..861f0a10e340 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -102,6 +102,8 @@ class MockDispatcher : public Dispatcher { } // Event::Dispatcher + MOCK_METHOD(void, registerWatchdog, + (const Server::WatchDogSharedPtr&, std::chrono::milliseconds)); MOCK_METHOD(void, initializeStats, (Stats::Scope&, const absl::optional&)); MOCK_METHOD(void, clearDeferredDeleteList, ()); MOCK_METHOD(Network::ServerConnection*, createServerConnection_, ()); diff --git a/test/mocks/event/wrapped_dispatcher.h b/test/mocks/event/wrapped_dispatcher.h index db9fd1212c14..74e935328984 100644 --- a/test/mocks/event/wrapped_dispatcher.h +++ b/test/mocks/event/wrapped_dispatcher.h @@ -20,6 +20,11 @@ class WrappedDispatcher : public Dispatcher { // Event::Dispatcher const std::string& name() override { return impl_.name(); } + void registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) override { + impl_.registerWatchdog(watchdog, min_touch_interval); + } + TimeSource& timeSource() override { return impl_.timeSource(); } void initializeStats(Stats::Scope& scope, const absl::optional& prefix) override { @@ -109,4 +114,4 @@ class WrappedDispatcher : public Dispatcher { }; } // namespace Event -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/mocks/http/BUILD b/test/mocks/http/BUILD index ca7705d8881f..e5593a908c55 100644 --- a/test/mocks/http/BUILD +++ b/test/mocks/http/BUILD @@ -85,6 +85,7 @@ envoy_cc_mock( deps = [ ":stream_mock", "//include/envoy/http:codec_interface", + "//source/common/http:header_utility_lib", ], ) diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index ce9eb19fb646..407956f8aa93 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -58,6 +58,7 @@ class MockFilterManagerCallbacks : public FilterManagerCallbacks { MOCK_METHOD(void, encodeData, (Buffer::Instance&, bool)); MOCK_METHOD(void, encodeTrailers, (ResponseTrailerMap&)); MOCK_METHOD(void, encodeMetadata, (MetadataMapVector&)); + MOCK_METHOD(void, chargeStats, (const ResponseHeaderMap&)); MOCK_METHOD(void, setRequestTrailers, (RequestTrailerMapPtr &&)); MOCK_METHOD(void, setContinueHeaders, (ResponseHeaderMapPtr &&)); MOCK_METHOD(void, setResponseHeaders_, (ResponseHeaderMap&)); @@ -201,7 +202,7 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, MOCK_METHOD(void, removeDownstreamWatermarkCallbacks, (DownstreamWatermarkCallbacks&)); MOCK_METHOD(void, setDecoderBufferLimit, (uint32_t)); MOCK_METHOD(uint32_t, decoderBufferLimit, ()); - MOCK_METHOD(bool, recreateStream, ()); + MOCK_METHOD(bool, recreateStream, (const ResponseHeaderMap* headers)); MOCK_METHOD(void, addUpstreamSocketOptions, (const Network::Socket::OptionsSharedPtr& options)); MOCK_METHOD(Network::Socket::OptionsSharedPtr, getUpstreamSocketOptions, (), (const)); diff --git a/test/mocks/http/stream_encoder.cc b/test/mocks/http/stream_encoder.cc index ad9b646af7d8..5d872c64db15 100644 --- a/test/mocks/http/stream_encoder.cc +++ b/test/mocks/http/stream_encoder.cc @@ -1,5 +1,7 @@ #include "test/mocks/http/stream_encoder.h" +#include "common/http/header_utility.h" + using testing::_; using testing::Invoke; @@ -12,10 +14,11 @@ MockHttp1StreamEncoderOptions::~MockHttp1StreamEncoderOptions() = default; MockRequestEncoder::MockRequestEncoder() { ON_CALL(*this, getStream()).WillByDefault(ReturnRef(stream_)); ON_CALL(*this, encodeHeaders(_, _)) - .WillByDefault(Invoke([](const RequestHeaderMap& headers, bool) { + .WillByDefault(Invoke([](const RequestHeaderMap& headers, bool) -> Status { // Check to see that method is not-null. Path can be null for CONNECT and authority can be // null at the codec level. - ASSERT_NE(nullptr, headers.Method()); + ASSERT(HeaderUtility::checkRequiredHeaders(headers).ok()); + return okStatus(); })); } MockRequestEncoder::~MockRequestEncoder() = default; diff --git a/test/mocks/http/stream_encoder.h b/test/mocks/http/stream_encoder.h index d2c682889ea3..df3d62c00e71 100644 --- a/test/mocks/http/stream_encoder.h +++ b/test/mocks/http/stream_encoder.h @@ -2,6 +2,8 @@ #include "envoy/http/codec.h" +#include "common/http/status.h" + #include "test/mocks/http/stream.h" #include "gmock/gmock.h" @@ -23,7 +25,7 @@ class MockRequestEncoder : public RequestEncoder { ~MockRequestEncoder() override; // Http::RequestEncoder - MOCK_METHOD(void, encodeHeaders, (const RequestHeaderMap& headers, bool end_stream)); + MOCK_METHOD(Status, encodeHeaders, (const RequestHeaderMap& headers, bool end_stream)); MOCK_METHOD(void, encodeTrailers, (const RequestTrailerMap& trailers)); // Http::StreamEncoder diff --git a/test/mocks/network/transport_socket.h b/test/mocks/network/transport_socket.h index 164e393c23ff..ed8fa15b7be3 100644 --- a/test/mocks/network/transport_socket.h +++ b/test/mocks/network/transport_socket.h @@ -36,9 +36,9 @@ class MockTransportSocketFactory : public TransportSocketFactory { ~MockTransportSocketFactory() override; MOCK_METHOD(bool, implementsSecureTransport, (), (const)); + MOCK_METHOD(bool, usesProxyProtocolOptions, (), (const)); MOCK_METHOD(TransportSocketPtr, createTransportSocket, (TransportSocketOptionsSharedPtr), (const)); - MOCK_METHOD(bool, isReady, (), (const)); }; } // namespace Network diff --git a/test/mocks/server/BUILD b/test/mocks/server/BUILD index a80a10220f4e..7214ca820371 100644 --- a/test/mocks/server/BUILD +++ b/test/mocks/server/BUILD @@ -23,6 +23,7 @@ envoy_cc_mock( hdrs = ["admin.h"], deps = [ "//include/envoy/server:admin_interface", + "//test/mocks/network:socket_mocks", "//test/mocks/server:config_tracker_mocks", ], ) @@ -160,7 +161,7 @@ envoy_cc_mock( srcs = ["overload_manager.cc"], hdrs = ["overload_manager.h"], deps = [ - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//test/mocks/event:event_mocks", ], ) @@ -206,7 +207,7 @@ envoy_cc_mock( hdrs = ["main.h"], deps = [ "//include/envoy/server:configuration_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", ], ) diff --git a/test/mocks/server/admin.cc b/test/mocks/server/admin.cc index 2f873c547633..61d5e6dea175 100644 --- a/test/mocks/server/admin.cc +++ b/test/mocks/server/admin.cc @@ -3,11 +3,16 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Return; +using testing::ReturnRef; + namespace Envoy { namespace Server { + MockAdmin::MockAdmin() { - ON_CALL(*this, getConfigTracker()).WillByDefault(testing::ReturnRef(config_tracker_)); - ON_CALL(*this, concurrency()).WillByDefault(testing::Return(1)); + ON_CALL(*this, getConfigTracker()).WillByDefault(ReturnRef(config_tracker_)); + ON_CALL(*this, concurrency()).WillByDefault(Return(1)); + ON_CALL(*this, socket()).WillByDefault(ReturnRef(socket_)); } MockAdmin::~MockAdmin() = default; diff --git a/test/mocks/server/admin.h b/test/mocks/server/admin.h index 8805ee969709..1e8802f6abd6 100644 --- a/test/mocks/server/admin.h +++ b/test/mocks/server/admin.h @@ -4,12 +4,17 @@ #include "envoy/server/admin.h" +#include "test/mocks/network/socket.h" + #include "absl/strings/string_view.h" #include "config_tracker.h" #include "gmock/gmock.h" +using testing::NiceMock; + namespace Envoy { namespace Server { + class MockAdmin : public Admin { public: MockAdmin(); @@ -33,7 +38,9 @@ class MockAdmin : public Admin { MOCK_METHOD(void, addListenerToHandler, (Network::ConnectionHandler * handler)); MOCK_METHOD(uint32_t, concurrency, (), (const)); - ::testing::NiceMock config_tracker_; + NiceMock config_tracker_; + NiceMock socket_; }; + } // namespace Server } // namespace Envoy diff --git a/test/mocks/server/guard_dog.cc b/test/mocks/server/guard_dog.cc index e5e552c234f5..4a9263ffa8ca 100644 --- a/test/mocks/server/guard_dog.cc +++ b/test/mocks/server/guard_dog.cc @@ -11,7 +11,7 @@ using ::testing::NiceMock; using ::testing::Return; MockGuardDog::MockGuardDog() : watch_dog_(new NiceMock()) { - ON_CALL(*this, createWatchDog(_, _)).WillByDefault(Return(watch_dog_)); + ON_CALL(*this, createWatchDog(_, _, _)).WillByDefault(Return(watch_dog_)); } MockGuardDog::~MockGuardDog() = default; diff --git a/test/mocks/server/guard_dog.h b/test/mocks/server/guard_dog.h index fed29041db3e..4dbe70ea36a6 100644 --- a/test/mocks/server/guard_dog.h +++ b/test/mocks/server/guard_dog.h @@ -14,7 +14,8 @@ class MockGuardDog : public GuardDog { // Server::GuardDog MOCK_METHOD(WatchDogSharedPtr, createWatchDog, - (Thread::ThreadId thread_id, const std::string& thread_name)); + (Thread::ThreadId thread_id, const std::string& thread_name, + Event::Dispatcher& dispatcher)); MOCK_METHOD(void, stopWatching, (WatchDogSharedPtr wd)); std::shared_ptr watch_dog_; diff --git a/test/mocks/server/main.h b/test/mocks/server/main.h index 59f6259250d7..17e00013c27d 100644 --- a/test/mocks/server/main.h +++ b/test/mocks/server/main.h @@ -6,7 +6,7 @@ #include #include "envoy/server/configuration.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "gmock/gmock.h" diff --git a/test/mocks/server/overload_manager.h b/test/mocks/server/overload_manager.h index 1dc9f4dc4b23..03fe54aa3232 100644 --- a/test/mocks/server/overload_manager.h +++ b/test/mocks/server/overload_manager.h @@ -2,7 +2,8 @@ #include -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" +#include "envoy/server/overload/thread_local_overload_state.h" #include "gmock/gmock.h" diff --git a/test/mocks/server/watch_dog.h b/test/mocks/server/watch_dog.h index a658dd33ac94..30b9810927eb 100644 --- a/test/mocks/server/watch_dog.h +++ b/test/mocks/server/watch_dog.h @@ -12,7 +12,6 @@ class MockWatchDog : public WatchDog { ~MockWatchDog() override; // Server::WatchDog - MOCK_METHOD(void, startWatchdog, (Event::Dispatcher & dispatcher)); MOCK_METHOD(void, touch, ()); MOCK_METHOD(Thread::ThreadId, threadId, (), (const)); }; diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 8fafd921cc48..3cc0ac4e7d55 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -97,7 +97,6 @@ class MockClientContextConfig : public ClientContextConfig { MOCK_METHOD(bool, allowRenegotiation, (), (const)); MOCK_METHOD(size_t, maxSessionKeys, (), (const)); MOCK_METHOD(const std::string&, signingAlgorithmsForTest, (), (const)); - MOCK_METHOD(bool, isSecretReady, (), (const)); }; class MockServerContextConfig : public ServerContextConfig { diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index cc43bd084e10..ba12c326c33d 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -280,10 +280,13 @@ class MockMetricSnapshot : public MetricSnapshot { MOCK_METHOD(const std::vector>&, histograms, ()); MOCK_METHOD(const std::vector>&, textReadouts, ()); + SystemTime snapshotTime() const override { return snapshot_time_; } + std::vector counters_; std::vector> gauges_; std::vector> histograms_; std::vector> text_readouts_; + SystemTime snapshot_time_; }; class MockSink : public Sink { diff --git a/test/mocks/thread_local/mocks.h b/test/mocks/thread_local/mocks.h index dc6518c5068a..7b3097f5e0fa 100644 --- a/test/mocks/thread_local/mocks.h +++ b/test/mocks/thread_local/mocks.h @@ -61,11 +61,10 @@ class MockInstance : public Instance { ThreadLocalObjectSharedPtr get() override { return parent_.data_[index_]; } bool currentThreadRegistered() override { return parent_.registered_; } void runOnAllThreads(const UpdateCb& cb) override { - parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }); + parent_.runOnAllThreads([cb, this]() { cb(parent_.data_[index_]); }); } - void runOnAllThreads(const UpdateCb& cb, Event::PostCb main_callback) override { - parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }, - main_callback); + void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& main_callback) override { + parent_.runOnAllThreads([cb, this]() { cb(parent_.data_[index_]); }, main_callback); } void set(InitializeCb cb) override { diff --git a/test/mocks/upstream/cluster_manager.h b/test/mocks/upstream/cluster_manager.h index c24b1b045acd..cc3071052f67 100644 --- a/test/mocks/upstream/cluster_manager.h +++ b/test/mocks/upstream/cluster_manager.h @@ -42,7 +42,8 @@ class MockClusterManager : public ClusterManager { MOCK_METHOD(void, setInitializedCb, (InitializationCompleteCallback)); MOCK_METHOD(void, initializeSecondaryClusters, (const envoy::config::bootstrap::v3::Bootstrap& bootstrap)); - MOCK_METHOD(ClusterInfoMap, clusters, ()); + MOCK_METHOD(ClusterInfoMaps, clusters, ()); + MOCK_METHOD(const ClusterSet&, primaryClusters, ()); MOCK_METHOD(ThreadLocalCluster*, get, (absl::string_view cluster)); MOCK_METHOD(Http::ConnectionPool::Instance*, httpConnPoolForCluster, diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 9fd20dc2dec5..8de0092d2981 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -11,6 +11,8 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/singleton:95.1" "source/common/api:72.9" "source/common/api/posix:71.8" +"source/common/filter:96.3" +"source/common/filter/http:96.3" "source/common/init:96.2" "source/common/json:90.6" "source/common/thread:0.0" # Death tests don't report LCOV @@ -65,6 +67,9 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/transport_sockets/tls:94.2" "source/extensions/transport_sockets/tls/ocsp:95.3" "source/extensions/transport_sockets/tls/private_key:76.9" +"source/extensions/wasm_runtime:50.0" +"source/extensions/wasm_runtime/wasmtime:0.0" # Not enabled in coverage build +"source/extensions/wasm_runtime/wavm:0.0" # Noe enabled in coverage build "source/extensions/watchdog:69.6" # Death tests within extensions "source/extensions/watchdog/profile_action:84.9" "source/server:94.6" diff --git a/test/proto/bookstore.proto b/test/proto/bookstore.proto index 62e697e219ee..559ec4da0ace 100644 --- a/test/proto/bookstore.proto +++ b/test/proto/bookstore.proto @@ -134,6 +134,11 @@ service Bookstore { get: "/bigbook" }; } + rpc PostWildcard(EchoBodyRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/wildcard/{arg=**}" + }; + } } service ServiceWithResponseBody { diff --git a/test/server/BUILD b/test/server/BUILD index 1f459ab6f9ac..c26a2ebcc87a 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -60,6 +60,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/server:instance_mocks", "//test/test_common:environment_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -128,8 +129,6 @@ envoy_cc_test( name = "guarddog_impl_test", size = "small", srcs = ["guarddog_impl_test.cc"], - # Fails intermittantly on local build - tags = ["flaky_on_windows"], deps = [ "//include/envoy/common:time_interface", "//source/common/api:api_lib", @@ -138,6 +137,7 @@ envoy_cc_test( "//source/common/stats:stats_lib", "//source/server:guarddog_lib", "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", "//test/mocks/server:watchdog_config_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:registry_lib", @@ -259,6 +259,7 @@ envoy_cc_test( "//source/server:active_raw_udp_listener_config", "//test/test_common:network_utility_lib", "//test/test_common:registry_lib", + "//test/test_common:test_runtime_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", @@ -269,12 +270,7 @@ envoy_cc_test( envoy_cc_test( name = "listener_manager_impl_quic_only_test", srcs = ["listener_manager_impl_quic_only_test.cc"], - tags = [ - "nofips", - # Skipping as quiche quic_gso_batch_writer.h does not exist on Windows - # required by quic_stream_send_buffer.cc - "skip_on_windows", - ], + tags = ["nofips"], deps = [ ":listener_manager_impl_test_lib", ":utility_lib", diff --git a/test/server/admin/clusters_handler_test.cc b/test/server/admin/clusters_handler_test.cc index f0ba3f5ae7e5..4d41b6c24969 100644 --- a/test/server/admin/clusters_handler_test.cc +++ b/test/server/admin/clusters_handler_test.cc @@ -14,11 +14,11 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, AdminInstanceTest, TestUtility::ipTestParamsToString); TEST_P(AdminInstanceTest, ClustersJson) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster; - cluster_map.emplace(cluster.info_->name_, cluster); + cluster_maps.active_clusters_.emplace(cluster.info_->name_, cluster); NiceMock outlier_detector; ON_CALL(Const(cluster), outlierDetector()).WillByDefault(Return(&outlier_detector)); diff --git a/test/server/admin/config_dump_handler_test.cc b/test/server/admin/config_dump_handler_test.cc index 7c7f5f57781f..6075dffa4a67 100644 --- a/test/server/admin/config_dump_handler_test.cc +++ b/test/server/admin/config_dump_handler_test.cc @@ -113,11 +113,11 @@ TEST_P(AdminInstanceTest, ConfigDumpMaintainsOrder) { // Test that using ?include_eds parameter adds EDS to the config dump. TEST_P(AdminInstanceTest, ConfigDumpWithEndpoint) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster; - cluster_map.emplace(cluster.info_->name_, cluster); + cluster_maps.active_clusters_.emplace(cluster.info_->name_, cluster); ON_CALL(*cluster.info_, addedViaApi()).WillByDefault(Return(false)); @@ -186,11 +186,11 @@ TEST_P(AdminInstanceTest, ConfigDumpWithEndpoint) { // Test EDS config dump while multiple localities and priorities exist TEST_P(AdminInstanceTest, ConfigDumpWithLocalityEndpoint) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster; - cluster_map.emplace(cluster.info_->name_, cluster); + cluster_maps.active_clusters_.emplace(cluster.info_->name_, cluster); ON_CALL(*cluster.info_, addedViaApi()).WillByDefault(Return(false)); @@ -398,11 +398,11 @@ TEST_P(AdminInstanceTest, ConfigDumpFiltersByResource) { // We add both static and dynamic endpoint config to the dump, but expect only // dynamic in the JSON with ?resource=dynamic_endpoint_configs. TEST_P(AdminInstanceTest, ConfigDumpWithEndpointFiltersByResource) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster_1; - cluster_map.emplace(cluster_1.info_->name_, cluster_1); + cluster_maps.active_clusters_.emplace(cluster_1.info_->name_, cluster_1); ON_CALL(*cluster_1.info_, addedViaApi()).WillByDefault(Return(true)); @@ -419,7 +419,7 @@ TEST_P(AdminInstanceTest, ConfigDumpWithEndpointFiltersByResource) { NiceMock cluster_2; cluster_2.info_->name_ = "fake_cluster_2"; - cluster_map.emplace(cluster_2.info_->name_, cluster_2); + cluster_maps.active_clusters_.emplace(cluster_2.info_->name_, cluster_2); ON_CALL(*cluster_2.info_, addedViaApi()).WillByDefault(Return(false)); diff --git a/test/server/api_listener_test.cc b/test/server/api_listener_test.cc index ff9fa0d02fd0..f337d985a5ea 100644 --- a/test/server/api_listener_test.cc +++ b/test/server/api_listener_test.cc @@ -40,7 +40,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -100,7 +100,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 37fdca915316..4058746d0a21 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -121,9 +121,9 @@ envoy_cc_test_library( "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", - "@envoy_api//envoy/config/filter/network/http_connection_manager/v2:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], ) diff --git a/test/server/config_validation/xds_fuzz.cc b/test/server/config_validation/xds_fuzz.cc index f55905f1fb67..3784e14af2ec 100644 --- a/test/server/config_validation/xds_fuzz.cc +++ b/test/server/config_validation/xds_fuzz.cc @@ -386,7 +386,7 @@ envoy::admin::v3::ListenersConfigDump XdsFuzzTest::getListenersConfigDump() { return dynamic_cast(*message_ptr); } -std::vector XdsFuzzTest::getRoutesConfigDump() { +std::vector XdsFuzzTest::getRoutesConfigDump() { auto map = test_server_->server().admin().getConfigTracker().getCallbacksMap(); // There is no route config dump before envoy has a route. @@ -399,9 +399,9 @@ std::vector XdsFuzzTest::getRoutesConfigDump // Since the route config dump gives the RouteConfigurations as an Any, go through and cast them // back to RouteConfigurations. - std::vector dump_routes; + std::vector dump_routes; for (const auto& route : dump.dynamic_route_configs()) { - envoy::api::v2::RouteConfiguration dyn_route; + envoy::config::route::v3::RouteConfiguration dyn_route; route.route_config().UnpackTo(&dyn_route); dump_routes.push_back(dyn_route); } diff --git a/test/server/config_validation/xds_fuzz.h b/test/server/config_validation/xds_fuzz.h index b2fcbf063937..602c312ee8a0 100644 --- a/test/server/config_validation/xds_fuzz.h +++ b/test/server/config_validation/xds_fuzz.h @@ -63,7 +63,7 @@ class XdsFuzzTest : public HttpIntegrationTest { void verifyRoutes(); envoy::admin::v3::ListenersConfigDump getListenersConfigDump(); - std::vector getRoutesConfigDump(); + std::vector getRoutesConfigDump(); bool eraseListener(const std::string& listener_name); bool hasRoute(const std::string& route_name); diff --git a/test/server/config_validation/xds_fuzz_test.cc b/test/server/config_validation/xds_fuzz_test.cc index 207d1132589a..5b41d4a01bbb 100644 --- a/test/server/config_validation/xds_fuzz_test.cc +++ b/test/server/config_validation/xds_fuzz_test.cc @@ -12,7 +12,7 @@ DEFINE_PROTO_FUZZER(const test::server::config_validation::XdsTestCase& input) { ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); return; } - XdsFuzzTest test(input, envoy::config::core::v3::ApiVersion::V2); + XdsFuzzTest test(input, envoy::config::core::v3::ApiVersion::V3); test.replay(); } diff --git a/test/server/config_validation/xds_verifier.cc b/test/server/config_validation/xds_verifier.cc index 90b6a8821d8e..4107fd23eabe 100644 --- a/test/server/config_validation/xds_verifier.cc +++ b/test/server/config_validation/xds_verifier.cc @@ -20,7 +20,7 @@ XdsVerifier::XdsVerifier(test::server::config_validation::Config::SotwOrDelta so */ std::string XdsVerifier::getRoute(const envoy::config::listener::v3::Listener& listener) { envoy::config::listener::v3::Filter filter0 = listener.filter_chains()[0].filters()[0]; - envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager conn_man; + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager conn_man; filter0.typed_config().UnpackTo(&conn_man); return conn_man.rds().route_config_name(); } diff --git a/test/server/config_validation/xds_verifier.h b/test/server/config_validation/xds_verifier.h index d19ad8f6c1ab..61d576ec1f40 100644 --- a/test/server/config_validation/xds_verifier.h +++ b/test/server/config_validation/xds_verifier.h @@ -2,9 +2,9 @@ #include "envoy/common/exception.h" #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/config/endpoint/v3/endpoint.pb.h" -#include "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.pb.h" #include "envoy/config/listener/v3/listener.pb.h" #include "envoy/config/route/v3/route.pb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" #include "common/common/assert.h" diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index ea6da93626b9..c8e63cb180f6 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -20,6 +20,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/instance.h" #include "test/test_common/environment.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "fmt/printf.h" @@ -160,10 +161,10 @@ TEST_F(ConfigurationImplTest, SetUpstreamClusterPerConnectionBufferLimit) { MainImpl config; config.initialize(bootstrap, server_, cluster_manager_factory_); - ASSERT_EQ(1U, config.clusterManager()->clusters().count("test_cluster")); + ASSERT_EQ(1U, config.clusterManager()->clusters().active_clusters_.count("test_cluster")); EXPECT_EQ(8192U, config.clusterManager() ->clusters() - .find("test_cluster") + .active_clusters_.find("test_cluster") ->second.get() .info() ->perConnectionBufferLimitBytes()); @@ -480,6 +481,7 @@ TEST(InitialImplTest, EmptyDeprecatedRuntime) { // A deprecated Runtime is transformed to the equivalent LayeredRuntime. TEST(InitialImplTest, DeprecatedRuntimeTranslation) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string bootstrap_yaml = R"EOF( runtime: symlink_root: /srv/runtime/current diff --git a/test/server/filter_chain_manager_impl_test.cc b/test/server/filter_chain_manager_impl_test.cc index 92fdec6d8997..bf009afbf27e 100644 --- a/test/server/filter_chain_manager_impl_test.cc +++ b/test/server/filter_chain_manager_impl_test.cc @@ -119,7 +119,7 @@ class FilterChainManagerImplTest : public testing::Test { transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } diff --git a/test/server/guarddog_impl_test.cc b/test/server/guarddog_impl_test.cc index 4e45ffa83716..9641c5afc2a4 100644 --- a/test/server/guarddog_impl_test.cc +++ b/test/server/guarddog_impl_test.cc @@ -18,6 +18,7 @@ #include "server/guarddog_impl.h" #include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" #include "test/mocks/server/watchdog_config.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/registry.h" @@ -93,6 +94,7 @@ class GuardDogTestBase : public testing::TestWithParam { std::unique_ptr time_system_; Stats::TestUtil::TestStore stats_store_; Api::ApiPtr api_; + NiceMock mock_dispatcher_; std::unique_ptr guard_dog_; }; @@ -117,10 +119,11 @@ class GuardDogDeathTest : public GuardDogTestBase { * This does everything but the final forceCheckForTest() that should cause * death for the single kill case. */ - void SetupForDeath() { + void setupForDeath() { InSequence s; initGuardDog(fakestats_, config_kill_); - unpet_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + unpet_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread", + mock_dispatcher_); dogs_.emplace_back(unpet_dog_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(99)); // 1 ms shy of death. @@ -130,15 +133,15 @@ class GuardDogDeathTest : public GuardDogTestBase { * This does everything but the final forceCheckForTest() that should cause * death for the multiple kill case. */ - void SetupForMultiDeath() { + void setupForMultiDeath() { InSequence s; initGuardDog(fakestats_, config_multikill_); - auto unpet_dog_ = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); dogs_.emplace_back(unpet_dog_); guard_dog_->forceCheckForTest(); - auto second_dog_ = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto second_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); dogs_.emplace_back(second_dog_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(499)); // 1 ms shy of multi-death. @@ -154,7 +157,8 @@ class GuardDogDeathTest : public GuardDogTestBase { // Creates 5 watchdogs. for (int i = 0; i < 5; ++i) { - auto dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread", + mock_dispatcher_); dogs_.emplace_back(dog); if (i == 0) { @@ -193,7 +197,7 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(GuardDogDeathTest, KillDeathTest) { // Is it German for "The Function"? Almost... auto die_function = [&]() -> void { - SetupForDeath(); + setupForDeath(); time_system_->advanceTimeWait(std::chrono::milliseconds(401)); // 400 ms past death. guard_dog_->forceCheckForTest(); }; @@ -207,12 +211,12 @@ TEST_P(GuardDogAlmostDeadTest, KillNoFinalCheckTest) { // This does everything the death test does, except allow enough time to // expire to reach the death panic. The death test does not verify that there // was not a crash *before* the expected line, so this test checks that. - SetupForDeath(); + setupForDeath(); } TEST_P(GuardDogDeathTest, MultiKillDeathTest) { auto die_function = [&]() -> void { - SetupForMultiDeath(); + setupForMultiDeath(); time_system_->advanceTimeWait(std::chrono::milliseconds(2)); // 1 ms past multi-death. guard_dog_->forceCheckForTest(); }; @@ -223,7 +227,7 @@ TEST_P(GuardDogAlmostDeadTest, MultiKillNoFinalCheckTest) { // This does everything the death test does not except the final force check that // should actually result in dying. The death test does not verify that there // was not a crash *before* the expected line, so this test checks that. - SetupForMultiDeath(); + setupForMultiDeath(); } TEST_P(GuardDogDeathTest, MultiKillThresholdDeathTest) { @@ -259,9 +263,10 @@ TEST_P(GuardDogAlmostDeadTest, NearDeathTest) { // there is no death. The positive case is covered in MultiKillDeathTest. InSequence s; initGuardDog(fakestats_, config_multikill_); - auto unpet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); - auto pet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); + auto pet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread", + mock_dispatcher_); // This part "waits" 600 milliseconds while one dog is touched every 100, and // the other is not. 600ms is over the threshold of 500ms for multi-kill but // only one is nonresponsive, so there should be no kill (single kill @@ -307,8 +312,8 @@ TEST_P(GuardDogMissTest, MissTest) { // This test checks the actual collected statistics after doing some timer // advances that should and shouldn't increment the counters. initGuardDog(stats_store_, config_miss_); - auto unpet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); // We'd better start at 0: checkMiss(0, "MissTest check 1"); @@ -333,8 +338,8 @@ TEST_P(GuardDogMissTest, MegaMissTest) { // This test checks the actual collected statistics after doing some timer // advances that should and shouldn't increment the counters. initGuardDog(stats_store_, config_mega_); - auto unpet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); // We'd better start at 0: checkMegaMiss(0, "MegaMissTest check 1"); @@ -360,8 +365,8 @@ TEST_P(GuardDogMissTest, MissCountTest) { // spurious condition_variable wakeup causes the counter to get incremented // more than it should be. initGuardDog(stats_store_, config_miss_); - auto sometimes_pet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto sometimes_pet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); // These steps are executed once without ever touching the watchdog. // Then the last step is to touch the watchdog and repeat the steps. @@ -424,8 +429,8 @@ TEST_P(GuardDogTestBase, WatchDogThreadIdTest) { NiceMock stats; NiceMock config(100, 90, 1000, 500, 0, std::vector{}); initGuardDog(stats, config); - auto watched_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto watched_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); EXPECT_EQ(watched_dog->threadId().debugString(), api_->threadFactory().currentThreadId().debugString()); guard_dog_->stopWatching(watched_dog); @@ -590,7 +595,7 @@ class GuardDogActionsTest : public GuardDogTestBase { void setupFirstDog(const NiceMock& config, Thread::ThreadId tid) { initGuardDog(fake_stats_, config); - first_dog_ = guard_dog_->createWatchDog(tid, "test_thread"); + first_dog_ = guard_dog_->createWatchDog(tid, "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); } @@ -612,7 +617,7 @@ TEST_P(GuardDogActionsTest, MissShouldOnlyReportRelevantThreads) { const NiceMock config(100, DISABLE_MEGAMISS, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); setupFirstDog(config, Thread::ThreadId(10)); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); time_system_->advanceTimeWait(std::chrono::milliseconds(50)); second_dog_->touch(); @@ -630,8 +635,8 @@ TEST_P(GuardDogActionsTest, MissShouldBeAbleToReportMultipleThreads) { const NiceMock config(100, DISABLE_MEGAMISS, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); first_dog_->touch(); second_dog_->touch(); @@ -674,7 +679,7 @@ TEST_P(GuardDogActionsTest, MegaMissShouldOnlyReportRelevantThreads) { const NiceMock config(DISABLE_MISS, 100, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); setupFirstDog(config, Thread::ThreadId(10)); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); time_system_->advanceTimeWait(std::chrono::milliseconds(50)); second_dog_->touch(); @@ -692,8 +697,8 @@ TEST_P(GuardDogActionsTest, MegaMissShouldBeAbleToReportMultipleThreads) { const NiceMock config(DISABLE_MISS, 100, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); first_dog_->touch(); second_dog_->touch(); @@ -740,8 +745,10 @@ TEST_P(GuardDogActionsTest, ShouldRespectEventPriority) { auto kill_function = [&]() -> void { const NiceMock config(100, 100, 100, 100, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - auto first_dog = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - auto second_dog = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + auto first_dog = + guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + auto second_dog = + guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(101)); guard_dog_->forceCheckForTest(); @@ -755,8 +762,10 @@ TEST_P(GuardDogActionsTest, ShouldRespectEventPriority) { const NiceMock config(100, 100, DISABLE_KILL, 100, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - auto first_dog = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - auto second_dog = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + auto first_dog = + guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + auto second_dog = + guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(101)); guard_dog_->forceCheckForTest(); @@ -790,7 +799,7 @@ TEST_P(GuardDogActionsTest, MultikillShouldTriggerGuardDogActions) { const NiceMock config(DISABLE_MISS, DISABLE_MEGAMISS, DISABLE_KILL, 100, 0, getActionsConfig()); setupFirstDog(config, Thread::ThreadId(10)); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(101)); guard_dog_->forceCheckForTest(); diff --git a/test/server/lds_api_test.cc b/test/server/lds_api_test.cc index 1cfeea582ca8..4f59dbf6723e 100644 --- a/test/server/lds_api_test.cc +++ b/test/server/lds_api_test.cc @@ -223,13 +223,13 @@ TEST_F(LdsApiTest, Basic) { "version_info": "0", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener2", "address": { "socket_address": { "address": "tcp://0.0.0.2", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -256,13 +256,13 @@ TEST_F(LdsApiTest, Basic) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener3", "address": { "socket_address": { "address": "tcp://0.0.0.3", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -296,7 +296,7 @@ TEST_F(LdsApiTest, UpdateVersionOnListenerRemove) { "version_info": "0", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -345,7 +345,7 @@ TEST_F(LdsApiTest, TlsConfigWithoutCaCert) { std::string response1_yaml = R"EOF( version_info: '1' resources: -- "@type": type.googleapis.com/envoy.api.v2.Listener +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener name: listener0 address: socket_address: @@ -368,7 +368,7 @@ version_info: '1' std::string response2_basic = R"EOF( version_info: '1' resources: -- "@type": type.googleapis.com/envoy.api.v2.Listener +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener name: listener-8080 address: socket_address: @@ -378,7 +378,7 @@ version_info: '1' - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: @@ -427,13 +427,13 @@ TEST_F(LdsApiTest, ReplacingListenerWithSameAddress) { "version_info": "0", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener2", "address": { "socket_address": { "address": "tcp://0.0.0.2", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -460,13 +460,13 @@ TEST_F(LdsApiTest, ReplacingListenerWithSameAddress) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener3", "address": { "socket_address": { "address": "tcp://0.0.0.2", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] diff --git a/test/server/listener_manager_impl_quic_only_test.cc b/test/server/listener_manager_impl_quic_only_test.cc index 891949861423..78d6600a529f 100644 --- a/test/server/listener_manager_impl_quic_only_test.cc +++ b/test/server/listener_manager_impl_quic_only_test.cc @@ -20,6 +20,7 @@ class ListenerManagerImplQuicOnlyTest : public ListenerManagerImplTest { public: NiceMock udp_gso_syscall_; TestThreadsafeSingletonInjector os_calls{&udp_gso_syscall_}; + Api::OsSysCallsImpl os_sys_calls_actual_; }; TEST_F(ListenerManagerImplQuicOnlyTest, QuicListenerFactoryAndSslContext) { @@ -61,7 +62,8 @@ reuse_port: true Network::Address::IpVersion::v4); envoy::config::listener::v3::Listener listener_proto = parseListenerFromV3Yaml(yaml); - ON_CALL(udp_gso_syscall_, supportsUdpGso()).WillByDefault(Return(true)); + ON_CALL(udp_gso_syscall_, supportsUdpGso()) + .WillByDefault(Return(os_sys_calls_actual_.supportsUdpGso())); EXPECT_CALL(server_.api_.random_, uuid()); expectCreateListenSocket(envoy::config::core::v3::SocketOption::STATE_PREBIND, #ifdef SO_RXQ_OVFL // SO_REUSEPORT is on as configured @@ -87,12 +89,14 @@ reuse_port: true /* expected_sockopt_name */ SO_REUSEPORT, /* expected_value */ 1, /* expected_num_calls */ 1); +#ifdef UDP_GRO if (Api::OsSysCallsSingleton::get().supportsUdpGro()) { expectSetsockopt(/* expected_sockopt_level */ SOL_UDP, /* expected_sockopt_name */ UDP_GRO, /* expected_value */ 1, /* expected_num_calls */ 1); } +#endif manager_->addOrUpdateListener(listener_proto, "", true); EXPECT_EQ(1u, manager_->listeners().size()); @@ -103,7 +107,7 @@ reuse_port: true Network::UdpPacketWriterPtr udp_packet_writer = manager_->listeners().front().get().udpPacketWriterFactory()->get().createUdpPacketWriter( listen_socket->ioHandle(), manager_->listeners()[0].get().listenerScope()); - EXPECT_TRUE(udp_packet_writer->isBatchMode()); + EXPECT_EQ(udp_packet_writer->isBatchMode(), Api::OsSysCallsSingleton::get().supportsUdpGso()); // No filter chain found with non-matching transport protocol. EXPECT_EQ(nullptr, findFilterChain(1234, "127.0.0.1", "", "tls", {}, "8.8.8.8", 111)); diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 38934ce66feb..bf60d318a573 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -29,6 +29,7 @@ #include "test/server/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/registry.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/strings/escaping.h" @@ -213,7 +214,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsTransportSocket) { transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: @@ -240,6 +241,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsTransportSocket) { TEST_F(ListenerManagerImplWithRealFiltersTest, DEPRECATED_FEATURE_TEST(TlsTransportSocketLegacyConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = TestEnvironment::substitute(R"EOF( address: socket_address: @@ -286,7 +288,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, DEPRECATED_FEATURE_TEST(TlsContex transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: @@ -1517,7 +1519,7 @@ reuse_port: true ASSERT_TRUE(SOCKET_VALID(syscall_result.rc_)); // On Windows if the socket has not been bound to an address with bind - // the call to getsockname fails with WSAEINVAL. To avoid that we make sure + // the call to getsockname fails with `WSAEINVAL`. To avoid that we make sure // that the bind system actually happens and it does not get mocked. ON_CALL(os_sys_calls_, bind(_, _, _)) .WillByDefault(Invoke( @@ -2154,7 +2156,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationP transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2200,7 +2202,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationI transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2246,7 +2248,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithServerNamesM transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2293,7 +2295,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithTransportPro transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2336,7 +2338,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithApplicationP transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2382,7 +2384,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceTypeMa transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2443,7 +2445,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceIpMatc transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2503,7 +2505,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceIpv6Ma transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2542,7 +2544,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourcePortMa transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2588,7 +2590,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2599,7 +2601,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2609,7 +2611,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2676,7 +2678,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2686,7 +2688,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2696,7 +2698,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2762,7 +2764,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2772,7 +2774,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2782,7 +2784,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2848,7 +2850,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2861,7 +2863,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2874,7 +2876,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2949,7 +2951,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithTransport transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2994,7 +2996,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithApplicati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3044,7 +3046,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithMultipleR transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3103,7 +3105,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDifferent transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3116,7 +3118,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDifferent transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3147,7 +3149,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3160,7 +3162,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3435,7 +3437,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInline) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { inline_string: ")EOF", @@ -3463,7 +3465,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateChainInlinePrivateK - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_key.pem" } @@ -3486,7 +3488,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateIncomplete) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } @@ -3509,7 +3511,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidCertificateC - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { inline_string: "invalid" } @@ -3533,7 +3535,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidIntermediate - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { inline_string: ")EOF", @@ -3555,7 +3557,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } @@ -3564,7 +3566,9 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) Network::Address::IpVersion::v4); EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), - EnvoyException, "Failed to load private key from "); + EnvoyException, + "Failed to load private key from , " + "Cause: error:0900006e:PEM routines:OPENSSL_internal:NO_START_LINE"); } TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) { @@ -3575,7 +3579,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } @@ -3589,6 +3593,28 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) { EnvoyException, "Failed to load trusted CA certificates from "); } +TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateCertPrivateKeyMismatch) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + filter_chains: + - transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } + private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns2_key.pem" } + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_THROW_WITH_REGEX( + manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), EnvoyException, + "Failed to load private key from .*, " + "Cause: error:0b000074:X.509 certificate routines:OPENSSL_internal:KEY_VALUES_MISMATCH"); +} + TEST_F(ListenerManagerImplWithRealFiltersTest, Metadata) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: @@ -3600,7 +3626,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, Metadata) { filters: - name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: metadata_test route_config: virtual_hosts: @@ -3983,7 +4009,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, CRLFilename) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4010,7 +4036,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, CRLInline) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4036,7 +4062,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, InvalidCRLInline) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4059,7 +4085,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, CRLWithNoCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4081,7 +4107,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, VerifySanWithNoCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4107,7 +4133,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, VerifyIgnoreExpirationWithNoCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4131,7 +4157,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, VerifyIgnoreExpirationWithCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4162,7 +4188,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -4191,7 +4217,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -4220,7 +4246,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -4243,7 +4269,7 @@ name: test_api_listener_2 port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router diff --git a/test/server/options_impl_test.cc b/test/server/options_impl_test.cc index 3ab6be0e647c..bf22008af5a3 100644 --- a/test/server/options_impl_test.cc +++ b/test/server/options_impl_test.cc @@ -300,8 +300,7 @@ TEST_F(OptionsImplTest, OptionsAreInSyncWithProto) { // 4. allow-unknown-fields - deprecated alias of allow-unknown-static-fields. // 5. use-fake-symbol-table - short-term override for rollout of real symbol-table implementation. // 6. hot restart version - print the hot restart version and exit. - // 7. log-format-prefix-with-location - short-term override for rollout of dynamic log format. - const uint32_t options_not_in_proto = 7; + const uint32_t options_not_in_proto = 6; // There are two deprecated options: "max_stats" and "max_obj_name_len". const uint32_t deprecated_options = 2; @@ -457,17 +456,10 @@ TEST_F(OptionsImplTest, LogFormatDefault) { EXPECT_EQ(options->logFormat(), "[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"); } -TEST_F(OptionsImplTest, LogFormatDefaultNoPrefix) { - std::unique_ptr options = - createOptionsImpl({"envoy", "-c", "hello", "--log-format-prefix-with-location", "0"}); - EXPECT_EQ(options->logFormat(), "[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"); -} - TEST_F(OptionsImplTest, LogFormatOverride) { std::unique_ptr options = - createOptionsImpl({"envoy", "-c", "hello", "--log-format", "%%v %v %t %v", - "--log-format-prefix-with-location 1"}); - EXPECT_EQ(options->logFormat(), "%%v [%g:%#] %v %t [%g:%#] %v"); + createOptionsImpl({"envoy", "-c", "hello", "--log-format", "%%v %v %t %v"}); + EXPECT_EQ(options->logFormat(), "%%v %v %t %v"); } TEST_F(OptionsImplTest, LogFormatOverrideNoPrefix) { diff --git a/test/server/overload_manager_impl_test.cc b/test/server/overload_manager_impl_test.cc index e2e9ffb80f65..a134cfab6075 100644 --- a/test/server/overload_manager_impl_test.cc +++ b/test/server/overload_manager_impl_test.cc @@ -1,7 +1,7 @@ #include #include "envoy/config/overload/v3/overload.pb.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/server/resource_monitor.h" #include "envoy/server/resource_monitor_config.h" diff --git a/test/server/server_test.cc b/test/server/server_test.cc index a26da0f646e6..d7abaf788c1d 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -52,6 +52,7 @@ TEST(ServerInstanceUtil, flushHelper) { InSequence s; Stats::TestUtil::TestStore store; + Event::SimulatedTimeSystem time_system; Stats::Counter& c = store.counter("hello"); c.inc(); store.gauge("world", Stats::Gauge::ImportMode::Accumulate).set(5); @@ -59,7 +60,7 @@ TEST(ServerInstanceUtil, flushHelper) { store.textReadout("text").set("is important"); std::list sinks; - InstanceUtil::flushMetricsToSinks(sinks, store); + InstanceUtil::flushMetricsToSinks(sinks, store, time_system); // Make sure that counters have been latched even if there are no sinks. EXPECT_EQ(1UL, c.value()); EXPECT_EQ(0, c.latch()); @@ -80,7 +81,7 @@ TEST(ServerInstanceUtil, flushHelper) { EXPECT_EQ(snapshot.textReadouts()[0].get().value(), "is important"); })); c.inc(); - InstanceUtil::flushMetricsToSinks(sinks, store); + InstanceUtil::flushMetricsToSinks(sinks, store, time_system); // Histograms don't currently work with the isolated store so test those with a mock store. NiceMock mock_store; @@ -93,7 +94,7 @@ TEST(ServerInstanceUtil, flushHelper) { EXPECT_EQ(snapshot.histograms().size(), 1); EXPECT_TRUE(snapshot.textReadouts().empty()); })); - InstanceUtil::flushMetricsToSinks(sinks, mock_store); + InstanceUtil::flushMetricsToSinks(sinks, mock_store, time_system); } class RunHelperTest : public testing::Test { @@ -661,22 +662,50 @@ TEST_P(ServerInstanceImplTest, LoadsBootstrapFromPbText) { EXPECT_EQ("bootstrap_id", server_->localInfo().node().id()); } -// Validate that bootstrap v2 pb_text with deprecated fields loads. -TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(LoadsV2BootstrapFromPbText)) { - EXPECT_LOG_CONTAINS( - "trace", "Configuration does not parse cleanly as v3", - initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text")); +// Validate that bootstrap v2 is rejected when --bootstrap-version is not set. +TEST_P(ServerInstanceImplTest, + DEPRECATED_FEATURE_TEST(FailToLoadV2BootstrapWithoutExplicitVersion)) { + EXPECT_THROW_WITH_REGEX( + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text"), + DeprecatedMajorVersionException, + "Support for v2 will be removed from Envoy at the start of Q1 2021. You may make use of v2 " + "in Q3 2020 by setting"); +} + +// Validate that bootstrap v2 pb_text with deprecated fields loads when --bootstrap-version is set. +TEST_P(ServerInstanceImplTest, + DEPRECATED_FEATURE_TEST(LoadsV2BootstrapWithExplicitVersionFromPbText)) { + options_.bootstrap_version_ = 2; + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text"); EXPECT_FALSE(server_->localInfo().node().hidden_envoy_deprecated_build_version().empty()); } -// Validate that bootstrap v2 YAML with deprecated fields loads. -TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(LoadsV2BootstrapFromYaml)) { +// Validate that bootstrap v2 pb_text with deprecated fields fails to load when +// --bootstrap-version is not set. +TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(FailToLoadV2BootstrapFromPbText)) { + EXPECT_THROW_WITH_REGEX( + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text"), + EnvoyException, "The v2 xDS major version is deprecated and disabled by default."); +} + +// Validate that bootstrap v2 YAML with deprecated fields loads when --bootstrap-version is set. +TEST_P(ServerInstanceImplTest, + DEPRECATED_FEATURE_TEST(LoadsV2BootstrapWithExplicitVersionFromYaml)) { + options_.bootstrap_version_ = 2; EXPECT_LOG_CONTAINS( "trace", "Configuration does not parse cleanly as v3", initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.yaml")); EXPECT_FALSE(server_->localInfo().node().hidden_envoy_deprecated_build_version().empty()); } +// Validate that bootstrap v2 YAML with deprecated fields fails to load when +// --bootstrap-version is not set. +TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(FailsToLoadV2BootstrapFromYaml)) { + EXPECT_THROW_WITH_REGEX( + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.yaml"), + EnvoyException, "The v2 xDS major version is deprecated and disabled by default."); +} + // Validate that bootstrap v3 pb_text with new fields loads fails if V2 config is specified. TEST_P(ServerInstanceImplTest, FailToLoadV3ConfigWhenV2SelectedFromPbText) { options_.bootstrap_version_ = 2; @@ -848,6 +877,7 @@ TEST_P(ServerInstanceImplTest, BootstrapRtdsThroughAdsViaEdsFails) { } TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(InvalidLegacyBootstrapRuntime)) { + options_.bootstrap_version_ = 2; EXPECT_THROW_WITH_MESSAGE( initialize("test/server/test_data/server/invalid_legacy_runtime_bootstrap.yaml"), EnvoyException, "Invalid runtime entry value for foo"); @@ -1108,6 +1138,7 @@ TEST_P(ServerInstanceImplTest, NoHttpTracing) { TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(ZipkinHttpTracingEnabled)) { options_.service_cluster_name_ = "some_cluster_name"; options_.service_node_name_ = "some_node_name"; + options_.bootstrap_version_ = 2; EXPECT_NO_THROW(initialize("test/server/test_data/server/zipkin_tracing_deprecated_config.yaml")); EXPECT_EQ("zipkin", server_->httpContext().defaultTracingConfig().http().name()); } diff --git a/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml b/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml index e79bd34b52e7..3de1e923fbfc 100644 --- a/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml +++ b/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml @@ -10,3 +10,8 @@ tracing: collector_cluster: zipkin collector_endpoint: "/api/v1/spans" collector_endpoint_version: HTTP_JSON +layered_runtime: + layers: + - name: static_layer + static_layer: + envoy.reloadable_features.enable_deprecated_v2_api: true diff --git a/test/test_common/resources.h b/test/test_common/resources.h index 1f0d762b68e8..e6508837f3cf 100644 --- a/test/test_common/resources.h +++ b/test/test_common/resources.h @@ -12,18 +12,20 @@ namespace Config { */ class TypeUrlValues { public: - const std::string Listener{"type.googleapis.com/envoy.api.v2.Listener"}; - const std::string Cluster{"type.googleapis.com/envoy.api.v2.Cluster"}; - const std::string ClusterLoadAssignment{"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"}; - const std::string Secret{"type.googleapis.com/envoy.api.v2.auth.Secret"}; - const std::string RouteConfiguration{"type.googleapis.com/envoy.api.v2.RouteConfiguration"}; - const std::string VirtualHost{"type.googleapis.com/envoy.api.v2.route.VirtualHost"}; + const std::string Listener{"type.googleapis.com/envoy.config.listener.v3.Listener"}; + const std::string Cluster{"type.googleapis.com/envoy.config.cluster.v3.Cluster"}; + const std::string ClusterLoadAssignment{ + "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"}; + const std::string Secret{"type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"}; + const std::string RouteConfiguration{ + "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"}; + const std::string VirtualHost{"type.googleapis.com/envoy.config.route.v3.VirtualHost"}; const std::string ScopedRouteConfiguration{ - "type.googleapis.com/envoy.api.v2.ScopedRouteConfiguration"}; - const std::string Runtime{"type.googleapis.com/envoy.service.discovery.v2.Runtime"}; + "type.googleapis.com/envoy.config.route.v3.ScopedRouteConfiguration"}; + const std::string Runtime{"type.googleapis.com/envoy.service.runtime.v3.Runtime"}; }; using TypeUrl = ConstSingleton; } // namespace Config -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/test_common/status_utility.h b/test/test_common/status_utility.h index 5a48a7c2e0f3..7611830b26af 100644 --- a/test/test_common/status_utility.h +++ b/test/test_common/status_utility.h @@ -13,7 +13,7 @@ namespace StatusHelpers { // StatusOr status(3); // EXPECT_THAT(status, IsOkAndHolds(3)); MATCHER_P(IsOkAndHolds, expected, "") { - if (!arg) { + if (!arg.ok()) { *result_listener << "which has unexpected status: " << arg.status(); return false; } diff --git a/test/test_common/test_runtime.h b/test/test_common/test_runtime.h index 08e0c441efc3..4a4ae54c8632 100644 --- a/test/test_common/test_runtime.h +++ b/test/test_common/test_runtime.h @@ -40,10 +40,10 @@ class TestScopedRuntime { generator_, validation_visitor_, *api_)); } -private: +protected: Event::MockDispatcher dispatcher_; testing::NiceMock tls_; - Stats::IsolatedStoreImpl store_; + Stats::TestUtil::TestStore store_; Random::MockRandomGenerator generator_; Api::ApiPtr api_; testing::NiceMock local_info_; @@ -51,4 +51,14 @@ class TestScopedRuntime { std::unique_ptr loader_; }; +class TestDeprecatedV2Api : public TestScopedRuntime { +public: + TestDeprecatedV2Api() { + Runtime::LoaderSingleton::getExisting()->mergeValues({ + {"envoy.reloadable_features.enable_deprecated_v2_api", "true"}, + {"envoy.features.enable_all_deprecated_features", "true"}, + }); + } +}; + } // namespace Envoy diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index 928cb440c5f2..08c04e889c74 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -249,55 +249,6 @@ std::vector TestUtility::listFiles(const std::string& path, bool re return file_names; } -std::string TestUtility::xdsResourceName(const ProtobufWkt::Any& resource) { - if (resource.type_url() == Config::TypeUrl::get().Listener) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().RouteConfiguration) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().Cluster) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().ClusterLoadAssignment) { - return TestUtility::anyConvert(resource) - .cluster_name(); - } - if (resource.type_url() == Config::TypeUrl::get().VirtualHost) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().Runtime) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource) - .cluster_name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - throw EnvoyException( - absl::StrCat("xdsResourceName does not know about type URL ", resource.type_url())); -} - std::string TestUtility::addLeftAndRightPadding(absl::string_view to_pad, int desired_length) { int line_fill_len = desired_length - to_pad.length(); int first_half_len = line_fill_len / 2; @@ -428,6 +379,10 @@ ApiPtr createApiForTest() { Filesystem::fileSystemForTest()); } +ApiPtr createApiForTest(Filesystem::Instance& filesystem) { + return std::make_unique(Thread::threadFactoryForTest(), filesystem); +} + ApiPtr createApiForTest(Random::RandomGenerator& random) { return std::make_unique(Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr, nullptr, &random); diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 3dc15c8ccac0..6b4abcd7c63c 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -438,13 +438,6 @@ class TestUtility { return AssertionSuccess(); } - /** - * Returns the closest thing to a sensible "name" field for the given xDS resource. - * @param resource the resource to extract the name of. - * @return the resource's name. - */ - static std::string xdsResourceName(const ProtobufWkt::Any& resource); - /** * Returns a "novel" IPv4 loopback address, if available. * For many tests, we want a loopback address other than 127.0.0.1 where possible. For some @@ -1041,6 +1034,7 @@ makeHeaderMap(const std::initializer_list>& namespace Api { ApiPtr createApiForTest(); +ApiPtr createApiForTest(Filesystem::Instance& filesystem); ApiPtr createApiForTest(Random::RandomGenerator& random); ApiPtr createApiForTest(Stats::Store& stat_store); ApiPtr createApiForTest(Stats::Store& stat_store, Random::RandomGenerator& random); diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index d049460d3e41..2cfc796084eb 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -80,12 +80,13 @@ template class WasmTestBase : public Base { lifecycle_notifier_, remote_data_provider_, [this](WasmHandleSharedPtr wasm) { wasm_ = wasm; }, create_root); if (wasm_) { - wasm_ = getOrCreateThreadLocalWasm( + plugin_handle_ = getOrCreateThreadLocalPlugin( wasm_, plugin_, dispatcher_, [this, create_root](Wasm* wasm, const std::shared_ptr& plugin) { root_context_ = static_cast(create_root(wasm, plugin)); return root_context_; }); + wasm_ = plugin_handle_->wasmHandleForTest(); } } @@ -101,6 +102,7 @@ template class WasmTestBase : public Base { NiceMock init_manager_; WasmHandleSharedPtr wasm_; PluginSharedPtr plugin_; + PluginHandleSharedPtr plugin_handle_; NiceMock ssl_; NiceMock connection_; NiceMock decoder_callbacks_; @@ -114,9 +116,9 @@ template class WasmTestBase : public Base { template class WasmHttpFilterTestBase : public WasmTestBase { public: - template void setupFilterBase(const std::string root_id = "") { + template void setupFilterBase() { auto wasm = WasmTestBase::wasm_ ? WasmTestBase::wasm_->wasm().get() : nullptr; - int root_context_id = wasm ? wasm->getRootContext(root_id)->id() : 0; + int root_context_id = wasm ? wasm->getRootContext(WasmTestBase::plugin_, false)->id() : 0; context_ = std::make_unique(wasm, root_context_id, WasmTestBase::plugin_); context_->setDecoderFilterCallbacks(decoder_callbacks_); context_->setEncoderFilterCallbacks(encoder_callbacks_); @@ -131,9 +133,9 @@ template class WasmHttpFilterTestBase : public W template class WasmNetworkFilterTestBase : public WasmTestBase { public: - template void setupFilterBase(const std::string root_id = "") { + template void setupFilterBase() { auto wasm = WasmTestBase::wasm_ ? WasmTestBase::wasm_->wasm().get() : nullptr; - int root_context_id = wasm ? wasm->getRootContext(root_id)->id() : 0; + int root_context_id = wasm ? wasm->getRootContext(WasmTestBase::plugin_, false)->id() : 0; context_ = std::make_unique(wasm, root_context_id, WasmTestBase::plugin_); context_->initializeReadFilterCallbacks(read_filter_callbacks_); context_->initializeWriteFilterCallbacks(write_filter_callbacks_); diff --git a/test/tools/router_check/BUILD b/test/tools/router_check/BUILD index 5eaeef6cebba..1da1d8c084d2 100644 --- a/test/tools/router_check/BUILD +++ b/test/tools/router_check/BUILD @@ -38,6 +38,7 @@ envoy_cc_test_library( "//source/exe:platform_impl_lib", "//test/mocks/server:instance_mocks", "//test/test_common:printers_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", diff --git a/test/tools/router_check/router_check.cc b/test/tools/router_check/router_check.cc index 5e03f132e3a7..fea89d3eca98 100644 --- a/test/tools/router_check/router_check.cc +++ b/test/tools/router_check/router_check.cc @@ -4,6 +4,7 @@ #include "exe/platform_impl.h" +#include "test/test_common/test_runtime.h" #include "test/tools/router_check/router.h" int main(int argc, char* argv[]) { @@ -12,6 +13,8 @@ int main(int argc, char* argv[]) { const bool enforce_coverage = options.failUnder() != 0.0; // We need this to ensure WSAStartup is called on Windows Envoy::PlatformImpl platform_impl_; + // Until we remove v2 API, the tool will warn but not fail. + Envoy::TestDeprecatedV2Api _deprecated_v2_api; try { Envoy::RouterCheckTool checktool = diff --git a/third_party/statusor/BUILD b/third_party/statusor/BUILD deleted file mode 100644 index fe17659a5378..000000000000 --- a/third_party/statusor/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -licenses(["notice"]) # Apache 2 - -load( - "//bazel:envoy_build_system.bzl", - "envoy_cc_library", - "envoy_cc_test", - "envoy_package", -) - -envoy_package() - -envoy_cc_library( - name = "statusor_lib", - srcs = [ - "statusor.cc", - ], - hdrs = [ - "statusor.h", - "statusor_internals.h", - ], - external_deps = [ - "abseil_status", - ], - deps = ["//source/common/common:assert_lib"], -) - -envoy_cc_test( - name = "statusor_test", - srcs = ["statusor_test.cc"], - deps = [ - ":statusor_lib", - ], -) diff --git a/third_party/statusor/statusor.cc b/third_party/statusor/statusor.cc deleted file mode 100644 index 20105404217a..000000000000 --- a/third_party/statusor/statusor.cc +++ /dev/null @@ -1,43 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* - * Copyright 2019 Google LLC - * - * 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. - */ - -#include "third_party/statusor/statusor.h" - -#include - -#include "common/common/assert.h" - -namespace absl { - -namespace internal_statusor { - -void Helper::HandleInvalidStatusCtorArg(absl::Status* status) { - const char* kMessage = "An OK status is not a valid constructor argument to StatusOr"; - ASSERT(false, kMessage); - // In optimized builds, we will fall back to ::util::error::INTERNAL. - *status = absl::Status(absl::StatusCode::kInternal, kMessage); -} - -void Helper::Crash(const absl::Status&) { abort(); } - -} // namespace internal_statusor - -} // namespace Envoy diff --git a/third_party/statusor/statusor.h b/third_party/statusor/statusor.h deleted file mode 100644 index d8a7d48329e0..000000000000 --- a/third_party/statusor/statusor.h +++ /dev/null @@ -1,352 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* - * Copyright 2019 Google LLC - * - * 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. - */ - -// StatusOr is the union of a Status object and a T -// object. StatusOr models the concept of an object that is either a -// usable value, or an error Status explaining why such a value is -// not present. To this end, StatusOr does not allow its Status -// value to be OkStatus(). -// -// The primary use-case for StatusOr is as the return value of a -// function which may fail. -// -// Example usage of a StatusOr: -// -// StatusOr result = DoBigCalculationThatCouldFail(); -// if (result) { -// result->DoSomethingCool(); -// } else { -// GOOGLE_LOG(ERROR) << result.status(); -// } -// -// Example that is guaranteed crash if the result holds no value: -// -// StatusOr result = DoBigCalculationThatCouldFail(); -// const Foo& foo = result.value(); -// foo.DoSomethingCool(); -// -// Example usage of a StatusOr>: -// -// StatusOr> result = FooFactory::MakeNewFoo(arg); -// if (!result) { -// GOOGLE_LOG(ERROR) << result.status(); -// } else if (*result == nullptr) { -// GOOGLE_LOG(ERROR) << "Unexpected null pointer"; -// } else { -// (*result)->DoSomethingCool(); -// } -// -// Example factory implementation returning StatusOr: -// -// StatusOr FooFactory::MakeFoo(int arg) { -// if (arg <= 0) { -// return ::cel_base::Status(::cel_base::INVALID_ARGUMENT, -// "Arg must be positive"); -// } -// return Foo(arg); -// } -// - -#include -#include -#include -#include - -#include "third_party/statusor/statusor_internals.h" - -#include "absl/base/attributes.h" -#include "absl/base/macros.h" - -namespace absl { - -// Returned StatusOr objects may not be ignored. -template class ABSL_MUST_USE_RESULT StatusOr; - -template -class StatusOr : private internal_statusor::StatusOrData, - private internal_statusor::TraitsBase::value, - std::is_move_constructible::value> { - template friend class StatusOr; - - typedef internal_statusor::StatusOrData Base; - -public: - using element_type = T; - - // Constructs a new StatusOr with Status::UNKNOWN status. This is marked - // 'explicit' to try to catch cases like 'return {};', where people think - // StatusOr> will be initialized with an empty vector, - // instead of a Status::UNKNOWN status. - explicit StatusOr(); - - // StatusOr will be copy constructible/assignable if T is copy - // constructible. - StatusOr(const StatusOr&) = default; - StatusOr& operator=(const StatusOr&) = default; - - // StatusOr will be move constructible/assignable if T is move - // constructible. - StatusOr(StatusOr&&) = default; - StatusOr& operator=(StatusOr&&) = default; - - // Conversion copy/move constructor, T must be convertible from U. - // These should not participate in overload resolution if U - // is not convertible to T. - template StatusOr(const StatusOr& other); - template StatusOr(StatusOr&& other); - - // Conversion copy/move assignment operator, T must be convertible from U. - template StatusOr& operator=(const StatusOr& other); - template StatusOr& operator=(StatusOr&& other); - - // Constructs a new StatusOr with the given value. After calling this - // constructor, this->ok() will be true and the contained value may be - // retrieved with value(), operator*(), or operator->(). - // - // NOTE: Not explicit - we want to use StatusOr as a return type - // so it is convenient and sensible to be able to do 'return T()' - // when the return type is StatusOr. - // - // REQUIRES: T is copy constructible. - StatusOr(const T& value); - - // Constructs a new StatusOr with the given non-ok status. After calling this - // constructor, this->ok() will be false and calls to value() will - // CHECK-fail. - // - // NOTE: Not explicit - we want to use StatusOr as a return - // value, so it is convenient and sensible to be able to do 'return - // Status()' when the return type is StatusOr. - // - // REQUIRES: !status.ok(). This requirement is checked by ASSERT. - // In optimized builds, passing OkStatus() here will have the effect - // of passing INTERNAL as a fallback. - StatusOr(const absl::Status& status); - StatusOr& operator=(const absl::Status& status); - - // Similar to the `const T&` overload. - // - // REQUIRES: T is move constructible. - StatusOr(T&& value); - - // RValue versions of the operations declared above. - StatusOr(absl::Status&& status); - StatusOr& operator=(absl::Status&& status); - - // Returns this->ok() - explicit operator bool() const { return ok(); } - - // Returns this->status().ok() - ABSL_MUST_USE_RESULT bool ok() const { return this->status_.ok(); } - - // Returns a reference to our status. If this contains a T, then - // returns OkStatus(). - const absl::Status& status() const&; - absl::Status status() &&; - - // Returns a reference to our current value, or ASSERT-fails if !this->ok(). If - // you have already checked the status using this->ok() or operator bool(), - // then you probably want to use operator*() or operator->() to access the - // current value instead of value(). - // - // Note: for value types that are cheap to copy, prefer simple code: - // - // T value = status_or.value(); - // - // Otherwise, if the value type is expensive to copy, but can be left - // in the StatusOr, simply assign to a reference: - // - // T& value = status_or.value(); // or `const T&` - // - // Otherwise, if the value type supports an efficient move, it can be - // used as follows: - // - // T value = std::move(status_or).value(); - // - // The std::move on status_or instead of on the whole expression enables - // warnings about possible uses of the status_or object after the move. - - const T& value() const&; - T& value() &; - const T&& value() const&&; - T&& value() &&; - - // Returns a reference to the current value. - // - // REQUIRES: this->ok() == true, otherwise the behavior is undefined. - // - // Use this->ok() or `operator bool()` to verify that there is a current - // value. Alternatively, see value() for a similar API that guarantees - // ASSERT-failing if there is no current value. - const T& operator*() const&; - T& operator*() &; - const T&& operator*() const&&; - T&& operator*() &&; - - // Returns a pointer to the current value. - // - // REQUIRES: this->ok() == true, otherwise the behavior is undefined. - // - // Use this->ok() or `operator bool()` to verify that there is a current - // value. - const T* operator->() const; - T* operator->(); - - // Returns a copy of the current value if this->ok() == true. Otherwise - // returns a default value. - template T value_or(U&& default_value) const&; - template T value_or(U&& default_value) &&; - - // Ignores any errors. This method does nothing except potentially suppress - // complaints from any tools that are checking that errors are not dropped on - // the floor. - void IgnoreError() const; -}; - -//////////////////////////////////////////////////////////////////////////////// -// Implementation details for StatusOr - -template -StatusOr::StatusOr() : Base(absl::Status(absl::StatusCode::kUnknown, "")) {} - -template StatusOr::StatusOr(const T& value) : Base(value) {} - -template StatusOr::StatusOr(const absl::Status& status) : Base(status) {} - -template StatusOr& StatusOr::operator=(const absl::Status& status) { - this->Assign(status); - return *this; -} - -template StatusOr::StatusOr(T&& value) : Base(std::move(value)) {} - -template StatusOr::StatusOr(absl::Status&& status) : Base(std::move(status)) {} - -template StatusOr& StatusOr::operator=(absl::Status&& status) { - this->Assign(std::move(status)); - return *this; -} - -template -template -inline StatusOr::StatusOr(const StatusOr& other) - : Base(static_cast::Base&>(other)) {} - -template -template -inline StatusOr& StatusOr::operator=(const StatusOr& other) { - if (other.ok()) - this->Assign(other.value()); - else - this->Assign(other.status()); - return *this; -} - -template -template -inline StatusOr::StatusOr(StatusOr&& other) - : Base(static_cast::Base&&>(other)) {} - -template -template -inline StatusOr& StatusOr::operator=(StatusOr&& other) { - if (other.ok()) { - this->Assign(std::move(other).value()); - } else { - this->Assign(std::move(other).status()); - } - return *this; -} - -template const absl::Status& StatusOr::status() const& { return this->status_; } -template absl::Status StatusOr::status() && { - return ok() ? absl::OkStatus() : std::move(this->status_); -} - -template const T& StatusOr::value() const& { - this->EnsureOk(); - return this->data_; -} - -template T& StatusOr::value() & { - this->EnsureOk(); - return this->data_; -} - -template const T&& StatusOr::value() const&& { - this->EnsureOk(); - return std::move(this->data_); -} - -template T&& StatusOr::value() && { - this->EnsureOk(); - return std::move(this->data_); -} - -template const T& StatusOr::operator*() const& { - this->EnsureOk(); - return this->data_; -} - -template T& StatusOr::operator*() & { - this->EnsureOk(); - return this->data_; -} - -template const T&& StatusOr::operator*() const&& { - this->EnsureOk(); - return std::move(this->data_); -} - -template T&& StatusOr::operator*() && { - this->EnsureOk(); - return std::move(this->data_); -} - -template const T* StatusOr::operator->() const { - this->EnsureOk(); - return &this->data_; -} - -template T* StatusOr::operator->() { - this->EnsureOk(); - return &this->data_; -} - -template template T StatusOr::value_or(U&& default_value) const& { - if (ok()) { - return this->data_; - } - return std::forward(default_value); -} - -template template T StatusOr::value_or(U&& default_value) && { - if (ok()) { - return std::move(this->data_); - } - return std::forward(default_value); -} - -template void StatusOr::IgnoreError() const { - // no-op -} - -} // namespace absl diff --git a/third_party/statusor/statusor_internals.h b/third_party/statusor/statusor_internals.h deleted file mode 100644 index ffe3f91af2c7..000000000000 --- a/third_party/statusor/statusor_internals.h +++ /dev/null @@ -1,244 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* - * Copyright 2019 Google LLC - * - * 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. - */ - -#include -#include -#include - -#include "absl/base/attributes.h" -#include "absl/meta/type_traits.h" -#include "absl/status/status.h" - -namespace absl { - -namespace internal_statusor { - -class Helper { -public: - // Move type-agnostic error handling to the .cc. - static void HandleInvalidStatusCtorArg(absl::Status*); - ABSL_ATTRIBUTE_NORETURN static void Crash(const absl::Status& status); -}; - -// Construct an instance of T in `p` through placement new, passing Args... to -// the constructor. -// This abstraction is here mostly for the gcc performance fix. -template void PlacementNew(void* p, Args&&... args) { -#if defined(__GNUC__) && !defined(__clang__) - // Teach gcc that 'p' cannot be null, fixing code size issues. - if (p == nullptr) - __builtin_unreachable(); -#endif - new (p) T(std::forward(args)...); -} - -// Helper base class to hold the data and all operations. -// We move all this to a base class to allow mixing with the appropriate -// TraitsBase specialization. -template class StatusOrData { - template friend class StatusOrData; - -public: - StatusOrData() = delete; - - StatusOrData(const StatusOrData& other) { - if (other.ok()) { - MakeValue(other.data_); - MakeStatus(); - } else { - MakeStatus(other.status_); - } - } - - StatusOrData(StatusOrData&& other) noexcept { - if (other.ok()) { - MakeValue(std::move(other.data_)); - MakeStatus(); - } else { - MakeStatus(std::move(other.status_)); - } - } - - template StatusOrData(const StatusOrData& other) { - if (other.ok()) { - MakeValue(other.data_); - MakeStatus(); - } else { - MakeStatus(other.status_); - } - } - - template StatusOrData(StatusOrData&& other) { - if (other.ok()) { - MakeValue(std::move(other.data_)); - MakeStatus(); - } else { - MakeStatus(std::move(other.status_)); - } - } - - explicit StatusOrData(const T& value) : data_(value) { MakeStatus(); } - explicit StatusOrData(T&& value) : data_(std::move(value)) { MakeStatus(); } - - explicit StatusOrData(const absl::Status& status) : status_(status) { EnsureNotOk(); } - explicit StatusOrData(absl::Status&& status) : status_(std::move(status)) { EnsureNotOk(); } - - StatusOrData& operator=(const StatusOrData& other) { - if (this == &other) - return *this; - if (other.ok()) - Assign(other.data_); - else - Assign(other.status_); - return *this; - } - - StatusOrData& operator=(StatusOrData&& other) { - if (this == &other) - return *this; - if (other.ok()) - Assign(std::move(other.data_)); - else - Assign(std::move(other.status_)); - return *this; - } - - ~StatusOrData() { - if (ok()) { - status_.~Status(); - data_.~T(); - } else { - status_.~Status(); - } - } - - void Assign(const T& value) { - if (ok()) { - data_.~T(); - MakeValue(value); - } else { - MakeValue(value); - status_ = absl::OkStatus(); - } - } - - void Assign(T&& value) { - if (ok()) { - data_.~T(); - MakeValue(std::move(value)); - } else { - MakeValue(std::move(value)); - status_ = absl::OkStatus(); - } - } - - void Assign(const absl::Status& status) { - Clear(); - status_ = status; - EnsureNotOk(); - } - - void Assign(absl::Status&& status) { - Clear(); - status_ = std::move(status); - EnsureNotOk(); - } - - bool ok() const { return status_.ok(); } - -protected: - // status_ will always be active after the constructor. - // We make it a union to be able to initialize exactly how we need without - // waste. - // E/g. in the copy constructor we use the default constructor of - // Status in the ok() path to avoid an extra Ref call. - union { - absl::Status status_; - }; - - // data_ is active iff status_.ok()==true - struct Dummy {}; - union { - // When T is const, we need some non-const object we can cast to void* for - // the placement new. dummy_ is that object. - Dummy dummy_; - T data_; - }; - - void Clear() { - if (ok()) - data_.~T(); - } - - void EnsureOk() const { - if (!ok()) - Helper::Crash(status_); - } - - void EnsureNotOk() { - if (ok()) - Helper::HandleInvalidStatusCtorArg(&status_); - } - - // Construct the value (i.e. data_) through placement new with the passed - // argument. - template void MakeValue(Arg&& arg) { - internal_statusor::PlacementNew(&dummy_, std::forward(arg)); - } - - // Construct the status (i.e. status_) through placement new with the passed - // argument. - template void MakeStatus(Args&&... args) { - internal_statusor::PlacementNew(&status_, std::forward(args)...); - } -}; - -// Helper base class to allow implicitly deleted constructors and assignment -// operations in StatusOr. -// TraitsBase will explicitly delete what it can't support and StatusOr will -// inherit that behavior implicitly. -template struct TraitsBase { - TraitsBase() = default; - TraitsBase(const TraitsBase&) = default; - TraitsBase(TraitsBase&&) = default; - TraitsBase& operator=(const TraitsBase&) = default; - TraitsBase& operator=(TraitsBase&&) = default; -}; - -template <> struct TraitsBase { - TraitsBase() = default; - TraitsBase(const TraitsBase&) = delete; - TraitsBase(TraitsBase&&) = default; - TraitsBase& operator=(const TraitsBase&) = delete; - TraitsBase& operator=(TraitsBase&&) = default; -}; - -template <> struct TraitsBase { - TraitsBase() = default; - TraitsBase(const TraitsBase&) = delete; - TraitsBase(TraitsBase&&) = delete; - TraitsBase& operator=(const TraitsBase&) = delete; - TraitsBase& operator=(TraitsBase&&) = delete; -}; - -} // namespace internal_statusor - -} // namespace absl diff --git a/third_party/statusor/statusor_test.cc b/third_party/statusor/statusor_test.cc deleted file mode 100644 index b627c518476e..000000000000 --- a/third_party/statusor/statusor_test.cc +++ /dev/null @@ -1,441 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* Copyright 2017 The TensorFlow 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. -==============================================================================*/ - -// Unit tests for StatusOr - -#include -#include - -#include "third_party/statusor/statusor.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace absl { -namespace { - - -class Base1 { - public: - virtual ~Base1() {} - int pad_; -}; - -class Base2 { - public: - virtual ~Base2() {} - int yetotherpad_; -}; - -class Derived : public Base1, public Base2 { - public: - ~Derived() override {} - int evenmorepad_; -}; - -class CopyNoAssign { - public: - explicit CopyNoAssign(int value) : foo_(value) {} - CopyNoAssign(const CopyNoAssign& other) : foo_(other.foo_) {} - int foo_; - - private: - const CopyNoAssign& operator=(const CopyNoAssign&); -}; - -class NoDefaultConstructor { - public: - explicit NoDefaultConstructor(int foo); -}; - -static_assert(!std::is_default_constructible(), - "Should not be default-constructible."); - -StatusOr> ReturnUniquePtr() { - // Uses implicit constructor from T&& - return std::unique_ptr(new int(0)); -} - -TEST(StatusOr, ElementType) { - static_assert(std::is_same::element_type, int>(), ""); - static_assert(std::is_same::element_type, char>(), ""); -} - -TEST(StatusOr, NullPointerStatusOr) { - // As a very special case, null-plain-pointer StatusOr used to be an - // error. Test that it no longer is. - StatusOr null_status(nullptr); - EXPECT_TRUE(null_status.ok()); - EXPECT_EQ(null_status.value(), nullptr); -} - -TEST(StatusOr, TestNoDefaultConstructorInitialization) { - // Explicitly initialize it with an error code. - StatusOr statusor(absl::CancelledError("")); - EXPECT_FALSE(statusor.ok()); - EXPECT_EQ(statusor.status().code(), absl::StatusCode::kCancelled); - - // Default construction of StatusOr initializes it with an UNKNOWN error code. - StatusOr statusor2; - EXPECT_FALSE(statusor2.ok()); - EXPECT_EQ(statusor2.status().code(), absl::StatusCode::kUnknown); -} - -TEST(StatusOr, TestMoveOnlyInitialization) { - StatusOr> thing(ReturnUniquePtr()); - ASSERT_TRUE(thing.ok()); - EXPECT_EQ(0, *thing.value()); - int* previous = thing.value().get(); - - thing = ReturnUniquePtr(); - EXPECT_TRUE(thing.ok()); - EXPECT_EQ(0, *thing.value()); - EXPECT_NE(previous, thing.value().get()); -} - -TEST(StatusOr, TestMoveOnlyStatusCtr) { - StatusOr> thing(absl::CancelledError("")); - ASSERT_FALSE(thing.ok()); -} - -TEST(StatusOr, TestMoveOnlyValueExtraction) { - StatusOr> thing(ReturnUniquePtr()); - ASSERT_TRUE(thing.ok()); - std::unique_ptr ptr = std::move(thing).value(); - EXPECT_EQ(0, *ptr); - - thing = std::move(ptr); - ptr = std::move(thing.value()); - EXPECT_EQ(0, *ptr); -} - -TEST(StatusOr, TestMoveOnlyConversion) { - StatusOr> const_thing(ReturnUniquePtr()); - EXPECT_TRUE(const_thing.ok()); - EXPECT_EQ(0, *const_thing.value()); - - // Test rvalue converting assignment - const int* const_previous = const_thing.value().get(); - const_thing = ReturnUniquePtr(); - EXPECT_TRUE(const_thing.ok()); - EXPECT_EQ(0, *const_thing.value()); - EXPECT_NE(const_previous, const_thing.value().get()); -} - -TEST(StatusOr, TestMoveOnlyVector) { - // Sanity check that StatusOr works in vector. - std::vector>> vec; - vec.push_back(ReturnUniquePtr()); - vec.resize(2); - auto another_vec = std::move(vec); - EXPECT_EQ(0, *another_vec[0].value()); - EXPECT_EQ(absl::StatusCode::kUnknown, another_vec[1].status().code()); -} - -TEST(StatusOr, TestMoveWithValuesAndErrors) { - StatusOr status_or(std::string(1000, '0')); - StatusOr value1(std::string(1000, '1')); - StatusOr value2(std::string(1000, '2')); - StatusOr error1(Status(absl::StatusCode::kUnknown, "error1")); - StatusOr error2(Status(absl::StatusCode::kUnknown, "error2")); - - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '0'), status_or.value()); - - // Overwrite the value in status_or with another value. - status_or = std::move(value1); - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '1'), status_or.value()); - - // Overwrite the value in status_or with an error. - status_or = std::move(error1); - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error1", status_or.status().message()); - - // Overwrite the error in status_or with another error. - status_or = std::move(error2); - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error2", status_or.status().message()); - - // Overwrite the error with a value. - status_or = std::move(value2); - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '2'), status_or.value()); -} - -TEST(StatusOr, TestCopyWithValuesAndErrors) { - StatusOr status_or(std::string(1000, '0')); - StatusOr value1(std::string(1000, '1')); - StatusOr value2(std::string(1000, '2')); - StatusOr error1(Status(absl::StatusCode::kUnknown, "error1")); - StatusOr error2(Status(absl::StatusCode::kUnknown, "error2")); - - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '0'), status_or.value()); - - // Overwrite the value in status_or with another value. - status_or = value1; - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '1'), status_or.value()); - - // Overwrite the value in status_or with an error. - status_or = error1; - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error1", status_or.status().message()); - - // Overwrite the error in status_or with another error. - status_or = error2; - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error2", status_or.status().message()); - - // Overwrite the error with a value. - status_or = value2; - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '2'), status_or.value()); - - // Verify original values unchanged. - EXPECT_EQ(std::string(1000, '1'), value1.value()); - EXPECT_EQ("error1", error1.status().message()); - EXPECT_EQ("error2", error2.status().message()); - EXPECT_EQ(std::string(1000, '2'), value2.value()); -} - -TEST(StatusOr, TestDefaultCtor) { - StatusOr thing; - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status().code(), absl::StatusCode::kUnknown); -} - -TEST(StatusOrDeathTest, TestDefaultCtorValue) { - StatusOr thing; - EXPECT_DEATH(thing.value(), ""); - - const StatusOr thing2; - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOr, TestStatusCtor) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "")); - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status().code(), absl::StatusCode::kCancelled); -} - -TEST(StatusOr, TestValueCtor) { - const int kI = 4; - const StatusOr thing(kI); - EXPECT_TRUE(thing.ok()); - EXPECT_EQ(kI, thing.value()); -} - -TEST(StatusOr, TestCopyCtorStatusOk) { - const int kI = 4; - const StatusOr original(kI); - const StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(original.value(), copy.value()); -} - -TEST(StatusOr, TestCopyCtorStatusNotOk) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestCopyCtorNonAssignable) { - const int kI = 4; - CopyNoAssign value(kI); - StatusOr original(value); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(original.value().foo_, copy.value().foo_); -} - -TEST(StatusOr, TestCopyCtorStatusOKConverting) { - const int kI = 4; - StatusOr original(kI); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_DOUBLE_EQ(original.value(), copy.value()); -} - -TEST(StatusOr, TestCopyCtorStatusNotOkConverting) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestAssignmentStatusOk) { - const int kI = 4; - StatusOr source(kI); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); - EXPECT_EQ(source.value(), target.value()); -} - -TEST(StatusOr, TestAssignmentStatusNotOk) { - StatusOr source(Status(absl::StatusCode::kCancelled, "")); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); -} - -TEST(StatusOr, TestStatus) { - StatusOr good(4); - EXPECT_TRUE(good.ok()); - StatusOr bad(Status(absl::StatusCode::kCancelled, "")); - EXPECT_FALSE(bad.ok()); - EXPECT_EQ(bad.status(), Status(absl::StatusCode::kCancelled, "")); -} - -TEST(StatusOr, TestValue) { - const int kI = 4; - StatusOr thing(kI); - EXPECT_EQ(kI, thing.value()); -} - -TEST(StatusOr, TestValueConst) { - const int kI = 4; - const StatusOr thing(kI); - EXPECT_EQ(kI, thing.value()); -} - -TEST(StatusOrDeathTest, TestValueNotOk) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "cancelled")); - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOrDeathTest, TestValueNotOkConst) { - const StatusOr thing(Status(absl::StatusCode::kUnknown, "")); - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOr, TestPointerDefaultCtor) { - StatusOr thing; - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status().code(), absl::StatusCode::kUnknown); -} - -TEST(StatusOrDeathTest, TestPointerDefaultCtorValue) { - StatusOr thing; - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOr, TestPointerStatusCtor) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "")); - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status(), Status(absl::StatusCode::kCancelled, "")); -} - -TEST(StatusOr, TestPointerValueCtor) { - const int kI = 4; - StatusOr thing(&kI); - EXPECT_TRUE(thing.ok()); - EXPECT_EQ(&kI, thing.value()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusOk) { - const int kI = 0; - StatusOr original(&kI); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(original.value(), copy.value()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusNotOk) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusOKConverting) { - Derived derived; - StatusOr original(&derived); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(static_cast(original.value()), - copy.value()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusNotOkConverting) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestPointerAssignmentStatusOk) { - const int kI = 0; - StatusOr source(&kI); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); - EXPECT_EQ(source.value(), target.value()); -} - -TEST(StatusOr, TestPointerAssignmentStatusNotOk) { - StatusOr source(Status(absl::StatusCode::kCancelled, "")); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); -} - -TEST(StatusOr, TestPointerStatus) { - const int kI = 0; - StatusOr good(&kI); - EXPECT_TRUE(good.ok()); - StatusOr bad(Status(absl::StatusCode::kCancelled, "")); - EXPECT_EQ(bad.status(), Status(absl::StatusCode::kCancelled, "")); -} - -TEST(StatusOr, TestPointerValue) { - const int kI = 0; - StatusOr thing(&kI); - EXPECT_EQ(&kI, thing.value()); -} - -TEST(StatusOr, TestPointerValueConst) { - const int kI = 0; - const StatusOr thing(&kI); - EXPECT_EQ(&kI, thing.value()); -} - -// NOTE(tucker): StatusOr does not support this kind -// of resize op. -// TEST(StatusOr, StatusOrVectorOfUniquePointerCanResize) { -// using EvilType = std::vector>; -// static_assert(std::is_copy_constructible::value, ""); -// std::vector> v(5); -// v.reserve(v.capacity() + 10); -// } - -TEST(StatusOrDeathTest, TestPointerValueNotOk) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "cancelled")); - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOrDeathTest, TestPointerValueNotOkConst) { - const StatusOr thing(Status(absl::StatusCode::kCancelled, "cancelled")); - EXPECT_DEATH(thing.value(), ""); -} - -// Benchmarks were removed as we not intend to change forked code. - -} // namespace -} // namespace absl diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 5eaaceff978f..7911b3134fd7 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -6,7 +6,6 @@ ABI ACK ACL AES -AFAICT ALPN ALS AMZ @@ -19,7 +18,6 @@ ASM ASSERTs AST AWS -Allowlisted BACKTRACE BSON BPF @@ -31,8 +29,8 @@ CEL DSR HEXDIG HEXDIGIT -LTT OWS +SkyWalking TIDs ceil CHACHA @@ -53,14 +51,12 @@ CPU CQ CRC CRL -CRLFs CRT CSDS CSRF CSS CSV CTX -CTXs CVC CVE CX @@ -72,38 +68,25 @@ DFATAL DGRAM DLOG DNS -DNSSEC DQUOTE -DRYs DS DST DW DWORD -EADDRINUSE -EADDRNOTAVAIL EAGAIN ECDH ECDHE ECDS ECDSA -ECDSAs -ECMP ECONNREFUSED EDESTRUCTION EDF -EINPROGRESS EINVAL ELB -EMSGSIZE ENOENT -ENOTFOUND -ENOTSUP ENV EOF EOS -EOY -EPOLLOUT -EPOLLRDHUP EQ ERANGE EV @@ -254,6 +237,7 @@ Postgre Postgres Prereq QDCOUNT +QPACK QUIC QoS RAII @@ -366,7 +350,6 @@ WRR WS WSA WSABUF -WSAEINVAL WSS Welford's Wi @@ -392,6 +375,7 @@ alignas alignof alloc alloca +allocatable allocator allowlist allowlisted @@ -1222,6 +1206,7 @@ virtualize vptr wakeup wakeups +wasmtime websocket wepoll whitespace