diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index 66b737caf2bf..6ff43dd470b3 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -176,31 +176,68 @@ steps: tmpfsDockerDisabled: "${{ parameters.tmpfsDockerDisabled }}" - script: | - if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then - unset GOOGLE_BES_PROJECT_ID + ENVOY_SHARED_TMP_DIR=/tmp/bazel-shared + mkdir -p "$ENVOY_SHARED_TMP_DIR" + BAZEL_BUILD_EXTRA_OPTIONS="${{ parameters.bazelBuildExtraOptions }}" + if [[ "${{ parameters.rbe }}" == "True" ]]; then + # mktemp will create a tempfile with u+rw permission minus umask, it will not be readable by all + # users by default. + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -p "${ENVOY_SHARED_TMP_DIR}" -t gcp_service_account.XXXXXX.json) + bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" + BAZEL_BUILD_EXTRA_OPTIONS+=" ${{ parameters.bazelConfigRBE }} --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" + ENVOY_RBE=1 + if [[ "${{ parameters.bazelUseBES }}" == "True" ]]; then + BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" + fi + else + echo "using local build cache." + # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` + TARGET_BRANCH=$(echo "${CI_TARGET_BRANCH}" | cut -d/ -f2-) + BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" + if [[ "$BRANCH_NAME" == "merge" ]]; then + # Manually run PR commit - there is no easy way of telling which branch + # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` + BRANCH_NAME=main + fi + BAZEL_REMOTE_INSTANCE="branch/${BRANCH_NAME}" + echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." + BAZEL_BUILD_EXTRA_OPTIONS+=" --config=ci --config=cache-local --remote_instance_name=${BAZEL_REMOTE_INSTANCE} --remote_timeout=600" fi - ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' - condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + if [[ "${{ parameters.cacheTestResults }}" != "True" ]]; then + VERSION_DEV="$(cut -d- -f2 "VERSION.txt")" + # Use uncached test results for non-release scheduledruns. + if [[ $VERSION_DEV == "dev" ]]; then + BAZEL_EXTRA_TEST_OPTIONS+=" --nocache_test_results" + fi + fi + # Any PR or CI run in envoy-presubmit uses the fake SCM hash + if [[ "${{ variables['Build.Reason'] }}" == "PullRequest" || "${{ variables['Build.DefinitionName'] }}" == 'envoy-presubmit' ]]; then + # sha1sum of `ENVOY_PULL_REQUEST` + BAZEL_FAKE_SCM_REVISION=e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 + fi + echo "##vso[task.setvariable variable=BAZEL_BUILD_EXTRA_OPTIONS]${BAZEL_BUILD_EXTRA_OPTIONS}" + echo "##vso[task.setvariable variable=BAZEL_EXTRA_TEST_OPTIONS]${BAZEL_EXTRA_TEST_OPTIONS}" + echo "##vso[task.setvariable variable=BAZEL_FAKE_SCM_REVISION]${BAZEL_FAKE_SCM_REVISION}" + echo "##vso[task.setvariable variable=BAZEL_STARTUP_EXTRA_OPTIONS]${{ parameters.bazelStartupExtraOptions }}" + echo "##vso[task.setvariable variable=CI_TARGET_BRANCH]${CI_TARGET_BRANCH}" + echo "##vso[task.setvariable variable=ENVOY_BUILD_FILTER_EXAMPLE]${{ parameters.envoyBuildFilterExample }}" + echo "##vso[task.setvariable variable=ENVOY_DOCKER_BUILD_DIR]$(Build.StagingDirectory)" + echo "##vso[task.setvariable variable=ENVOY_RBE]${ENVOY_RBE}" + echo "##vso[task.setvariable variable=ENVOY_SHARED_TMP_DIR]${ENVOY_SHARED_TMP_DIR}" + echo "##vso[task.setvariable variable=GCP_SERVICE_ACCOUNT_KEY_PATH]${GCP_SERVICE_ACCOUNT_KEY_PATH}" + echo "##vso[task.setvariable variable=GITHUB_TOKEN]${{ parameters.authGithub }}" workingDirectory: $(Build.SourcesDirectory) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - GITHUB_TOKEN: "${{ parameters.authGithub }}" - BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" - # Any PR or CI run in envoy-presubmit uses the fake SCM hash - ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: - # sha1sum of `ENVOY_PULL_REQUEST` - BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 - ${{ if parameters.rbe }}: - GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" - ${{ if eq(parameters.rbe, false) }}: - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: $(LocalBuildCache) + displayName: "CI env ${{ parameters.ciTarget }}" + +- script: ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' + condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + workingDirectory: $(Build.SourcesDirectory) + env: ${{ each var in parameters.env }}: ${{ var.key }}: ${{ var.value }} displayName: "Fetch assets (${{ parameters.ciTarget }})" @@ -231,34 +268,10 @@ steps: displayName: "Enable IPv6" condition: ${{ parameters.managedAgent }} -- script: | - if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then - unset GOOGLE_BES_PROJECT_ID - fi - ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' +- script: ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' workingDirectory: $(Build.SourcesDirectory) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_BUILD_FILTER_EXAMPLE: ${{ parameters.envoyBuildFilterExample }} - GITHUB_TOKEN: "${{ parameters.authGithub }}" - BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" - ${{ if ne(parameters['cacheTestResults'], true) }}: - BAZEL_NO_CACHE_TEST_RESULTS: 1 - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" - # Any PR or CI run in envoy-presubmit uses the fake SCM hash - ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: - # sha1sum of `ENVOY_PULL_REQUEST` - BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 - ${{ if parameters.rbe }}: - GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" - ${{ if eq(parameters.rbe, false) }}: - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: $(LocalBuildCache) ${{ each var in parameters.env }}: ${{ var.key }}: ${{ var.value }} displayName: "Run CI script ${{ parameters.ciTarget }}" @@ -296,6 +309,13 @@ steps: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} +- bash: | + if [[ -n "$GCP_SERVICE_ACCOUNT_KEY_PATH" && -e "$GCP_SERVICE_ACCOUNT_KEY_PATH" ]]; then + echo "Removed key: ${GCP_SERVICE_ACCOUNT_KEY_PATH}" + rm -rf "$GCP_SERVICE_ACCOUNT_KEY_PATH" + fi + condition: not(canceled()) + - script: | set -e sudo .azure-pipelines/docker/save_cache.sh "$(Build.StagingDirectory)" /mnt/cache/all true true diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index f39eec4787d9..8c03249e227b 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -101,15 +101,7 @@ jobs: displayName: "Upload $(CI_TARGET) Report to GCS" condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'coverage'), eq(variables['CI_TARGET'], 'fuzz_coverage'))) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(Build.SourceBranchName)" - job: complete displayName: "Checks complete" diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml index 6089bd89ee89..4b7f99e718d3 100644 --- a/.azure-pipelines/stage/macos.yml +++ b/.azure-pipelines/stage/macos.yml @@ -27,9 +27,11 @@ jobs: - script: ./ci/mac_ci_steps.sh displayName: "Run Mac CI" env: - BAZEL_BUILD_EXTRA_OPTIONS: "--remote_download_toplevel --flaky_test_attempts=2" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: >- + --remote_download_toplevel + --flaky_test_attempts=2 + --remote_cache=grpcs://remotebuildexecution.googleapis.com + --remote_instance_name=projects/envoy-ci/instances/default_instance GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} ENVOY_RBE: 1 diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 846c97c723f1..b699a960eace 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -99,7 +99,7 @@ jobs: authGPGKey: ${{ parameters.authGPGKey }} # GNUPGHOME inside the container pathGPGConfiguredHome: /build/.gnupg - pathGPGHome: /tmp/envoy-docker-build/.gnupg + pathGPGHome: $(Build.StagingDirectory)/.gnupg - bash: | set -e ci/run_envoy_docker.sh " @@ -107,7 +107,7 @@ jobs: && gpg --clearsign /tmp/authority \ && cat /tmp/authority.asc \ && gpg --verify /tmp/authority.asc" - rm -rf /tmp/envoy-docker-build/.gnupg + rm -rf $(Build.StagingDirectory)/.gnupg displayName: "Ensure container CI can sign with GPG" condition: and(not(canceled()), eq(variables['CI_TARGET'], 'docs')) @@ -129,10 +129,6 @@ jobs: ci/run_envoy_docker.sh 'ci/do_ci.sh dockerhub-readme' displayName: "Dockerhub publishing test" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') @@ -155,14 +151,9 @@ jobs: condition: and(failed(), eq(variables['CI_TARGET'], 'check_and_fix_proto_format')) # Publish docs - - script: | - ci/run_envoy_docker.sh 'ci/do_ci.sh docs-upload' + - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs-upload' displayName: "Upload Docs to GCS" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 1eb1d57584cb..b361552e4e20 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -123,10 +123,6 @@ jobs: eq(${{ parameters.publishDockerhub }}, 'true')) displayName: "Publish Dockerhub description and README" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} @@ -277,6 +273,16 @@ jobs: pool: vmImage: $(agentUbuntu) steps: + - task: DownloadSecureFile@1 + name: WorkflowTriggerKey + displayName: 'Download workflow trigger key' + inputs: + secureFile: '${{ parameters.authGithubWorkflow }}' + - bash: | + set -e + KEY="$(cat $(WorkflowTriggerKey.secureFilePath) | base64 -w0)" + echo "##vso[task.setvariable variable=value;isoutput=true]$KEY" + name: key - template: ../ci.yml parameters: ciTarget: verify.trigger @@ -310,13 +316,3 @@ jobs: mkdir -p $(Build.StagingDirectory)/release.signed mv release.signed.tar.zst $(Build.StagingDirectory)/release.signed displayName: Fetch signed release - - task: DownloadSecureFile@1 - name: WorkflowTriggerKey - displayName: 'Download workflow trigger key' - inputs: - secureFile: '${{ parameters.authGithubWorkflow }}' - - bash: | - set -e - KEY="$(cat $(WorkflowTriggerKey.secureFilePath) | base64 -w0)" - echo "##vso[task.setvariable variable=value;isoutput=true]$KEY" - name: key diff --git a/.bazelrc b/.bazelrc index 4cbb370a591c..ab2f17cf6ab2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -235,6 +235,8 @@ build:fuzz-coverage --config=plain-fuzzer build:fuzz-coverage --run_under=@envoy//bazel/coverage:fuzz_coverage_wrapper.sh build:fuzz-coverage --test_tag_filters=-nocoverage +build:cache-local --remote_cache=grpc://localhost:9092 + # Remote execution: https://docs.bazel.build/versions/master/remote-execution.html build:rbe-toolchain --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 @@ -353,7 +355,7 @@ build:compile-time-options --@envoy//source/extensions/filters/http/kill_request # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker @@ -503,6 +505,18 @@ build:rbe-engflow --remote_timeout=3600s build:rbe-engflow --bes_timeout=3600s build:rbe-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --google_default_credentials=false +build:rbe-envoy-engflow --remote_cache=grpcs://morganite.cluster.engflow.com +build:rbe-envoy-engflow --remote_executor=grpcs://morganite.cluster.engflow.com +build:rbe-envoy-engflow --bes_backend=grpcs://morganite.cluster.engflow.com/ +build:rbe-envoy-engflow --bes_results_url=https://morganite.cluster.engflow.com/invocation/ +build:rbe-envoy-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:rbe-envoy-engflow --grpc_keepalive_time=30s +build:rbe-envoy-engflow --remote_timeout=3600s +build:rbe-envoy-engflow --bes_timeout=3600s +build:rbe-envoy-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e + ############################################################################# # debug: Various Bazel debugging flags ############################################################################# diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3859774ea0b0..066695f4922a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:94e5d873c145ae86f205117e76276161c9af4806@sha256:3c3d299423a878a219a333153726cddf7cc5e5ff30f596dc97bafba521f2f928 +FROM gcr.io/envoy-ci/envoy-build:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:2a473cd9808182735d54e03b158975389948b9559b8e8fc624cfafbaf7059e62 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 77852d506a02..f24d57d22ede 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,34 +16,38 @@ "containerEnv": { "ENVOY_SRCDIR": "${containerWorkspaceFolder}", }, - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "bazel.buildifierFixOnFormat": true, - "clangd.path": "/opt/llvm/bin/clangd", - "python.pythonPath": "/usr/bin/python3", - "python.formatting.provider": "yapf", - "python.formatting.yapfArgs": [ - "--style=${workspaceFolder}/.style.yapf" - ], - "files.exclude": { - "**/.clangd/**": true, - "**/bazel-*/**": true - }, - "files.watcherExclude": { - "**/.clangd/**": true, - "**/bazel-*/**": true - } - }, "remoteUser": "vscode", "containerUser": "vscode", "postCreateCommand": ".devcontainer/setup.sh", - "extensions": [ - "github.vscode-pull-request-github", - "zxh404.vscode-proto3", - "bazelbuild.vscode-bazel", - "llvm-vs-code-extensions.vscode-clangd", - "vadimcn.vscode-lldb", - "webfreak.debug", - "ms-python.python" - ] + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "bazel.buildifierFixOnFormat": true, + "clangd.path": "/opt/llvm/bin/clangd", + "python.pythonPath": "/usr/bin/python3", + "python.formatting.provider": "yapf", + "python.formatting.yapfArgs": [ + "--style=${workspaceFolder}/.style.yapf" + ], + "files.exclude": { + "**/.clangd/**": true, + "**/bazel-*/**": true + }, + "files.watcherExclude": { + "**/.clangd/**": true, + "**/bazel-*/**": true + } + }, + "extensions": [ + "github.vscode-pull-request-github", + "zxh404.vscode-proto3", + "bazelbuild.vscode-bazel", + "llvm-vs-code-extensions.vscode-clangd", + "vadimcn.vscode-lldb", + "webfreak.debug", + "ms-python.python" + ] + } + }, } diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index b400d76a6e6e..1a8b4fbf5ffd 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -2,6 +2,9 @@ name: Envoy CI on: workflow_call: + secrets: + app_id: + app_key: inputs: target: required: true @@ -96,6 +99,20 @@ jobs: with: image_tag: ${{ inputs.cache_build_image }} + - name: Check workflow context + id: context + run: | + if [[ "${{ inputs.trusted }}" != "false" && -n "${{ secrets.app_id }}" && -n "${{ secrets.app_key }}" ]]; then + echo "use_appauth=true" >> $GITHUB_OUTPUT + fi + - if: ${{ steps.context.outputs.use_appauth == 'true' }} + name: Fetch token for app auth + id: appauth + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.18 + with: + app_id: ${{ secrets.app_id }} + key: ${{ secrets.app_key }} + - uses: actions/checkout@v4 name: Checkout Envoy repository with: @@ -104,6 +121,7 @@ jobs: # If this is set, then anything before or after in the job should be regarded as # compromised. ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + token: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} # If we are in a trusted CI run then the provided commit _must_ be either the latest for # this branch, or an antecdent. @@ -148,7 +166,7 @@ jobs: command_prefix: ${{ inputs.command_prefix }} command_ci: ${{ inputs.command_ci }} env: ${{ inputs.env }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 5df5c9da9ca4..db135fe2d139 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -12,13 +12,13 @@ on: default: envoyproxy/envoy-build-ubuntu build_image_sha: type: string - default: 8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 + default: 3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e build_image_mobile_sha: type: string - default: 0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95 + default: f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e build_image_tag: type: string - default: 94e5d873c145ae86f205117e76276161c9af4806 + default: fdd65c6270a8507a18d5acd6cf19a18cb695e4fa check_mobile_run: type: boolean @@ -27,10 +27,6 @@ on: type: boolean default: false - start_check_status: - type: string - default: - repo_ref: type: string default: @@ -173,16 +169,6 @@ jobs: echo "PR: https://github.com/envoyproxy/envoy/pull/${{ steps.env.outputs.repo_ref_pr_number }}" fi - check: - if: ${{ inputs.start_check_status && github.event_name != 'pull_request' }} - uses: ./.github/workflows/_workflow-start.yml - permissions: - contents: read - statuses: write - with: - workflow_name: ${{ inputs.start_check_status }} - sha: ${{ inputs.repo_ref_sha }} - cache: if: ${{ inputs.prime_build_image }} uses: ./.github/workflows/_cache_docker.yml diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 7765531c85eb..f57206d4ff5b 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -26,11 +26,13 @@ on: default: '' repo_ref: type: string - given_ref: + sha: type: string secrets: ENVOY_CI_SYNC_APP_ID: ENVOY_CI_SYNC_APP_KEY: + ENVOY_CI_PUBLISH_APP_ID: + ENVOY_CI_PUBLISH_APP_KEY: concurrency: group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-publish @@ -48,10 +50,11 @@ jobs: name: github run_pre: ./.github/actions/publish/release/setup run_pre_with: | - ref: ${{ inputs.given_ref }} + ref: ${{ inputs.repo_ref }} bucket: envoy-pr env: | export ENVOY_PUBLISH_DRY_RUN=1 + export ENVOY_COMMIT=${{ inputs.sha }} uses: ./.github/workflows/_ci.yml with: target: ${{ matrix.target }} @@ -68,7 +71,8 @@ jobs: if: ${{ inputs.trusted }} name: ${{ matrix.name || matrix.target }} permissions: - contents: write + contents: read + packages: read strategy: fail-fast: false matrix: @@ -77,9 +81,10 @@ jobs: name: github run_pre: ./.github/actions/publish/release/setup run_pre_with: | - ref: ${{ inputs.given_ref }} + ref: ${{ inputs.repo_ref }} bucket: envoy-postsubmit env: | + export ENVOY_COMMIT=${{ inputs.sha }} if [[ '${{ inputs.version_dev }}' == 'dev' ]]; then export ENVOY_PUBLISH_DRY_RUN=1 fi @@ -94,6 +99,9 @@ jobs: env: ${{ matrix.env }} trusted: true repo_ref: ${{ inputs.repo_ref }} + secrets: + app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} publish_docs: # For normal commits to Envoy main this will trigger an update in the website repo, diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 4a20abdc435f..b12ceef1bfa1 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -30,8 +30,6 @@ jobs: permissions: contents: read packages: read - # TODO(phlax): figure out how to remove this - statuses: write prechecks: needs: @@ -45,8 +43,13 @@ jobs: managed: true uses: ./.github/workflows/_ci.yml name: CI ${{ matrix.target }} + permissions: + contents: read + packages: read with: target: ${{ matrix.target }} rbe: ${{ matrix.rbe }} + bazel_extra: '--config=rbe-envoy-engflow' managed: ${{ matrix.managed }} cache_build_image: ${{ needs.env.outputs.build_image_ubuntu }} + repo_ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 36c346b880ae..9890338b00f5 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -35,31 +35,43 @@ jobs: with: check_mobile_run: false prime_build_image: true - start_check_status: Verify/examples repo_ref: ${{ inputs.ref }} repo_ref_sha: ${{ inputs.sha }} repo_ref_name: ${{ inputs.head_ref }} + permissions: + contents: read + packages: read + check: + if: ${{ github.event_name != 'pull_request' }} + uses: ./.github/workflows/_workflow-start.yml permissions: contents: read statuses: write + with: + workflow_name: Verify/examples + sha: ${{ inputs.sha }} publish: needs: - env + - check uses: ./.github/workflows/_stage_publish.yml name: Publish ${{ needs.env.outputs.repo_ref_title }} with: build_image_ubuntu: ${{ needs.env.outputs.build_image_ubuntu }} trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} version_dev: ${{ needs.env.outputs.version_dev }} - given_ref: ${{ inputs.ref }} repo_ref: ${{ inputs.ref }} + sha: ${{ inputs.sha }} permissions: - contents: write + contents: read + packages: read secrets: ENVOY_CI_SYNC_APP_ID: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} ENVOY_CI_SYNC_APP_KEY: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} + ENVOY_CI_PUBLISH_APP_ID: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + ENVOY_CI_PUBLISH_APP_KEY: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} verify: uses: ./.github/workflows/_stage_verify.yml diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index dfe15a713b04..322bc5a3337b 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write androidbuild: if: ${{ needs.env.outputs.mobile_android_build == 'true' }} diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index 98620ebc44b5..f9b381021241 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -20,78 +20,38 @@ jobs: prime_build_image: true permissions: contents: read - statuses: write - kotlintestsmac: + javatestslinux: if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} needs: env permissions: contents: read packages: read - # revert to //test/kotlin/... once fixed - # https://github.com/envoyproxy/envoy-mobile/issues/1932 - name: kotlin_tests_mac - runs-on: macos-12 + name: java_tests_linux + runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 90 steps: + - name: Pre-cleanup + # Using the defaults in + # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.0.18 - uses: actions/checkout@v4 - - name: 'Java setup' - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: | - cd mobile - ./ci/mac_ci_setup.sh - - name: 'Run Kotlin library tests' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd mobile - ./bazelw test \ - --build_tests_only \ - --config=mobile-remote-ci-macos \ - --define=signal_trace=disabled \ - //test/kotlin/io/... - - javatestsmac: - if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} - needs: env - permissions: - contents: read - packages: read - name: java_tests_mac - runs-on: macos-12 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v4 - - name: 'Java setup' - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 - with: - java-version: '8' - java-package: jdk - architecture: x64 - distribution: zulu - - name: 'Install dependencies' - run: | - cd mobile - ./ci/mac_ci_setup.sh - - name: 'Run Java library tests' + - name: Add safe directory + run: git config --global --add safe.directory /__w/envoy/envoy + - name: 'Run Kotlin library integration tests' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CC: /opt/llvm/bin/clang + CXX: /opt/llvm/bin/clang++ run: | - cd mobile - ./bazelw test \ - --build_tests_only \ - --config test-android \ - --define envoy_mobile_listener=enabled \ - --config=mobile-remote-ci-macos \ - --define=signal_trace=disabled \ - --define=system-helper=android \ - //test/java/... + docker run --volume="${PWD}:/source" --workdir="/source/mobile" \ + -e GITHUB_TOKEN -e CC -e CXX ${{ needs.env.outputs.build_image_ubuntu_mobile }} \ + ./bazelw test \ + --build_tests_only \ + --config=test-android \ + --config=mobile-remote-ci \ + --define=signal_trace=disabled \ + //test/java/... kotlintestslinux: if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} diff --git a/.github/workflows/mobile-asan.yml b/.github/workflows/mobile-asan.yml index c129661893cd..a92e3730cfe3 100644 --- a/.github/workflows/mobile-asan.yml +++ b/.github/workflows/mobile-asan.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write asan: if: ${{ needs.env.outputs.mobile_asan == 'true' }} diff --git a/.github/workflows/mobile-cc_tests.yml b/.github/workflows/mobile-cc_tests.yml index fdf1f1ed2ce3..a9001c1df8d8 100644 --- a/.github/workflows/mobile-cc_tests.yml +++ b/.github/workflows/mobile-cc_tests.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write cctests: if: ${{ needs.env.outputs.mobile_cc_tests == 'true' }} diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml index 38a4c3a34014..a3a541fb6499 100644 --- a/.github/workflows/mobile-compile_time_options.yml +++ b/.github/workflows/mobile-compile_time_options.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write cc_test_no_yaml: needs: env diff --git a/.github/workflows/mobile-core.yml b/.github/workflows/mobile-core.yml index 02c16ec1fc7b..037eb72b3284 100644 --- a/.github/workflows/mobile-core.yml +++ b/.github/workflows/mobile-core.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write unittests: if: ${{ github.repository == 'envoyproxy/envoy' }} diff --git a/.github/workflows/mobile-coverage.yml b/.github/workflows/mobile-coverage.yml index 6dd105b56c87..8d3aaa8e93b5 100644 --- a/.github/workflows/mobile-coverage.yml +++ b/.github/workflows/mobile-coverage.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write coverage: if: ${{ needs.env.outputs.mobile_coverage == 'true' }} diff --git a/.github/workflows/mobile-docs.yml b/.github/workflows/mobile-docs.yml index c1f70ad46055..936674a46568 100644 --- a/.github/workflows/mobile-docs.yml +++ b/.github/workflows/mobile-docs.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write docs: if: ${{ github.repository == 'envoyproxy/envoy' }} diff --git a/.github/workflows/mobile-format.yml b/.github/workflows/mobile-format.yml index 5f61d284adb2..777a62f56c93 100644 --- a/.github/workflows/mobile-format.yml +++ b/.github/workflows/mobile-format.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write formatall: if: ${{ needs.env.outputs.mobile_formatting == 'true' }} diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 3a6ca86f5dba..ca5b865880b5 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write iosbuild: if: ${{ needs.env.outputs.mobile_ios_build == 'true' }} diff --git a/.github/workflows/mobile-ios_tests.yml b/.github/workflows/mobile-ios_tests.yml index 9329d26a6133..150429a30d05 100644 --- a/.github/workflows/mobile-ios_tests.yml +++ b/.github/workflows/mobile-ios_tests.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write swifttests: if: ${{ needs.env.outputs.mobile_ios_tests == 'true' }} diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index effa6cd4192c..a411a93cb925 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -15,7 +15,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write android_release_artifacts: if: >- diff --git a/.github/workflows/mobile-release_validation.yml b/.github/workflows/mobile-release_validation.yml index d55af440e54a..156ad5fbd71d 100644 --- a/.github/workflows/mobile-release_validation.yml +++ b/.github/workflows/mobile-release_validation.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write validate_swiftpm_example: if: ${{ needs.env.outputs.mobile_release_validation == 'true' }} diff --git a/.github/workflows/mobile-tsan.yml b/.github/workflows/mobile-tsan.yml index 281445793e70..27386c81fd3a 100644 --- a/.github/workflows/mobile-tsan.yml +++ b/.github/workflows/mobile-tsan.yml @@ -19,7 +19,6 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read - statuses: write tsan: if: ${{ needs.env.outputs.mobile_tsan == 'true' }} diff --git a/CODEOWNERS b/CODEOWNERS index 42413b217617..257c2577dbdd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,7 +54,7 @@ extensions/filters/common/original_src @klarose @mattklein123 # sni_dynamic_forward_proxy extension /*/extensions/filters/network/sni_dynamic_forward_proxy @rshriram @lizan # tracers.datadog extension -/*/extensions/tracers/datadog @cgilmour @palazzem @mattklein123 +/*/extensions/tracers/datadog @cgilmour @dgoffredo @dmehala @mattklein123 # tracers.xray extension /*/extensions/tracers/xray @abaptiste @suniltheta @mattklein123 # tracers.skywalking extension @@ -325,6 +325,9 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /*/extensions/health_check/event_sinks/file @botengyao @yanavlasov # IP Geolocation /*/extensions/filters/http/geoip @nezdolik @ravenblackx +/*/extensions/geoip_providers/common @nezdolik @ravenblackx +# Maxmind geolocation provider +/*/extensions/geoip_providers/maxmind @nezdolik @ravenblackx /*/extensions/health_checkers/common @zuercher @botengyao diff --git a/api/BUILD b/api/BUILD index bde7ff3852bb..76facfe2dda1 100644 --- a/api/BUILD +++ b/api/BUILD @@ -239,11 +239,14 @@ proto_library( "//envoy/extensions/filters/network/wasm/v3:pkg", "//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg", "//envoy/extensions/filters/udp/dns_filter/v3:pkg", + "//envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3:pkg", "//envoy/extensions/filters/udp/udp_proxy/v3:pkg", "//envoy/extensions/formatter/cel/v3:pkg", "//envoy/extensions/formatter/metadata/v3:pkg", "//envoy/extensions/formatter/req_without_query/v3:pkg", + "//envoy/extensions/geoip_providers/common/v3:pkg", + "//envoy/extensions/geoip_providers/maxmind/v3:pkg", "//envoy/extensions/health_check/event_sinks/file/v3:pkg", "//envoy/extensions/health_checkers/redis/v3:pkg", "//envoy/extensions/health_checkers/thrift/v3:pkg", diff --git a/api/envoy/config/route/v3/route.proto b/api/envoy/config/route/v3/route.proto index 237bddebdef6..0d5867d00714 100644 --- a/api/envoy/config/route/v3/route.proto +++ b/api/envoy/config/route/v3/route.proto @@ -23,7 +23,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Routing :ref:`architecture overview ` // * HTTP :ref:`router filter ` -// [#next-free-field: 17] +// [#next-free-field: 18] message RouteConfiguration { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.RouteConfiguration"; @@ -151,6 +151,13 @@ message RouteConfiguration { // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 16; + + // The metadata field can be used to provide additional information + // about the route configuration. It can be used for configuration, stats, and logging. + // The metadata should go under the filter namespace that will need it. + // For instance, if the metadata is intended for the Router filter, + // the filter name should be specified as ``envoy.filters.http.router``. + core.v3.Metadata metadata = 17; } message Vhds { diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 9346e7ade939..bb212ca77264 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -41,7 +41,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // host header. This allows a single listener to service multiple top level domain path trees. Once // a virtual host is selected based on the domain, the routes are processed in order to see which // upstream cluster to route to or whether to perform a redirect. -// [#next-free-field: 24] +// [#next-free-field: 25] message VirtualHost { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.VirtualHost"; @@ -215,6 +215,13 @@ message VirtualHost { // It takes precedence over the route config mirror policy entirely. // That is, policies are not merged, the most specific non-empty one becomes the mirror policies. repeated RouteAction.RequestMirrorPolicy request_mirror_policies = 22; + + // The metadata field can be used to provide additional information + // about the virtual host. It can be used for configuration, stats, and logging. + // The metadata should go under the filter namespace that will need it. + // For instance, if the metadata is intended for the Router filter, + // the filter name should be specified as ``envoy.filters.http.router``. + core.v3.Metadata metadata = 24; } // A filter-defined action type. @@ -524,6 +531,16 @@ message RouteMatch { // If specified, the route will match against whether or not a certificate is validated. // If not specified, certificate validation status (true or false) will not be considered when route matching. + // + // .. warning:: + // + // Client certificate validation is not currently performed upon TLS session resumption. For + // a resumed TLS session the route will match only when ``validated`` is false, regardless of + // whether the client TLS certificate is valid. + // + // The only known workaround for this issue is to disable TLS session resumption entirely, by + // setting both :ref:`disable_stateless_session_resumption ` + // and :ref:`disable_stateful_session_resumption ` on the DownstreamTlsContext. google.protobuf.BoolValue validated = 2; } @@ -619,7 +636,7 @@ message RouteMatch { // match. The router will check the query string from the ``path`` header // against all the specified query parameters. If the number of specified // query parameters is nonzero, they all must match the ``path`` header's - // query string for a match to occur. In the event query parameters are + // query string for a match to occur. In the event query parameters are // repeated, only the first value for each key will be considered. // // .. note:: diff --git a/api/envoy/extensions/filters/http/compressor/v3/compressor.proto b/api/envoy/extensions/filters/http/compressor/v3/compressor.proto index 5260c23ccfae..6fe4b137da6e 100644 --- a/api/envoy/extensions/filters/http/compressor/v3/compressor.proto +++ b/api/envoy/extensions/filters/http/compressor/v3/compressor.proto @@ -132,8 +132,11 @@ message Compressor { } // Per-route overrides of ``ResponseDirectionConfig``. Anything added here should be optional, -// to allow overriding arbitrary subsets of configuration. Omitted fields must have no affect. +// to allow overriding arbitrary subsets of configuration. Omitted fields must have no effect. message ResponseDirectionOverrides { + // If set, overrides the filter-level + // :ref:`remove_accept_encoding_header`. + google.protobuf.BoolValue remove_accept_encoding_header = 1; } // Per-route overrides. As per-route overrides are needed, they should be diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index cd2d1f6f4e21..bba080473fa2 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -26,7 +26,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization :ref:`configuration overview `. // [#extension: envoy.filters.http.ext_authz] -// [#next-free-field: 19] +// [#next-free-field: 20] message ExtAuthz { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v2.ExtAuthz"; @@ -63,6 +63,12 @@ message ExtAuthz { // `. bool failure_mode_allow = 2; + // When ``failure_mode_allow`` and ``failure_mode_allow_header_add`` are both set to true, + // ``x-envoy-auth-failure-mode-allowed: true`` will be added to request headers if the communication + // with the authorization service has failed, or if the authorization service has returned a + // HTTP 5xx error. + bool failure_mode_allow_header_add = 19; + // Enables filter to buffer the client request body and send it within the authorization request. // A ``x-envoy-auth-partial-body: false|true`` metadata header will be added to the authorization // request message indicating if the body data is partial. diff --git a/api/envoy/extensions/filters/http/geoip/v3/geoip.proto b/api/envoy/extensions/filters/http/geoip/v3/geoip.proto index dfab28e02d05..4ef26a8245e2 100644 --- a/api/envoy/extensions/filters/http/geoip/v3/geoip.proto +++ b/api/envoy/extensions/filters/http/geoip/v3/geoip.proto @@ -21,52 +21,6 @@ option (xds.annotations.v3.file_status).work_in_progress = true; // [#extension: envoy.filters.http.geoip] message Geoip { - // The set of geolocation headers to add to request. If any of the configured headers is present - // in the incoming request, it will be overridden by Geoip filter. - // [#next-free-field: 10] - message GeolocationHeadersToAdd { - // If set, the header will be used to populate the country ISO code associated with the IP address. - string country = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the header will be used to populate the city associated with the IP address. - string city = 2 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the header will be used to populate the region ISO code associated with the IP address. - string region = 3 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the header will be used to populate the ASN associated with the IP address. - string asn = 4 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the IP address will be checked if it belongs to any type of anonymization network (e.g. VPN, public proxy etc) - // and header will be populated with the check result. Header value will be set to either "true" or "false" depending on the check result. - string is_anon = 5 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the IP address will be checked if it belongs to a VPN and header will be populated with the check result. - // Header value will be set to either "true" or "false" depending on the check result. - string anon_vpn = 6 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the IP address will be checked if it belongs to a hosting provider and header will be populated with the check result. - // Header value will be set to either "true" or "false" depending on the check result. - string anon_hosting = 7 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the IP address will be checked if it belongs to a TOR exit node and header will be populated with the check result. - // Header value will be set to either "true" or "false" depending on the check result. - string anon_tor = 8 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - - // If set, the IP address will be checked if it belongs to a public proxy and header will be populated with the check result. - // Header value will be set to either "true" or "false" depending on the check result. - string anon_proxy = 9 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; - } - message XffConfig { // The number of additional ingress proxy hops from the right side of the // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when @@ -82,9 +36,10 @@ message Geoip { // [#next-free-field: 2] XffConfig xff_config = 1; - // Configuration for geolocation headers to add to request. - GeolocationHeadersToAdd geo_headers_to_add = 2 [(validate.rules).message = {required: true}]; - - // Geolocation provider specific configuration. + // Geoip driver specific configuration which depends on the driver being instantiated. + // See the geoip drivers for examples: + // + // - :ref:`MaxMindConfig ` + // [#extension-category: envoy.geoip_providers] config.core.v3.TypedExtensionConfig provider = 3 [(validate.rules).message = {required: true}]; } 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 ea776a742e60..f0ef0978a5a4 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 @@ -1054,7 +1054,9 @@ message ScopedRoutes { // .. note:: // // If the header appears multiple times only the first value is used. - string name = 1 [(validate.rules).string = {min_len: 1}]; + string name = 1 [ + (validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_NAME strict: false} + ]; // The element separator (e.g., ';' separates 'a;b;c;d'). // Default: empty string. This causes the entirety of the header field to be extracted. diff --git a/api/envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/BUILD b/api/envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/BUILD new file mode 100644 index 000000000000..05f25a2fe5d9 --- /dev/null +++ b/api/envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/extensions/common/dynamic_forward_proxy/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto b/api/envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto new file mode 100644 index 000000000000..a264f4e3c56f --- /dev/null +++ b/api/envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package envoy.extensions.filters.udp.udp_proxy.session.dynamic_forward_proxy.v3; + +import "envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.udp.udp_proxy.session.dynamic_forward_proxy.v3"; +option java_outer_classname = "DynamicForwardProxyProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3;dynamic_forward_proxyv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Filter state dynamic forward proxy] + +// Configuration for the filter state based dynamic forward proxy filter. See the +// :ref:`architecture overview ` for +// more information. Note this filter must be used in conjunction to another filter that +// sets the 'envoy.upstream.dynamic_host' and the 'envoy.upstream.dynamic_port' filter +// state keys for the required upstream UDP session. +// [#extension: envoy.filters.udp.session.dynamic_forward_proxy] +message FilterConfig { + // Configuration for UDP datagrams buffering. + message BufferOptions { + // If set, the filter will only buffer datagrams up to the requested limit, and will drop + // new UDP datagrams if the buffer contains the max_buffered_datagrams value at the time + // of a new datagram arrival. If not set, the default value is 1024 datagrams. + google.protobuf.UInt32Value max_buffered_datagrams = 1; + + // If set, the filter will only buffer datagrams up to the requested total buffered bytes limit, + // and will drop new UDP datagrams if the buffer contains the max_buffered_datagrams value + // at the time of a new datagram arrival. If not set, the default value is 16,384 (16KB). + google.protobuf.UInt64Value max_buffered_bytes = 2; + } + + // The prefix to use when emitting :ref:`statistics `. + string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; + + oneof implementation_specifier { + // The DNS cache configuration that the filter will attach to. Note this + // configuration must match that of associated :ref:`dynamic forward proxy cluster configuration + // `. + common.dynamic_forward_proxy.v3.DnsCacheConfig dns_cache_config = 2 + [(validate.rules).message = {required: true}]; + } + + // If configured, the filter will buffer datagrams in case that it is waiting for a DNS response. + // If this field is not configured, there will be no buffering and downstream datagrams that arrive + // while the DNS resolution is in progress will be dropped. In case this field is set but the options + // are not configured, the default values will be applied as described in the ``BufferOptions``. + BufferOptions buffer_options = 3; +} diff --git a/api/envoy/extensions/geoip_providers/common/v3/BUILD b/api/envoy/extensions/geoip_providers/common/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/extensions/geoip_providers/common/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/geoip_providers/common/v3/common.proto b/api/envoy/extensions/geoip_providers/common/v3/common.proto new file mode 100644 index 000000000000..91a9126cfef8 --- /dev/null +++ b/api/envoy/extensions/geoip_providers/common/v3/common.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package envoy.extensions.geoip_providers.common.v3; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.geoip_providers.common.v3"; +option java_outer_classname = "CommonProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/geoip_providers/common/v3;commonv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Common Geolocation Provider Configuration] +// Common geolocation provider :ref:`configuration overview `. +// Common configuration shared across geolocation providers. + +message CommonGeoipProviderConfig { + // The set of geolocation headers to add to request. If any of the configured headers is present + // in the incoming request, it will be overridden by the :ref:`Geoip filter `. + // [#next-free-field: 10] + message GeolocationHeadersToAdd { + // If set, the header will be used to populate the country ISO code associated with the IP address. + string country = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the header will be used to populate the city associated with the IP address. + string city = 2 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the header will be used to populate the region ISO code associated with the IP address. + // The least specific subdivision will be selected as region value. + string region = 3 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the header will be used to populate the ASN associated with the IP address. + string asn = 4 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the IP address will be checked if it belongs to any type of anonymization network (e.g. VPN, public proxy etc) + // and header will be populated with the check result. Header value will be set to either "true" or "false" depending on the check result. + string is_anon = 5 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the IP address will be checked if it belongs to a VPN and header will be populated with the check result. + // Header value will be set to either "true" or "false" depending on the check result. + string anon_vpn = 6 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the IP address will be checked if it belongs to a hosting provider and header will be populated with the check result. + // Header value will be set to either "true" or "false" depending on the check result. + string anon_hosting = 7 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the IP address will be checked if it belongs to a TOR exit node and header will be populated with the check result. + // Header value will be set to either "true" or "false" depending on the check result. + string anon_tor = 8 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + + // If set, the IP address will be checked if it belongs to a public proxy and header will be populated with the check result. + // Header value will be set to either "true" or "false" depending on the check result. + string anon_proxy = 9 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}]; + } + + // Configuration for geolocation headers to add to request. + GeolocationHeadersToAdd geo_headers_to_add = 1 [(validate.rules).message = {required: true}]; +} diff --git a/api/envoy/extensions/geoip_providers/maxmind/v3/BUILD b/api/envoy/extensions/geoip_providers/maxmind/v3/BUILD new file mode 100644 index 000000000000..082f67d1e00a --- /dev/null +++ b/api/envoy/extensions/geoip_providers/maxmind/v3/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/extensions/geoip_providers/common/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + "@com_github_cncf_udpa//xds/annotations/v3:pkg", + ], +) diff --git a/api/envoy/extensions/geoip_providers/maxmind/v3/maxmind.proto b/api/envoy/extensions/geoip_providers/maxmind/v3/maxmind.proto new file mode 100644 index 000000000000..3fc7f7c16082 --- /dev/null +++ b/api/envoy/extensions/geoip_providers/maxmind/v3/maxmind.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package envoy.extensions.geoip_providers.maxmind.v3; + +import "envoy/extensions/geoip_providers/common/v3/common.proto"; + +import "xds/annotations/v3/status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.geoip_providers.maxmind.v3"; +option java_outer_classname = "MaxmindProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/geoip_providers/maxmind/v3;maxmindv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: MaxMind Geolocation Provider] +// MaxMind geolocation provider :ref:`configuration overview `. +// At least one geolocation database path :ref:`city_db_path `, +// :ref:`isp_db_path ` or +// :ref:`anon_db_path ` must be configured. +// [#extension: envoy.geoip_providers.maxmind] + +message MaxMindConfig { + // Full file path to the Maxmind city database, e.g. /etc/GeoLite2-City.mmdb. + // Database file is expected to have .mmdb extension. + string city_db_path = 1 [(validate.rules).string = {pattern: "^$|^.*\\.mmdb$"}]; + + // Full file path to the Maxmind ASN database, e.g. /etc/GeoLite2-ASN.mmdb. + // Database file is expected to have .mmdb extension. + string isp_db_path = 2 [(validate.rules).string = {pattern: "^$|^.*\\.mmdb$"}]; + + // Full file path to the Maxmind anonymous IP database, e.g. /etc/GeoIP2-Anonymous-IP.mmdb. + // Database file is expected to have .mmdb extension. + string anon_db_path = 3 [(validate.rules).string = {pattern: "^$|^.*\\.mmdb$"}]; + + // Common provider configuration that specifies which geolocation headers will be populated with geolocation data. + common.v3.CommonGeoipProviderConfig common_provider_config = 4 + [(validate.rules).message = {required: true}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index aebd101772fc..9f8638e33ee2 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -177,11 +177,14 @@ proto_library( "//envoy/extensions/filters/network/wasm/v3:pkg", "//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg", "//envoy/extensions/filters/udp/dns_filter/v3:pkg", + "//envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/filters/udp/udp_proxy/session/http_capsule/v3:pkg", "//envoy/extensions/filters/udp/udp_proxy/v3:pkg", "//envoy/extensions/formatter/cel/v3:pkg", "//envoy/extensions/formatter/metadata/v3:pkg", "//envoy/extensions/formatter/req_without_query/v3:pkg", + "//envoy/extensions/geoip_providers/common/v3:pkg", + "//envoy/extensions/geoip_providers/maxmind/v3:pkg", "//envoy/extensions/health_check/event_sinks/file/v3:pkg", "//envoy/extensions/health_checkers/redis/v3:pkg", "//envoy/extensions/health_checkers/thrift/v3:pkg", diff --git a/bazel/dependency_imports.bzl b/bazel/dependency_imports.bzl index b743a1936d0d..21ff0abc420c 100644 --- a/bazel/dependency_imports.bzl +++ b/bazel/dependency_imports.bzl @@ -18,7 +18,7 @@ load("@com_google_cel_cpp//bazel:deps.bzl", "parser_deps") load("@com_github_chrusty_protoc_gen_jsonschema//:deps.bzl", protoc_gen_jsonschema_go_dependencies = "go_dependencies") # go version for rules_go -GO_VERSION = "1.18" +GO_VERSION = "1.20" JQ_VERSION = "1.6" YQ_VERSION = "4.24.4" diff --git a/bazel/engflow-bazel-credential-helper.sh b/bazel/engflow-bazel-credential-helper.sh new file mode 100755 index 000000000000..c6c1bd339b62 --- /dev/null +++ b/bazel/engflow-bazel-credential-helper.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Bazel expects the helper to read stdin. +# See https://github.com/bazelbuild/bazel/pull/17666 +cat /dev/stdin > /dev/null + +# `GITHUB_TOKEN` is provided as a secret. +echo "{\"headers\":{\"Authorization\":[\"Bearer ${GITHUB_TOKEN}\"]}}" diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 3219ad847af0..cddb802593c5 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -3041,15 +3041,11 @@ envoy_cc_library( envoy_quic_cc_library( name = "quic_core_http_client_lib", srcs = [ - "quiche/quic/core/http/quic_client_promised_info.cc", - "quiche/quic/core/http/quic_client_push_promise_index.cc", "quiche/quic/core/http/quic_spdy_client_session.cc", "quiche/quic/core/http/quic_spdy_client_session_base.cc", "quiche/quic/core/http/quic_spdy_client_stream.cc", ], hdrs = [ - "quiche/quic/core/http/quic_client_promised_info.h", - "quiche/quic/core/http/quic_client_push_promise_index.h", "quiche/quic/core/http/quic_spdy_client_session.h", "quiche/quic/core/http/quic_spdy_client_session_base.h", "quiche/quic/core/http/quic_spdy_client_stream.h", @@ -3059,6 +3055,7 @@ envoy_quic_cc_library( ":quic_core_alarm_lib", ":quic_core_crypto_encryption_lib", ":quic_core_http_server_initiated_spdy_stream_lib", + ":quic_core_http_spdy_server_push_utils_header", ":quic_core_http_spdy_session_lib", ":quic_core_packets_lib", ":quic_core_qpack_qpack_streams_lib", @@ -3068,7 +3065,6 @@ envoy_quic_cc_library( ":quic_platform_base", ":spdy_core_framer_lib", ":spdy_core_protocol_lib", - "@envoy//source/common/quic:spdy_server_push_utils_for_envoy_lib", ], ) diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index 4d2b98fd2eb0..8d1a0d6cd2d8 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -579,3 +579,27 @@ envoy_cmake( }), working_directory = "build/cmake", ) + +envoy_cmake( + name = "maxmind", + cache_entries = { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INSTALL_LIBDIR": "lib", + "CMAKE_CXX_COMPILER_FORCED": "on", + "BUILD_SHARED_LIBS": "off", + "BUILD_TESTING": "off", + }, + defines = ["MAXMIND_STATICLIB"], + lib_source = "@com_github_maxmind_libmaxminddb//:all", + out_static_libs = ["libmaxminddb.a"], + tags = ["skip_on_windows"], +) + +envoy_cc_library( + name = "maxmind_linux", + srcs = [], + deps = select({ + "//bazel:linux": [":maxmind"], + "//conditions:default": [], + }), +) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index a50a42376ab6..d7edd752b43b 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -331,6 +331,7 @@ def envoy_dependencies(skip_targets = []): external_http_archive("bazel_toolchains") external_http_archive("bazel_compdb") external_http_archive("envoy_build_tools") + _com_github_maxmind_libmaxminddb() # TODO(keith): Remove patch when we update rules_pkg external_http_archive( @@ -988,7 +989,10 @@ cc_library(name = "curl", visibility = ["//visibility:public"], deps = ["@envoy/ def _v8(): external_http_archive( name = "v8", - patches = ["@envoy//bazel:v8.patch"], + patches = [ + "@envoy//bazel:v8.patch", + "@envoy//bazel:v8_include.patch", + ], patch_args = ["-p1"], ) native.bind( @@ -1398,3 +1402,13 @@ def _is_linux_s390x(ctxt): def _is_linux_x86_64(ctxt): return _is_linux(ctxt) and _is_arch(ctxt, "x86_64") + +def _com_github_maxmind_libmaxminddb(): + external_http_archive( + name = "com_github_maxmind_libmaxminddb", + build_file_content = BUILD_ALL_CONTENT, + ) + native.bind( + name = "maxmind", + actual = "@envoy//bazel/foreign_cc:maxmind_linux", + ) diff --git a/bazel/repositories_extra.bzl b/bazel/repositories_extra.bzl index 40d348073fa4..d7d7cc76dfd7 100644 --- a/bazel/repositories_extra.bzl +++ b/bazel/repositories_extra.bzl @@ -1,8 +1,8 @@ +load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies") load("@emsdk//:deps.bzl", emsdk_deps = "deps") -load("@rules_python//python:repositories.bzl", "python_register_toolchains") load("@proxy_wasm_cpp_host//bazel/cargo/wasmtime:crates.bzl", "wasmtime_fetch_remote_crates") +load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains") load("//bazel/external/cargo:crates.bzl", "raze_fetch_remote_crates") -load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies") def _python_minor_version(python_version): return "_".join(python_version.split(".")[:-1]) @@ -18,6 +18,7 @@ def envoy_dependencies_extra( emsdk_deps() raze_fetch_remote_crates() wasmtime_fetch_remote_crates() + py_repositories() # Registers underscored Python minor version - eg `python3_10` python_register_toolchains( diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index cfc26bf27579..1dd9ab801c31 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -102,11 +102,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "fc1ab3e96cf275ecaac913be2a22bce4a74b9272", - sha256 = "75fff0c28766ccb4e625244e35c950eb071d4bfb4a443b387140e1c037eeb6cc", + version = "f727ec142156c8076384a35c0e2d51da3c1d7813", + sha256 = "72510592f34f3fd6269c5fdd2286465a05ce6ca438ac1faebfdb88ed309fe9da", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2023-09-20", + release_date = "2023-10-16", use_category = ["build"], license = "Apache-2.0", license_url = "https://github.com/envoyproxy/envoy-build-tools/blob/{version}/LICENSE", @@ -937,9 +937,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Python rules for Bazel", project_desc = "Bazel rules for the Python language", project_url = "https://github.com/bazelbuild/rules_python", - version = "0.25.0", - sha256 = "5868e73107a8e85d8f323806e60cad7283f34b32163ea6ff1020cf27abef6036", - release_date = "2023-08-22", + version = "0.26.0", + sha256 = "9d04041ac92a0985e344235f5d946f71ac543f1b1565f2cdbc9a2aaee8adf55b", + release_date = "2023-10-06", strip_prefix = "rules_python-{version}", urls = ["https://github.com/bazelbuild/rules_python/archive/{version}.tar.gz"], use_category = ["build"], @@ -1123,12 +1123,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "5cdf937c378cdf08ff55ea9e86cfbf05bec54df2", - sha256 = "d7e9019d3bac864050cc3b2bbaac16c4dfd4f78af7991941f97ec88c90c50b0b", + version = "92faee243386c6234f39ab5f3debbbd480cfcff6", + sha256 = "1e7e5c08c4b00dccc1d41a5db9ffe856db6d4174149f9d32561b07fead532229", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2023-09-27", + release_date = "2023-10-11", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", @@ -1459,6 +1459,21 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "MIT", license_url = "https://github.com/protocolbuffers/utf8_range/blob/{version}/LICENSE", ), + com_github_maxmind_libmaxminddb = dict( + project_name = "maxmind_libmaxminddb", + project_desc = "C library for reading MaxMind DB files", + project_url = "https://github.com/maxmind/libmaxminddb", + version = "1.7.1", + sha256 = "e8414f0dedcecbc1f6c31cb65cd81650952ab0677a4d8c49cab603b3b8fb083e", + strip_prefix = "libmaxminddb-{version}", + urls = ["https://github.com/maxmind/libmaxminddb/releases/download/{version}/libmaxminddb-{version}.tar.gz"], + use_category = ["dataplane_ext"], + extensions = ["envoy.geoip_providers.maxmind"], + release_date = "2022-09-30", + cpe = "cpe:2.3:a:maxmind:libmaxminddb:*", + license = "Apache-2.0", + license_url = "https://github.com/maxmind/libmaxminddb/blob/{version}/LICENSE", + ), ) def _compiled_protoc_deps(locations, versions): diff --git a/bazel/v8_include.patch b/bazel/v8_include.patch new file mode 100644 index 000000000000..3e6b492bf05d --- /dev/null +++ b/bazel/v8_include.patch @@ -0,0 +1,41 @@ +# fix include types for late clang (15.0.7) / gcc (13.2.1) +# for Arch linux / Fedora, like in +# In file included from external/v8/src/torque/torque.cc:5: +# In file included from external/v8/src/torque/source-positions.h:10: +# In file included from external/v8/src/torque/contextual.h:10: +# In file included from external/v8/src/base/macros.h:12: +# external/v8/src/base/logging.h:154:26: error: use of undeclared identifier 'uint16_t' + +diff --git a/src/base/logging.h b/src/base/logging.h +--- a/src/base/logging.h ++++ b/src/base/logging.h +@@ -5,6 +5,7 @@ + #ifndef V8_BASE_LOGGING_H_ + #define V8_BASE_LOGGING_H_ + ++#include + #include + #include + #include +diff --git a/src/base/macros.h b/src/base/macros.h +--- a/src/base/macros.h ++++ b/src/base/macros.h +@@ -5,6 +5,7 @@ + #ifndef V8_BASE_MACROS_H_ + #define V8_BASE_MACROS_H_ + ++#include + #include + #include + +diff --git a/src/inspector/v8-string-conversions.h b/src/inspector/v8-string-conversions.h +--- a/src/inspector/v8-string-conversions.h ++++ b/src/inspector/v8-string-conversions.h +@@ -5,6 +5,7 @@ + #ifndef V8_INSPECTOR_V8_STRING_CONVERSIONS_H_ + #define V8_INSPECTOR_V8_STRING_CONVERSIONS_H_ + ++#include + #include + + // Conversion routines between UT8 and UTF16, used by string-16.{h,cc}. You may diff --git a/changelogs/current.yaml b/changelogs/current.yaml index bcaa4ac34884..a198ea6519e9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -127,6 +127,10 @@ minor_behavior_changes: - area: router change: | Enable environment_variable in router direct response. +- area: access_log + change: | + When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried + to find the keys configured in filter_state_objects_to_log. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* @@ -183,19 +187,30 @@ bug_fixes: - area: redis change: | Fixed a bug where redis key with % in the key is failing with a validation error. +- area: tracing + change: | + Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. - area: http change: | Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, with the default value of 500, determines the number of requests received from a connection before the check for premature - resets is applied. The connection is disconnected if more than 50% of resets are premature. + resets is applied. The connection is disconnected if more than 50% of resets are premature, or if + the number of suspect streams is already large enough to guarantee that more than 50% of the streams will be suspect + upon reaching the total stream threshold (even if all the remaining streams are considered benign). Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables this check. - area: http change: | Fixed a bug that could cause metadata to be decoded after a local reply has been triggered. Can be disabled by setting ``envoy.reloadable_features.stop_decode_metadata_on_local_reply`` to false. +- area: docker/publishing + change: | + Update base images to resolve various glibc vulnerabilities. +- area: xds + change: | + Fix a bug where the nonce was not reset after reconnecting to the xDS server, when using State-of-the-World. - area: xds change: | Fixed a bug (https://github.com/envoyproxy/envoy/issues/27702) that caused ADS initialization @@ -239,12 +254,23 @@ removed_config_or_runtime: substituted it with :ref:`outlier_detection.successful_active_health_check_uneject_host ` outlier detection configuration flag. +- area: ext_authz + change: | + Removed ``envoy.reloadable_features.http_ext_auth_failure_mode_allow_header_add`` + runtime option and substituted it with :ref:`failure_mode_allow_header_add + ` + configuration flag. new_features: - area: access_log change: | - Added ``%RESPONSE_FLAGS_LONG%`` substitution string, that will output a pascal case string representing the resonse flags. - The output response flags will correspond with ``%RESPONSE_FLAGS%1``, only with a long textual string representation. + added %RESPONSE_FLAGS_LONG% substitution string, that will output a pascal case string representing the response flags. + The output response flags will correspond with %RESPONSE_FLAGS%, only with a long textual string representation. +- area: compression + change: | + added :ref:`remove_accept_encoding_header + ` + for per-route configuration of this value. - area: config change: | Added the capability to defer broadcasting of certain cluster (CDS, EDS) to @@ -336,6 +362,11 @@ new_features: change: | Added :ref:`record_headers_received_time ` to control writing request and response headers received time in trace output. +- area: udp_proxy + change: | + added :ref:`dynamic_forward_proxy ` + UDP session filter that can be used to have dynamic forward proxy UDP flows, when used in conjunction with another session filter + that sets required filter state values. - area: zookeeper change: | Added support for emitting per opcode request bytes metrics via :ref:`enable_per_opcode_request_bytes_metrics @@ -374,11 +405,24 @@ new_features: to control whether to create separate upstream span for upstream request. - area: original_dst change: | - Added support for the internal listener address recovery using the original destination listener filter. + added support for the internal listener address recovery using the original destination listener filter. - area: filters change: | Added filters to update the filter state for :ref:`the HTTP requests ` and :ref:`the TCP connections `. +- area: admin_logging + change: | + added support for glob control of fine-grain loggers in admin /logging interface. +- area: geoip + change: | + Added support for :ref:`Maxmind geolocation provider `. +- area: admin + change: | + Added a new ``skip_exit`` query parameter to ``/drain_listeners`` to skip exiting after the drain period. +- area: router + change: | + Added ``metadata`` support for :ref:`virtual host ` and + :ref:`route configuration `. deprecated: - area: tracing diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 2d54fa423bc3..00f4c2c75227 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -119,14 +119,6 @@ bazel () { export _bazel export -f bazel -if [[ -n "$BAZEL_NO_CACHE_TEST_RESULTS" ]]; then - VERSION_DEV="$(cut -d- -f2 "${ENVOY_SRCDIR}/VERSION.txt")" - # Use uncached test results for non-release commits to a branch. - if [[ $VERSION_DEV == "dev" ]]; then - BAZEL_EXTRA_TEST_OPTIONS+=("--nocache_test_results") - fi -fi - # Use https://docs.bazel.build/versions/master/command-line-reference.html#flag--experimental_repository_cache_hardlinks # to save disk space. BAZEL_GLOBAL_OPTIONS=( diff --git a/ci/do_ci.sh b/ci/do_ci.sh index af329e6abb91..1f145bc705e9 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -525,6 +525,10 @@ case $CI_TARGET in echo "Check dependabot ..." bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ //tools/dependency:dependatool + # Run pip requirements tests + echo "Check pip requirements ..." + bazel test "${BAZEL_BUILD_OPTIONS[@]}" \ + //tools/base:requirements_test ;; dev) @@ -786,9 +790,11 @@ case $CI_TARGET in publish) setup_clang_toolchain BUILD_SHA="$(git rev-parse HEAD)" + ENVOY_COMMIT="${ENVOY_COMMIT:-${BUILD_SHA}}" VERSION_DEV="$(cut -d- -f2 < VERSION.txt)" PUBLISH_ARGS=( - --publish-commitish="$BUILD_SHA" + --publish-commitish="$ENVOY_COMMIT" + --publish-commit-message --publish-assets=/build/release.signed/release.signed.tar.zst) if [[ "$VERSION_DEV" == "dev" ]] || [[ -n "$ENVOY_PUBLISH_DRY_RUN" ]]; then PUBLISH_ARGS+=(--dry-run) diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index bbfda3beeb03..2aa752e5baad 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -18,10 +18,6 @@ export GOPROXY="${GOPROXY:-${go_proxy:-}}" if is_windows; then [[ -z "${IMAGE_NAME}" ]] && IMAGE_NAME="envoyproxy/envoy-build-windows2019" - # Container networking is unreliable in the most recently built images, pin Windows to a known - # good container. This can create a mismatch between the host environment, and the toolchain - # environment. - ENVOY_BUILD_SHA=41c5a05d708972d703661b702a63ef5060125c33 # TODO(sunjayBhatia): Currently ENVOY_DOCKER_OPTIONS is ignored on Windows because # CI sets it to a Linux-specific value. Undo this once https://github.com/envoyproxy/envoy/issues/13272 # is resolved. @@ -95,13 +91,13 @@ VOLUMES=( -v "${ENVOY_DOCKER_BUILD_DIR}":"${BUILD_DIR_MOUNT_DEST}" -v "${SOURCE_DIR}":"${SOURCE_DIR_MOUNT_DEST}") -if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" ]]; then +if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" || -n "$ENVOY_SHARED_TMP_DIR" ]]; then # Create a "shared" directory that has the same path in/outside the container # This allows the host docker engine to see artefacts using a temporary path created inside the container, # at the same path. # For example, a directory created with `mktemp -d --tmpdir /tmp/bazel-shared` can be mounted as a volume # from within the build container. - SHARED_TMP_DIR=/tmp/bazel-shared + SHARED_TMP_DIR="${ENVOY_SHARED_TMP_DIR:-/tmp/bazel-shared}" mkdir -p "${SHARED_TMP_DIR}" chmod +rwx "${SHARED_TMP_DIR}" VOLUMES+=(-v "${SHARED_TMP_DIR}":"${SHARED_TMP_DIR}") @@ -111,7 +107,6 @@ if [[ -n "${ENVOY_DOCKER_PULL}" ]]; then time docker pull "${ENVOY_BUILD_IMAGE}" fi - # Since we specify an explicit hash, docker-run will pull from the remote repo if missing. docker run --rm \ "${ENVOY_DOCKER_OPTIONS[@]}" \ @@ -133,10 +128,9 @@ docker run --rm \ -e DOCKERHUB_PASSWORD \ -e ENVOY_STDLIB \ -e BUILD_REASON \ - -e BAZEL_NO_CACHE_TEST_RESULTS \ -e BAZEL_REMOTE_INSTANCE \ - -e GOOGLE_BES_PROJECT_ID \ -e GCP_SERVICE_ACCOUNT_KEY \ + -e GCP_SERVICE_ACCOUNT_KEY_PATH \ -e NUM_CPUS \ -e ENVOY_BRANCH \ -e ENVOY_RBE \ diff --git a/ci/setup_cache.sh b/ci/setup_cache.sh index da0b189dd4a8..ca910ec1a090 100755 --- a/ci/setup_cache.sh +++ b/ci/setup_cache.sh @@ -14,37 +14,22 @@ if [[ -n "${GCP_SERVICE_ACCOUNT_KEY:0:1}" ]]; then trap gcp_service_account_cleanup EXIT + echo "Setting GCP_SERVICE_ACCOUNT_KEY is deprecated, please place your decoded GCP key in " \ + "an exported/shared tmp directory and add it to BAZEL_BUILD_EXTRA_OPTIONS, eg: " >&2 + # shellcheck disable=SC2086 + echo "$ export ENVOY_SHARED_TMP_DIR=/tmp/envoy-shared" \ + "$ ENVOY_RBE_KEY_PATH=$(mktemp -p \"${ENVOY_SHARED_TMP_DIR}\" -t gcp_service_account.XXXXXX.json)" \ + "$ bash -c 'echo \"$(GcpServiceAccountKey)\"' | base64 --decode > \"${ENVOY_RBE_KEY_PATH}\"" \ + "$ export BAZEL_BUILD_EXTRA_OPTIONS+=\" --google_credentials=${ENVOY_RBE_KEY_PATH}\"" >&2 bash -c 'echo "${GCP_SERVICE_ACCOUNT_KEY}"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_FILE}" - export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_FILE}" - - if [[ -n "${GOOGLE_BES_PROJECT_ID}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" - fi - fi if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then + echo "Setting BAZEL_REMOTE_CACHE is deprecated, please use BAZEL_BUILD_EXTRA_OPTIONS " \ + "or use a user.bazelrc config " >&2 export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_cache=${BAZEL_REMOTE_CACHE}" echo "Set up bazel remote read/write cache at ${BAZEL_REMOTE_CACHE}." - - if [[ -z "${ENVOY_RBE}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_timeout=600" - echo "using local build cache." - # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` - TARGET_BRANCH="${CI_TARGET_BRANCH}" - if [[ "$TARGET_BRANCH" =~ ^origin/ ]]; then - TARGET_BRANCH=$(echo "$TARGET_BRANCH" | cut -d/ -f2-) - fi - BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" - if [[ "$BRANCH_NAME" == "merge" ]]; then - # Manually run PR commit - there is no easy way of telling which branch - # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` - BRANCH_NAME=main - fi - BAZEL_REMOTE_INSTANCE="branch/${BRANCH_NAME}" - fi - if [[ -n "${BAZEL_REMOTE_INSTANCE}" ]]; then export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_instance_name=${BAZEL_REMOTE_INSTANCE}" echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." diff --git a/ci/upload_gcs_artifact.sh b/ci/upload_gcs_artifact.sh index 6367184a408b..339a4e98dc4d 100755 --- a/ci/upload_gcs_artifact.sh +++ b/ci/upload_gcs_artifact.sh @@ -7,27 +7,17 @@ if [[ -z "${GCS_ARTIFACT_BUCKET}" ]]; then exit 1 fi -if [[ -z "${GCP_SERVICE_ACCOUNT_KEY}" ]]; then - echo "GCP key is not set, not uploading artifacts." - exit 1 -fi - read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTION_LIST:-}" read -ra BAZEL_BUILD_OPTIONS <<< "${BAZEL_BUILD_OPTION_LIST:-}" -remove_key () { - rm -rf "$KEYFILE" -} - -trap remove_key EXIT - -# Fail when service account key is not specified -KEYFILE="$(mktemp)" -bash -c 'echo ${GCP_SERVICE_ACCOUNT_KEY}' | base64 --decode > "$KEYFILE" +if [[ ! -s "${GCP_SERVICE_ACCOUNT_KEY_PATH}" ]]; then + echo "GCP key is not set, not uploading artifacts." + exit 1 +fi cat < ~/.boto [Credentials] -gs_service_key_file=${KEYFILE} +gs_service_key_file=${GCP_SERVICE_ACCOUNT_KEY_PATH} EOF SOURCE_DIRECTORY="$1" diff --git a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h index 5983bc8b10d6..763a480e2995 100644 --- a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h +++ b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h @@ -39,12 +39,15 @@ class DubboRequest : public Request { setByKey(key, val); } void setByReference(absl::string_view key, absl::string_view val) override { setByKey(key, val); } - absl::string_view host() const override { return inner_metadata_->request().serviceName(); } absl::string_view path() const override { return inner_metadata_->request().serviceName(); } - absl::string_view method() const override { return inner_metadata_->request().methodName(); } + // StreamFrame + FrameFlags frameFlags() const override { return stream_frame_flags_; } + + FrameFlags stream_frame_flags_; + Common::Dubbo::MessageMetadataSharedPtr inner_metadata_; }; @@ -69,9 +72,13 @@ class DubboResponse : public Response { void setByKey(absl::string_view, absl::string_view) override{}; void setByReferenceKey(absl::string_view, absl::string_view) override {} void setByReference(absl::string_view, absl::string_view) override {} - Status status() const override { return status_; } + // StreamFrame + FrameFlags frameFlags() const override { return stream_frame_flags_; } + + FrameFlags stream_frame_flags_; + Status status_; Common::Dubbo::MessageMetadataSharedPtr inner_metadata_; }; @@ -121,10 +128,13 @@ class DubboDecoderBase : public DubboCodecBase, public DecoderType { } ASSERT(decode_status == Common::Dubbo::DecodeStatus::Success); - ExtendedOptions extended_options{metadata_->requestId(), metadata_->context().isTwoWay(), - false, metadata_->context().heartbeat()}; - callback_->onDecodingSuccess(std::make_unique(std::move(metadata_)), - extended_options); + + auto message = std::make_unique(metadata_); + message->stream_frame_flags_ = {{static_cast(metadata_->requestId()), + !metadata_->context().isTwoWay(), false, + metadata_->context().heartbeat()}, + true}; + callback_->onDecodingSuccess(std::move(message)); metadata_.reset(); } catch (const EnvoyException& error) { ENVOY_LOG(warn, "Dubbo codec: decoding error: {}", error.what()); @@ -145,13 +155,13 @@ class DubboRequestEncoder : public RequestEncoder, public DubboCodecBase { public: using DubboCodecBase::DubboCodecBase; - void encode(const Request& request, RequestEncoderCallback& callback) override { + void encode(const StreamFrame& request, RequestEncoderCallback& callback) override { ASSERT(dynamic_cast(&request) != nullptr); const auto* typed_request = static_cast(&request); Buffer::OwnedImpl buffer; codec_->encode(buffer, *typed_request->inner_metadata_); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); } }; @@ -159,13 +169,13 @@ class DubboResponseEncoder : public ResponseEncoder, public DubboCodecBase { public: using DubboCodecBase::DubboCodecBase; - void encode(const Response& response, ResponseEncoderCallback& callback) override { + void encode(const StreamFrame& response, ResponseEncoderCallback& callback) override { ASSERT(dynamic_cast(&response) != nullptr); const auto* typed_response = static_cast(&response); Buffer::OwnedImpl buffer; codec_->encode(buffer, *typed_response->inner_metadata_); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); } }; diff --git a/contrib/generic_proxy/filters/network/source/interface/codec.h b/contrib/generic_proxy/filters/network/source/interface/codec.h index 246ae6249a57..8f045aa655a3 100644 --- a/contrib/generic_proxy/filters/network/source/interface/codec.h +++ b/contrib/generic_proxy/filters/network/source/interface/codec.h @@ -41,12 +41,14 @@ class ResponseDecoder { /* * Encoder of request. + * TODO(wbpcode): to merge RequestEncoder and ResponseDecoder into one class. By + * this way, it possible to support stream remapping in the future. */ class RequestEncoder { public: virtual ~RequestEncoder() = default; - virtual void encode(const Request&, RequestEncoderCallback& callback) PURE; + virtual void encode(const StreamFrame&, RequestEncoderCallback& callback) PURE; }; /* @@ -56,7 +58,7 @@ class ResponseEncoder { public: virtual ~ResponseEncoder() = default; - virtual void encode(const Response&, ResponseEncoderCallback& callback) PURE; + virtual void encode(const StreamFrame&, ResponseEncoderCallback& callback) PURE; }; class MessageCreator { diff --git a/contrib/generic_proxy/filters/network/source/interface/codec_callbacks.h b/contrib/generic_proxy/filters/network/source/interface/codec_callbacks.h index 246bcb4f0021..898cf700529e 100644 --- a/contrib/generic_proxy/filters/network/source/interface/codec_callbacks.h +++ b/contrib/generic_proxy/filters/network/source/interface/codec_callbacks.h @@ -12,76 +12,21 @@ namespace Extensions { namespace NetworkFilters { namespace GenericProxy { -/** - * Extended options from request or response to control the behavior of the - * generic proxy filter. - * All these options are optional for the simple ping-pong use case. - */ -class ExtendedOptions { -public: - ExtendedOptions(absl::optional stream_id, bool wait_response, bool drain_close, - bool is_heartbeat) - : stream_id_(stream_id.value_or(0)), has_stream_id_(stream_id.has_value()), - wait_response_(wait_response), drain_close_(drain_close), is_heartbeat_(is_heartbeat) {} - ExtendedOptions() = default; - - /** - * @return the stream id of the request or response. This is used to match the - * downstream request with the upstream response. - - * NOTE: In most cases, the stream id is not needed and will be ignored completely. - * The stream id is only used when we can't match the downstream request - * with the upstream response by the active stream instance self directly. - * For example, when the multiple downstream requests are multiplexed into one - * upstream connection. - */ - absl::optional streamId() const { - return has_stream_id_ ? absl::optional(stream_id_) : absl::nullopt; - } - - /** - * @return whether the current request requires an upstream response. - * NOTE: This is only used for the request. - */ - bool waitResponse() const { return wait_response_; } - - /** - * @return whether the downstream/upstream connection should be drained after - * current active requests are finished. - * NOTE: This is only used for the response. - */ - bool drainClose() const { return drain_close_; } - - /** - * @return whether the current request/response is a heartbeat request/response. - * NOTE: It would be better to handle heartbeat request/response by another L4 - * filter. Then the generic proxy filter can be used for the simple ping-pong - * use case. - */ - bool isHeartbeat() const { return is_heartbeat_; } - -private: - uint64_t stream_id_{0}; - bool has_stream_id_{false}; - - bool wait_response_{true}; - bool drain_close_{false}; - bool is_heartbeat_{false}; -}; - /** * Decoder callback of request. */ class RequestDecoderCallback { public: virtual ~RequestDecoderCallback() = default; - /** * If request decoding success then this method will be called. - * @param request request from decoding. - * @param options extended options from request. + * @param frame request frame from decoding. Frist frame should be StreamRequest + * frame. + * NOTE: This method will be called multiple times for the multiple frames request. + * FrameFlags and embedded StreamFlags could be used to correlate frames of same + * request. */ - virtual void onDecodingSuccess(RequestPtr request, ExtendedOptions options) PURE; + virtual void onDecodingSuccess(StreamFramePtr frame) PURE; /** * If request decoding failure then this method will be called. @@ -114,10 +59,14 @@ class ResponseDecoderCallback { /** * If response decoding success then this method will be called. - * @param response response from decoding. - * @param options extended options from response. + * @param frame response frame from decoding. Frist frame should be StreamResponse + * frame. + * NOTE: This method will be called multiple times for the multiple frames response. + * FrameFlags and embedded StreamFlags could be used to correlate frames of same + * request. And the StreamFlags could also be used to correlate the response with + * the request. */ - virtual void onDecodingSuccess(ResponsePtr response, ExtendedOptions options) PURE; + virtual void onDecodingSuccess(StreamFramePtr frame) PURE; /** * If response decoding failure then this method will be called. @@ -151,8 +100,9 @@ class RequestEncoderCallback { /** * If request encoding success then this method will be called. * @param buffer encoding result buffer. + * @param end_stream if last frame is encoded. */ - virtual void onEncodingSuccess(Buffer::Instance& buffer) PURE; + virtual void onEncodingSuccess(Buffer::Instance& buffer, bool end_stream) PURE; }; /** @@ -165,8 +115,9 @@ class ResponseEncoderCallback { /** * If response encoding success then this method will be called. * @param buffer encoding result buffer. + * @param end_stream if last frame is encoded. */ - virtual void onEncodingSuccess(Buffer::Instance& buffer) PURE; + virtual void onEncodingSuccess(Buffer::Instance& buffer, bool end_stream) PURE; }; } // namespace GenericProxy diff --git a/contrib/generic_proxy/filters/network/source/interface/filter.h b/contrib/generic_proxy/filters/network/source/interface/filter.h index e8bcdb06c861..ec382855c663 100644 --- a/contrib/generic_proxy/filters/network/source/interface/filter.h +++ b/contrib/generic_proxy/filters/network/source/interface/filter.h @@ -15,6 +15,20 @@ namespace GenericProxy { using ResponseUpdateFunction = std::function; +/** + * StreamFrameHandler to handle the frames from the stream (if exists). + */ +class StreamFrameHandler { +public: + virtual ~StreamFrameHandler() = default; + + /** + * Handle the frame from the stream. + * @param frame frame from the stream. + */ + virtual void onStreamFrame(StreamFramePtr frame) PURE; +}; + /** * The stream filter callbacks are passed to all filters to use for writing response data and * interacting with the underlying stream in general. @@ -66,16 +80,6 @@ class StreamFilterCallbacks { */ virtual OptRef tracingConfig() const PURE; - /** - * @return absl::optional the extended options from downstream request. - */ - virtual absl::optional requestOptions() const PURE; - - /** - * @return absl::optional the extended options from upstream response. - */ - virtual absl::optional responseOptions() const PURE; - /** * @return const Network::Connection* downstream connection. */ @@ -134,7 +138,29 @@ class DecoderFilterCallback : public virtual StreamFilterCallbacks { virtual void continueDecoding() PURE; - virtual void upstreamResponse(ResponsePtr response, ExtendedOptions options) PURE; + /** + * Called when the upstream response frame is received. This should only be called once. + * @param response supplies the upstream response frame. + */ + virtual void onResponseStart(StreamResponsePtr response) PURE; + + /** + * Called when the upstream response frame is received. This should only be called once. + * @param frame supplies the upstream frame. + */ + virtual void onResponseFrame(StreamFramePtr frame) PURE; + + /** + * Register a request frames handler to used to handle the request frames (except the special + * StreamRequest frame). + * This handler will be Called when the filter chain is completed. + * @param handler supplies the request frames handler. + * + * TODO(wbpcode): this is used by the terminal filter the handle the request frames because + * the filter chain doesn't support to handle extra frames. We should remove this when the + * filter chain supports to handle extra frames. + */ + virtual void setRequestFramesHandler(StreamFrameHandler& handler) PURE; virtual void completeDirectly() PURE; @@ -165,7 +191,7 @@ class DecoderFilter { virtual void onDestroy() PURE; virtual void setDecoderFilterCallbacks(DecoderFilterCallback& callbacks) PURE; - virtual FilterStatus onStreamDecoded(Request& request) PURE; + virtual FilterStatus onStreamDecoded(StreamRequest& request) PURE; }; class EncoderFilter { @@ -175,7 +201,7 @@ class EncoderFilter { virtual void onDestroy() PURE; virtual void setEncoderFilterCallbacks(EncoderFilterCallback& callbacks) PURE; - virtual FilterStatus onStreamEncoded(Response& response) PURE; + virtual FilterStatus onStreamEncoded(StreamResponse& response) PURE; }; class StreamFilter : public DecoderFilter, public EncoderFilter {}; diff --git a/contrib/generic_proxy/filters/network/source/interface/stream.h b/contrib/generic_proxy/filters/network/source/interface/stream.h index c8a628fcd918..c84b43a684be 100644 --- a/contrib/generic_proxy/filters/network/source/interface/stream.h +++ b/contrib/generic_proxy/filters/network/source/interface/stream.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -16,10 +17,112 @@ namespace Extensions { namespace NetworkFilters { namespace GenericProxy { -class StreamBase { +/** + * Stream flags from request or response to control the behavior of the + * generic proxy filter. This is mainly used as part of FrameFlags. + * All these flags could be ignored for the simple ping-pong use case. + */ +class StreamFlags { +public: + StreamFlags(uint64_t stream_id = 0, bool one_way_stream = false, bool drain_close = false, + bool is_heartbeat = false) + : stream_id_(stream_id), one_way_stream_(one_way_stream), drain_close_(drain_close), + is_heartbeat_(is_heartbeat) {} + + /** + * @return the stream id of the request or response. This is used to match the + * downstream request with the upstream response. + + * NOTE: In most cases, the stream id is not needed and will be ignored completely. + * The stream id is only used when we can't match the downstream request + * with the upstream response by the active stream instance self directly. + * For example, when the multiple downstream requests are multiplexed into one + * upstream connection. + */ + uint64_t streamId() const { return stream_id_; } + + /** + * @return whether the stream is one way stream. If request is one way stream, the + * generic proxy filter will not wait for the response from the upstream. + */ + bool oneWayStream() const { return one_way_stream_; } + + /** + * @return whether the downstream/upstream connection should be drained after + * current active stream are finished. + */ + bool drainClose() const { return drain_close_; } + + /** + * @return whether the current request/response is a heartbeat request/response. + * NOTE: It would be better to handle heartbeat request/response by another L4 + * filter. Then the generic proxy filter can be used for the simple ping-pong + * use case. + */ + bool isHeartbeat() const { return is_heartbeat_; } + +private: + uint64_t stream_id_{0}; + + bool one_way_stream_{false}; + bool drain_close_{false}; + bool is_heartbeat_{false}; +}; + +/** + * Flags of stream frame. This is used to control the behavior of the generic proxy filter. + * All these flags could be ignored for the simple ping-pong use case. + */ +class FrameFlags { public: - virtual ~StreamBase() = default; + /** + * Construct FrameFlags with stream flags and end stream flag. The stream flags MUST be + * same for all frames of the same stream. + * @param stream_flags StreamFlags of the stream. + * @param end_stream whether the current frame is the last frame of the request or response. + */ + FrameFlags(StreamFlags stream_flags = StreamFlags(), bool end_stream = true) + : stream_flags_(stream_flags), end_stream_(end_stream) {} + /** + * Get flags of stream that the frame belongs to. The flags MUST be same for all frames of the + * same stream. Copy semantics is used because the flags are lightweight (only 16 bytes for now). + * @return StreamFlags of the stream. + */ + StreamFlags streamFlags() const { return stream_flags_; } + + /** + * @return whether the current frame is the last frame of the request or response. + */ + bool endStream() const { return end_stream_; } + +private: + StreamFlags stream_flags_{}; + + // Default to true for backward compatibility. + bool end_stream_{true}; +}; + +/** + * Stream frame interface. This is used to represent the stream frame of request or response. + */ +class StreamFrame { +public: + virtual ~StreamFrame() = default; + + /** + * Get stream frame flags of current frame. The default implementation returns empty flags + * that could be used for the simple ping-pong use case. + * @return FrameFlags of the current frame. + */ + virtual FrameFlags frameFlags() const { return {}; } +}; + +using StreamFramePtr = std::unique_ptr; +using StreamFrameSharedPtr = std::shared_ptr; + +class StreamBase : public StreamFrame { +public: using IterateCallback = std::function; /** @@ -76,14 +179,23 @@ class StreamBase { }; /** - * Using interface that provided by the TraceContext as the interface of generic request. + * Interface of generic request. This is derived from StreamFrame that contains the request + * specific information. First frame of the request MUST be a StreamRequest. + * + * NOTE: using interface that provided by the TraceContext as the interface of generic request here + * to simplify the tracing integration. This is not a good design. This should be changed in the + * future. */ -class Request : public Tracing::TraceContext { +class StreamRequest : public Tracing::TraceContext, public StreamFrame { public: // Used for matcher. static constexpr absl::string_view name() { return "generic_proxy"; } }; +using StreamRequestPtr = std::unique_ptr; +using StreamRequestSharedPtr = std::shared_ptr; +// Alias for backward compatibility. +using Request = StreamRequest; using RequestPtr = std::unique_ptr; using RequestSharedPtr = std::shared_ptr; @@ -98,7 +210,11 @@ enum class Event { using Status = absl::Status; using StatusCode = absl::StatusCode; -class Response : public StreamBase { +/** + * Interface of generic response. This is derived from StreamFrame that contains the response + * specific information. First frame of the response MUST be a StreamResponse. + */ +class StreamResponse : public StreamBase { public: /** * Get response status. @@ -108,9 +224,35 @@ class Response : public StreamBase { virtual Status status() const PURE; }; +using StreamResponsePtr = std::unique_ptr; +using StreamResponseSharedPtr = std::shared_ptr; +// Alias for backward compatibility. +using Response = StreamResponse; using ResponsePtr = std::unique_ptr; using ResponseSharedPtr = std::shared_ptr; +template class StreamFramePtrHelper { +public: + StreamFramePtrHelper(StreamFramePtr frame) { + auto frame_ptr = frame.release(); + + auto typed_frame_ptr = dynamic_cast(frame_ptr); + + if (typed_frame_ptr == nullptr) { + // If the frame is not the expected type, wrap it + // in the original StreamFramePtr. + frame_ = StreamFramePtr{frame_ptr}; + } else { + // If the frame is the expected type, wrap it + // in the typed frame unique pointer. + typed_frame_ = std::unique_ptr{typed_frame_ptr}; + } + } + + StreamFramePtr frame_; + std::unique_ptr typed_frame_; +}; + } // namespace GenericProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/contrib/generic_proxy/filters/network/source/proxy.cc b/contrib/generic_proxy/filters/network/source/proxy.cc index 0fa86ff0f7ff..0f48eb4b0273 100644 --- a/contrib/generic_proxy/filters/network/source/proxy.cc +++ b/contrib/generic_proxy/filters/network/source/proxy.cc @@ -33,13 +33,19 @@ Tracing::Decision tracingDecision(const Tracing::ConnectionManagerTracingConfig& } // namespace -ActiveStream::ActiveStream(Filter& parent, RequestPtr request, ExtendedOptions options) - : parent_(parent), downstream_request_stream_(std::move(request)), - downstream_request_options_(options), +ActiveStream::ActiveStream(Filter& parent, StreamRequestPtr request) + : parent_(parent), request_stream_(std::move(request)), + request_stream_end_(request_stream_->frameFlags().endStream()), stream_info_(parent_.time_source_, parent_.callbacks_->connection().connectionInfoProviderSharedPtr()), request_timer_(new Stats::HistogramCompletableTimespanImpl(parent_.stats_.request_time_ms_, parent_.time_source_)) { + if (!request_stream_end_) { + // If the request is not fully received, register the stream to the frame handler map. + parent_.registerFrameHandler(requestStreamId(), this); + registered_in_frame_handlers_ = true; + } + parent_.stats_.request_.inc(); parent_.stats_.request_active_.inc(); @@ -55,7 +61,7 @@ ActiveStream::ActiveStream(Filter& parent, RequestPtr request, ExtendedOptions o if (decision.traced) { stream_info_.setTraceReason(decision.reason); } - active_span_ = tracer->startSpan(*this, *downstream_request_stream_, stream_info_, decision); + active_span_ = tracer->startSpan(*this, *request_stream_, stream_info_, decision); } Tracing::OperationName ActiveStream::operationName() const { @@ -95,33 +101,80 @@ void ActiveStream::resetStream() { parent_.deferredStream(*this); } +void ActiveStream::sendResponseStartToDownstream() { + ASSERT(response_stream_ != nullptr); + response_filter_chain_complete_ = true; + + parent_.sendFrameToDownstream(*response_stream_, *this); +} + +void ActiveStream::sendResponseFrameToDownstream() { + if (!response_filter_chain_complete_) { + // Wait for the response header frame to be sent first. It may be blocked by + // the filter chain. + return; + } + + while (!response_stream_frames_.empty()) { + // Pop the first frame from the queue. + auto frame = std::move(response_stream_frames_.front()); + response_stream_frames_.pop_front(); + + // Send the frame to downstream. + parent_.sendFrameToDownstream(*frame, *this); + } +} + +void ActiveStream::sendRequestFrameToUpstream() { + if (!request_filter_chain_complete_) { + // Wait for the request header frame to be sent first. It may be blocked by + // the filter chain. + return; + } + + if (request_stream_frame_handler_ == nullptr) { + // The request stream frame handler is not ready yet. + return; + } + + while (!request_stream_frames_.empty()) { + // Pop the first frame from the queue. + auto frame = std::move(request_stream_frames_.front()); + request_stream_frames_.pop_front(); + + // Send the frame to upstream. + request_stream_frame_handler_->onStreamFrame(std::move(frame)); + } +} + void ActiveStream::sendLocalReply(Status status, ResponseUpdateFunction&& func) { - ASSERT(parent_.message_creator_ != nullptr); - local_or_upstream_response_stream_ = - parent_.message_creator_->response(status, *downstream_request_stream_); + response_stream_ = parent_.message_creator_->response(status, *request_stream_); + response_stream_frames_.clear(); + // Only one frame is allowed in the local reply. + response_stream_end_ = true; - ASSERT(local_or_upstream_response_stream_ != nullptr); + ASSERT(response_stream_ != nullptr); if (func != nullptr) { - func(*local_or_upstream_response_stream_); + func(*response_stream_); } - parent_.sendReplyDownstream(*local_or_upstream_response_stream_, *this); + sendResponseStartToDownstream(); } void ActiveStream::continueDecoding() { - if (active_stream_reset_ || downstream_request_stream_ == nullptr) { + if (active_stream_reset_ || request_stream_ == nullptr) { return; } if (cached_route_entry_ == nullptr) { - cached_route_entry_ = parent_.config_->routeEntry(*downstream_request_stream_); + cached_route_entry_ = parent_.config_->routeEntry(*request_stream_); } - ASSERT(downstream_request_stream_ != nullptr); + ASSERT(request_stream_ != nullptr); for (; next_decoder_filter_index_ < decoder_filters_.size();) { - auto status = decoder_filters_[next_decoder_filter_index_]->filter_->onStreamDecoded( - *downstream_request_stream_); + auto status = + decoder_filters_[next_decoder_filter_index_]->filter_->onStreamDecoded(*request_stream_); next_decoder_filter_index_++; if (status == FilterStatus::StopIteration) { break; @@ -129,17 +182,45 @@ void ActiveStream::continueDecoding() { } if (next_decoder_filter_index_ == decoder_filters_.size()) { ENVOY_LOG(debug, "Complete decoder filters"); + request_filter_chain_complete_ = true; + sendRequestFrameToUpstream(); + } +} + +void ActiveStream::onRequestFrame(StreamFramePtr frame) { + request_stream_end_ = frame->frameFlags().endStream(); + request_stream_frames_.emplace_back(std::move(frame)); + + ASSERT(registered_in_frame_handlers_); + if (request_stream_end_) { + // If the request is fully received, remove the stream from the + // frame handler map. + parent_.unregisterFrameHandler(requestStreamId()); + registered_in_frame_handlers_ = false; } + + // Try to send the frame to upstream immediately. + sendRequestFrameToUpstream(); } -void ActiveStream::upstreamResponse(ResponsePtr response, ExtendedOptions options) { - local_or_upstream_response_stream_ = std::move(response); - local_or_upstream_response_options_ = options; - parent_.stream_drain_decision_ = options.drainClose(); +void ActiveStream::onResponseStart(ResponsePtr response) { + response_stream_ = std::move(response); + response_stream_end_ = response_stream_->frameFlags().endStream(); + parent_.stream_drain_decision_ = response_stream_->frameFlags().streamFlags().drainClose(); continueEncoding(); } -void ActiveStream::completeDirectly() { parent_.deferredStream(*this); }; +void ActiveStream::onResponseFrame(StreamFramePtr frame) { + response_stream_end_ = frame->frameFlags().endStream(); + response_stream_frames_.emplace_back(std::move(frame)); + // Try to send the frame to downstream immediately. + sendResponseFrameToDownstream(); +} + +void ActiveStream::completeDirectly() { + response_stream_end_ = true; + parent_.deferredStream(*this); +}; void ActiveStream::ActiveDecoderFilter::bindUpstreamConn(Upstream::TcpPoolData&& pool_data) { parent_.parent_.bindUpstreamConn(std::move(pool_data)); @@ -153,14 +234,14 @@ const Network::Connection* ActiveStream::ActiveFilterBase::connection() const { } void ActiveStream::continueEncoding() { - if (active_stream_reset_ || local_or_upstream_response_stream_ == nullptr) { + if (active_stream_reset_ || response_stream_ == nullptr) { return; } - ASSERT(local_or_upstream_response_stream_ != nullptr); + ASSERT(response_stream_ != nullptr); for (; next_encoder_filter_index_ < encoder_filters_.size();) { - auto status = encoder_filters_[next_encoder_filter_index_]->filter_->onStreamEncoded( - *local_or_upstream_response_stream_); + auto status = + encoder_filters_[next_encoder_filter_index_]->filter_->onStreamEncoded(*response_stream_); next_encoder_filter_index_++; if (status == FilterStatus::StopIteration) { break; @@ -169,13 +250,24 @@ void ActiveStream::continueEncoding() { if (next_encoder_filter_index_ == encoder_filters_.size()) { ENVOY_LOG(debug, "Complete encoder filters"); - parent_.sendReplyDownstream(*local_or_upstream_response_stream_, *this); + sendResponseStartToDownstream(); + sendResponseFrameToDownstream(); } } -void ActiveStream::onEncodingSuccess(Buffer::Instance& buffer) { +void ActiveStream::onEncodingSuccess(Buffer::Instance& buffer, bool end_stream) { ASSERT(parent_.downstreamConnection().state() == Network::Connection::State::Open); parent_.downstreamConnection().write(buffer, false); + + if (!end_stream) { + return; + } + + ENVOY_LOG(debug, "Generic proxy: downstream response complete"); + + ASSERT(response_stream_end_); + ASSERT(response_stream_frames_.empty()); + parent_.stats_.response_.inc(); parent_.deferredStream(*this); } @@ -188,19 +280,23 @@ void ActiveStream::initializeFilterChain(FilterChainFactory& factory) { } void ActiveStream::completeRequest() { + if (registered_in_frame_handlers_) { + parent_.unregisterFrameHandler(requestStreamId()); + registered_in_frame_handlers_ = false; + } + stream_info_.onRequestComplete(); request_timer_->complete(); parent_.stats_.request_active_.dec(); if (active_span_) { - Tracing::TracerUtility::finalizeSpan(*active_span_, *downstream_request_stream_, stream_info_, - *this, false); + Tracing::TracerUtility::finalizeSpan(*active_span_, *request_stream_, stream_info_, *this, + false); } for (const auto& access_log : parent_.config_->accessLogs()) { - access_log->log({downstream_request_stream_.get(), local_or_upstream_response_stream_.get()}, - stream_info_); + access_log->log({request_stream_.get(), response_stream_.get()}, stream_info_); } for (auto& filter : decoder_filters_) { @@ -314,22 +410,27 @@ void UpstreamManagerImpl::onPoolFailureImpl(ConnectionPool::PoolFailureReason re parent_.onBoundUpstreamConnectionEvent(Network::ConnectionEvent::RemoteClose); } -void UpstreamManagerImpl::onDecodingSuccess(ResponsePtr response, ExtendedOptions options) { +void UpstreamManagerImpl::onDecodingSuccess(StreamFramePtr response) { // registered_upstream_callbacks_ should be empty because after upstream connection is ready. ASSERT(registered_upstream_callbacks_.empty()); - uint64_t stream_id = options.streamId().value_or(0); + const uint64_t stream_id = response->frameFlags().streamFlags().streamId(); + const bool end_stream = response->frameFlags().endStream(); auto it = registered_response_callbacks_.find(stream_id); if (it == registered_response_callbacks_.end()) { - ENVOY_LOG(error, "generic proxy: stream_id {} not found", stream_id); + ENVOY_LOG(error, "generic proxy: id {} not found for frame", stream_id); return; } auto cb = it->second; - registered_response_callbacks_.erase(it); - cb->onDecodingSuccess(std::move(response), options); + // If the response is end, remove the callback from the map. + if (end_stream) { + registered_response_callbacks_.erase(it); + } + + return cb->onDecodingSuccess(std::move(response)); } void UpstreamManagerImpl::onDecodingFailure() { @@ -382,8 +483,28 @@ Envoy::Network::FilterStatus Filter::onData(Envoy::Buffer::Instance& data, bool) return Envoy::Network::FilterStatus::StopIteration; } -void Filter::onDecodingSuccess(RequestPtr request, ExtendedOptions options) { - newDownstreamRequest(std::move(request), options); +void Filter::onDecodingSuccess(StreamFramePtr request) { + const uint64_t stream_id = request->frameFlags().streamFlags().streamId(); + // One existing stream expects this frame. + if (auto iter = frame_handlers_.find(stream_id); iter != frame_handlers_.end()) { + iter->second->onRequestFrame(std::move(request)); + return; + } + + StreamFramePtrHelper helper(std::move(request)); + + // Create a new active stream for the leading StreamRequest frame. + if (helper.typed_frame_ != nullptr) { + newDownstreamRequest(std::move(helper.typed_frame_)); + return; + } + + ASSERT(helper.frame_ != nullptr); + // No existing stream expects this non-leading frame. It should not happen. + // We treat it as request decoding failure. + ENVOY_LOG(error, "generic proxy: id {} not found for stream frame", + helper.frame_->frameFlags().streamFlags().streamId()); + onDecodingFailure(); } void Filter::onDecodingFailure() { @@ -407,12 +528,27 @@ OptRef Filter::connection() { return {downstreamConnection()}; } -void Filter::sendReplyDownstream(Response& response, ResponseEncoderCallback& callback) { - response_encoder_->encode(response, callback); +void Filter::sendFrameToDownstream(StreamFrame& frame, ResponseEncoderCallback& callback) { + response_encoder_->encode(frame, callback); +} + +void Filter::registerFrameHandler(uint64_t stream_id, ActiveStream* raw_stream) { + // If the stream expects variable length frames, then add it to the frame + // handler map. + // This map entry will be removed when the request or response end frame is + // received. + if (frame_handlers_.contains(stream_id)) { + ENVOY_LOG(error, "generic proxy: repetitive stream id: {} at same time", stream_id); + onDecodingFailure(); + return; + } + frame_handlers_[stream_id] = raw_stream; } -void Filter::newDownstreamRequest(RequestPtr request, ExtendedOptions options) { - auto stream = std::make_unique(*this, std::move(request), options); +void Filter::unregisterFrameHandler(uint64_t stream_id) { frame_handlers_.erase(stream_id); } + +void Filter::newDownstreamRequest(StreamRequestPtr request) { + auto stream = std::make_unique(*this, std::move(request)); auto raw_stream = stream.get(); LinkedList::moveIntoList(std::move(stream), active_streams_); diff --git a/contrib/generic_proxy/filters/network/source/proxy.h b/contrib/generic_proxy/filters/network/source/proxy.h index 5c0b90faa1e9..f57cc442fb41 100644 --- a/contrib/generic_proxy/filters/network/source/proxy.h +++ b/contrib/generic_proxy/filters/network/source/proxy.h @@ -139,12 +139,6 @@ class ActiveStream : public FilterChainManager, StreamInfo::StreamInfo& streamInfo() override { return parent_.stream_info_; } Tracing::Span& activeSpan() override { return parent_.activeSpan(); } OptRef tracingConfig() const override { return parent_.tracingConfig(); } - absl::optional requestOptions() const override { - return parent_.downstream_request_options_; - } - absl::optional responseOptions() const override { - return parent_.local_or_upstream_response_options_; - } const Network::Connection* connection() const override; bool isDualFilter() const { return is_dual_; } @@ -167,8 +161,16 @@ class ActiveStream : public FilterChainManager, parent_.sendLocalReply(status, std::move(func)); } void continueDecoding() override { parent_.continueDecoding(); } - void upstreamResponse(ResponsePtr response, ExtendedOptions options) override { - parent_.upstreamResponse(std::move(response), options); + void onResponseStart(ResponsePtr response) override { + parent_.onResponseStart(std::move(response)); + } + void onResponseFrame(StreamFramePtr frame) override { + parent_.onResponseFrame(std::move(frame)); + } + void setRequestFramesHandler(StreamFrameHandler& handler) override { + ASSERT(parent_.request_stream_frame_handler_ == nullptr, + "request frames handler is already set"); + parent_.request_stream_frame_handler_ = &handler; } void completeDirectly() override { parent_.completeDirectly(); } void bindUpstreamConn(Upstream::TcpPoolData&& pool_data) override; @@ -219,7 +221,7 @@ class ActiveStream : public FilterChainManager, FilterContext context_; }; - ActiveStream(Filter& parent, RequestPtr request, ExtendedOptions request_options); + ActiveStream(Filter& parent, StreamRequestPtr request); void addDecoderFilter(ActiveDecoderFilterPtr filter) { decoder_filters_.emplace_back(std::move(filter)); @@ -237,7 +239,8 @@ class ActiveStream : public FilterChainManager, void sendLocalReply(Status status, ResponseUpdateFunction&&); void continueDecoding(); - void upstreamResponse(ResponsePtr response, ExtendedOptions options); + void onResponseStart(StreamResponsePtr response); + void onResponseFrame(StreamFramePtr frame); void completeDirectly(); void continueEncoding(); @@ -249,7 +252,9 @@ class ActiveStream : public FilterChainManager, } // ResponseEncoderCallback - void onEncodingSuccess(Buffer::Instance& buffer) override; + void onEncodingSuccess(Buffer::Instance& buffer, bool end_stream) override; + + void onRequestFrame(StreamFramePtr frame); std::vector& decoderFiltersForTest() { return decoder_filters_; } std::vector& encoderFiltersForTest() { return encoder_filters_; } @@ -273,6 +278,10 @@ class ActiveStream : public FilterChainManager, void completeRequest(); + uint64_t requestStreamId() const { + return request_stream_->frameFlags().streamFlags().streamId(); + } + private: // Keep these methods private to ensure that these methods are only called by the reference // returned by the public tracingConfig() method. @@ -283,15 +292,28 @@ class ActiveStream : public FilterChainManager, uint32_t maxPathTagLength() const override; bool spawnUpstreamSpan() const override; + void sendRequestFrameToUpstream(); + + void sendResponseStartToDownstream(); + void sendResponseFrameToDownstream(); + bool active_stream_reset_{false}; + bool registered_in_frame_handlers_{false}; + Filter& parent_; - RequestPtr downstream_request_stream_; - absl::optional downstream_request_options_; + StreamRequestPtr request_stream_; + std::list request_stream_frames_; + bool request_stream_end_{false}; + bool request_filter_chain_complete_{false}; + + StreamFrameHandler* request_stream_frame_handler_{nullptr}; - ResponsePtr local_or_upstream_response_stream_; - absl::optional local_or_upstream_response_options_; + StreamResponsePtr response_stream_; + std::list response_stream_frames_; + bool response_stream_end_{false}; + bool response_filter_chain_complete_{false}; RouteEntryConstSharedPtr cached_route_entry_; @@ -323,7 +345,7 @@ class UpstreamManagerImpl : public UpstreamConnection, void onEventImpl(Network::ConnectionEvent event) override; // ResponseDecoderCallback - void onDecodingSuccess(ResponsePtr response, ExtendedOptions options) override; + void onDecodingSuccess(StreamFramePtr response) override; void onDecodingFailure() override; void writeToConnection(Buffer::Instance& buffer) override; OptRef connection() override; @@ -366,7 +388,7 @@ class Filter : public Envoy::Network::ReadFilter, } // RequestDecoderCallback - void onDecodingSuccess(RequestPtr request, ExtendedOptions options) override; + void onDecodingSuccess(StreamFramePtr request) override; void onDecodingFailure() override; void writeToConnection(Buffer::Instance& buffer) override; OptRef connection() override; @@ -391,7 +413,7 @@ class Filter : public Envoy::Network::ReadFilter, void onAboveWriteBufferHighWatermark() override {} void onBelowWriteBufferLowWatermark() override {} - void sendReplyDownstream(Response& response, ResponseEncoderCallback& callback); + void sendFrameToDownstream(StreamFrame& frame, ResponseEncoderCallback& callback); Network::Connection& downstreamConnection() { ASSERT(callbacks_ != nullptr); @@ -401,9 +423,8 @@ class Filter : public Envoy::Network::ReadFilter, /** * Create a new active stream and add it to the active stream list. * @param request the request to be processed. - * @param options the extended options for the request. */ - void newDownstreamRequest(RequestPtr request, ExtendedOptions options); + void newDownstreamRequest(StreamRequestPtr request); /** * Move the stream to the deferred delete stream list. This is called when the stream is reset @@ -417,6 +438,7 @@ class Filter : public Envoy::Network::ReadFilter, } std::list& activeStreamsForTest() { return active_streams_; } + const auto& frameHandlersForTest() { return frame_handlers_; } // This may be called multiple times in some scenarios. But it is safe. void resetStreamsForUnexpectedError(); @@ -443,6 +465,9 @@ class Filter : public Envoy::Network::ReadFilter, friend class ActiveStream; friend class UpstreamManagerImpl; + void registerFrameHandler(uint64_t stream_id, ActiveStream* stream); + void unregisterFrameHandler(uint64_t stream_id); + bool downstream_connection_closed_{}; FilterConfigSharedPtr config_{}; @@ -463,6 +488,7 @@ class Filter : public Envoy::Network::ReadFilter, std::unique_ptr upstream_manager_; std::list active_streams_; + absl::flat_hash_map frame_handlers_; }; } // namespace GenericProxy diff --git a/contrib/generic_proxy/filters/network/source/router/router.cc b/contrib/generic_proxy/filters/network/source/router/router.cc index f6fdd0c550e9..caad3a261a5e 100644 --- a/contrib/generic_proxy/filters/network/source/router/router.cc +++ b/contrib/generic_proxy/filters/network/source/router/router.cc @@ -61,10 +61,9 @@ UpstreamRequest::UpstreamRequest(RouterFilter& parent, stream_info_.setUpstreamClusterInfo(parent_.cluster_); // Set request options. - auto options = decoder_callbacks_.requestOptions(); - ASSERT(options.has_value()); - stream_id_ = options->streamId().value_or(0); - wait_response_ = options->waitResponse(); + auto options = parent_.request_stream_->frameFlags().streamFlags(); + stream_id_ = options.streamId(); + wait_response_ = !options.oneWayStream(); // Set tracing config. tracing_config_ = decoder_callbacks_.tracingConfig(); @@ -118,7 +117,7 @@ void UpstreamRequest::resetStream(StreamResetReason reason) { if (span_ != nullptr) { span_->setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); span_->setTag(Tracing::Tags::get().ErrorReason, resetReasonToStringView(reason)); - Tracing::TracerUtility::finalizeSpan(*span_, *parent_.request_, stream_info_, + Tracing::TracerUtility::finalizeSpan(*span_, *parent_.request_stream_, stream_info_, tracing_config_.value().get(), true); } @@ -137,7 +136,7 @@ void UpstreamRequest::clearStream(bool close_connection) { ENVOY_LOG(debug, "generic proxy upstream request: complete upstream request"); if (span_ != nullptr) { - Tracing::TracerUtility::finalizeSpan(*span_, *parent_.request_, stream_info_, + Tracing::TracerUtility::finalizeSpan(*span_, *parent_.request_stream_, stream_info_, tracing_config_.value().get(), true); } @@ -159,10 +158,37 @@ void UpstreamRequest::deferredDelete() { } } -void UpstreamRequest::onEncodingSuccess(Buffer::Instance& buffer) { - ENVOY_LOG(debug, "upstream request encoding success"); +void UpstreamRequest::sendRequestStartToUpstream() { + request_stream_header_sent_ = true; + + parent_.request_encoder_->encode(*parent_.request_stream_, *this); +} + +void UpstreamRequest::sendRequestFrameToUpstream() { + if (!request_stream_header_sent_) { + // Do not send request frame to upstream until the request header is sent. It may be blocked + // by the upstream connecting. + return; + } + + while (!parent_.request_stream_frames_.empty()) { + auto frame = std::move(parent_.request_stream_frames_.front()); + parent_.request_stream_frames_.pop_front(); + + parent_.request_encoder_->encode(*frame, *this); + } +} + +void UpstreamRequest::onEncodingSuccess(Buffer::Instance& buffer, bool end_stream) { encodeBufferToUpstream(buffer); + if (!end_stream) { + return; + } + + // Request is complete. + ENVOY_LOG(debug, "upstream request encoding success"); + // Need not to wait for the upstream response and complete directly. if (!wait_response_) { clearStream(false); @@ -206,15 +232,32 @@ void UpstreamRequest::onBindSuccess(Network::ClientConnection& conn, upstream_conn_ = &conn; if (span_ != nullptr) { - span_->injectContext(*parent_.request_, upstream_host_); + span_->injectContext(*parent_.request_stream_, upstream_host_); } - parent_.request_encoder_->encode(*parent_.request_, *this); + sendRequestStartToUpstream(); + sendRequestFrameToUpstream(); } -void UpstreamRequest::onDecodingSuccess(ResponsePtr response, ExtendedOptions options) { - clearStream(options.drainClose()); - parent_.onUpstreamResponse(std::move(response), options); +void UpstreamRequest::onDecodingSuccess(StreamFramePtr response) { + const bool end_stream = response->frameFlags().endStream(); + if (end_stream) { + clearStream(response->frameFlags().streamFlags().drainClose()); + } + + if (response_stream_header_received_) { + parent_.onResponseFrame(std::move(response)); + return; + } + + StreamFramePtrHelper helper(std::move(response)); + if (helper.typed_frame_ == nullptr) { + ENVOY_LOG(error, "upstream request: first frame is not StreamResponse"); + resetStream(StreamResetReason::ProtocolError); + return; + } + response_stream_header_received_ = true; + parent_.onResponseStart(std::move(helper.typed_frame_)); } void UpstreamRequest::onDecodingFailure() { resetStream(StreamResetReason::ProtocolError); } @@ -273,9 +316,15 @@ void UpstreamRequest::encodeBufferToUpstream(Buffer::Instance& buffer) { upstream_conn_->write(buffer, false); } -void RouterFilter::onUpstreamResponse(ResponsePtr response, ExtendedOptions options) { - filter_complete_ = true; - callbacks_->upstreamResponse(std::move(response), std::move(options)); +void RouterFilter::onResponseStart(ResponsePtr response) { + filter_complete_ = response->frameFlags().endStream(); + callbacks_->onResponseStart(std::move(response)); +} + +void RouterFilter::onResponseFrame(StreamFramePtr frame) { + ASSERT(!filter_complete_, "response frame received after response complete"); + filter_complete_ = frame->frameFlags().endStream(); + callbacks_->onResponseFrame(std::move(frame)); } void RouterFilter::completeDirectly() { @@ -389,20 +438,32 @@ void RouterFilter::kickOffNewUpstreamRequest() { raw_upstream_request->startStream(); } -FilterStatus RouterFilter::onStreamDecoded(Request& request) { +void RouterFilter::onStreamFrame(StreamFramePtr frame) { + request_stream_end_ = frame->frameFlags().endStream(); + request_stream_frames_.emplace_back(std::move(frame)); + + if (upstream_requests_.empty()) { + return; + } + + upstream_requests_.front()->sendRequestFrameToUpstream(); +} + +FilterStatus RouterFilter::onStreamDecoded(StreamRequest& request) { ENVOY_LOG(debug, "Try route request to the upstream based on the route entry"); setRouteEntry(callbacks_->routeEntry()); - request_ = &request; + request_stream_end_ = request.frameFlags().endStream(); + request_stream_ = &request; - if (route_entry_ == nullptr) { - ENVOY_LOG(debug, "No route for current request and send local reply"); - callbacks_->sendLocalReply(Status(StatusCode::kNotFound, "route_not_found")); + if (route_entry_ != nullptr) { + request_encoder_ = callbacks_->downstreamCodec().requestEncoder(); + kickOffNewUpstreamRequest(); return FilterStatus::StopIteration; } - request_encoder_ = callbacks_->downstreamCodec().requestEncoder(); - kickOffNewUpstreamRequest(); + ENVOY_LOG(debug, "No route for current request and send local reply"); + callbacks_->sendLocalReply(Status(StatusCode::kNotFound, "route_not_found")); return FilterStatus::StopIteration; } diff --git a/contrib/generic_proxy/filters/network/source/router/router.h b/contrib/generic_proxy/filters/network/source/router/router.h index d64d9bdb8e9d..6b23053625a0 100644 --- a/contrib/generic_proxy/filters/network/source/router/router.h +++ b/contrib/generic_proxy/filters/network/source/router/router.h @@ -81,18 +81,21 @@ class UpstreamRequest : public UpstreamBindingCallback, Upstream::HostDescriptionConstSharedPtr host) override; // PendingResponseCallback - void onDecodingSuccess(ResponsePtr response, ExtendedOptions options) override; + void onDecodingSuccess(StreamFramePtr response) override; void onDecodingFailure() override; void writeToConnection(Buffer::Instance& buffer) override; OptRef connection() override; void onConnectionClose(Network::ConnectionEvent event) override; // RequestEncoderCallback - void onEncodingSuccess(Buffer::Instance& buffer) override; + void onEncodingSuccess(Buffer::Instance& buffer, bool end_stream) override; void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host); void encodeBufferToUpstream(Buffer::Instance& buffer); + void sendRequestStartToUpstream(); + void sendRequestFrameToUpstream(); + bool stream_reset_{}; RouterFilter& parent_; @@ -101,6 +104,9 @@ class UpstreamRequest : public UpstreamBindingCallback, uint64_t stream_id_{}; bool wait_response_{}; + bool request_stream_header_sent_{}; + bool response_stream_header_received_{}; + absl::optional tcp_pool_data_; std::unique_ptr upstream_manager_; @@ -120,6 +126,7 @@ using UpstreamRequestPtr = std::unique_ptr; class RouterFilter : public DecoderFilter, public Upstream::LoadBalancerContextBase, + public StreamFrameHandler, Logger::Loggable { public: RouterFilter(Server::Configuration::FactoryContext& context) : context_(context) {} @@ -129,11 +136,14 @@ class RouterFilter : public DecoderFilter, void setDecoderFilterCallbacks(DecoderFilterCallback& callbacks) override { callbacks_ = &callbacks; + // Set handler for following request frames. + callbacks_->setRequestFramesHandler(*this); protocol_options_ = callbacks_->downstreamCodec().protocolOptions(); } - FilterStatus onStreamDecoded(Request& request) override; + FilterStatus onStreamDecoded(StreamRequest& request) override; - void onUpstreamResponse(ResponsePtr response, ExtendedOptions options); + void onResponseStart(StreamResponsePtr response); + void onResponseFrame(StreamFramePtr frame); void completeDirectly(); void onUpstreamRequestReset(UpstreamRequest& upstream_request, StreamResetReason reason); @@ -147,6 +157,9 @@ class RouterFilter : public DecoderFilter, const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() override; const Network::Connection* downstreamConnection() const override; + // StreamFrameHandler + void onStreamFrame(StreamFramePtr frame) override; + private: friend class UpstreamRequest; friend class UpstreamManagerImpl; @@ -162,7 +175,9 @@ class RouterFilter : public DecoderFilter, const RouteEntry* route_entry_{}; Upstream::ClusterInfoConstSharedPtr cluster_; - Request* request_{}; + Request* request_stream_{}; + std::list request_stream_frames_; + bool request_stream_end_{}; Envoy::Router::MetadataMatchCriteriaConstPtr metadata_match_; diff --git a/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc b/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc index 9e5a6c8b8401..346fe4ba5d0b 100644 --- a/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc +++ b/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc @@ -221,6 +221,7 @@ TEST(RequestDecoderTest, RequestDecoderTest) { // Decode failure. { + decoder.metadata_.reset(); Buffer::OwnedImpl buffer; buffer.writeBEInt(0); buffer.writeBEInt(0); @@ -231,6 +232,8 @@ TEST(RequestDecoderTest, RequestDecoderTest) { // Waiting for header. { + decoder.metadata_.reset(); + Buffer::OwnedImpl buffer; buffer.add(std::string({'\xda', '\xbb', '\xc2', 0x00})); @@ -240,6 +243,8 @@ TEST(RequestDecoderTest, RequestDecoderTest) { // Waiting for data. { + decoder.metadata_.reset(); + Buffer::OwnedImpl buffer; buffer.add(std::string({'\xda', '\xbb', '\xc2', 0x00})); buffer.writeBEInt(1); @@ -251,6 +256,8 @@ TEST(RequestDecoderTest, RequestDecoderTest) { // Decode request. { + decoder.metadata_.reset(); + Buffer::OwnedImpl buffer; buffer.add(std::string({'\xda', '\xbb', '\xc2', 0x00})); buffer.writeBEInt(1); @@ -260,7 +267,7 @@ TEST(RequestDecoderTest, RequestDecoderTest) { EXPECT_CALL(*raw_serializer, deserializeRpcRequest(_, _)) .WillOnce(Return(ByMove(std::make_unique()))); - EXPECT_CALL(callback, onDecodingSuccess(_, _)); + EXPECT_CALL(callback, onDecodingSuccess(_)); decoder.decode(buffer); } } @@ -278,6 +285,8 @@ TEST(ResponseDecoderTest, ResponseDecoderTest) { // Decode failure. { + decoder.metadata_.reset(); + Buffer::OwnedImpl buffer; buffer.writeBEInt(0); buffer.writeBEInt(0); @@ -288,6 +297,8 @@ TEST(ResponseDecoderTest, ResponseDecoderTest) { // Waiting for header. { + decoder.metadata_.reset(); + Buffer::OwnedImpl buffer; buffer.add(std::string({'\xda', '\xbb', '\x02', 20})); @@ -297,6 +308,8 @@ TEST(ResponseDecoderTest, ResponseDecoderTest) { // Waiting for data. { + decoder.metadata_.reset(); + Buffer::OwnedImpl buffer; buffer.add(std::string({'\xda', '\xbb', '\x02', 20})); buffer.writeBEInt(1); @@ -308,6 +321,8 @@ TEST(ResponseDecoderTest, ResponseDecoderTest) { // Decode response. { + decoder.metadata_.reset(); + Buffer::OwnedImpl buffer; buffer.add(std::string({'\xda', '\xbb', '\x02', 20})); buffer.writeBEInt(1); @@ -320,7 +335,7 @@ TEST(ResponseDecoderTest, ResponseDecoderTest) { EXPECT_CALL(*raw_serializer, deserializeRpcResponse(_, _)) .WillOnce(Return(ByMove(std::move(response)))); - EXPECT_CALL(callback, onDecodingSuccess(_, _)); + EXPECT_CALL(callback, onDecodingSuccess(_)); decoder.decode(buffer); } } @@ -340,7 +355,7 @@ TEST(RequestEncoderTest, RequestEncoderTest) { DubboRequest request(createDubboRequst(false)); EXPECT_CALL(*raw_serializer, serializeRpcRequest(_, _)); - EXPECT_CALL(callback, onEncodingSuccess(_)); + EXPECT_CALL(callback, onEncodingSuccess(_, _)); encoder.encode(request, callback); } @@ -350,7 +365,7 @@ TEST(RequestEncoderTest, RequestEncoderTest) { DubboRequest request(createDubboRequst(true)); EXPECT_CALL(*raw_serializer, serializeRpcRequest(_, _)); - EXPECT_CALL(callback, onEncodingSuccess(_)); + EXPECT_CALL(callback, onEncodingSuccess(_, _)); encoder.encode(request, callback); } @@ -373,7 +388,7 @@ TEST(ResponseEncoderTest, ResponseEncoderTest) { createDubboResponse(request, ResponseStatus::Ok, RpcResponseType::ResponseWithValue)); EXPECT_CALL(*raw_serializer, serializeRpcResponse(_, _)); - EXPECT_CALL(callback, onEncodingSuccess(_)); + EXPECT_CALL(callback, onEncodingSuccess(_, _)); encoder.encode(response, callback); } diff --git a/contrib/generic_proxy/filters/network/test/fake_codec.h b/contrib/generic_proxy/filters/network/test/fake_codec.h index de3f9ae35678..6a57fbbe1c51 100644 --- a/contrib/generic_proxy/filters/network/test/fake_codec.h +++ b/contrib/generic_proxy/filters/network/test/fake_codec.h @@ -33,6 +33,11 @@ template class FakeStreamBase : public InterfaceType { } void setByReference(absl::string_view key, absl::string_view val) override { setByKey(key, val); } + // StreamFrame + FrameFlags frameFlags() const override { return stream_frame_flags_; } + + FrameFlags stream_frame_flags_; + absl::flat_hash_map data_; }; @@ -93,16 +98,24 @@ class FakeStreamCodecFactory : public CodecFactory { request->data_.emplace(pair); } absl::optional stream_id; - bool wait_response = true; + bool one_way_stream = false; if (auto it = request->data_.find("stream_id"); it != request->data_.end()) { stream_id = std::stoull(it->second); } - if (auto it = request->data_.find("wait_response"); it != request->data_.end()) { - wait_response = it->second == "true"; + if (auto it = request->data_.find("one_way"); it != request->data_.end()) { + one_way_stream = it->second == "true"; + } + + // Mock multiple frames in one request. + bool end_stream = true; + if (auto it = request->data_.find("end_stream"); it != request->data_.end()) { + end_stream = it->second == "true"; } - ExtendedOptions request_options{stream_id, wait_response, false, false}; - callback_->onDecodingSuccess(std::move(request), request_options); + request->stream_frame_flags_ = + FrameFlags(StreamFlags(stream_id.value_or(0), one_way_stream, false, false), end_stream); + + callback_->onDecodingSuccess(std::move(request)); return true; } @@ -170,9 +183,17 @@ class FakeStreamCodecFactory : public CodecFactory { if (auto it = response->data_.find("close_connection"); it != response->data_.end()) { close_connection = it->second == "true"; } - ExtendedOptions response_options{stream_id, false, close_connection, false}; - callback_->onDecodingSuccess(std::move(response), response_options); + // Mock multiple frames in one response. + bool end_stream = true; + if (auto it = response->data_.find("end_stream"); it != response->data_.end()) { + end_stream = it->second == "true"; + } + + response->stream_frame_flags_ = FrameFlags( + StreamFlags(stream_id.value_or(0), false, close_connection, false), end_stream); + + callback_->onDecodingSuccess(std::move(response)); return true; } @@ -214,7 +235,7 @@ class FakeStreamCodecFactory : public CodecFactory { class FakeRequestEncoder : public RequestEncoder { public: - void encode(const Request& request, RequestEncoderCallback& callback) override { + void encode(const StreamFrame& request, RequestEncoderCallback& callback) override { const FakeRequest* typed_request = dynamic_cast(&request); ASSERT(typed_request != nullptr); @@ -228,7 +249,7 @@ class FakeStreamCodecFactory : public CodecFactory { buffer_.writeBEInt(body.size()); buffer_.add(body); - callback.onEncodingSuccess(buffer_); + callback.onEncodingSuccess(buffer_, request.frameFlags().endStream()); } Buffer::OwnedImpl buffer_; @@ -236,7 +257,7 @@ class FakeStreamCodecFactory : public CodecFactory { class FakeResponseEncoder : public ResponseEncoder { public: - void encode(const Response& response, ResponseEncoderCallback& callback) override { + void encode(const StreamFrame& response, ResponseEncoderCallback& callback) override { const FakeResponse* typed_response = dynamic_cast(&response); ASSERT(typed_response != nullptr); @@ -251,7 +272,7 @@ class FakeStreamCodecFactory : public CodecFactory { buffer_.writeBEInt(static_cast(typed_response->status_.raw_code())); buffer_.add(body); - callback.onEncodingSuccess(buffer_); + callback.onEncodingSuccess(buffer_, response.frameFlags().endStream()); } Buffer::OwnedImpl buffer_; diff --git a/contrib/generic_proxy/filters/network/test/integration_test.cc b/contrib/generic_proxy/filters/network/test/integration_test.cc index c919f738c21e..72ffaca56155 100644 --- a/contrib/generic_proxy/filters/network/test/integration_test.cc +++ b/contrib/generic_proxy/filters/network/test/integration_test.cc @@ -66,25 +66,13 @@ class IntegrationTest : public testing::TestWithParam; struct TestRequestEncoderCallback : public RequestEncoderCallback { - void onEncodingSuccess(Buffer::Instance& buffer) override { - buffer_.move(buffer); - complete_ = true; - request_bytes_ = buffer_.length(); - } - bool complete_{}; - size_t request_bytes_{}; + void onEncodingSuccess(Buffer::Instance& buffer, bool) override { buffer_.move(buffer); } Buffer::OwnedImpl buffer_; }; using TestRequestEncoderCallbackSharedPtr = std::shared_ptr; struct TestResponseEncoderCallback : public ResponseEncoderCallback { - void onEncodingSuccess(Buffer::Instance& buffer) override { - buffer_.move(buffer); - complete_ = true; - response_bytes_ = buffer_.length(); - } - bool complete_{}; - size_t response_bytes_{}; + void onEncodingSuccess(Buffer::Instance& buffer, bool) override { buffer_.move(buffer); } Buffer::OwnedImpl buffer_; }; using TestResponseEncoderCallbackSharedPtr = std::shared_ptr; @@ -92,10 +80,31 @@ class IntegrationTest : public testing::TestWithParamdispatcher_->exit(); + struct SingleResponse { + bool end_stream_{}; + ResponsePtr response_; + std::list response_frames_; + }; + + void onDecodingSuccess(StreamFramePtr response_frame) override { + auto& response = responses_[response_frame->frameFlags().streamFlags().streamId()]; + + ASSERT(!response.end_stream_); + response.end_stream_ = response_frame->frameFlags().endStream(); + + if (response.response_ != nullptr) { + response.response_frames_.push_back(std::move(response_frame)); + } else { + ASSERT(response.response_frames_.empty()); + StreamFramePtrHelper helper(std::move(response_frame)); + ASSERT(helper.typed_frame_ != nullptr); + response.response_ = std::move(helper.typed_frame_); + } + + // Exit dispatcher if we have received all the expected response frames. + if (responses_[waiting_for_stream_id_].end_stream_) { + parent_.integration_->dispatcher_->exit(); + } } void onDecodingFailure() override {} void writeToConnection(Buffer::Instance&) override {} @@ -106,8 +115,8 @@ class IntegrationTest : public testing::TestWithParam responses_; IntegrationTest& parent_; }; using TestResponseDecoderCallbackSharedPtr = std::shared_ptr; @@ -195,11 +204,12 @@ class IntegrationTest : public testing::TestWithParamencode(request, *request_encoder_callback_); - RELEASE_ASSERT(request_encoder_callback_->complete_, "Encoding should complete Immediately"); client_connection_->write(request_encoder_callback_->buffer_, false); client_connection_->dispatcher().run(Envoy::Event::Dispatcher::RunType::NonBlock); + // Clear buffer for next encoding. + request_encoder_callback_->buffer_.drain(request_encoder_callback_->buffer_.length()); } // Waiting upstream connection to be created. @@ -208,39 +218,46 @@ class IntegrationTest : public testing::TestWithParamwaitForData(num_bytes, data); + void + waitForUpstreamRequestForTest(const std::function& data_validator) { + auto result = upstream_connection_->waitForData(data_validator, nullptr); RELEASE_ASSERT(result, result.failure_message()); // Clear data for next test. upstream_connection_->clearData(); } // Send upstream response. - void sendResponseForTest(const Response& response) { + void sendResponseForTest(const StreamFrame& response) { response_encoder_->encode(response, *response_encoder_callback_); - RELEASE_ASSERT(response_encoder_callback_->complete_, "Encoding should complete Immediately"); auto result = upstream_connection_->write(response_encoder_callback_->buffer_.toString(), false); + // Clear buffer for next encoding. + response_encoder_callback_->buffer_.drain(response_encoder_callback_->buffer_.length()); RELEASE_ASSERT(result, result.failure_message()); } // Waiting for downstream response. - AssertionResult waitDownstreamResponseForTest(std::chrono::milliseconds timeout) { + AssertionResult waitDownstreamResponseForTest(std::chrono::milliseconds timeout, + uint64_t stream_id) { bool timer_fired = false; - if (!response_decoder_callback_->complete_) { + if (!response_decoder_callback_->responses_[stream_id].end_stream_) { Envoy::Event::TimerPtr timer( integration_->dispatcher_->createTimer([this, &timer_fired]() -> void { timer_fired = true; integration_->dispatcher_->exit(); })); timer->enableTimer(timeout); + response_decoder_callback_->waiting_for_stream_id_ = stream_id; integration_->dispatcher_->run(Envoy::Event::Dispatcher::RunType::Block); if (timer_fired) { return AssertionFailure() << "Timed out waiting for response"; } + if (timer->enabled()) { + timer->disableTimer(); + } } - if (!response_decoder_callback_->complete_) { + if (!response_decoder_callback_->responses_[stream_id].end_stream_) { return AssertionFailure() << "No response or response not complete"; } return AssertionSuccess(); @@ -307,11 +324,12 @@ TEST_P(IntegrationTest, RequestRouteNotFound) { sendRequestForTest(request); - RELEASE_ASSERT(waitDownstreamResponseForTest(std::chrono::milliseconds(200)), + RELEASE_ASSERT(waitDownstreamResponseForTest(TestUtility::DefaultTimeout, 0), "unexpected timeout"); - EXPECT_NE(response_decoder_callback_->response_, nullptr); - EXPECT_EQ(response_decoder_callback_->response_->status().message(), "route_not_found"); + EXPECT_NE(response_decoder_callback_->responses_[0].response_, nullptr); + EXPECT_EQ(response_decoder_callback_->responses_[0].response_->status().message(), + "route_not_found"); cleanup(); } @@ -334,7 +352,9 @@ TEST_P(IntegrationTest, RequestAndResponse) { sendRequestForTest(request); waitForUpstreamConnectionForTest(); - waitForUpstreamRequestForTest(request_encoder_callback_->request_bytes_, nullptr); + const std::function data_validator = + [](const std::string& data) -> bool { return data.find("v1") != std::string::npos; }; + waitForUpstreamRequestForTest(data_validator); FakeStreamCodecFactory::FakeResponse response; response.protocol_ = "fake_fake_fake"; @@ -343,12 +363,12 @@ TEST_P(IntegrationTest, RequestAndResponse) { sendResponseForTest(response); - RELEASE_ASSERT(waitDownstreamResponseForTest(std::chrono::milliseconds(200)), + RELEASE_ASSERT(waitDownstreamResponseForTest(TestUtility::DefaultTimeout, 0), "unexpected timeout"); - EXPECT_NE(response_decoder_callback_->response_, nullptr); - EXPECT_EQ(response_decoder_callback_->response_->status().code(), StatusCode::kOk); - EXPECT_EQ(response_decoder_callback_->response_->getByKey("zzzz"), "xxxx"); + EXPECT_NE(response_decoder_callback_->responses_[0].response_, nullptr); + EXPECT_EQ(response_decoder_callback_->responses_[0].response_->status().code(), StatusCode::kOk); + EXPECT_EQ(response_decoder_callback_->responses_[0].response_->getByKey("zzzz"), "xxxx"); cleanup(); } @@ -375,7 +395,9 @@ TEST_P(IntegrationTest, MultipleRequestsWithSameStreamId) { sendRequestForTest(request_1); waitForUpstreamConnectionForTest(); - waitForUpstreamRequestForTest(request_encoder_callback_->request_bytes_, nullptr); + const std::function data_validator = + [](const std::string& data) -> bool { return data.find("v1") != std::string::npos; }; + waitForUpstreamRequestForTest(data_validator); FakeStreamCodecFactory::FakeRequest request_2; request_2.host_ = "service_name_0"; @@ -411,26 +433,34 @@ TEST_P(IntegrationTest, MultipleRequests) { request_1.method_ = "hello"; request_1.path_ = "/path_or_anything"; request_1.protocol_ = "fake_fake_fake"; - request_1.data_ = {{"version", "v1"}, {"stream_id", "1"}}; + request_1.data_ = {{"version", "v1"}, {"stream_id", "1"}, {"frame", "1_header"}}; sendRequestForTest(request_1); waitForUpstreamConnectionForTest(); - waitForUpstreamRequestForTest(request_encoder_callback_->request_bytes_, nullptr); + const std::function data_validator_1 = + [](const std::string& data) -> bool { + return data.find("frame:1_header") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_1); FakeStreamCodecFactory::FakeRequest request_2; request_2.host_ = "service_name_0"; request_2.method_ = "hello"; request_2.path_ = "/path_or_anything"; request_2.protocol_ = "fake_fake_fake"; - request_2.data_ = {{"version", "v1"}, {"stream_id", "2"}}; + request_2.data_ = {{"version", "v1"}, {"stream_id", "2"}, {"frame", "2_header"}}; // Reset request encoder callback. request_encoder_callback_ = std::make_shared(); // Send the second request with the different stream id and expect the connection to be alive. sendRequestForTest(request_2); - waitForUpstreamRequestForTest(request_encoder_callback_->request_bytes_, nullptr); + const std::function data_validator_2 = + [](const std::string& data) -> bool { + return data.find("frame:2_header") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_2); FakeStreamCodecFactory::FakeResponse response_2; response_2.protocol_ = "fake_fake_fake"; @@ -440,31 +470,168 @@ TEST_P(IntegrationTest, MultipleRequests) { sendResponseForTest(response_2); - RELEASE_ASSERT(waitDownstreamResponseForTest(std::chrono::milliseconds(200)), + RELEASE_ASSERT(waitDownstreamResponseForTest(TestUtility::DefaultTimeout, 2), + "unexpected timeout"); + + EXPECT_NE(response_decoder_callback_->responses_[2].response_, nullptr); + EXPECT_EQ(response_decoder_callback_->responses_[2].response_->status().code(), StatusCode::kOk); + EXPECT_EQ(response_decoder_callback_->responses_[2].response_->getByKey("zzzz"), "xxxx"); + EXPECT_EQ(response_decoder_callback_->responses_[2].response_->getByKey("stream_id"), "2"); + + FakeStreamCodecFactory::FakeResponse response_1; + response_1.protocol_ = "fake_fake_fake"; + response_1.status_ = Status(); + response_1.data_["zzzz"] = "yyyy"; + response_1.data_["stream_id"] = "1"; + + sendResponseForTest(response_1); + + RELEASE_ASSERT(waitDownstreamResponseForTest(TestUtility::DefaultTimeout, 1), "unexpected timeout"); - EXPECT_NE(response_decoder_callback_->response_, nullptr); - EXPECT_EQ(response_decoder_callback_->response_->status().code(), StatusCode::kOk); - EXPECT_EQ(response_decoder_callback_->response_->getByKey("zzzz"), "xxxx"); - EXPECT_EQ(response_decoder_callback_->response_->getByKey("stream_id"), "2"); + EXPECT_NE(response_decoder_callback_->responses_[1].response_, nullptr); + EXPECT_EQ(response_decoder_callback_->responses_[1].response_->status().code(), StatusCode::kOk); + EXPECT_EQ(response_decoder_callback_->responses_[1].response_->getByKey("zzzz"), "yyyy"); + EXPECT_EQ(response_decoder_callback_->responses_[1].response_->getByKey("stream_id"), "1"); + + cleanup(); +} + +TEST_P(IntegrationTest, MultipleRequestsWithMultipleFrames) { + FakeStreamCodecFactoryConfig codec_factory_config; + codec_factory_config.protocol_options_ = ProtocolOptions{true}; + Registry::InjectFactory registration(codec_factory_config); + + auto codec_factory = std::make_unique(); + codec_factory->protocol_options_ = ProtocolOptions{true}; + + initialize(defaultConfig(), std::move(codec_factory)); + + EXPECT_TRUE(makeClientConnectionForTest()); + + FakeStreamCodecFactory::FakeRequest request_1; + request_1.host_ = "service_name_0"; + request_1.method_ = "hello"; + request_1.path_ = "/path_or_anything"; + request_1.protocol_ = "fake_fake_fake"; + request_1.data_ = { + {"version", "v1"}, {"stream_id", "1"}, {"end_stream", "false"}, {"frame", "1_header"}}; + + FakeStreamCodecFactory::FakeRequest request_1_frame_1; + request_1_frame_1.data_ = {{"stream_id", "1"}, {"end_stream", "false"}, {"frame", "1_frame_1"}}; + + FakeStreamCodecFactory::FakeRequest request_1_frame_2; + request_1_frame_2.data_ = {{"stream_id", "1"}, {"end_stream", "true"}, {"frame", "1_frame_2"}}; + + FakeStreamCodecFactory::FakeRequest request_2; + request_2.host_ = "service_name_0"; + request_2.method_ = "hello"; + request_2.path_ = "/path_or_anything"; + request_2.protocol_ = "fake_fake_fake"; + request_2.data_ = { + {"version", "v1"}, {"stream_id", "2"}, {"end_stream", "false"}, {"frame", "2_header"}}; + + FakeStreamCodecFactory::FakeRequest request_2_frame_1; + request_2_frame_1.data_ = {{"stream_id", "2"}, {"end_stream", "false"}, {"frame", "2_frame_1"}}; + + FakeStreamCodecFactory::FakeRequest request_2_frame_2; + request_2_frame_2.data_ = {{"stream_id", "2"}, {"end_stream", "true"}, {"frame", "2_frame_2"}}; + + // We handle frame one by one to make sure the order is correct. + + sendRequestForTest(request_1); + waitForUpstreamConnectionForTest(); + + // First frame of request 1. + const std::function data_validator_1 = + [](const std::string& data) -> bool { + return data.find("frame:1_header") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_1); + + // Second frame of request 1. + sendRequestForTest(request_1_frame_1); + const std::function data_validator_1_frame_1 = + [](const std::string& data) -> bool { + return data.find("frame:1_frame_1") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_1_frame_1); + + // First frame of request 2. + sendRequestForTest(request_2); + const std::function data_validator_2 = + [](const std::string& data) -> bool { + return data.find("frame:2_header") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_2); + + // Second frame of request 2. + sendRequestForTest(request_2_frame_1); + const std::function data_validator_2_frame_1 = + [](const std::string& data) -> bool { + return data.find("frame:2_frame_1") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_2_frame_1); + + // Third frame of request 1. + sendRequestForTest(request_1_frame_2); + const std::function data_validator_1_frame_2 = + [](const std::string& data) -> bool { + return data.find("frame:1_frame_2") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_1_frame_2); + + // Third frame of request 2. + sendRequestForTest(request_2_frame_2); + const std::function data_validator_2_frame_2 = + [](const std::string& data) -> bool { + return data.find("frame:2_frame_2") != std::string::npos; + }; + waitForUpstreamRequestForTest(data_validator_2_frame_2); + + FakeStreamCodecFactory::FakeResponse response_2; + response_2.protocol_ = "fake_fake_fake"; + response_2.status_ = Status(); + response_2.data_["zzzz"] = "xxxx"; + response_2.data_["stream_id"] = "2"; + response_2.data_["end_stream"] = "false"; + + FakeStreamCodecFactory::FakeResponse response_2_frame_1; + response_2_frame_1.data_["stream_id"] = "2"; + response_2_frame_1.data_["end_stream"] = "true"; - response_decoder_callback_->complete_ = false; + sendResponseForTest(response_2); + sendResponseForTest(response_2_frame_1); + + RELEASE_ASSERT(waitDownstreamResponseForTest(TestUtility::DefaultTimeout, 2), + "unexpected timeout"); + + EXPECT_NE(response_decoder_callback_->responses_[2].response_, nullptr); + EXPECT_EQ(response_decoder_callback_->responses_[2].response_->status().code(), StatusCode::kOk); + EXPECT_EQ(response_decoder_callback_->responses_[2].response_->getByKey("zzzz"), "xxxx"); + EXPECT_EQ(response_decoder_callback_->responses_[2].response_->getByKey("stream_id"), "2"); FakeStreamCodecFactory::FakeResponse response_1; response_1.protocol_ = "fake_fake_fake"; response_1.status_ = Status(); response_1.data_["zzzz"] = "yyyy"; response_1.data_["stream_id"] = "1"; + response_1.data_["end_stream"] = "false"; + + FakeStreamCodecFactory::FakeResponse response_1_frame_1; + response_1_frame_1.data_["stream_id"] = "1"; + response_1_frame_1.data_["end_stream"] = "true"; sendResponseForTest(response_1); + sendResponseForTest(response_1_frame_1); - RELEASE_ASSERT(waitDownstreamResponseForTest(std::chrono::milliseconds(200)), + RELEASE_ASSERT(waitDownstreamResponseForTest(TestUtility::DefaultTimeout, 1), "unexpected timeout"); - EXPECT_NE(response_decoder_callback_->response_, nullptr); - EXPECT_EQ(response_decoder_callback_->response_->status().code(), StatusCode::kOk); - EXPECT_EQ(response_decoder_callback_->response_->getByKey("zzzz"), "yyyy"); - EXPECT_EQ(response_decoder_callback_->response_->getByKey("stream_id"), "1"); + EXPECT_NE(response_decoder_callback_->responses_[1].response_, nullptr); + EXPECT_EQ(response_decoder_callback_->responses_[1].response_->status().code(), StatusCode::kOk); + EXPECT_EQ(response_decoder_callback_->responses_[1].response_->getByKey("zzzz"), "yyyy"); + EXPECT_EQ(response_decoder_callback_->responses_[1].response_->getByKey("stream_id"), "1"); cleanup(); } diff --git a/contrib/generic_proxy/filters/network/test/mocks/codec.h b/contrib/generic_proxy/filters/network/test/mocks/codec.h index 650e9d054b0b..94eddce4cc15 100644 --- a/contrib/generic_proxy/filters/network/test/mocks/codec.h +++ b/contrib/generic_proxy/filters/network/test/mocks/codec.h @@ -10,7 +10,7 @@ namespace GenericProxy { class MockRequestDecoderCallback : public RequestDecoderCallback { public: - MOCK_METHOD(void, onDecodingSuccess, (RequestPtr request, ExtendedOptions)); + MOCK_METHOD(void, onDecodingSuccess, (StreamFramePtr request)); MOCK_METHOD(void, onDecodingFailure, ()); MOCK_METHOD(void, writeToConnection, (Buffer::Instance & buffer)); MOCK_METHOD(OptRef, connection, ()); @@ -18,7 +18,7 @@ class MockRequestDecoderCallback : public RequestDecoderCallback { class MockResponseDecoderCallback : public ResponseDecoderCallback { public: - MOCK_METHOD(void, onDecodingSuccess, (ResponsePtr response, ExtendedOptions)); + MOCK_METHOD(void, onDecodingSuccess, (StreamFramePtr response)); MOCK_METHOD(void, onDecodingFailure, ()); MOCK_METHOD(void, writeToConnection, (Buffer::Instance & buffer)); MOCK_METHOD(OptRef, connection, ()); @@ -26,7 +26,7 @@ class MockResponseDecoderCallback : public ResponseDecoderCallback { class MockRequestEncoderCallback : public RequestEncoderCallback { public: - MOCK_METHOD(void, onEncodingSuccess, (Buffer::Instance & buffer)); + MOCK_METHOD(void, onEncodingSuccess, (Buffer::Instance & buffer, bool end_stream)); }; /** @@ -34,7 +34,7 @@ class MockRequestEncoderCallback : public RequestEncoderCallback { */ class MockResponseEncoderCallback : public ResponseEncoderCallback { public: - MOCK_METHOD(void, onEncodingSuccess, (Buffer::Instance & buffer)); + MOCK_METHOD(void, onEncodingSuccess, (Buffer::Instance & buffer, bool end_stream)); }; class MockRequestDecoder : public RequestDecoder { @@ -51,12 +51,12 @@ class MockResponseDecoder : public ResponseDecoder { class MockRequestEncoder : public RequestEncoder { public: - MOCK_METHOD(void, encode, (const Request&, RequestEncoderCallback& callback)); + MOCK_METHOD(void, encode, (const StreamFrame&, RequestEncoderCallback& callback)); }; class MockResponseEncoder : public ResponseEncoder { public: - MOCK_METHOD(void, encode, (const Response&, ResponseEncoderCallback& callback)); + MOCK_METHOD(void, encode, (const StreamFrame&, ResponseEncoderCallback& callback)); }; class MockMessageCreator : public MessageCreator { diff --git a/contrib/generic_proxy/filters/network/test/mocks/filter.cc b/contrib/generic_proxy/filters/network/test/mocks/filter.cc index 7a2bba0efc3d..1ecd3cf36b3f 100644 --- a/contrib/generic_proxy/filters/network/test/mocks/filter.cc +++ b/contrib/generic_proxy/filters/network/test/mocks/filter.cc @@ -13,6 +13,8 @@ namespace Extensions { namespace NetworkFilters { namespace GenericProxy { +MockStreamFrameHandler::MockStreamFrameHandler() = default; + MockStreamFilterConfig::MockStreamFilterConfig() { ON_CALL(*this, createEmptyConfigProto()).WillByDefault(Invoke([]() { return std::make_unique(); diff --git a/contrib/generic_proxy/filters/network/test/mocks/filter.h b/contrib/generic_proxy/filters/network/test/mocks/filter.h index 4bd32066c82f..e78a031d954c 100644 --- a/contrib/generic_proxy/filters/network/test/mocks/filter.h +++ b/contrib/generic_proxy/filters/network/test/mocks/filter.h @@ -12,6 +12,13 @@ namespace Extensions { namespace NetworkFilters { namespace GenericProxy { +class MockStreamFrameHandler : public StreamFrameHandler { +public: + MockStreamFrameHandler(); + + MOCK_METHOD(void, onStreamFrame, (StreamFramePtr frame)); +}; + class MockDecoderFilter : public DecoderFilter { public: MockDecoderFilter(); @@ -86,7 +93,7 @@ class MockPendingResponseCallback : public PendingResponseCallback { public: MockPendingResponseCallback(); - MOCK_METHOD(void, onDecodingSuccess, (ResponsePtr response, ExtendedOptions options)); + MOCK_METHOD(void, onDecodingSuccess, (StreamFramePtr response)); MOCK_METHOD(void, onDecodingFailure, ()); MOCK_METHOD(void, writeToConnection, (Buffer::Instance & buffer)); MOCK_METHOD(OptRef, connection, ()); @@ -114,8 +121,6 @@ template class MockStreamFilterCallbacks : public Base { MOCK_METHOD(StreamInfo::StreamInfo&, streamInfo, ()); MOCK_METHOD(Tracing::Span&, activeSpan, ()); MOCK_METHOD(OptRef, tracingConfig, (), (const)); - MOCK_METHOD(absl::optional, requestOptions, (), (const)); - MOCK_METHOD(absl::optional, responseOptions, (), (const)); MOCK_METHOD(const Network::Connection*, connection, (), (const)); }; @@ -150,13 +155,15 @@ class MockUpstreamManager : public UpstreamManager { cb->onBindFailure(reason, "", upstream_host_); } - void callOnDecodingSuccess(uint64_t stream_id, ResponsePtr response, ExtendedOptions options) { + void callOnDecodingSuccess(uint64_t stream_id, StreamFramePtr response) { auto it = response_callbacks_.find(stream_id); auto cb = it->second; - response_callbacks_.erase(it); + if (response->frameFlags().endStream()) { + response_callbacks_.erase(it); + } - cb->onDecodingSuccess(std::move(response), options); + cb->onDecodingSuccess(std::move(response)); } void callOnConnectionClose(uint64_t stream_id, Network::ConnectionEvent event) { @@ -195,7 +202,9 @@ class MockDecoderFilterCallback : public MockStreamFilterCallbacks, boundUpstreamConn, ()); diff --git a/contrib/generic_proxy/filters/network/test/proxy_test.cc b/contrib/generic_proxy/filters/network/test/proxy_test.cc index 10253829422a..bb772f958ed2 100644 --- a/contrib/generic_proxy/filters/network/test/proxy_test.cc +++ b/contrib/generic_proxy/filters/network/test/proxy_test.cc @@ -265,7 +265,7 @@ TEST_F(FilterTest, OnDecodingSuccessWithNormalRequest) { // Three mock factories was added. EXPECT_CALL(*mock_stream_filter, onStreamDecoded(_)).Times(3); - decoder_callback_->onDecodingSuccess(std::move(request), ExtendedOptions()); + decoder_callback_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); } @@ -298,18 +298,19 @@ TEST_F(FilterTest, SendReplyDownstream) { EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); - EXPECT_CALL(encoder_callback, onEncodingSuccess(_)) - .WillOnce(Invoke( - [&](Buffer::Instance& buffer) { filter_callbacks_.connection_.write(buffer, false); })); + EXPECT_CALL(encoder_callback, onEncodingSuccess(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + filter_callbacks_.connection_.write(buffer, false); + })); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response&, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame&, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); - filter_->sendReplyDownstream(*response, encoder_callback); + filter_->sendFrameToDownstream(*response, encoder_callback); } TEST_F(FilterTest, GetConnection) { @@ -323,7 +324,7 @@ TEST_F(FilterTest, NewStreamAndResetStream) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -346,7 +347,7 @@ TEST_F(FilterTest, NewStreamAndResetStreamFromFilter) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -363,7 +364,7 @@ TEST_F(FilterTest, NewStreamAndDispatcher) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -375,10 +376,10 @@ TEST_F(FilterTest, OnDecodingFailureWithActiveStreams) { initializeFilter(); auto request_0 = std::make_unique(); - filter_->newDownstreamRequest(std::move(request_0), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request_0)); auto request_1 = std::make_unique(); - filter_->newDownstreamRequest(std::move(request_1), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request_1)); EXPECT_EQ(2, filter_->activeStreamsForTest().size()); @@ -397,7 +398,7 @@ TEST_F(FilterTest, ActiveStreamRouteEntry) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -413,7 +414,7 @@ TEST_F(FilterTest, ActiveStreamPerFilterConfig) { EXPECT_CALL(*route_matcher_, routeEntry(_)).WillOnce(Return(mock_route_entry_)); auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -437,7 +438,7 @@ TEST_F(FilterTest, ActiveStreamPerFilterConfigNoRouteEntry) { EXPECT_CALL(*route_matcher_, routeEntry(_)).WillOnce(Return(nullptr)); auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -457,7 +458,7 @@ TEST_F(FilterTest, ActiveStreamConnection) { initializeFilter(); auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -476,7 +477,7 @@ TEST_F(FilterTest, ActiveStreamAddFilters) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -524,7 +525,7 @@ TEST_F(FilterTest, ActiveStreamAddFiltersOrder) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -562,7 +563,7 @@ TEST_F(FilterTest, ActiveStreamFiltersContinueDecoding) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -601,7 +602,7 @@ TEST_F(FilterTest, ActiveStreamFiltersContinueEncoding) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -614,8 +615,8 @@ TEST_F(FilterTest, ActiveStreamFiltersContinueEncoding) { EXPECT_EQ(0, active_stream->nextEncoderFilterIndexForTest()); auto response = std::make_unique(); - // `continueEncoding` will be called in the `upstreamResponse`. - active_stream->upstreamResponse(std::move(response), ExtendedOptions()); + // `continueEncoding` will be called in the `onResponseStart`. + active_stream->onResponseStart(std::move(response)); // Encoding will be stopped when `onStreamEncoded` of `mock_stream_filter_1` is called. EXPECT_EQ(2, active_stream->nextEncoderFilterIndexForTest()); @@ -623,10 +624,10 @@ TEST_F(FilterTest, ActiveStreamFiltersContinueEncoding) { EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response&, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame&, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); active_stream->encoderFiltersForTest()[1]->continueEncoding(); @@ -637,7 +638,7 @@ TEST_F(FilterTest, ActiveStreamSendLocalReply) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -652,11 +653,11 @@ TEST_F(FilterTest, ActiveStreamSendLocalReply) { EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response& response, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame& response, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; - EXPECT_EQ(response.status().message(), "test_detail"); + EXPECT_EQ(dynamic_cast(&response)->status().message(), "test_detail"); buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); active_stream->sendLocalReply(Status(StatusCode::kUnknown, "test_detail"), [](Response&) {}); @@ -667,7 +668,7 @@ TEST_F(FilterTest, ActiveStreamCompleteDirectly) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -684,7 +685,7 @@ TEST_F(FilterTest, ActiveStreamCompleteDirectlyFromFilter) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -708,7 +709,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormally) { request->protocol_ = "protocol-value"; request->data_["request-key"] = "request-value"; - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -716,12 +717,73 @@ TEST_F(FilterTest, NewStreamAndReplyNormally) { EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response&, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame&, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); + })); + + EXPECT_CALL( + *factory_context_.access_log_manager_.file_, + write("host-value /path-value method-value protocol-value request-value response-value -")); + + EXPECT_CALL(factory_context_.drain_manager_, drainClose()).WillOnce(Return(false)); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); + + auto response = std::make_unique(); + response->data_["response-key"] = "response-value"; + + active_stream->onResponseStart(std::move(response)); +} + +TEST_F(FilterTest, NewStreamAndReplyNormallyWithMultipleFrames) { + auto mock_decoder_filter_0 = std::make_shared>(); + mock_decoder_filters_ = {{"mock_0", mock_decoder_filter_0}}; + + NiceMock mock_stream_frame_handler; + + EXPECT_CALL(*mock_decoder_filter_0, setDecoderFilterCallbacks(_)) + .WillOnce(Invoke([&mock_stream_frame_handler](DecoderFilterCallback& callbacks) { + callbacks.setRequestFramesHandler(mock_stream_frame_handler); })); + // The logger is used to test the log format. + initializeFilter(false, false, loggerFormFormat()); + + auto request = std::make_unique(); + request->host_ = "host-value"; + request->path_ = "/path-value"; + request->method_ = "method-value"; + request->protocol_ = "protocol-value"; + request->data_["request-key"] = "request-value"; + request->stream_frame_flags_ = FrameFlags(StreamFlags(), false); + + // The first frame is not the end stream and we will create a frame handler for it. + filter_->onDecodingSuccess(std::move(request)); + EXPECT_EQ(1, filter_->activeStreamsForTest().size()); + EXPECT_EQ(1, filter_->frameHandlersForTest().size()); + + // stream_frame_handler will be called twice to handle the two frames (except the first + // StreamRequest frame). + EXPECT_CALL(mock_stream_frame_handler, onStreamFrame(_)).Times(2); + + auto request_frame_1 = std::make_unique(); + request_frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(), false); + filter_->onDecodingSuccess(std::move(request_frame_1)); + EXPECT_EQ(1, filter_->activeStreamsForTest().size()); + EXPECT_EQ(1, filter_->frameHandlersForTest().size()); + + // When the last frame is the end stream, we will delete the frame handler. + auto request_frame_2 = std::make_unique(); + request_frame_2->stream_frame_flags_ = FrameFlags(StreamFlags(), true); + filter_->onDecodingSuccess(std::move(request_frame_2)); + EXPECT_EQ(1, filter_->activeStreamsForTest().size()); + EXPECT_EQ(0, filter_->frameHandlersForTest().size()); + + std::cout << "OK decoding" << std::endl; + + auto active_stream = filter_->activeStreamsForTest().begin()->get(); + EXPECT_CALL( *factory_context_.access_log_manager_.file_, write("host-value /path-value method-value protocol-value request-value response-value -")); @@ -729,10 +791,25 @@ TEST_F(FilterTest, NewStreamAndReplyNormally) { EXPECT_CALL(factory_context_.drain_manager_, drainClose()).WillOnce(Return(false)); EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)).Times(2); + EXPECT_CALL(*encoder_, encode(_, _)) + .Times(2) + .WillRepeatedly(Invoke([&](const StreamFrame& frame, ResponseEncoderCallback& callback) { + Buffer::OwnedImpl buffer; + buffer.add("test"); + callback.onEncodingSuccess(buffer, frame.frameFlags().endStream()); + })); + auto response = std::make_unique(); response->data_["response-key"] = "response-value"; + response->stream_frame_flags_ = FrameFlags(StreamFlags(), false); + + active_stream->onResponseStart(std::move(response)); - active_stream->upstreamResponse(std::move(response), ExtendedOptions()); + auto response_frame_1 = std::make_unique(); + response_frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(), true); + + active_stream->onResponseFrame(std::move(response_frame_1)); } TEST_F(FilterTest, NewStreamAndReplyNormallyWithDrainClose) { @@ -743,7 +820,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithDrainClose) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -751,10 +828,10 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithDrainClose) { EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response&, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame&, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); EXPECT_CALL(factory_context_.drain_manager_, drainClose()).WillOnce(Return(true)); @@ -762,7 +839,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithDrainClose) { EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); auto response = std::make_unique(); - active_stream->upstreamResponse(std::move(response), ExtendedOptions()); + active_stream->onResponseStart(std::move(response)); } TEST_F(FilterTest, NewStreamAndReplyNormallyWithStreamDrainClose) { @@ -773,7 +850,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithStreamDrainClose) { auto request = std::make_unique(); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -781,10 +858,10 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithStreamDrainClose) { EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response&, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame&, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); // The drain close of factory_context_.drain_manager_ is false, but the drain close of @@ -794,8 +871,8 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithStreamDrainClose) { EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); auto response = std::make_unique(); - active_stream->upstreamResponse(std::move(response), - ExtendedOptions({}, false, /*drain_close*/ true, false)); + response->stream_frame_flags_ = FrameFlags(StreamFlags(0, false, true, false), true); + active_stream->onResponseStart(std::move(response)); } TEST_F(FilterTest, NewStreamAndReplyNormallyWithTracing) { @@ -815,7 +892,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithTracing) { return span; })); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -826,17 +903,17 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithTracing) { EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response&, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame&, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); EXPECT_CALL(factory_context_.drain_manager_, drainClose()).WillOnce(Return(false)); EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); auto response = std::make_unique(); - active_stream->upstreamResponse(std::move(response), ExtendedOptions()); + active_stream->onResponseStart(std::move(response)); } TEST_F(FilterTest, BindUpstreamConnectionFailure) { @@ -846,8 +923,9 @@ TEST_F(FilterTest, BindUpstreamConnectionFailure) { initializeFilter(false, true); auto request = std::make_unique(); + request->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions(123, true, false, false)); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -874,11 +952,11 @@ TEST_F(FilterTest, BindUpstreamConnectionFailure) { EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(2); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response& response, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame& response, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; - EXPECT_EQ("test_detail", response.status().message()); + EXPECT_EQ("test_detail", dynamic_cast(&response)->status().message()); buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); EXPECT_CALL(*creator_, response(_, _)) @@ -907,8 +985,9 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessButCloseBeforeUpstreamResponse) initializeFilter(false, true); auto request = std::make_unique(); + request->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions(123, true, false, false)); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -940,11 +1019,11 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessButCloseBeforeUpstreamResponse) EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(2); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response& response, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame& response, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; - EXPECT_EQ("test_detail", response.status().message()); + EXPECT_EQ("test_detail", dynamic_cast(&response)->status().message()); buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); EXPECT_CALL(*creator_, response(_, _)) @@ -979,8 +1058,9 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessButDecodingFailure) { initializeFilter(false, true); auto request = std::make_unique(); + request->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions(123, true, false, false)); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -1009,11 +1089,11 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessButDecodingFailure) { EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(2); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response& response, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame& response, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; - EXPECT_EQ("test_detail", response.status().message()); + EXPECT_EQ("test_detail", dynamic_cast(&response)->status().message()); buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); EXPECT_CALL(*creator_, response(_, _)) @@ -1045,8 +1125,9 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndDecodingSuccess) { initializeFilter(false, true); auto request = std::make_unique(); + request->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions(123, true, false, false)); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -1073,16 +1154,22 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndDecodingSuccess) { EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response& response, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame& response, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; - EXPECT_EQ("response_2", response.status().message()); + EXPECT_EQ("response_2", dynamic_cast(&response)->status().message()); buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); - EXPECT_CALL(response_callback, onDecodingSuccess(_, _)) - .WillOnce(Invoke([&](ResponsePtr response, ExtendedOptions options) { - active_stream->upstreamResponse(std::move(response), options); + EXPECT_CALL(response_callback, onDecodingSuccess(_)) + .WillOnce(Invoke([&](StreamFramePtr response) { + StreamFramePtrHelper helper(std::move(response)); + + if (helper.typed_frame_ != nullptr) { + active_stream->onResponseStart(std::move(helper.typed_frame_)); + } else { + active_stream->onResponseFrame(std::move(helper.frame_)); + } })); EXPECT_CALL(upstream_callback, onBindSuccess(_, _)) @@ -1096,16 +1183,16 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndDecodingSuccess) { auto response_1 = std::make_unique(); response_1->status_ = Status(StatusCode::kUnknown, "response_1"); + response_1->stream_frame_flags_ = FrameFlags(StreamFlags(321, false, false, false), true); // This response will be ignored because the there is no related callback registered for it. - response_decoder_callback->onDecodingSuccess(std::move(response_1), - ExtendedOptions(321, false, false, false)); + response_decoder_callback->onDecodingSuccess(std::move(response_1)); auto response_2 = std::make_unique(); response_2->status_ = Status(StatusCode::kUnknown, "response_2"); + response_2->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - response_decoder_callback->onDecodingSuccess(std::move(response_2), - ExtendedOptions(123, false, false, false)); + response_decoder_callback->onDecodingSuccess(std::move(response_2)); } TEST_F(FilterTest, BindUpstreamConnectionSuccessAndMultipleDecodingSuccess) { @@ -1115,10 +1202,13 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndMultipleDecodingSuccess) { initializeFilter(false, true); auto request_1 = std::make_unique(); + request_1->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); + auto request_2 = std::make_unique(); + request_2->stream_frame_flags_ = FrameFlags(StreamFlags(321, false, false, false), true); - filter_->newDownstreamRequest(std::move(request_1), ExtendedOptions(123, true, false, false)); - filter_->newDownstreamRequest(std::move(request_2), ExtendedOptions(321, true, false, false)); + filter_->onDecodingSuccess(std::move(request_1)); + filter_->onDecodingSuccess(std::move(request_2)); EXPECT_EQ(2, filter_->activeStreamsForTest().size()); @@ -1153,22 +1243,36 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndMultipleDecodingSuccess) { EXPECT_CALL(*encoder_, encode(_, _)) .Times(2) - .WillRepeatedly(Invoke([&](const Response&, ResponseEncoderCallback& callback) { + .WillRepeatedly(Invoke([&](const StreamFrame&, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); - EXPECT_CALL(response_callback_1, onDecodingSuccess(_, _)) - .WillOnce(Invoke([&](ResponsePtr response, ExtendedOptions options) { - EXPECT_EQ(123, options.streamId().value()); - active_stream_1->upstreamResponse(std::move(response), options); + EXPECT_CALL(response_callback_1, onDecodingSuccess(_)) + .WillOnce(Invoke([&](StreamFramePtr response) { + EXPECT_EQ(123, response->frameFlags().streamFlags().streamId()); + + StreamFramePtrHelper helper(std::move(response)); + + if (helper.typed_frame_ != nullptr) { + active_stream_1->onResponseStart(std::move(helper.typed_frame_)); + } else { + active_stream_1->onResponseFrame(std::move(helper.frame_)); + } })); - EXPECT_CALL(response_callback_2, onDecodingSuccess(_, _)) - .WillOnce(Invoke([&](ResponsePtr response, ExtendedOptions options) { - EXPECT_EQ(321, options.streamId().value()); - active_stream_2->upstreamResponse(std::move(response), options); + EXPECT_CALL(response_callback_2, onDecodingSuccess(_)) + .WillOnce(Invoke([&](StreamFramePtr response) { + EXPECT_EQ(321, response->frameFlags().streamFlags().streamId()); + + StreamFramePtrHelper helper(std::move(response)); + + if (helper.typed_frame_ != nullptr) { + active_stream_2->onResponseStart(std::move(helper.typed_frame_)); + } else { + active_stream_2->onResponseFrame(std::move(helper.frame_)); + } })); EXPECT_CALL(upstream_callback_1, onBindSuccess(_, _)) @@ -1189,15 +1293,168 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndMultipleDecodingSuccess) { auto response_1 = std::make_unique(); response_1->status_ = Status(StatusCode::kUnknown, "response_1"); + response_1->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - response_decoder_callback->onDecodingSuccess(std::move(response_1), - ExtendedOptions(123, false, false, false)); + response_decoder_callback->onDecodingSuccess(std::move(response_1)); auto response_2 = std::make_unique(); response_2->status_ = Status(StatusCode::kUnknown, "response_2"); + response_2->stream_frame_flags_ = FrameFlags(StreamFlags(321, false, false, false), true); - response_decoder_callback->onDecodingSuccess(std::move(response_2), - ExtendedOptions(321, false, false, false)); + response_decoder_callback->onDecodingSuccess(std::move(response_2)); +} + +TEST_F(FilterTest, BindUpstreamConnectionSuccessAndMultipleDecodingSuccessAndWithMultipleFrames) { + auto mock_decoder_filter_0 = std::make_shared>(); + mock_decoder_filters_ = {{"mock_0", mock_decoder_filter_0}}; + + NiceMock mock_stream_frame_handler; + + EXPECT_CALL(*mock_decoder_filter_0, setDecoderFilterCallbacks(_)) + .Times(2) + .WillRepeatedly(Invoke([&mock_stream_frame_handler](DecoderFilterCallback& callbacks) { + callbacks.setRequestFramesHandler(mock_stream_frame_handler); + })); + + initializeFilter(false, true); + + auto request_1 = std::make_unique(); + request_1->stream_frame_flags_ = FrameFlags(StreamFlags(123), false); + + auto request_2 = std::make_unique(); + request_2->stream_frame_flags_ = FrameFlags(StreamFlags(321), false); + + filter_->onDecodingSuccess(std::move(request_1)); + filter_->onDecodingSuccess(std::move(request_2)); + + EXPECT_EQ(2, filter_->activeStreamsForTest().size()); + EXPECT_EQ(2, filter_->frameHandlersForTest().size()); + + auto request_1_frame_1 = std::make_unique(); + request_1_frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(123), false); + auto request_2_frame_1 = std::make_unique(); + request_2_frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(321), false); + auto request_1_frame_2 = std::make_unique(); + request_1_frame_2->stream_frame_flags_ = FrameFlags(StreamFlags(123), true); + auto request_2_frame_2 = std::make_unique(); + request_2_frame_2->stream_frame_flags_ = FrameFlags(StreamFlags(321), true); + + // stream_frame_handler will be called 4 times to handle the 4 frames (except the first + // StreamRequest frame) of two requests. + EXPECT_CALL(mock_stream_frame_handler, onStreamFrame(_)).Times(4); + filter_->onDecodingSuccess(std::move(request_1_frame_1)); + filter_->onDecodingSuccess(std::move(request_2_frame_1)); + filter_->onDecodingSuccess(std::move(request_1_frame_2)); + filter_->onDecodingSuccess(std::move(request_2_frame_2)); + + EXPECT_EQ(2, filter_->activeStreamsForTest().size()); + EXPECT_EQ(0, filter_->frameHandlersForTest().size()); + + auto active_stream_1 = (++filter_->activeStreamsForTest().begin())->get(); + auto active_stream_2 = (filter_->activeStreamsForTest().begin())->get(); + + auto response_decoder = std::make_unique>(); + auto raw_response_decoder = response_decoder.get(); + ResponseDecoderCallback* response_decoder_callback{}; + EXPECT_CALL(*codec_factory_, responseDecoder()) + .WillOnce(Return(ByMove(std::move(response_decoder)))); + EXPECT_CALL(*raw_response_decoder, setDecoderCallback(_)) + .WillOnce(Invoke( + [&](ResponseDecoderCallback& callback) { response_decoder_callback = &callback; })); + + NiceMock upstream_callback_1; + NiceMock upstream_callback_2; + + NiceMock response_callback_1; + NiceMock response_callback_2; + + filter_->bindUpstreamConn(Upstream::TcpPoolData([]() {}, &tcp_conn_pool_)); + + filter_->boundUpstreamConn()->registerUpstreamCallback(123, upstream_callback_1); + filter_->boundUpstreamConn()->registerUpstreamCallback(321, upstream_callback_2); + + EXPECT_CALL(factory_context_.drain_manager_, drainClose()).Times(2).WillRepeatedly(Return(false)); + // Both for the active streams. + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(2); + + EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)).Times(6); + EXPECT_CALL(*encoder_, encode(_, _)) + .Times(6) + .WillRepeatedly(Invoke([&](const StreamFrame& frame, ResponseEncoderCallback& callback) { + Buffer::OwnedImpl buffer; + buffer.add("test"); + callback.onEncodingSuccess(buffer, frame.frameFlags().endStream()); + })); + + EXPECT_CALL(response_callback_1, onDecodingSuccess(_)) + .Times(3) + .WillRepeatedly(Invoke([&](StreamFramePtr response) { + EXPECT_EQ(123, response->frameFlags().streamFlags().streamId()); + + StreamFramePtrHelper helper(std::move(response)); + + if (helper.typed_frame_ != nullptr) { + active_stream_1->onResponseStart(std::move(helper.typed_frame_)); + } else { + active_stream_1->onResponseFrame(std::move(helper.frame_)); + } + })); + + EXPECT_CALL(response_callback_2, onDecodingSuccess(_)) + .Times(3) + .WillRepeatedly(Invoke([&](StreamFramePtr response) { + EXPECT_EQ(321, response->frameFlags().streamFlags().streamId()); + + StreamFramePtrHelper helper(std::move(response)); + + if (helper.typed_frame_ != nullptr) { + active_stream_2->onResponseStart(std::move(helper.typed_frame_)); + } else { + active_stream_2->onResponseFrame(std::move(helper.frame_)); + } + })); + + EXPECT_CALL(upstream_callback_1, onBindSuccess(_, _)) + .WillOnce(Invoke([&](Network::ClientConnection& conn, + Upstream::HostDescriptionConstSharedPtr) { + EXPECT_EQ(&upstream_connection_, &conn); + filter_->boundUpstreamConn()->registerResponseCallback(123, response_callback_1); // NOLINT + })); + + EXPECT_CALL(upstream_callback_2, onBindSuccess(_, _)) + .WillOnce(Invoke([&](Network::ClientConnection& conn, + Upstream::HostDescriptionConstSharedPtr) { + EXPECT_EQ(&upstream_connection_, &conn); + filter_->boundUpstreamConn()->registerResponseCallback(321, response_callback_2); // NOLINT + })); + + tcp_conn_pool_.poolReady(upstream_connection_); + + auto response_1 = std::make_unique(); + response_1->status_ = Status(StatusCode::kUnknown, "response_1"); + response_1->stream_frame_flags_ = FrameFlags(StreamFlags(123), false); + auto response_2 = std::make_unique(); + response_2->status_ = Status(StatusCode::kUnknown, "response_2"); + response_2->stream_frame_flags_ = FrameFlags(StreamFlags(321), false); + + auto response_1_frame_1 = std::make_unique(); + response_1_frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(123), false); + + auto response_2_frame_1 = std::make_unique(); + response_2_frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(321), false); + + auto response_1_frame_2 = std::make_unique(); + response_1_frame_2->stream_frame_flags_ = FrameFlags(StreamFlags(123), true); + + auto response_2_frame_2 = std::make_unique(); + response_2_frame_2->stream_frame_flags_ = FrameFlags(StreamFlags(321), true); + + response_decoder_callback->onDecodingSuccess(std::move(response_1)); + response_decoder_callback->onDecodingSuccess(std::move(response_2)); + response_decoder_callback->onDecodingSuccess(std::move(response_1_frame_1)); + response_decoder_callback->onDecodingSuccess(std::move(response_2_frame_1)); + response_decoder_callback->onDecodingSuccess(std::move(response_1_frame_2)); + response_decoder_callback->onDecodingSuccess(std::move(response_2_frame_2)); } TEST_F(FilterTest, BindUpstreamConnectionSuccessButMultipleRequestHasSameStreamId) { @@ -1207,10 +1464,13 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessButMultipleRequestHasSameStreamI initializeFilter(false, true); auto request_1 = std::make_unique(); + request_1->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); + auto request_2 = std::make_unique(); + request_2->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - filter_->newDownstreamRequest(std::move(request_1), ExtendedOptions(123, true, false, false)); - filter_->newDownstreamRequest(std::move(request_2), ExtendedOptions(123, true, false, false)); + filter_->onDecodingSuccess(std::move(request_1)); + filter_->onDecodingSuccess(std::move(request_2)); EXPECT_EQ(2, filter_->activeStreamsForTest().size()); @@ -1249,8 +1509,9 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndWriteSomethinToConnection) { initializeFilter(false, true); auto request = std::make_unique(); + request->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions(123, true, false, false)); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); auto active_stream = filter_->activeStreamsForTest().begin()->get(); @@ -1287,11 +1548,11 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndWriteSomethinToConnection) { EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(2); EXPECT_CALL(*encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Response& response, ResponseEncoderCallback& callback) { + .WillOnce(Invoke([&](const StreamFrame& response, ResponseEncoderCallback& callback) { Buffer::OwnedImpl buffer; - EXPECT_EQ("test_detail", response.status().message()); + EXPECT_EQ("test_detail", dynamic_cast(&response)->status().message()); buffer.add("test"); - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); EXPECT_CALL(*creator_, response(_, _)) @@ -1327,8 +1588,9 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndWriteSomethinToConnection) { TEST_F(FilterTest, TestStats) { initializeFilter(false, true); auto request = std::make_unique(); + request->stream_frame_flags_ = FrameFlags(StreamFlags(123, false, false, false), true); - filter_->newDownstreamRequest(std::move(request), ExtendedOptions(123, true, false, false)); + filter_->onDecodingSuccess(std::move(request)); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); EXPECT_EQ(1, filter_config_->stats().request_.value()); EXPECT_EQ(1, filter_config_->stats().request_active_.value()); @@ -1336,7 +1598,7 @@ TEST_F(FilterTest, TestStats) { auto active_stream = filter_->activeStreamsForTest().begin()->get(); Buffer::OwnedImpl buffer; buffer.add("123"); - active_stream->onEncodingSuccess(buffer); + active_stream->onEncodingSuccess(buffer, true); EXPECT_EQ(1, filter_config_->stats().response_.value()); EXPECT_EQ(0, filter_config_->stats().request_active_.value()); } diff --git a/contrib/generic_proxy/filters/network/test/router/router_test.cc b/contrib/generic_proxy/filters/network/test/router/router_test.cc index 62384272689e..5a3999a356bd 100644 --- a/contrib/generic_proxy/filters/network/test/router/router_test.cc +++ b/contrib/generic_proxy/filters/network/test/router/router_test.cc @@ -49,21 +49,19 @@ class RouterFilterTest : public testing::TestWithParam { ON_CALL(mock_filter_callback_, streamInfo()).WillByDefault(ReturnRef(mock_stream_info_)); } - void setup(ExtendedOptions request_options = ExtendedOptions{}) { + void setup(FrameFlags frame_flags = FrameFlags{}) { auto parameter = GetParam(); protocol_options_ = ProtocolOptions{parameter.bind_upstream}; bound_already_ = parameter.bound_already; with_tracing_ = parameter.with_tracing; - request_options_ = request_options; - ON_CALL(mock_codec_factory_, protocolOptions()).WillByDefault(Return(protocol_options_)); - ON_CALL(mock_filter_callback_, requestOptions()).WillByDefault(Return(request_options_)); filter_ = std::make_shared(factory_context_); filter_->setDecoderFilterCallbacks(mock_filter_callback_); request_ = std::make_unique(); + request_->stream_frame_flags_ = frame_flags; } void cleanUp() { @@ -172,12 +170,13 @@ class RouterFilterTest : public testing::TestWithParam { void notifyPoolReady() { if (!protocol_options_.bindUpstreamConnection()) { - EXPECT_CALL(mock_upstream_connection_, write(_, _)); + EXPECT_CALL(mock_upstream_connection_, write(_, _)).Times(testing::AtLeast(1)); factory_context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.poolReady( mock_upstream_connection_); } else { ASSERT(!mock_filter_callback_.upstream_manager_.upstream_callbacks_.empty()); - EXPECT_CALL(mock_filter_callback_.upstream_manager_.upstream_conn_, write(_, _)); + EXPECT_CALL(mock_filter_callback_.upstream_manager_.upstream_conn_, write(_, _)) + .Times(testing::AtLeast(1)); mock_filter_callback_.upstream_manager_.callOnBindSuccess(0); } } @@ -193,7 +192,7 @@ class RouterFilterTest : public testing::TestWithParam { } } - void notifyDecodingSuccess(ResponsePtr&& response, ExtendedOptions response_options) { + void notifyDecodingSuccess(StreamFramePtr&& response) { if (!protocol_options_.bindUpstreamConnection()) { ASSERT(!filter_->upstreamRequestsForTest().empty()); @@ -202,7 +201,7 @@ class RouterFilterTest : public testing::TestWithParam { EXPECT_CALL(*mock_response_decoder_, decode(BufferStringEqual("test_1"))) .WillOnce(Invoke([&](Buffer::Instance& buffer) { buffer.drain(buffer.length()); - upstream_request->onDecodingSuccess(std::move(response), response_options); + upstream_request->onDecodingSuccess(std::move(response)); })); Buffer::OwnedImpl test_buffer; @@ -211,8 +210,7 @@ class RouterFilterTest : public testing::TestWithParam { upstream_request->upstream_manager_->onUpstreamData(test_buffer, false); } else { ASSERT(!mock_filter_callback_.upstream_manager_.response_callbacks_.empty()); - mock_filter_callback_.upstream_manager_.callOnDecodingSuccess(0, std::move(response), - response_options); + mock_filter_callback_.upstream_manager_.callOnDecodingSuccess(0, std::move(response)); } } @@ -329,7 +327,6 @@ class RouterFilterTest : public testing::TestWithParam { ProtocolOptions protocol_options_; bool bound_already_{}; - ExtendedOptions request_options_; std::unique_ptr request_; NiceMock tracing_config_; @@ -521,7 +518,7 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolFailureConnctionTimeout) { } TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndExpectNoResponse) { - setup(ExtendedOptions{{}, false, false, false}); + setup(FrameFlags(StreamFlags(0, true, false, false), true)); kickOffNewUpstreamRequest(); EXPECT_CALL(mock_filter_callback_, completeDirectly()).WillOnce(Invoke([this]() -> void { @@ -529,11 +526,11 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndExpectNoResponse) { })); EXPECT_CALL(*mock_request_encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Request&, RequestEncoderCallback& callback) -> void { + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { Buffer::OwnedImpl buffer; buffer.add("hello"); // Expect no response. - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); if (with_tracing_) { @@ -556,11 +553,11 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyButConnectionErrorBeforeRespons expectSetResponseCallback(upstream_request); EXPECT_CALL(*mock_request_encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Request&, RequestEncoderCallback& callback) -> void { + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { Buffer::OwnedImpl buffer; buffer.add("hello"); // Expect response. - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); notifyPoolReady(); @@ -586,11 +583,11 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyButConnectionTerminationBeforeR expectSetResponseCallback(upstream_request); EXPECT_CALL(*mock_request_encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Request&, RequestEncoderCallback& callback) -> void { + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { Buffer::OwnedImpl buffer; buffer.add("hello"); // Expect response. - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); notifyPoolReady(); @@ -616,11 +613,11 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyButStreamDestroyBeforeResponse) expectSetResponseCallback(upstream_request); EXPECT_CALL(*mock_request_encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Request&, RequestEncoderCallback& callback) -> void { + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { Buffer::OwnedImpl buffer; buffer.add("hello"); // Expect response. - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); notifyPoolReady(); @@ -643,11 +640,11 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponse) { expectSetResponseCallback(upstream_request); EXPECT_CALL(*mock_request_encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Request&, RequestEncoderCallback& callback) -> void { + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { Buffer::OwnedImpl buffer; buffer.add("hello"); // Expect response. - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); if (with_tracing_) { @@ -664,14 +661,90 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponse) { EXPECT_CALL(*child_span_, finishSpan()); } - EXPECT_CALL(mock_filter_callback_, upstreamResponse(_, _)) - .WillOnce(Invoke([this](ResponsePtr, ExtendedOptions) { - // When the response is sent to callback, the upstream request should be removed. - EXPECT_EQ(0, filter_->upstreamRequestsForTest().size()); + EXPECT_CALL(mock_filter_callback_, onResponseStart(_)).WillOnce(Invoke([this](ResponsePtr) { + // When the response is sent to callback, the upstream request should be removed. + EXPECT_EQ(0, filter_->upstreamRequestsForTest().size()); + })); + + auto response = std::make_unique(); + notifyDecodingSuccess(std::move(response)); +} + +TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponseWithMultipleFrames) { + // There are multiple frames in the request. + setup(FrameFlags(StreamFlags(0, false, false, true), /*end_stream*/ false)); + kickOffNewUpstreamRequest(); + + auto upstream_request = filter_->upstreamRequestsForTest().begin()->get(); + + expectSetResponseCallback(upstream_request); + + if (with_tracing_) { + // Inject tracing context. + EXPECT_CALL(*child_span_, injectContext(_, _)); + } + + auto frame_1 = std::make_unique(); + frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(0, false, false, true), false); + + // This only store the frame and does nothing else because the pool is not ready yet. + filter_->onStreamFrame(std::move(frame_1)); + + EXPECT_CALL(*mock_request_encoder_, encode(_, _)) + .Times(2) + .WillRepeatedly(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { + Buffer::OwnedImpl buffer; + buffer.add("hello"); + // Expect response. + callback.onEncodingSuccess(buffer, false); + })); + + // This will trigger two frames to be sent. + notifyPoolReady(); + EXPECT_NE(upstream_request->upstream_conn_, nullptr); + + EXPECT_CALL(*mock_request_encoder_, encode(_, _)) + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { + Buffer::OwnedImpl buffer; + buffer.add("hello"); + // Expect response. + callback.onEncodingSuccess(buffer, true); + })); + + // End stream is set to true by default. + auto frame_2 = std::make_unique(); + // This will trigger the last frame to be sent directly because connection is ready and other + // frames are already sent. + filter_->onStreamFrame(std::move(frame_2)); + + if (with_tracing_) { + EXPECT_CALL(*child_span_, setTag(_, _)).Times(testing::AnyNumber()); + EXPECT_CALL(*child_span_, finishSpan()); + } + + EXPECT_CALL(mock_filter_callback_, onResponseStart(_)); + EXPECT_CALL(mock_filter_callback_, onResponseFrame(_)) + .Times(2) + .WillRepeatedly(Invoke([this](StreamFramePtr frame) { + // When the entire response is sent to callback, the upstream request should be removed. + if (frame->frameFlags().endStream()) { + EXPECT_EQ(0, filter_->upstreamRequestsForTest().size()); + } else { + EXPECT_EQ(1, filter_->upstreamRequestsForTest().size()); + } })); auto response = std::make_unique(); - notifyDecodingSuccess(std::move(response), ExtendedOptions()); + response->stream_frame_flags_ = FrameFlags(StreamFlags(0, false, false, false), false); + notifyDecodingSuccess(std::move(response)); + + auto response_frame_1 = std::make_unique(); + response_frame_1->stream_frame_flags_ = FrameFlags(StreamFlags(0, false, false, false), false); + notifyDecodingSuccess(std::move(response_frame_1)); + + // End stream is set to true by default. + auto response_frame_2 = std::make_unique(); + notifyDecodingSuccess(std::move(response_frame_2)); } TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponseWithDrainCloseSetInResponse) { @@ -685,11 +758,11 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponseWithDrainCloseSetInR expectSetResponseCallback(upstream_request); EXPECT_CALL(*mock_request_encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Request&, RequestEncoderCallback& callback) -> void { + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { Buffer::OwnedImpl buffer; buffer.add("hello"); // Expect response. - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); if (with_tracing_) { @@ -701,16 +774,16 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponseWithDrainCloseSetInR EXPECT_NE(upstream_request->upstream_conn_, nullptr); - EXPECT_CALL(mock_filter_callback_, upstreamResponse(_, _)) - .WillOnce(Invoke([this](ResponsePtr, ExtendedOptions) { - // When the response is sent to callback, the upstream request should be removed. - EXPECT_EQ(0, filter_->upstreamRequestsForTest().size()); - })); + EXPECT_CALL(mock_filter_callback_, onResponseStart(_)).WillOnce(Invoke([this](ResponsePtr) { + // When the response is sent to callback, the upstream request should be removed. + EXPECT_EQ(0, filter_->upstreamRequestsForTest().size()); + })); EXPECT_CALL(mock_upstream_connection_, close(Network::ConnectionCloseType::FlushWrite)); auto response = std::make_unique(); - notifyDecodingSuccess(std::move(response), ExtendedOptions({}, false, true, false)); + response->stream_frame_flags_ = FrameFlags(StreamFlags(0, false, true, false), true); + notifyDecodingSuccess(std::move(response)); } TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponseDecodingFailure) { @@ -722,11 +795,11 @@ TEST_P(RouterFilterTest, UpstreamRequestPoolReadyAndResponseDecodingFailure) { expectSetResponseCallback(upstream_request); EXPECT_CALL(*mock_request_encoder_, encode(_, _)) - .WillOnce(Invoke([&](const Request&, RequestEncoderCallback& callback) -> void { + .WillOnce(Invoke([&](const StreamFrame&, RequestEncoderCallback& callback) -> void { Buffer::OwnedImpl buffer; buffer.add("hello"); // Expect response. - callback.onEncodingSuccess(buffer); + callback.onEncodingSuccess(buffer, true); })); notifyPoolReady(); diff --git a/contrib/golang/common/go/api/BUILD b/contrib/golang/common/go/api/BUILD index 2a9245ed571d..9d3d70db763e 100644 --- a/contrib/golang/common/go/api/BUILD +++ b/contrib/golang/common/go/api/BUILD @@ -10,7 +10,6 @@ go_library( srcs = [ "api.h", "capi.go", - "cgocheck.go", "filter.go", "logger.go", "type.go", diff --git a/contrib/golang/common/go/api/api.h b/contrib/golang/common/go/api/api.h index b9f73b7718fa..504d412d3e14 100644 --- a/contrib/golang/common/go/api/api.h +++ b/contrib/golang/common/go/api/api.h @@ -10,20 +10,20 @@ extern "C" { typedef struct { // NOLINT(modernize-use-using) const char* data; - unsigned long long int len; + uint64_t len; } Cstring; typedef struct { // NOLINT(modernize-use-using) Cstring plugin_name; - unsigned long long int configId; + uint64_t configId; int phase; } httpRequest; typedef struct { // NOLINT(modernize-use-using) - unsigned long long int plugin_name_ptr; - unsigned long long int plugin_name_len; - unsigned long long int config_ptr; - unsigned long long int config_len; + uint64_t plugin_name_ptr; + uint64_t plugin_name_len; + uint64_t config_ptr; + uint64_t config_len; int is_route_config; } httpConfig; @@ -52,45 +52,56 @@ typedef enum { // NOLINT(modernize-use-using) } CAPIStatus; CAPIStatus envoyGoFilterHttpContinue(void* r, int status); -CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text, - void* headers, long long int grpc_status, void* details); -CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details); - -CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key, void* value); +CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text_data, + int body_text_len, void* headers, int headers_num, + long long int grpc_status, void* details_data, + int details_len); +CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details_data, int details_len); + +CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key_data, int key_len, uint64_t* value_data, + int* value_len); CAPIStatus envoyGoFilterHttpCopyHeaders(void* r, void* strs, void* buf); -CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key, void* value, headerAction action); -CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key); +CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key_data, int key_len, void* value_data, + int value_len, headerAction action); +CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key_data, int key_len); -CAPIStatus envoyGoFilterHttpGetBuffer(void* r, unsigned long long int buffer, void* value); -CAPIStatus envoyGoFilterHttpDrainBuffer(void* r, unsigned long long int buffer, uint64_t length); -CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, unsigned long long int buffer, void* data, - int length, bufferAction action); +CAPIStatus envoyGoFilterHttpGetBuffer(void* r, uint64_t buffer, void* value); +CAPIStatus envoyGoFilterHttpDrainBuffer(void* r, uint64_t buffer, uint64_t length); +CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, uint64_t buffer, void* data, int length, + bufferAction action); CAPIStatus envoyGoFilterHttpCopyTrailers(void* r, void* strs, void* buf); -CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key, void* value, headerAction action); -CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key); +CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key_data, int key_len, void* value, + int value_len, headerAction action); +CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key_data, int key_len); -CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, void* value); -CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, void* value); +CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, uint64_t* value_data, int* value_len); +CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, uint64_t* value); -CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name, void* hand); -CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name, void* key, void* buf); +CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name_data, int name_len, + uint64_t* value_data, int* value_len); +CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name_data, int name_len, + void* key_data, int key_len, void* buf_data, + int buf_len); -void envoyGoFilterLog(uint32_t level, void* message); +void envoyGoFilterLog(uint32_t level, void* message_data, int message_len); uint32_t envoyGoFilterLogLevel(); void envoyGoFilterHttpFinalize(void* r, int reason); void envoyGoConfigHttpFinalize(void* c); -CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key, void* value, int state_type, +CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key_data, int key_len, + void* value_data, int value_len, int state_type, int life_span, int stream_sharing); -CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key, void* value); -CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key, void* value, int* rc); +CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len); +CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len, int* rc); -CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name, - void* metric_id); +CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name_data, + int name_len, uint32_t* metric_id); CAPIStatus envoyGoFilterHttpIncrementMetric(void* c, uint32_t metric_id, int64_t offset); -CAPIStatus envoyGoFilterHttpGetMetric(void* c, uint32_t metric_id, void* value); +CAPIStatus envoyGoFilterHttpGetMetric(void* c, uint32_t metric_id, uint64_t* value); CAPIStatus envoyGoFilterHttpRecordMetric(void* c, uint32_t metric_id, uint64_t value); // downstream @@ -99,7 +110,7 @@ CAPIStatus envoyGoFilterDownstreamWrite(void* f, void* buffer_ptr, int buffer_le void envoyGoFilterDownstreamFinalize(void* wrapper, int reason); CAPIStatus envoyGoFilterDownstreamInfo(void* wrapper, int t, void* ret); -void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, unsigned long long int conn_id); +void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, uint64_t conn_id); CAPIStatus envoyGoFilterUpstreamWrite(void* u, void* buffer_ptr, int buffer_len, int end_stream); CAPIStatus envoyGoFilterUpstreamClose(void* wrapper, int close_type); void envoyGoFilterUpstreamFinalize(void* wrapper, int reason); diff --git a/contrib/golang/common/go/api/capi.go b/contrib/golang/common/go/api/capi.go index 1e6d2c3e860b..77d9fdd602f1 100644 --- a/contrib/golang/common/go/api/capi.go +++ b/contrib/golang/common/go/api/capi.go @@ -27,10 +27,10 @@ type HttpCAPI interface { // when unhandled panics are detected. HttpSendPanicReply(r unsafe.Pointer, details string) // experience api, memory unsafe - HttpGetHeader(r unsafe.Pointer, key *string, value *string) + HttpGetHeader(r unsafe.Pointer, key string) string HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string - HttpSetHeader(r unsafe.Pointer, key *string, value *string, add bool) - HttpRemoveHeader(r unsafe.Pointer, key *string) + HttpSetHeader(r unsafe.Pointer, key string, value string, add bool) + HttpRemoveHeader(r unsafe.Pointer, key string) HttpGetBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) []byte HttpDrainBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) @@ -38,8 +38,8 @@ type HttpCAPI interface { HttpSetBytesBufferHelper(r unsafe.Pointer, bufferPtr uint64, value []byte, action BufferAction) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string - HttpSetTrailer(r unsafe.Pointer, key *string, value *string, add bool) - HttpRemoveTrailer(r unsafe.Pointer, key *string) + HttpSetTrailer(r unsafe.Pointer, key string, value string, add bool) + HttpRemoveTrailer(r unsafe.Pointer, key string) HttpGetStringValue(r unsafe.Pointer, id int) (string, bool) HttpGetIntegerValue(r unsafe.Pointer, id int) (uint64, bool) diff --git a/contrib/golang/common/go/api/cgocheck.go b/contrib/golang/common/go/api/cgocheck.go index 01c6f84c8c40..08245a877552 100644 --- a/contrib/golang/common/go/api/cgocheck.go +++ b/contrib/golang/common/go/api/cgocheck.go @@ -24,7 +24,6 @@ import ( func CgoCheckDisabled() bool { env := os.Getenv("GODEBUG") - // TODO: handle compile-time GODEBUG var after Go 1.21 is released if strings.Index(env, "cgocheck=0") != -1 { return true } diff --git a/contrib/golang/common/go/api/filter.go b/contrib/golang/common/go/api/filter.go index 6950cbb8f441..a6e4fc7f9dcc 100644 --- a/contrib/golang/common/go/api/filter.go +++ b/contrib/golang/common/go/api/filter.go @@ -162,7 +162,11 @@ type FilterCallbacks interface { // If the fetch succeeded, a string will be returned. // If the value is a timestamp, it is returned as a timestamp string like "2023-07-31T07:21:40.695646+00:00". // If the fetch failed (including the value is not found), an error will be returned. - // Currently, fetching requests/response attributes are mostly unsupported. + // + // The error can be one of: + // * ErrInternalFailure + // * ErrSerializationFailure (Currently, fetching attributes in List/Map type are unsupported) + // * ErrValueNotFound GetProperty(key string) (string, error) // TODO add more for filter callbacks } diff --git a/contrib/golang/common/go/api/logger.go b/contrib/golang/common/go/api/logger.go index 03dc81d9668a..c7f074140772 100644 --- a/contrib/golang/common/go/api/logger.go +++ b/contrib/golang/common/go/api/logger.go @@ -39,27 +39,27 @@ import ( // [2023-08-09 03:04:15.985][1390][critical][golang] [contrib/golang/common/log/cgo.cc:27] msg func LogTrace(message string) { - C.envoyGoFilterLog(C.uint32_t(Trace), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(Trace), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) } func LogDebug(message string) { - C.envoyGoFilterLog(C.uint32_t(Debug), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(Debug), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) } func LogInfo(message string) { - C.envoyGoFilterLog(C.uint32_t(Info), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(Info), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) } func LogWarn(message string) { - C.envoyGoFilterLog(C.uint32_t(Warn), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(Warn), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) } func LogError(message string) { - C.envoyGoFilterLog(C.uint32_t(Error), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(Error), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) } func LogCritical(message string) { - C.envoyGoFilterLog(C.uint32_t(Critical), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(Critical), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) } func LogTracef(format string, v ...any) { diff --git a/contrib/golang/common/go/api/type.go b/contrib/golang/common/go/api/type.go index 9a2ef9198822..e72cfe7b91a5 100644 --- a/contrib/golang/common/go/api/type.go +++ b/contrib/golang/common/go/api/type.go @@ -17,6 +17,8 @@ package api +import "errors" + // ****************** filter status start ******************// type StatusType int @@ -423,3 +425,13 @@ func (t ConnectionInfoType) String() string { } return "unknown" } + +// *************** errors start **************// +var ( + ErrInternalFailure = errors.New("internal failure") + ErrValueNotFound = errors.New("value not found") + // Failed to serialize the value when we fetch the value as string + ErrSerializationFailure = errors.New("serialization failure") +) + +// *************** errors end **************// diff --git a/contrib/golang/common/log/cgo.cc b/contrib/golang/common/log/cgo.cc index 97b26b9aeaf5..0aa799b3ea7f 100644 --- a/contrib/golang/common/log/cgo.cc +++ b/contrib/golang/common/log/cgo.cc @@ -42,22 +42,18 @@ uint32_t FilterLogger::level() const { return static_cast(ENVOY_LOGGER const FilterLogger& getFilterLogger() { CONSTRUCT_ON_FIRST_USE(FilterLogger); } -// The returned absl::string_view only refer to the GoString, won't copy the string content into -// C++, should not use it after the current cgo call returns. -absl::string_view referGoString(void* str) { - if (str == nullptr) { - return ""; - } - auto go_str = reinterpret_cast(str); - return {go_str->p, static_cast(go_str->n)}; +// The returned absl::string_view only refer to Go memory, +// should not use it after the current cgo call returns. +absl::string_view stringViewFromGoPointer(void* p, int len) { + return {static_cast(p), static_cast(len)}; } #ifdef __cplusplus extern "C" { #endif -void envoyGoFilterLog(uint32_t level, void* message) { - auto mesg = referGoString(message); +void envoyGoFilterLog(uint32_t level, void* message_data, int message_len) { + auto mesg = stringViewFromGoPointer(message_data, message_len); getFilterLogger().log(level, mesg); } diff --git a/contrib/golang/filters/http/source/cgo.cc b/contrib/golang/filters/http/source/cgo.cc index 4be18d0ba19f..f48803526093 100644 --- a/contrib/golang/filters/http/source/cgo.cc +++ b/contrib/golang/filters/http/source/cgo.cc @@ -12,24 +12,16 @@ namespace Golang { // thread. // -// Deep copy GoString into std::string, including the string content, +// Deep copy Go memory into std::string, // it's safe to use it after the current cgo call returns. -std::string copyGoString(void* str) { - if (str == nullptr) { - return ""; - } - auto go_str = reinterpret_cast(str); - return {go_str->p, static_cast(go_str->n)}; +std::string copyStringFromGoPointer(void* p, int len) { + return {static_cast(p), static_cast(len)}; } -// The returned absl::string_view only refer to the GoString, won't copy the string content into -// C++, should not use it after the current cgo call returns. -absl::string_view referGoString(void* str) { - if (str == nullptr) { - return ""; - } - auto go_str = reinterpret_cast(str); - return {go_str->p, static_cast(go_str->n)}; +// The returned absl::string_view only refer to Go memory, +// should not use it after the current cgo call returns. +absl::string_view stringViewFromGoPointer(void* p, int len) { + return {static_cast(p), static_cast(len)}; } absl::string_view stringViewFromGoSlice(void* slice) { @@ -40,18 +32,15 @@ absl::string_view stringViewFromGoSlice(void* slice) { return {static_cast(go_slice->data), static_cast(go_slice->len)}; } -std::vector stringsFromGoSlice(void* slice) { +std::vector stringsFromGoSlice(void* slice_data, int slice_len) { std::vector list; - if (slice == nullptr) { + if (slice_len == 0) { return list; } - auto go_slice = reinterpret_cast(slice); - auto str = reinterpret_cast(go_slice->data); - for (auto i = 0; i < go_slice->len; i += 2) { - auto key = std::string(static_cast(str->p), str->n); - str++; - auto value = std::string(static_cast(str->p), str->n); - str++; + auto strs = reinterpret_cast(slice_data); + for (auto i = 0; i < slice_len; i += 2) { + auto key = std::string(strs[i + 0]); + auto value = std::string(strs[i + 1]); list.push_back(key); list.push_back(value); } @@ -88,14 +77,15 @@ CAPIStatus envoyGoFilterHttpContinue(void* r, int status) { }); } -CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text, - void* headers, long long int grpc_status, - void* details) { +CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text_data, + int body_text_len, void* headers, int headers_num, + long long int grpc_status, void* details_data, + int details_len) { return envoyGoFilterHandlerWrapper( r, - [response_code, body_text, headers, grpc_status, - details](std::shared_ptr& filter) -> CAPIStatus { - auto header_values = stringsFromGoSlice(headers); + [response_code, body_text_data, body_text_len, headers, headers_num, grpc_status, + details_data, details_len](std::shared_ptr& filter) -> CAPIStatus { + auto header_values = stringsFromGoSlice(headers, headers_num); std::function modify_headers = [header_values](Http::ResponseHeaderMap& headers) -> void { for (size_t i = 0; i < header_values.size(); i += 2) { @@ -111,19 +101,20 @@ CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* bod // Deep clone the GoString into C++, since the GoString may be freed after the function // returns, while they may still be used in the callback. return filter->sendLocalReply(static_cast(response_code), - copyGoString(body_text), modify_headers, status, - copyGoString(details)); + copyStringFromGoPointer(body_text_data, body_text_len), + modify_headers, status, + copyStringFromGoPointer(details_data, details_len)); }); } // unsafe API, without copy memory from c to go. -CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key, void* value) { - return envoyGoFilterHandlerWrapper(r, - [key, value](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto go_value = reinterpret_cast(value); - return filter->getHeader(key_str, go_value); - }); +CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key_data, int key_len, uint64_t* value_data, + int* value_len) { + return envoyGoFilterHandlerWrapper( + r, [key_data, key_len, value_data, value_len](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->getHeader(key_str, value_data, value_len); + }); } CAPIStatus envoyGoFilterHttpCopyHeaders(void* r, void* strs, void* buf) { @@ -134,23 +125,27 @@ CAPIStatus envoyGoFilterHttpCopyHeaders(void* r, void* strs, void* buf) { }); } -CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key, void* value, headerAction act) { - return envoyGoFilterHandlerWrapper( - r, [key, value, act](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = referGoString(value); - return filter->setHeader(key_str, value_str, act); - }); +CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key_data, int key_len, void* value_data, + int value_len, headerAction act) { + return envoyGoFilterHandlerWrapper(r, + [key_data, key_len, value_data, value_len, + act](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = + stringViewFromGoPointer(value_data, value_len); + return filter->setHeader(key_str, value_str, act); + }); } -CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key) { - return envoyGoFilterHandlerWrapper(r, [key](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - return filter->removeHeader(key_str); - }); +CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key_data, int key_len) { + return envoyGoFilterHandlerWrapper( + r, [key_data, key_len](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->removeHeader(key_str); + }); } -CAPIStatus envoyGoFilterHttpGetBuffer(void* r, unsigned long long int buffer_ptr, void* data) { +CAPIStatus envoyGoFilterHttpGetBuffer(void* r, uint64_t buffer_ptr, void* data) { return envoyGoFilterHandlerWrapper( r, [buffer_ptr, data](std::shared_ptr& filter) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); @@ -158,8 +153,7 @@ CAPIStatus envoyGoFilterHttpGetBuffer(void* r, unsigned long long int buffer_ptr }); } -CAPIStatus envoyGoFilterHttpDrainBuffer(void* r, unsigned long long int buffer_ptr, - uint64_t length) { +CAPIStatus envoyGoFilterHttpDrainBuffer(void* r, uint64_t buffer_ptr, uint64_t length) { return envoyGoFilterHandlerWrapper( r, [buffer_ptr, length](std::shared_ptr& filter) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); @@ -167,12 +161,12 @@ CAPIStatus envoyGoFilterHttpDrainBuffer(void* r, unsigned long long int buffer_p }); } -CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, unsigned long long int buffer_ptr, void* data, - int length, bufferAction action) { +CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, uint64_t buffer_ptr, void* data, int length, + bufferAction action) { return envoyGoFilterHandlerWrapper( r, [buffer_ptr, data, length, action](std::shared_ptr& filter) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); - auto value = absl::string_view(reinterpret_cast(data), length); + auto value = stringViewFromGoPointer(data, length); return filter->setBufferHelper(buffer, value, action); }); } @@ -185,54 +179,62 @@ CAPIStatus envoyGoFilterHttpCopyTrailers(void* r, void* strs, void* buf) { }); } -CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key, void* value, headerAction act) { - return envoyGoFilterHandlerWrapper( - r, [key, value, act](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = referGoString(value); - return filter->setTrailer(key_str, value_str, act); - }); +CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key_data, int key_len, void* value_data, + int value_len, headerAction act) { + return envoyGoFilterHandlerWrapper(r, + [key_data, key_len, value_data, value_len, + act](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = + stringViewFromGoPointer(value_data, value_len); + return filter->setTrailer(key_str, value_str, act); + }); } -CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key) { - return envoyGoFilterHandlerWrapper(r, [key](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - return filter->removeTrailer(key_str); - }); +CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key_data, int key_len) { + return envoyGoFilterHandlerWrapper( + r, [key_data, key_len](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->removeTrailer(key_str); + }); } -CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, void* value) { - return envoyGoFilterHandlerWrapper(r, [id, value](std::shared_ptr& filter) -> CAPIStatus { - auto value_str = reinterpret_cast(value); - return filter->getStringValue(id, value_str); - }); +CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, uint64_t* value_data, int* value_len) { + return envoyGoFilterHandlerWrapper( + r, [id, value_data, value_len](std::shared_ptr& filter) -> CAPIStatus { + return filter->getStringValue(id, value_data, value_len); + }); } -CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, void* value) { +CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, uint64_t* value) { return envoyGoFilterHandlerWrapper(r, [id, value](std::shared_ptr& filter) -> CAPIStatus { - auto value_int = reinterpret_cast(value); - return filter->getIntegerValue(id, value_int); + return filter->getIntegerValue(id, value); }); } -CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name, void* buf) { - return envoyGoFilterHandlerWrapper(r, [name, buf](std::shared_ptr& filter) -> CAPIStatus { - auto name_str = copyGoString(name); - auto buf_slice = reinterpret_cast(buf); - return filter->getDynamicMetadata(name_str, buf_slice); - }); -} - -CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name, void* key, void* buf) { +CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name_data, int name_len, + uint64_t* buf_data, int* buf_len) { return envoyGoFilterHandlerWrapper( - r, [name, key, buf](std::shared_ptr& filter) -> CAPIStatus { - auto name_str = copyGoString(name); - auto key_str = copyGoString(key); - auto buf_str = stringViewFromGoSlice(buf); - return filter->setDynamicMetadata(name_str, key_str, buf_str); + r, [name_data, name_len, buf_data, buf_len](std::shared_ptr& filter) -> CAPIStatus { + auto name_str = copyStringFromGoPointer(name_data, name_len); + return filter->getDynamicMetadata(name_str, buf_data, buf_len); }); } +CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name_data, int name_len, + void* key_data, int key_len, void* buf_data, + int buf_len) { + return envoyGoFilterHandlerWrapper(r, + [name_data, name_len, key_data, key_len, buf_data, + buf_len](std::shared_ptr& filter) -> CAPIStatus { + auto name_str = copyStringFromGoPointer(name_data, name_len); + auto key_str = copyStringFromGoPointer(key_data, key_len); + auto buf_str = stringViewFromGoPointer(buf_data, buf_len); + return filter->setDynamicMetadata(name_str, key_str, + buf_str); + }); +} + void envoyGoFilterHttpFinalize(void* r, int reason) { UNREFERENCED_PARAMETER(reason); // req is used by go, so need to use raw memory and then it is safe to release at the gc finalize @@ -248,52 +250,57 @@ void envoyGoConfigHttpFinalize(void* c) { delete config; } -CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details) { - return envoyGoFilterHandlerWrapper(r, [details](std::shared_ptr& filter) -> CAPIStatus { - // Since this is only used for logs we don't need to deep copy. - return filter->sendPanicReply(referGoString(details)); - }); +CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details_data, int details_len) { + return envoyGoFilterHandlerWrapper( + r, [details_data, details_len](std::shared_ptr& filter) -> CAPIStatus { + // Since this is only used for logs we don't need to deep copy. + auto details = stringViewFromGoPointer(details_data, details_len); + return filter->sendPanicReply(details); + }); } -CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key, void* value, int state_type, +CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key_data, int key_len, + void* value_data, int value_len, int state_type, int life_span, int stream_sharing) { - return envoyGoFilterHandlerWrapper(r, - [key, value, state_type, life_span, stream_sharing]( - std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = referGoString(value); - return filter->setStringFilterState(key_str, value_str, - state_type, life_span, - stream_sharing); - }); -} - -CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key, void* value) { - return envoyGoFilterHandlerWrapper(r, - [key, value](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = reinterpret_cast(value); - return filter->getStringFilterState(key_str, value_str); - }); + return envoyGoFilterHandlerWrapper( + r, + [key_data, key_len, value_data, value_len, state_type, life_span, + stream_sharing](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = stringViewFromGoPointer(value_data, value_len); + return filter->setStringFilterState(key_str, value_str, state_type, life_span, + stream_sharing); + }); } -CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key, void* value, int* rc) { +CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len) { return envoyGoFilterHandlerWrapper( - r, [key, value, rc](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = referGoString(key); - auto value_str = reinterpret_cast(value); - return filter->getStringProperty(key_str, value_str, rc); + r, [key_data, key_len, value_data, value_len](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->getStringFilterState(key_str, value_data, value_len); }); } -CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name, - void* metric_id) { +CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key_data, int key_len, + uint64_t* value_data, int* value_len, int* rc) { + return envoyGoFilterHandlerWrapper(r, + [key_data, key_len, value_data, value_len, + rc](std::shared_ptr& filter) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + return filter->getStringProperty(key_str, value_data, + value_len, rc); + }); +} + +CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name_data, + int name_len, uint32_t* metric_id) { return envoyGoConfigHandlerWrapper( c, - [metric_type, name, metric_id](std::shared_ptr& filter_config) -> CAPIStatus { - auto name_str = referGoString(name); - auto metric_id_int = reinterpret_cast(metric_id); - return filter_config->defineMetric(metric_type, name_str, metric_id_int); + [metric_type, name_data, name_len, + metric_id](std::shared_ptr& filter_config) -> CAPIStatus { + auto name_str = stringViewFromGoPointer(name_data, name_len); + return filter_config->defineMetric(metric_type, name_str, metric_id); }); } @@ -304,11 +311,10 @@ CAPIStatus envoyGoFilterHttpIncrementMetric(void* c, uint32_t metric_id, int64_t }); } -CAPIStatus envoyGoFilterHttpGetMetric(void* c, uint32_t metric_id, void* value) { +CAPIStatus envoyGoFilterHttpGetMetric(void* c, uint32_t metric_id, uint64_t* value) { return envoyGoConfigHandlerWrapper( c, [metric_id, value](std::shared_ptr& filter_config) -> CAPIStatus { - auto value_int = reinterpret_cast(value); - return filter_config->getMetric(metric_id, value_int); + return filter_config->getMetric(metric_id, value); }); } diff --git a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go index f310b77e5be8..f624d9e41307 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go +++ b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go @@ -33,7 +33,6 @@ package http import "C" import ( "errors" - "reflect" "runtime" "strings" "sync/atomic" @@ -93,15 +92,22 @@ func capiStatusToStr(status C.CAPIStatus) string { return errNotInGo case C.CAPIInvalidPhase: return errInvalidPhase + } + + return "unknown status" +} + +func capiStatusToErr(status C.CAPIStatus) error { + switch status { case C.CAPIValueNotFound: - return errValueNotFound + return api.ErrValueNotFound case C.CAPIInternalFailure: - return errInternalFailure + return api.ErrInternalFailure case C.CAPISerializationFailure: - return errSerializationFailure + return api.ErrSerializationFailure } - return "unknown status" + return errors.New("unknown status") } func (c *httpCApiImpl) HttpContinue(r unsafe.Pointer, status uint64) { @@ -111,24 +117,38 @@ func (c *httpCApiImpl) HttpContinue(r unsafe.Pointer, status uint64) { // Only may panic with errRequestFinished, errFilterDestroyed or errNotInGo, // won't panic with errInvalidPhase and others, otherwise will cause deadloop, see RecoverPanic for the details. -func (c *httpCApiImpl) HttpSendLocalReply(r unsafe.Pointer, response_code int, body_text string, headers map[string]string, grpc_status int64, details string) { +func (c *httpCApiImpl) HttpSendLocalReply(r unsafe.Pointer, responseCode int, bodyText string, headers map[string]string, grpcStatus int64, details string) { hLen := len(headers) - strs := make([]string, 0, hLen) + strs := make([]*C.char, 0, hLen*2) + defer func() { + for _, s := range strs { + C.free(unsafe.Pointer(s)) + } + }() + // TODO: use runtime.Pinner after go1.22 release for better performance. for k, v := range headers { - strs = append(strs, k, v) + keyStr := C.CString(k) + valueStr := C.CString(v) + strs = append(strs, keyStr, valueStr) } - res := C.envoyGoFilterHttpSendLocalReply(r, C.int(response_code), unsafe.Pointer(&body_text), unsafe.Pointer(&strs), C.longlong(grpc_status), unsafe.Pointer(&details)) + res := C.envoyGoFilterHttpSendLocalReply(r, C.int(responseCode), + unsafe.Pointer(unsafe.StringData(bodyText)), C.int(len(bodyText)), + unsafe.Pointer(unsafe.SliceData(strs)), C.int(len(strs)), + C.longlong(grpcStatus), unsafe.Pointer(unsafe.StringData(details)), C.int(len(details))) handleCApiStatus(res) } func (c *httpCApiImpl) HttpSendPanicReply(r unsafe.Pointer, details string) { - res := C.envoyGoFilterHttpSendPanicReply(r, unsafe.Pointer(&details)) + res := C.envoyGoFilterHttpSendPanicReply(r, unsafe.Pointer(unsafe.StringData(details)), C.int(len(details))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetHeader(r unsafe.Pointer, key *string, value *string) { - res := C.envoyGoFilterHttpGetHeader(r, unsafe.Pointer(key), unsafe.Pointer(value)) +func (c *httpCApiImpl) HttpGetHeader(r unsafe.Pointer, key string) string { + var valueData C.uint64_t + var valueLen C.int + res := C.envoyGoFilterHttpGetHeader(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen) handleCApiStatus(res) + return unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) } func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string { @@ -146,10 +166,7 @@ func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint6 // we have to make sure the all strings is not using before reusing, // but strings may be alive beyond the request life. buf := make([]byte, bytes) - sHeader := (*reflect.SliceHeader)(unsafe.Pointer(&strs)) - bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) - - res := C.envoyGoFilterHttpCopyHeaders(r, unsafe.Pointer(sHeader.Data), unsafe.Pointer(bHeader.Data)) + res := C.envoyGoFilterHttpCopyHeaders(r, unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) m := make(map[string][]string, num) @@ -167,43 +184,41 @@ func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint6 return m } -func (c *httpCApiImpl) HttpSetHeader(r unsafe.Pointer, key *string, value *string, add bool) { +func (c *httpCApiImpl) HttpSetHeader(r unsafe.Pointer, key string, value string, add bool) { var act C.headerAction if add { act = C.HeaderAdd } else { act = C.HeaderSet } - res := C.envoyGoFilterHttpSetHeaderHelper(r, unsafe.Pointer(key), unsafe.Pointer(value), act) + res := C.envoyGoFilterHttpSetHeaderHelper(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpRemoveHeader(r unsafe.Pointer, key *string) { - res := C.envoyGoFilterHttpRemoveHeader(r, unsafe.Pointer(key)) +func (c *httpCApiImpl) HttpRemoveHeader(r unsafe.Pointer, key string) { + res := C.envoyGoFilterHttpRemoveHeader(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) handleCApiStatus(res) } func (c *httpCApiImpl) HttpGetBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) []byte { buf := make([]byte, length) - bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) - res := C.envoyGoFilterHttpGetBuffer(r, C.ulonglong(bufferPtr), unsafe.Pointer(bHeader.Data)) + res := C.envoyGoFilterHttpGetBuffer(r, C.uint64_t(bufferPtr), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) - return buf + return unsafe.Slice(unsafe.SliceData(buf), length) } func (c *httpCApiImpl) HttpDrainBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) { - res := C.envoyGoFilterHttpDrainBuffer(r, C.ulonglong(bufferPtr), C.uint64_t(length)) + res := C.envoyGoFilterHttpDrainBuffer(r, C.uint64_t(bufferPtr), C.uint64_t(length)) handleCApiStatus(res) } func (c *httpCApiImpl) HttpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, value string, action api.BufferAction) { - sHeader := (*reflect.StringHeader)(unsafe.Pointer(&value)) - c.httpSetBufferHelper(r, bufferPtr, unsafe.Pointer(sHeader.Data), C.int(sHeader.Len), action) + c.httpSetBufferHelper(r, bufferPtr, unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), action) } func (c *httpCApiImpl) HttpSetBytesBufferHelper(r unsafe.Pointer, bufferPtr uint64, value []byte, action api.BufferAction) { - bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&value)) - c.httpSetBufferHelper(r, bufferPtr, unsafe.Pointer(bHeader.Data), C.int(bHeader.Len), action) + c.httpSetBufferHelper(r, bufferPtr, unsafe.Pointer(unsafe.SliceData(value)), C.int(len(value)), action) } func (c *httpCApiImpl) httpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, data unsafe.Pointer, length C.int, action api.BufferAction) { @@ -216,7 +231,7 @@ func (c *httpCApiImpl) httpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, d case api.PrependBuffer: act = C.Prepend } - res := C.envoyGoFilterHttpSetBufferHelper(r, C.ulonglong(bufferPtr), data, length, act) + res := C.envoyGoFilterHttpSetBufferHelper(r, C.uint64_t(bufferPtr), data, length, act) handleCApiStatus(res) } @@ -235,10 +250,7 @@ func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint // we have to make sure the all strings is not using before reusing, // but strings may be alive beyond the request life. buf := make([]byte, bytes) - sHeader := (*reflect.SliceHeader)(unsafe.Pointer(&strs)) - bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) - - res := C.envoyGoFilterHttpCopyTrailers(r, unsafe.Pointer(sHeader.Data), unsafe.Pointer(bHeader.Data)) + res := C.envoyGoFilterHttpCopyTrailers(r, unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) m := make(map[string][]string, num) @@ -252,58 +264,66 @@ func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint m[key] = append(v, value) } } + runtime.KeepAlive(buf) return m } -func (c *httpCApiImpl) HttpSetTrailer(r unsafe.Pointer, key *string, value *string, add bool) { +func (c *httpCApiImpl) HttpSetTrailer(r unsafe.Pointer, key string, value string, add bool) { var act C.headerAction if add { act = C.HeaderAdd } else { act = C.HeaderSet } - res := C.envoyGoFilterHttpSetTrailer(r, unsafe.Pointer(key), unsafe.Pointer(value), act) + res := C.envoyGoFilterHttpSetTrailer(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpRemoveTrailer(r unsafe.Pointer, key *string) { - res := C.envoyGoFilterHttpRemoveTrailer(r, unsafe.Pointer(key)) +func (c *httpCApiImpl) HttpRemoveTrailer(r unsafe.Pointer, key string) { + res := C.envoyGoFilterHttpRemoveTrailer(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) handleCApiStatus(res) } func (c *httpCApiImpl) HttpGetStringValue(rr unsafe.Pointer, id int) (string, bool) { r := (*httpRequest)(rr) - var value string // add a lock to protect filter->req_->strValue field in the Envoy side, from being writing concurrency, // since there might be multiple concurrency goroutines invoking this API on the Go side. r.mutex.Lock() defer r.mutex.Unlock() - res := C.envoyGoFilterHttpGetStringValue(unsafe.Pointer(r.req), C.int(id), unsafe.Pointer(&value)) + + var valueData C.uint64_t + var valueLen C.int + res := C.envoyGoFilterHttpGetStringValue(unsafe.Pointer(r.req), C.int(id), &valueData, &valueLen) if res == C.CAPIValueNotFound { return "", false } handleCApiStatus(res) + value := unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) // copy the memory from c to Go. return strings.Clone(value), true } func (c *httpCApiImpl) HttpGetIntegerValue(r unsafe.Pointer, id int) (uint64, bool) { - var value uint64 - res := C.envoyGoFilterHttpGetIntegerValue(r, C.int(id), unsafe.Pointer(&value)) + var value C.uint64_t + res := C.envoyGoFilterHttpGetIntegerValue(r, C.int(id), &value) if res == C.CAPIValueNotFound { return 0, false } handleCApiStatus(res) - return value, true + return uint64(value), true } func (c *httpCApiImpl) HttpGetDynamicMetadata(rr unsafe.Pointer, filterName string) map[string]interface{} { r := (*httpRequest)(rr) - var buf []byte r.mutex.Lock() defer r.mutex.Unlock() r.sema.Add(1) - res := C.envoyGoFilterHttpGetDynamicMetadata(unsafe.Pointer(r.req), unsafe.Pointer(&filterName), unsafe.Pointer(&buf)) + + var valueData C.uint64_t + var valueLen C.int + res := C.envoyGoFilterHttpGetDynamicMetadata(unsafe.Pointer(r.req), + unsafe.Pointer(unsafe.StringData(filterName)), C.int(len(filterName)), &valueData, &valueLen) if res == C.CAPIYield { atomic.AddInt32(&r.waitingOnEnvoy, 1) r.sema.Wait() @@ -311,6 +331,7 @@ func (c *httpCApiImpl) HttpGetDynamicMetadata(rr unsafe.Pointer, filterName stri r.sema.Done() handleCApiStatus(res) } + buf := unsafe.Slice((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) // copy the memory from c to Go. var meta structpb.Struct proto.Unmarshal(buf, &meta) @@ -326,12 +347,15 @@ func (c *httpCApiImpl) HttpSetDynamicMetadata(r unsafe.Pointer, filterName strin if err != nil { panic(err) } - res := C.envoyGoFilterHttpSetDynamicMetadata(r, unsafe.Pointer(&filterName), unsafe.Pointer(&key), unsafe.Pointer(&buf)) + res := C.envoyGoFilterHttpSetDynamicMetadata(r, + unsafe.Pointer(unsafe.StringData(filterName)), C.int(len(filterName)), + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.SliceData(buf)), C.int(len(buf))) handleCApiStatus(res) } func (c *httpCApiImpl) HttpLog(level api.LogType, message string) { - C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) } func (c *httpCApiImpl) HttpLogLevel() api.LogType { @@ -354,17 +378,22 @@ func SetHttpCAPI(api api.HttpCAPI) { } func (c *httpCApiImpl) HttpSetStringFilterState(r unsafe.Pointer, key string, value string, stateType api.StateType, lifeSpan api.LifeSpan, streamSharing api.StreamSharing) { - res := C.envoyGoFilterHttpSetStringFilterState(r, unsafe.Pointer(&key), unsafe.Pointer(&value), C.int(stateType), C.int(lifeSpan), C.int(streamSharing)) + res := C.envoyGoFilterHttpSetStringFilterState(r, + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), + C.int(stateType), C.int(lifeSpan), C.int(streamSharing)) handleCApiStatus(res) } func (c *httpCApiImpl) HttpGetStringFilterState(rr unsafe.Pointer, key string) string { r := (*httpRequest)(rr) - var value string + var valueData C.uint64_t + var valueLen C.int r.mutex.Lock() defer r.mutex.Unlock() r.sema.Add(1) - res := C.envoyGoFilterHttpGetStringFilterState(unsafe.Pointer(r.req), unsafe.Pointer(&key), unsafe.Pointer(&value)) + res := C.envoyGoFilterHttpGetStringFilterState(unsafe.Pointer(r.req), + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen) if res == C.CAPIYield { atomic.AddInt32(&r.waitingOnEnvoy, 1) r.sema.Wait() @@ -373,18 +402,20 @@ func (c *httpCApiImpl) HttpGetStringFilterState(rr unsafe.Pointer, key string) s handleCApiStatus(res) } + value := unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) return strings.Clone(value) } func (c *httpCApiImpl) HttpGetStringProperty(rr unsafe.Pointer, key string) (string, error) { r := (*httpRequest)(rr) - var value string - var rc int + var valueData C.uint64_t + var valueLen C.int + var rc C.int r.mutex.Lock() defer r.mutex.Unlock() r.sema.Add(1) - res := C.envoyGoFilterHttpGetStringProperty(unsafe.Pointer(r.req), unsafe.Pointer(&key), - unsafe.Pointer(&value), (*C.int)(unsafe.Pointer(&rc))) + res := C.envoyGoFilterHttpGetStringProperty(unsafe.Pointer(r.req), + unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen, &rc) if res == C.CAPIYield { atomic.AddInt32(&r.waitingOnEnvoy, 1) r.sema.Wait() @@ -395,18 +426,18 @@ func (c *httpCApiImpl) HttpGetStringProperty(rr unsafe.Pointer, key string) (str } if res == C.CAPIOK { + value := unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) return strings.Clone(value), nil } - return "", errors.New(capiStatusToStr(res)) + return "", capiStatusToErr(res) } func (c *httpCApiImpl) HttpDefineMetric(cfg unsafe.Pointer, metricType api.MetricType, name string) uint32 { - var value uint32 - - res := C.envoyGoFilterHttpDefineMetric(unsafe.Pointer(cfg), C.uint32_t(metricType), unsafe.Pointer(&name), unsafe.Pointer(&value)) + var value C.uint32_t + res := C.envoyGoFilterHttpDefineMetric(unsafe.Pointer(cfg), C.uint32_t(metricType), unsafe.Pointer(unsafe.StringData(name)), C.int(len(name)), &value) handleCApiStatus(res) - return value + return uint32(value) } func (c *httpCApiImpl) HttpIncrementMetric(cc unsafe.Pointer, metricId uint32, offset int64) { @@ -417,10 +448,10 @@ func (c *httpCApiImpl) HttpIncrementMetric(cc unsafe.Pointer, metricId uint32, o func (c *httpCApiImpl) HttpGetMetric(cc unsafe.Pointer, metricId uint32) uint64 { cfg := (*httpConfig)(cc) - var value uint64 - res := C.envoyGoFilterHttpGetMetric(unsafe.Pointer(cfg.config), C.uint32_t(metricId), unsafe.Pointer(&value)) + var value C.uint64_t + res := C.envoyGoFilterHttpGetMetric(unsafe.Pointer(cfg.config), C.uint32_t(metricId), &value) handleCApiStatus(res) - return value + return uint64(value) } func (c *httpCApiImpl) HttpRecordMetric(cc unsafe.Pointer, metricId uint32, value uint64) { diff --git a/contrib/golang/filters/http/source/go/pkg/http/config.go b/contrib/golang/filters/http/source/go/pkg/http/config.go index b32b7fe14751..2d04e3c717fb 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/config.go +++ b/contrib/golang/filters/http/source/go/pkg/http/config.go @@ -67,11 +67,6 @@ func createConfig(c *C.httpConfig) *httpConfig { //export envoyGoFilterNewHttpPluginConfig func envoyGoFilterNewHttpPluginConfig(c *C.httpConfig) uint64 { - if !api.CgoCheckDisabled() { - cAPI.HttpLog(api.Error, "The Envoy Golang filter requires the `GODEBUG=cgocheck=0` environment variable set.") - return 0 - } - buf := utils.BytesToSlice(uint64(c.config_ptr), uint64(c.config_len)) var any anypb.Any proto.Unmarshal(buf, &any) diff --git a/contrib/golang/filters/http/source/go/pkg/http/type.go b/contrib/golang/filters/http/source/go/pkg/http/type.go index 12128b6bab71..e765ce727140 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/type.go +++ b/contrib/golang/filters/http/source/go/pkg/http/type.go @@ -31,10 +31,6 @@ const ( errFilterDestroyed = "golang filter has been destroyed" errNotInGo = "not proccessing Go" errInvalidPhase = "invalid phase, maybe headers/buffer already continued" - - errInternalFailure = "internal failure" - errValueNotFound = "value not found" - errSerializationFailure = "serialization failure" ) // api.HeaderMap @@ -57,9 +53,7 @@ func (h *requestOrResponseHeaderMapImpl) initHeaders() { } func (h *requestOrResponseHeaderMapImpl) GetRaw(key string) string { - var value string - cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), &key, &value) - return value + return cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), key) } func (h *requestOrResponseHeaderMapImpl) Get(key string) (string, bool) { @@ -94,7 +88,7 @@ func (h *requestOrResponseHeaderMapImpl) Set(key, value string) { if h.headers != nil { h.headers[key] = []string{value} } - cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), &key, &value, false) + cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), key, value, false) } func (h *requestOrResponseHeaderMapImpl) Add(key, value string) { @@ -108,7 +102,7 @@ func (h *requestOrResponseHeaderMapImpl) Add(key, value string) { h.headers[key] = []string{value} } } - cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), &key, &value, true) + cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), key, value, true) } func (h *requestOrResponseHeaderMapImpl) Del(key string) { @@ -119,7 +113,7 @@ func (h *requestOrResponseHeaderMapImpl) Del(key string) { defer h.mutex.Unlock() h.initHeaders() delete(h.headers, key) - cAPI.HttpRemoveHeader(unsafe.Pointer(h.request.req), &key) + cAPI.HttpRemoveHeader(unsafe.Pointer(h.request.req), key) } func (h *requestOrResponseHeaderMapImpl) Range(f func(key, value string) bool) { @@ -212,9 +206,7 @@ func (h *requestOrResponseTrailerMapImpl) initTrailers() { } func (h *requestOrResponseTrailerMapImpl) GetRaw(key string) string { - var value string - cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), &key, &value) - return value + return cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), key) } func (h *requestOrResponseTrailerMapImpl) Get(key string) (string, bool) { @@ -250,7 +242,7 @@ func (h *requestOrResponseTrailerMapImpl) Set(key, value string) { h.headers[key] = []string{value} } - cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), &key, &value, false) + cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), key, value, false) } func (h *requestOrResponseTrailerMapImpl) Add(key, value string) { @@ -264,7 +256,7 @@ func (h *requestOrResponseTrailerMapImpl) Add(key, value string) { h.headers[key] = []string{value} } } - cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), &key, &value, true) + cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), key, value, true) } func (h *requestOrResponseTrailerMapImpl) Del(key string) { @@ -272,7 +264,7 @@ func (h *requestOrResponseTrailerMapImpl) Del(key string) { defer h.mutex.Unlock() h.initTrailers() delete(h.headers, key) - cAPI.HttpRemoveTrailer(unsafe.Pointer(h.request.req), &key) + cAPI.HttpRemoveTrailer(unsafe.Pointer(h.request.req), key) } func (h *requestOrResponseTrailerMapImpl) Range(f func(key, value string) bool) { diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index 90e8410e537b..01642e6ac74b 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -599,7 +599,7 @@ CAPIStatus Filter::continueStatus(GolangStatus status) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getHeader(absl::string_view key, GoString* go_value) { +CAPIStatus Filter::getHeader(absl::string_view key, uint64_t* value_data, int* value_len) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); @@ -619,8 +619,8 @@ CAPIStatus Filter::getHeader(absl::string_view key, GoString* go_value) { if (!result.empty()) { auto str = result[0]->value().getStringView(); - go_value->p = str.data(); - go_value->n = str.length(); + *value_data = reinterpret_cast(str.data()); + *value_len = str.length(); } return CAPIStatus::CAPIOK; } @@ -1010,7 +1010,7 @@ CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getStringValue(int id, GoString* value_str) { +CAPIStatus Filter::getStringValue(int id, uint64_t* value_data, int* value_len) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { @@ -1081,12 +1081,13 @@ CAPIStatus Filter::getStringValue(int id, GoString* value_str) { RELEASE_ASSERT(false, absl::StrCat("invalid string value id: ", id)); } - value_str->p = req_->strValue.data(); - value_str->n = req_->strValue.length(); + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, GoSlice* buf_slice) { +CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, uint64_t* buf_data, + int* buf_len) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); @@ -1102,10 +1103,10 @@ CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, GoSlice* b if (!state.isThreadSafe()) { auto weak_ptr = weak_from_this(); ENVOY_LOG(debug, "golang filter getDynamicMetadata posting request to dispatcher"); - state.getDispatcher().post([this, &state, weak_ptr, filter_name, buf_slice] { + state.getDispatcher().post([this, &state, weak_ptr, filter_name, buf_data, buf_len] { ENVOY_LOG(debug, "golang filter getDynamicMetadata request in worker thread"); if (!weak_ptr.expired() && !hasDestroyed()) { - populateSliceWithMetadata(state, filter_name, buf_slice); + populateSliceWithMetadata(state, filter_name, buf_data, buf_len); dynamic_lib_->envoyGoRequestSemaDec(req_); } else { ENVOY_LOG(info, "golang filter has gone or destroyed in getDynamicMetadata"); @@ -1114,21 +1115,20 @@ CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, GoSlice* b return CAPIStatus::CAPIYield; } else { ENVOY_LOG(debug, "golang filter getDynamicMetadata replying directly"); - populateSliceWithMetadata(state, filter_name, buf_slice); + populateSliceWithMetadata(state, filter_name, buf_data, buf_len); } return CAPIStatus::CAPIOK; } void Filter::populateSliceWithMetadata(ProcessorState& state, const std::string& filter_name, - GoSlice* buf_slice) { + uint64_t* buf_data, int* buf_len) { const auto& metadata = state.streamInfo().dynamicMetadata().filter_metadata(); const auto filter_it = metadata.find(filter_name); if (filter_it != metadata.end()) { filter_it->second.SerializeToString(&req_->strValue); - buf_slice->data = req_->strValue.data(); - buf_slice->len = req_->strValue.length(); - buf_slice->cap = req_->strValue.length(); + *buf_data = reinterpret_cast(req_->strValue.data()); + *buf_len = req_->strValue.length(); } } @@ -1220,7 +1220,8 @@ CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getStringFilterState(absl::string_view key, GoString* value_str) { +CAPIStatus Filter::getStringFilterState(absl::string_view key, uint64_t* value_data, + int* value_len) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { @@ -1239,20 +1240,20 @@ CAPIStatus Filter::getStringFilterState(absl::string_view key, GoString* value_s state.streamInfo().filterState()->getDataReadOnly(key); if (go_filter_state) { req_->strValue = go_filter_state->value(); - value_str->p = req_->strValue.data(); - value_str->n = req_->strValue.length(); + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); } } else { auto key_str = std::string(key); auto weak_ptr = weak_from_this(); - state.getDispatcher().post([this, &state, weak_ptr, key_str, value_str] { + state.getDispatcher().post([this, &state, weak_ptr, key_str, value_data, value_len] { if (!weak_ptr.expired() && !hasDestroyed()) { auto go_filter_state = state.streamInfo().filterState()->getDataReadOnly(key_str); if (go_filter_state) { req_->strValue = go_filter_state->value(); - value_str->p = req_->strValue.data(); - value_str->n = req_->strValue.length(); + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); } dynamic_lib_->envoyGoRequestSemaDec(req_); } else { @@ -1264,7 +1265,8 @@ CAPIStatus Filter::getStringFilterState(absl::string_view key, GoString* value_s return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getStringProperty(absl::string_view path, GoString* value_str, int* rc) { +CAPIStatus Filter::getStringProperty(absl::string_view path, uint64_t* value_data, int* value_len, + int* rc) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { @@ -1286,13 +1288,13 @@ CAPIStatus Filter::getStringProperty(absl::string_view path, GoString* value_str } if (state.isThreadSafe()) { - return getStringPropertyCommon(path, value_str, state); + return getStringPropertyCommon(path, value_data, value_len, state); } auto weak_ptr = weak_from_this(); - state.getDispatcher().post([this, &state, weak_ptr, path, value_str, rc] { + state.getDispatcher().post([this, &state, weak_ptr, path, value_data, value_len, rc] { if (!weak_ptr.expired() && !hasDestroyed()) { - *rc = getStringPropertyCommon(path, value_str, state); + *rc = getStringPropertyCommon(path, value_data, value_len, state); dynamic_lib_->envoyGoRequestSemaDec(req_); } else { ENVOY_LOG(info, "golang filter has gone or destroyed in getStringProperty"); @@ -1301,13 +1303,13 @@ CAPIStatus Filter::getStringProperty(absl::string_view path, GoString* value_str return CAPIStatus::CAPIYield; } -CAPIStatus Filter::getStringPropertyCommon(absl::string_view path, GoString* value_str, - ProcessorState& state) { +CAPIStatus Filter::getStringPropertyCommon(absl::string_view path, uint64_t* value_data, + int* value_len, ProcessorState& state) { activation_info_ = &state.streamInfo(); CAPIStatus status = getStringPropertyInternal(path, &req_->strValue); if (status == CAPIStatus::CAPIOK) { - value_str->p = req_->strValue.data(); - value_str->n = req_->strValue.length(); + *value_data = reinterpret_cast(req_->strValue.data()); + *value_len = req_->strValue.length(); } return status; } diff --git a/contrib/golang/filters/http/source/golang_filter.h b/contrib/golang/filters/http/source/golang_filter.h index 1d2485e4aabf..925c182e44ae 100644 --- a/contrib/golang/filters/http/source/golang_filter.h +++ b/contrib/golang/filters/http/source/golang_filter.h @@ -220,7 +220,7 @@ class Filter : public Http::StreamFilter, CAPIStatus sendPanicReply(absl::string_view details); - CAPIStatus getHeader(absl::string_view key, GoString* go_value); + CAPIStatus getHeader(absl::string_view key, uint64_t* value_data, int* value_len); CAPIStatus copyHeaders(GoString* go_strs, char* go_buf); CAPIStatus setHeader(absl::string_view key, absl::string_view value, headerAction act); CAPIStatus removeHeader(absl::string_view key); @@ -231,15 +231,16 @@ class Filter : public Http::StreamFilter, CAPIStatus copyTrailers(GoString* go_strs, char* go_buf); CAPIStatus setTrailer(absl::string_view key, absl::string_view value, headerAction act); CAPIStatus removeTrailer(absl::string_view key); - CAPIStatus getStringValue(int id, GoString* value_str); + CAPIStatus getStringValue(int id, uint64_t* value_data, int* value_len); CAPIStatus getIntegerValue(int id, uint64_t* value); - CAPIStatus getDynamicMetadata(const std::string& filter_name, GoSlice* buf_slice); + CAPIStatus getDynamicMetadata(const std::string& filter_name, uint64_t* buf_data, int* buf_len); CAPIStatus setDynamicMetadata(std::string filter_name, std::string key, absl::string_view buf); CAPIStatus setStringFilterState(absl::string_view key, absl::string_view value, int state_type, int life_span, int stream_sharing); - CAPIStatus getStringFilterState(absl::string_view key, GoString* value_str); - CAPIStatus getStringProperty(absl::string_view path, GoString* value_str, GoInt32* rc); + CAPIStatus getStringFilterState(absl::string_view key, uint64_t* value_data, int* value_len); + CAPIStatus getStringProperty(absl::string_view path, uint64_t* value_data, int* value_len, + GoInt32* rc); private: bool hasDestroyed() { @@ -274,9 +275,9 @@ class Filter : public Http::StreamFilter, const absl::string_view& buf); void populateSliceWithMetadata(ProcessorState& state, const std::string& filter_name, - GoSlice* buf_slice); + uint64_t* buf_data, int* buf_len); - CAPIStatus getStringPropertyCommon(absl::string_view path, GoString* value_str, + CAPIStatus getStringPropertyCommon(absl::string_view path, uint64_t* value_data, int* value_len, ProcessorState& state); CAPIStatus getStringPropertyInternal(absl::string_view path, std::string* result); absl::optional findValue(absl::string_view name, diff --git a/contrib/golang/filters/http/test/BUILD b/contrib/golang/filters/http/test/BUILD index eefe0c5c64d3..20c55b491738 100644 --- a/contrib/golang/filters/http/test/BUILD +++ b/contrib/golang/filters/http/test/BUILD @@ -16,7 +16,6 @@ envoy_cc_test( data = [ "//contrib/golang/filters/http/test/test_data/passthrough:filter.so", ], - env = {"GODEBUG": "cgocheck=0"}, deps = [ "//contrib/golang/filters/http/source:config", "//test/mocks/server:factory_context_mocks", @@ -31,7 +30,6 @@ envoy_cc_test( "//contrib/golang/filters/http/test/test_data/passthrough:filter.so", "//contrib/golang/filters/http/test/test_data/routeconfig:filter.so", ], - env = {"GODEBUG": "cgocheck=0"}, deps = [ "//contrib/golang/filters/http/source:golang_filter_lib", "//source/common/stream_info:stream_info_lib", @@ -61,7 +59,6 @@ envoy_cc_test( "//contrib/golang/filters/http/test/test_data/property:filter.so", "//contrib/golang/filters/http/test/test_data/routeconfig:filter.so", ], - env = {"GODEBUG": "cgocheck=0"}, deps = [ "//contrib/golang/filters/http/source:config", "//source/exe:main_common_lib", diff --git a/contrib/golang/filters/http/test/test_data/access_log/go.mod b/contrib/golang/filters/http/test/test_data/access_log/go.mod index 5cedc2cba33b..01693aa92cd2 100644 --- a/contrib/golang/filters/http/test/test_data/access_log/go.mod +++ b/contrib/golang/filters/http/test/test_data/access_log/go.mod @@ -1,6 +1,6 @@ module example.com/access_log -go 1.18 +go 1.20 require github.com/envoyproxy/envoy v1.24.0 diff --git a/contrib/golang/filters/http/test/test_data/basic/go.mod b/contrib/golang/filters/http/test/test_data/basic/go.mod index 709dbc31fbce..b70054d4863a 100644 --- a/contrib/golang/filters/http/test/test_data/basic/go.mod +++ b/contrib/golang/filters/http/test/test_data/basic/go.mod @@ -1,6 +1,6 @@ module example.com/basic -go 1.18 +go 1.20 require github.com/envoyproxy/envoy v1.24.0 diff --git a/contrib/golang/filters/http/test/test_data/buffer/go.mod b/contrib/golang/filters/http/test/test_data/buffer/go.mod index 2110471b7b0f..b192be3f71b1 100644 --- a/contrib/golang/filters/http/test/test_data/buffer/go.mod +++ b/contrib/golang/filters/http/test/test_data/buffer/go.mod @@ -1,6 +1,6 @@ module example.com/buffer -go 1.18 +go 1.20 require ( github.com/envoyproxy/envoy v1.24.0 diff --git a/contrib/golang/filters/http/test/test_data/dummy/go.mod b/contrib/golang/filters/http/test/test_data/dummy/go.mod index 2e9286b62ba9..beaf9d59c743 100644 --- a/contrib/golang/filters/http/test/test_data/dummy/go.mod +++ b/contrib/golang/filters/http/test/test_data/dummy/go.mod @@ -1,6 +1,6 @@ module example.com/dummy -go 1.18 +go 1.20 require github.com/envoyproxy/envoy v1.24.0 diff --git a/contrib/golang/filters/http/test/test_data/echo/go.mod b/contrib/golang/filters/http/test/test_data/echo/go.mod index 8614081f88c5..7a258b6138a0 100644 --- a/contrib/golang/filters/http/test/test_data/echo/go.mod +++ b/contrib/golang/filters/http/test/test_data/echo/go.mod @@ -1,6 +1,6 @@ module example.com/echo -go 1.18 +go 1.20 require ( github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 diff --git a/contrib/golang/filters/http/test/test_data/metric/go.mod b/contrib/golang/filters/http/test/test_data/metric/go.mod index 709dbc31fbce..b70054d4863a 100644 --- a/contrib/golang/filters/http/test/test_data/metric/go.mod +++ b/contrib/golang/filters/http/test/test_data/metric/go.mod @@ -1,6 +1,6 @@ module example.com/basic -go 1.18 +go 1.20 require github.com/envoyproxy/envoy v1.24.0 diff --git a/contrib/golang/filters/http/test/test_data/passthrough/go.mod b/contrib/golang/filters/http/test/test_data/passthrough/go.mod index 3a42612f666c..d6c620393300 100644 --- a/contrib/golang/filters/http/test/test_data/passthrough/go.mod +++ b/contrib/golang/filters/http/test/test_data/passthrough/go.mod @@ -1,6 +1,6 @@ module example.com/passthrough -go 1.18 +go 1.20 require github.com/envoyproxy/envoy v1.24.0 diff --git a/contrib/golang/filters/http/test/test_data/property/filter.go b/contrib/golang/filters/http/test/test_data/property/filter.go index bd1934b66187..9f77dd5382f9 100644 --- a/contrib/golang/filters/http/test/test_data/property/filter.go +++ b/contrib/golang/filters/http/test/test_data/property/filter.go @@ -54,7 +54,7 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. f.assertProperty("request.useragent", "ua") f.assertProperty("request.id", "xri") - f.assertProperty("request.duration", "value not found") // available only when the request is finished + f.assertProperty("request.duration", api.ErrValueNotFound.Error()) // available only when the request is finished f.assertProperty("source.address", f.callbacks.StreamInfo().DownstreamRemoteAddress()) f.assertProperty("destination.address", f.callbacks.StreamInfo().DownstreamLocalAddress()) @@ -63,7 +63,7 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. f.assertProperty("xds.route_name", "test-route-name") // non-existed attribute - f.assertProperty("request.user_agent", "value not found") + f.assertProperty("request.user_agent", api.ErrValueNotFound.Error()) // access response attribute in the decode phase f.assertProperty("response.total_size", "0") @@ -74,7 +74,7 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. ".", ".total_size", } { - f.assertProperty(attr, "value not found") + f.assertProperty(attr, api.ErrValueNotFound.Error()) } // unsupported value type for _, attr := range []string{ @@ -84,7 +84,14 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. "request", "request.", } { - f.assertProperty(attr, "serialization failure") + f.assertProperty(attr, api.ErrSerializationFailure.Error()) + } + + // error handling + _, err := f.callbacks.GetProperty(".not_found") + if err != api.ErrValueNotFound { + f.callbacks.Log(api.Critical, "unexpected error "+err.Error()) + f.failed = true } return api.Continue } @@ -94,9 +101,8 @@ func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api f.assertProperty("xds.cluster_name", "cluster_0") f.assertProperty("xds.cluster_metadata", "") - // response.code is available only after the response has started to send code, _ := f.callbacks.StreamInfo().ResponseCode() - exp := "value not found" + exp := "" if code != 0 { exp = strconv.Itoa(int(code)) } diff --git a/contrib/golang/filters/http/test/test_data/property/go.mod b/contrib/golang/filters/http/test/test_data/property/go.mod index 4bcd808d5e6f..0deafbaba8fe 100644 --- a/contrib/golang/filters/http/test/test_data/property/go.mod +++ b/contrib/golang/filters/http/test/test_data/property/go.mod @@ -1,6 +1,6 @@ module example.com/property -go 1.18 +go 1.20 require ( github.com/envoyproxy/envoy v1.24.0 diff --git a/contrib/golang/filters/http/test/test_data/routeconfig/go.mod b/contrib/golang/filters/http/test/test_data/routeconfig/go.mod index 2df147ea5ed1..b11b4ac5d871 100644 --- a/contrib/golang/filters/http/test/test_data/routeconfig/go.mod +++ b/contrib/golang/filters/http/test/test_data/routeconfig/go.mod @@ -1,6 +1,6 @@ module example.com/routeconfig -go 1.18 +go 1.20 require ( github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 diff --git a/contrib/golang/filters/network/source/cgo.cc b/contrib/golang/filters/network/source/cgo.cc index e6411260813e..eb1f5442542d 100644 --- a/contrib/golang/filters/network/source/cgo.cc +++ b/contrib/golang/filters/network/source/cgo.cc @@ -116,7 +116,7 @@ CAPIStatus envoyGoFilterDownstreamInfo(void* f, int info_type, void* ret) { // Upstream // -void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, unsigned long long int conn_id) { +void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, uint64_t conn_id) { std::string id = copyGoString(library_id); auto dynamic_lib = Dso::DsoManager::getDsoByID(id); UpstreamConnPtr conn_ptr = diff --git a/contrib/golang/filters/network/source/go/pkg/network/capi.go b/contrib/golang/filters/network/source/go/pkg/network/capi.go index 6747e97322e7..bb884b563711 100644 --- a/contrib/golang/filters/network/source/go/pkg/network/capi.go +++ b/contrib/golang/filters/network/source/go/pkg/network/capi.go @@ -92,7 +92,7 @@ func (c *cgoApiImpl) SetFilterState(f unsafe.Pointer, key string, value string, } func (c *cgoApiImpl) UpstreamConnect(libraryID string, addr string, connID uint64) unsafe.Pointer { - return unsafe.Pointer(C.envoyGoFilterUpstreamConnect(unsafe.Pointer(&libraryID), unsafe.Pointer(&addr), C.ulonglong(connID))) + return unsafe.Pointer(C.envoyGoFilterUpstreamConnect(unsafe.Pointer(&libraryID), unsafe.Pointer(&addr), C.uint64_t(connID))) } func (c *cgoApiImpl) UpstreamWrite(f unsafe.Pointer, bufferPtr unsafe.Pointer, bufferLen int, endStream int) { diff --git a/docs/root/_configs/go/golang-with-per-route-config.yaml b/docs/root/_configs/go/golang-with-per-route-config.yaml index f44a43a907da..f91f8c9abcf9 100644 --- a/docs/root/_configs/go/golang-with-per-route-config.yaml +++ b/docs/root/_configs/go/golang-with-per-route-config.yaml @@ -20,6 +20,10 @@ static_resources: library_id: my-configurable-plugin-id library_path: "lib/my_configurable_plugin.so" plugin_name: my_configurable_plugin + plugin_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + value: + foo: default_foo - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router diff --git a/docs/root/_configs/go/golang-with-per-virtualhost-config.yaml b/docs/root/_configs/go/golang-with-per-virtualhost-config.yaml index cfea9d10c68f..e7f1ec05f812 100644 --- a/docs/root/_configs/go/golang-with-per-virtualhost-config.yaml +++ b/docs/root/_configs/go/golang-with-per-virtualhost-config.yaml @@ -20,6 +20,10 @@ static_resources: library_id: my-configurable-plugin-id library_path: "lib/my_configurable_plugin.so" plugin_name: my_configurable_plugin + plugin_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + value: + foo: default_foo - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router diff --git a/docs/root/api-v3/config/config.rst b/docs/root/api-v3/config/config.rst index ca859c388e68..015f5e20cb50 100644 --- a/docs/root/api-v3/config/config.rst +++ b/docs/root/api-v3/config/config.rst @@ -27,6 +27,7 @@ Extensions http/header_validators http/original_ip_detection http/stateful_session + geoip_provider/geoip_provider trace/trace internal_redirect/internal_redirect path/match/path_matcher diff --git a/docs/root/api-v3/config/geoip_provider/geoip_provider.rst b/docs/root/api-v3/config/geoip_provider/geoip_provider.rst new file mode 100644 index 000000000000..2b9ad10b9f6a --- /dev/null +++ b/docs/root/api-v3/config/geoip_provider/geoip_provider.rst @@ -0,0 +1,10 @@ +.. _api-v3_config_geoip_providers: + +Geolocation providers +===================== + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../extensions/geoip_providers/*/v3/* diff --git a/docs/root/configuration/advanced/advanced.rst b/docs/root/configuration/advanced/advanced.rst index d84ba2f53225..c330abbab617 100644 --- a/docs/root/configuration/advanced/advanced.rst +++ b/docs/root/configuration/advanced/advanced.rst @@ -6,3 +6,4 @@ Advanced well_known_dynamic_metadata well_known_filter_state + metadata_configurations diff --git a/docs/root/configuration/advanced/metadata_configurations.rst b/docs/root/configuration/advanced/metadata_configurations.rst new file mode 100644 index 000000000000..cbb8f473b790 --- /dev/null +++ b/docs/root/configuration/advanced/metadata_configurations.rst @@ -0,0 +1,19 @@ +.. _metadata_configurations: + +Metadata configurations +======================= + +Envoy utilizes :ref:`metadata ` to transport arbitrary untyped or typed +data from the control plane to Envoy. Metadata configurations can be applied to Listeners, clusters, routes, virtual hosts, +endpoints, and other elements. + + +Unlike other configurations, Envoy does not explicitly define the purpose of metadata configurations, which can be used for +stats, logging, or filter/extension behavior. Users can define the purpose of metadata configurations for their specific +use cases. Metadata configurations offer a flexible way to transport user-defined data from the control plane to Envoy without +modifying Envoy's core API or implementation. + + +For instance, users can add extra attributes to routes, such as the route owner or upstream service maintainer, to metadata. +They can then enable Envoy to log these attributes to the access log or report them to StatsD, among other possibilities. +Moreover, users can write a filter/extension to read these attributes and execute any specific logic. diff --git a/docs/root/configuration/http/http_filters/geoip_filter.rst b/docs/root/configuration/http/http_filters/geoip_filter.rst index 7c25cbe0114a..0c6c18d7ac15 100644 --- a/docs/root/configuration/http/http_filters/geoip_filter.rst +++ b/docs/root/configuration/http/http_filters/geoip_filter.rst @@ -4,15 +4,29 @@ IP Geolocation Filter ========================= This filter decorates HTTP requests with the geolocation data. Filter uses client address to lookup information (e.g., client's city, country) in the geolocation provider database. -Upon a successful lookup request will be enriched with the configured geolocation header and value from the database. +Upon a successful lookup request will be enriched with the configured geolocation header and the value from the database. In case the configured geolocation headers are present in the incoming request, they will be overriden by the filter. -Geolocation filter emits stats for the number of successful lookups and the number of total lookups. +Geolocation filter emits stats for the number of the successful lookups and the number of total lookups. +English language is used for the geolocation lookups, the result of the lookup will be UTF-8 encoded. +Please note that Geolocation filter and providers are not yet supported on Windows. Configuration ------------- * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.geoip.v3.Geoip``. * :ref:`v3 API reference ` +.. _config_geoip_providers_maxmind: + +Geolocation Providers +--------------------- +Currently only `Maxmind `_ geolocation provider is supported. +This provider should be configured with the type URL ``type.googleapis.com/envoy.extensions.geoip_providers.maxmind.v3.MaxMindConfig``. + +* :ref:`v3 API reference ` + +.. _config_geoip_providers_common: + +* :ref:`Common provider configuration ` Configuration example --------------------- @@ -22,25 +36,45 @@ Configuration example name: envoy.filters.http.geoip typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.geoip.v3.Geoip - use_xff: true - xff_num_trusted_hops: 1 - geo_headers_to_add: - country: "x-geo-country" - region: "x-geo-region" + xff_config: + xff_num_trusted_hops: 1 provider: name: "envoy.geoip_providers.maxmind" + typed_config: + "@type": type.googleapis.com/envoy.extensions.geoip_providers.maxmind.v3.MaxMindConfig + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + asn: "x-geo-asn" + city_db_path: "geoip/GeoLite2-City-Test.mmdb" + isp_db_path: "geoip/GeoLite2-ASN-Test.mmdb" + Statistics ----------- -Geolocation filter outputs statistics in the -``http..geoip..`` namespace. The :ref:`stat prefix +------------- + +Geolocation HTTP filter has a statistics tree rooted at ``http..``. The :ref:`stat prefix ` comes from the owning HTTP connection manager. .. csv-table:: - :header: Name, Type, Description - :widths: auto + :header: Name, Type, Description + :widths: 1, 1, 2 + + ``rq_total``, Counter, Total number of requests for which geolocation filter was invoked. + +Besides Geolocation filter level statisctics, there is statistics emitted by the :ref:`Maxmind geolocation provider ` +per geolocation database type (rooted at ``.maxmind.``). Database type can be one of `city_db `_, +`isp_db `_, `anon_db `_. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + ``.total``, Counter, Total number of lookups performed for a given geolocation database file. + ``.hit``, Counter, Total number of successful lookups (with non empty lookup result) performed for a given geolocation database file. + ``.lookup_error``, Counter, Total number of errors that occured during lookups for a given geolocation database file. - .hit, Counter, Number of successful lookups within geolocation database for a configured geolocation header. - .total, Counter, Number of total lookups within geolocation database for a configured geolocation header. diff --git a/docs/root/configuration/http/http_filters/golang_filter.rst b/docs/root/configuration/http/http_filters/golang_filter.rst index 8721ce9e838d..4c690f86f846 100644 --- a/docs/root/configuration/http/http_filters/golang_filter.rst +++ b/docs/root/configuration/http/http_filters/golang_filter.rst @@ -12,13 +12,6 @@ See the `Envoy's Golang extension proposal documentation `_ for more details on the filter's implementation. -.. warning:: - The Envoy Golang filter is designed to be run with the ``GODEBUG=cgocheck=0`` environment variable set. - - This disables the cgo pointer check. - - Failure to set this environment variable will cause Envoy to crash! - Developing a Go plugin ---------------------- diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 4fc3589c1267..a152a26e76f3 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -945,12 +945,22 @@ peerCertificateValidated() .. code-block:: lua - if downstreamSslConnection:peerCertificateVaidated() then - print("peer certificate is valiedated") + if downstreamSslConnection:peerCertificateValidated() then + print("peer certificate is validated") end Returns bool whether the peer certificate was validated. +.. warning:: + + Client certificate validation is not currently performed upon TLS session resumption. For a + resumed TLS session this method will return false, regardless of whether the peer certificate is + valid. + + The only known workaround for this issue is to disable TLS session resumption entirely, by + setting both :ref:`disable_stateless_session_resumption ` + and :ref:`disable_stateful_session_resumption ` on the DownstreamTlsContext. + uriSanLocalCertificate() ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/root/configuration/listeners/network_filters/_include/generic_proxy_filter.yaml b/docs/root/configuration/listeners/network_filters/_include/generic_proxy_filter.yaml index d11f74b213f7..03a96cdd75a9 100644 --- a/docs/root/configuration/listeners/network_filters/_include/generic_proxy_filter.yaml +++ b/docs/root/configuration/listeners/network_filters/_include/generic_proxy_filter.yaml @@ -16,7 +16,7 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.router.v3.Router codec_config: - name: http + name: dubbo typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.codecs.dubbo.v3.DubboCodecConfig route_config: diff --git a/docs/root/configuration/listeners/udp_filters/session_filters/dynamic_forward_proxy.rst b/docs/root/configuration/listeners/udp_filters/session_filters/dynamic_forward_proxy.rst new file mode 100644 index 000000000000..3c93d1fae433 --- /dev/null +++ b/docs/root/configuration/listeners/udp_filters/session_filters/dynamic_forward_proxy.rst @@ -0,0 +1,27 @@ +.. _config_udp_session_filters_dynamic_forward_proxy: + +Dynamic forward proxy +================================== + +Through the combination of a custom preceding filter that sets the ``envoy.upstream.dynamic_host`` and ``envoy.upstream.dynamic_port`` filter state +keys, and when used with the :ref:`dynamic forward proxy cluster `, +Envoy supports dynamic forward proxy for UDP flows. The implementation works just like the +:ref:`HTTP dynamic forward proxy `, but using the value in +UDP session's filter state as target host and port instead. + +* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.session.dynamic_forward_proxy.v3.FilterConfig``. +* :ref:`v3 API reference ` + +.. _config_udp_session_filters_dynamic_forward_proxy_stats: + +Statistics +---------- + +Every configured filter has statistics rooted at *udp.session.dynamic_forward_proxy..* +with the following statistics: + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + buffer_overflow, Counter, Number of datagrams dropped due to the filter buffer being overflowed diff --git a/docs/root/configuration/listeners/udp_filters/udp_proxy.rst b/docs/root/configuration/listeners/udp_filters/udp_proxy.rst index 79fc13f01788..b03c746cfa2d 100644 --- a/docs/root/configuration/listeners/udp_filters/udp_proxy.rst +++ b/docs/root/configuration/listeners/udp_filters/udp_proxy.rst @@ -97,6 +97,7 @@ Envoy has the following builtin UDP session filters. :maxdepth: 2 session_filters/http_capsule + session_filters/dynamic_forward_proxy .. _config_udp_listener_filters_udp_proxy_tunneling_over_http: diff --git a/docs/root/faq/debugging/how_to_dump_heap_profile_of_envoy.rst b/docs/root/faq/debugging/how_to_dump_heap_profile_of_envoy.rst index e104cb7ce4fb..4f5b60624fd8 100644 --- a/docs/root/faq/debugging/how_to_dump_heap_profile_of_envoy.rst +++ b/docs/root/faq/debugging/how_to_dump_heap_profile_of_envoy.rst @@ -26,7 +26,7 @@ And then you can analyze the outputted heap profile with pprof: .. code-block:: bash - $ pprof -http:localhost:9999 /heap/output/envoy.heap + $ pprof -http localhost:9999 /heap/output/envoy.heap .. note:: If you dump the heap profile in the production environment and analyze it in the local environment, please ensure diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst b/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst index 56d05dafc825..efc308c85bbd 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst @@ -63,3 +63,4 @@ Once endpoints E1 and E2 exit slow start mode, their load balancing weight remai :align: center *Note* in case when multiple priorities are used with slow start and lower priority has just one endpoint A, during cross-priority spillover there will be no progressive increase of traffic to endpoint A, all traffic will shift at once. +Same applies to locality weighted loadbalancing, when slow start is enabled for the upstream cluster and traffic is routed cross zone to a zone with one endpoint A, there will be no progressive increase of traffic to endpoint A. diff --git a/docs/root/operations/admin.rst b/docs/root/operations/admin.rst index 6954355b1013..573cf799a971 100644 --- a/docs/root/operations/admin.rst +++ b/docs/root/operations/admin.rst @@ -307,16 +307,32 @@ modify different aspects of the server: Enable/disable logging levels for different loggers. - - To change the logging level across all loggers, set the query parameter as level=. - - To change a particular logger's level, set the query parameter like so, =. + If the default component logger is used, the logger name should be exactlly the component name. + + - To change the logging level across all loggers, set the query parameter as ``level=``. + - To change a particular logger's level, set the query parameter like so, ``=``. - To change multiple logging levels at once, set the query parameter as ``paths=:,:``. - - To list the loggers, send a POST request to the /logging endpoint without a query parameter. + - To list the loggers, send a POST request to the ``/logging`` endpoint without a query parameter. + + If ``--enable-fine-grain-logging`` is set, the logger is represented by the path of the file it belongs to (to be specific, the path determined by ``__FILE__``), + so the logger list will show a list of file paths, and the specific path should be used as ```` to change the log level. - .. note:: + We also added the file basename, glob ``*`` and ``?`` support for fine-grain loggers. For example, we have the following active loggers with trace level. + + .. code-block:: text - Generally only used during development. With ``--enable-fine-grain-logging`` being set, the logger is represented - by the path of the file it belongs to (to be specific, the path determined by ``__FILE__``), so the logger list - will show a list of file paths, and the specific path should be used as to change the log level. + source/server/admin/admin_filter.cc: 0 + source/common/event/dispatcher_impl.cc: 0 + source/common/network/tcp_listener_impl.cc: 0 + source/common/network/udp_listener_impl.cc: 0 + + - ``/logging?paths=source/common/event/dispatcher_impl.cc:debug`` will make the level of ``source/common/event/dispatcher_impl.cc`` be debug. + - ``/logging?admin_filter=info`` will make the level of ``source/server/admin/admin_filter.cc`` be info, and other unmatched loggers will be the default trace. + - ``/logging?paths=source/common*:warning`` will make the level of ``source/common/event/dispatcher_impl.cc:``, ``source/common/network/tcp_listener_impl.cc`` be warning. + Other unmatched loggers will be the default trace, e.g., `admin_filter.cc`, even it was updated to info from the previous post update. + - ``/logging?paths=???_listener_impl:info`` will make the level of ``source/common/network/tcp_listener_impl.cc``, ``source/common/network/udp_listener_impl.cc`` be info. + - ``/logging?paths=???_listener_impl:info,tcp_listener_impl:warning``, the level of ``source/common/network/tcp_listener_impl.cc`` will be info, since the first match will take effect. + - ``/logging?level=info`` will change the default verbosity level to info. All the unmatched loggers in the following update will be this default level. .. http:get:: /memory @@ -350,6 +366,10 @@ modify different aspects of the server: This behaviour and duration is configurable via server options or CLI (:option:`--drain-time-s` and :option:`--drain-strategy`). + .. http:post:: /drain_listeners?graceful&skip_exit + + When draining listeners, do not exit after the drain period. This must be used with `graceful`. + .. attention:: This operation directly stops the matched listeners on workers. Once listeners in a given diff --git a/docs/root/start/sandboxes/golang-http.rst b/docs/root/start/sandboxes/golang-http.rst index f64e366cb89c..43680611d71b 100644 --- a/docs/root/start/sandboxes/golang-http.rst +++ b/docs/root/start/sandboxes/golang-http.rst @@ -40,15 +40,6 @@ Step 2: Start all of our containers Start all the containers. -.. warning:: - The Envoy Golang filter is designed to be run with the ``GODEBUG=cgocheck=0`` environment variable set. - - This disables the cgo pointer check. - - Failure to set this environment variable will cause Envoy to crash! - - Here, we have set this environment variable in :repo:`Dockerfile ` - .. code-block:: console $ docker compose pull diff --git a/envoy/geoip/BUILD b/envoy/geoip/BUILD new file mode 100644 index 000000000000..05d323b6a06b --- /dev/null +++ b/envoy/geoip/BUILD @@ -0,0 +1,28 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +# HTTP L7 filter that decorates request with geolocation data +# Public docs: https://envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/geoip_filter + +envoy_cc_extension( + name = "geoip_provider_driver_interface", + hdrs = [ + "geoip_provider_driver.h", + ], + tags = ["skip_on_windows"], + deps = [ + "//envoy/config:typed_config_interface", + "//envoy/network:address_interface", + "//envoy/protobuf:message_validator_interface", + "//envoy/server:factory_context_interface", + "//source/common/common:hash_lib", + "//source/common/protobuf:utility_lib", + ], +) diff --git a/source/extensions/filters/http/geoip/geoip_provider_config.h b/envoy/geoip/geoip_provider_driver.h similarity index 55% rename from source/extensions/filters/http/geoip/geoip_provider_config.h rename to envoy/geoip/geoip_provider_driver.h index 5928b6b599c4..fe038c0ffe16 100644 --- a/source/extensions/filters/http/geoip/geoip_provider_config.h +++ b/envoy/geoip/geoip_provider_driver.h @@ -1,22 +1,18 @@ #pragma once #include "envoy/config/typed_config.h" -#include "envoy/extensions/filters/http/geoip/v3/geoip.pb.h" -#include "envoy/extensions/filters/http/geoip/v3/geoip.pb.validate.h" #include "envoy/network/address.h" #include "envoy/protobuf/message_validator.h" +#include "envoy/server/factory_context.h" #include "absl/container/flat_hash_set.h" namespace Envoy { -namespace Extensions { -namespace HttpFilters { -namespace Geoip { +namespace Geolocation { // Actual result of the lookup. Each entry in the map represents the geolocation header (entry key) -// for which the lookup was invoked mapped to a lookup result from the database. Entry value will be -// set to absl::nullopt when database lookup yields an empty result. -using LookupResult = const absl::flat_hash_map>; +// for which the lookup was invoked mapped to a lookup result from the database. +using LookupResult = const absl::flat_hash_map; // Async callbacks used for geolocation provider lookups. using LookupGeoHeadersCallback = std::function; @@ -24,20 +20,13 @@ using LookupGeoHeadersCallback = std::function; class LookupRequest { public: LookupRequest() = default; - LookupRequest(Network::Address::InstanceConstSharedPtr&& remote_address, - absl::flat_hash_set&& geo_headers, - absl::flat_hash_set&& geo_anon_headers) - : remote_address_(std::move(remote_address)), geo_headers_(std::move(geo_headers)), - geo_anon_headers_(std::move(geo_anon_headers)){}; + LookupRequest(Network::Address::InstanceConstSharedPtr&& remote_address) + : remote_address_(std::move(remote_address)){}; - absl::flat_hash_set geoHeaders() const { return geo_headers_; } - absl::flat_hash_set geoAnonHeaders() const { return geo_anon_headers_; } const Network::Address::InstanceConstSharedPtr remoteAddress() const { return remote_address_; } private: Network::Address::InstanceConstSharedPtr remote_address_; - absl::flat_hash_set geo_headers_; - absl::flat_hash_set geo_anon_headers_; }; class Driver { @@ -55,22 +44,6 @@ class Driver { using DriverSharedPtr = std::shared_ptr; -/** - * Context passed to geolocation providers to access server resources. - */ -class GeoipProviderFactoryContext { -public: - virtual ~GeoipProviderFactoryContext() = default; - - /** - * @return ProtobufMessage::ValidationVisitor& validation visitor for geolocation provider - * configuration messages. - */ - virtual ProtobufMessage::ValidationVisitor& messageValidationVisitor() PURE; -}; - -using GeoipProviderFactoryContextPtr = std::unique_ptr; - /** * Implemented by each geolocation provider and registered via Registry::registerFactory() or the * convenience class RegisterFactory. @@ -89,13 +62,12 @@ class GeoipProviderFactory : public Config::TypedFactory { * @param config supplies the proto configuration for the geolocation provider * @param context supplies the factory context */ - virtual DriverSharedPtr createGeoipProviderDriver(const Protobuf::Message& config, - GeoipProviderFactoryContextPtr& context) PURE; + virtual DriverSharedPtr + createGeoipProviderDriver(const Protobuf::Message& config, const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) PURE; std::string category() const override { return "envoy.geoip_providers"; } }; -} // namespace Geoip -} // namespace HttpFilters -} // namespace Extensions +} // namespace Geolocation } // namespace Envoy diff --git a/envoy/router/router.h b/envoy/router/router.h index 73d639d640cd..99a1db56d604 100644 --- a/envoy/router/router.h +++ b/envoy/router/router.h @@ -677,6 +677,18 @@ class VirtualHost { virtual void traversePerFilterConfig( const std::string& filter_name, std::function cb) const PURE; + + /** + * @return const envoy::config::core::v3::Metadata& return the metadata provided in the config for + * this virtual host. + */ + virtual const envoy::config::core::v3::Metadata& metadata() const PURE; + + /** + * @return const Envoy::Config::TypedMetadata& return the typed metadata provided in the config + * for this virtual host. + */ + virtual const Envoy::Config::TypedMetadata& typedMetadata() const PURE; }; /** @@ -1313,6 +1325,18 @@ class CommonConfig { * TODO(dio): To allow overrides at different levels (e.g. per-route, virtual host, etc). */ virtual uint32_t maxDirectResponseBodySizeBytes() const PURE; + + /** + * @return const envoy::config::core::v3::Metadata& return the metadata provided in the config for + * this route configuration. + */ + virtual const envoy::config::core::v3::Metadata& metadata() const PURE; + + /** + * @return const Envoy::Config::TypedMetadata& return the typed metadata provided in the config + * for this route configuration. + */ + virtual const Envoy::Config::TypedMetadata& typedMetadata() const PURE; }; /** diff --git a/envoy/tracing/trace_driver.h b/envoy/tracing/trace_driver.h index 111500063e14..2b50c3dcd562 100644 --- a/envoy/tracing/trace_driver.h +++ b/envoy/tracing/trace_driver.h @@ -111,7 +111,7 @@ class Driver { virtual SpanPtr startSpan(const Config& config, TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision) PURE; + Tracing::Decision tracing_decision) PURE; }; using DriverPtr = std::unique_ptr; diff --git a/envoy/tracing/tracer.h b/envoy/tracing/tracer.h index 43aa84907e67..9e2eaf65f8fd 100644 --- a/envoy/tracing/tracer.h +++ b/envoy/tracing/tracer.h @@ -20,7 +20,7 @@ class Tracer { virtual SpanPtr startSpan(const Config& config, TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, - const Tracing::Decision tracing_decision) PURE; + Tracing::Decision tracing_decision) PURE; }; using TracerSharedPtr = std::shared_ptr; diff --git a/examples/cache/requirements.txt b/examples/cache/requirements.txt index 9a7ff5a03d3d..c160459ca0cf 100644 --- a/examples/cache/requirements.txt +++ b/examples/cache/requirements.txt @@ -1,11 +1,13 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: # # pip-compile --allow-unsafe --generate-hashes requirements.in # pyyaml==6.0.1 \ + --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ @@ -13,7 +15,10 @@ pyyaml==6.0.1 \ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ + --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ @@ -21,9 +26,12 @@ pyyaml==6.0.1 \ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ + --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ @@ -38,7 +46,9 @@ pyyaml==6.0.1 \ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ diff --git a/examples/ext_authz/auth/grpc-service/go.mod b/examples/ext_authz/auth/grpc-service/go.mod index d39c4d7c017b..a4cde6de3c6c 100644 --- a/examples/ext_authz/auth/grpc-service/go.mod +++ b/examples/ext_authz/auth/grpc-service/go.mod @@ -6,5 +6,5 @@ require ( github.com/envoyproxy/go-control-plane v0.11.1 github.com/golang/protobuf v1.5.3 google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 - google.golang.org/grpc v1.58.2 + google.golang.org/grpc v1.58.3 ) diff --git a/examples/ext_authz/auth/grpc-service/go.sum b/examples/ext_authz/auth/grpc-service/go.sum index 0dcc604b9d85..a70ac6a8b4dc 100644 --- a/examples/ext_authz/auth/grpc-service/go.sum +++ b/examples/ext_authz/auth/grpc-service/go.sum @@ -1656,8 +1656,8 @@ google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwS google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= -google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/examples/golang-http/simple/go.mod b/examples/golang-http/simple/go.mod index 2e96bd4b9d10..2862ede61690 100644 --- a/examples/golang-http/simple/go.mod +++ b/examples/golang-http/simple/go.mod @@ -1,7 +1,7 @@ module github.com/envoyproxy/envoy/examples/golang-http/simple // the version should >= 1.18 -go 1.18 +go 1.20 // NOTICE: these lines could be generated automatically by "go mod tidy" require ( diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index 7349c4a9726d..dc19e1950078 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile -# To update, run: +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: # # pip-compile --allow-unsafe --generate-hashes requirements.in # @@ -8,9 +8,97 @@ certifi==2023.7.22 \ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 # via requests -charset-normalizer==2.0.6 \ - --hash=sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6 \ - --hash=sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f +charset-normalizer==3.3.0 \ + --hash=sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843 \ + --hash=sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786 \ + --hash=sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e \ + --hash=sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8 \ + --hash=sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4 \ + --hash=sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa \ + --hash=sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d \ + --hash=sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82 \ + --hash=sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7 \ + --hash=sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895 \ + --hash=sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d \ + --hash=sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a \ + --hash=sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382 \ + --hash=sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678 \ + --hash=sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b \ + --hash=sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e \ + --hash=sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741 \ + --hash=sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4 \ + --hash=sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596 \ + --hash=sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9 \ + --hash=sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69 \ + --hash=sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c \ + --hash=sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77 \ + --hash=sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13 \ + --hash=sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459 \ + --hash=sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e \ + --hash=sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7 \ + --hash=sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908 \ + --hash=sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a \ + --hash=sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f \ + --hash=sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8 \ + --hash=sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482 \ + --hash=sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d \ + --hash=sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d \ + --hash=sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545 \ + --hash=sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34 \ + --hash=sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86 \ + --hash=sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6 \ + --hash=sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe \ + --hash=sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e \ + --hash=sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc \ + --hash=sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7 \ + --hash=sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd \ + --hash=sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c \ + --hash=sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557 \ + --hash=sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a \ + --hash=sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89 \ + --hash=sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078 \ + --hash=sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e \ + --hash=sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4 \ + --hash=sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403 \ + --hash=sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0 \ + --hash=sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89 \ + --hash=sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115 \ + --hash=sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9 \ + --hash=sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05 \ + --hash=sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a \ + --hash=sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec \ + --hash=sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56 \ + --hash=sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38 \ + --hash=sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479 \ + --hash=sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c \ + --hash=sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e \ + --hash=sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd \ + --hash=sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186 \ + --hash=sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455 \ + --hash=sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c \ + --hash=sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65 \ + --hash=sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78 \ + --hash=sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287 \ + --hash=sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df \ + --hash=sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43 \ + --hash=sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1 \ + --hash=sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7 \ + --hash=sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989 \ + --hash=sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a \ + --hash=sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63 \ + --hash=sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884 \ + --hash=sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649 \ + --hash=sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810 \ + --hash=sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828 \ + --hash=sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4 \ + --hash=sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2 \ + --hash=sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd \ + --hash=sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5 \ + --hash=sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe \ + --hash=sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293 \ + --hash=sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e \ + --hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e \ + --hash=sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8 # via requests grpcio==1.59.0 \ --hash=sha256:0ae444221b2c16d8211b55326f8ba173ba8f8c76349bfc1768198ba592b58f74 \ @@ -126,9 +214,9 @@ grpcio-tools==1.59.0 \ --hash=sha256:f6263b85261b62471cb97b7505df72d72b8b62e5e22d8184924871a6155b4dbf \ --hash=sha256:f965707da2b48a33128615bcfebedd215a3a30e346447e885bb3da37a143177a # via -r requirements.in -idna==3.2 \ - --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \ - --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 +idna==3.4 \ + --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests protobuf==4.24.4 \ --hash=sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe \ @@ -151,7 +239,13 @@ requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via -r requirements.in -urllib3==1.26.17 \ - --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ - --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b +urllib3==2.0.6 \ + --hash=sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 \ + --hash=sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564 # via requests + +# The following packages are considered to be unsafe in a requirements file: +setuptools==68.2.2 \ + --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ + --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a + # via grpcio-tools diff --git a/examples/load-reporting-service/go.mod b/examples/load-reporting-service/go.mod index e6ac81d4a248..6ae96610e097 100644 --- a/examples/load-reporting-service/go.mod +++ b/examples/load-reporting-service/go.mod @@ -5,5 +5,5 @@ go 1.13 require ( github.com/envoyproxy/go-control-plane v0.11.1 github.com/golang/protobuf v1.5.3 - google.golang.org/grpc v1.58.2 + google.golang.org/grpc v1.58.3 ) diff --git a/examples/load-reporting-service/go.sum b/examples/load-reporting-service/go.sum index 0dcc604b9d85..a70ac6a8b4dc 100644 --- a/examples/load-reporting-service/go.sum +++ b/examples/load-reporting-service/go.sum @@ -1656,8 +1656,8 @@ google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwS google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= -google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/examples/local_ratelimit/Dockerfile-nginx b/examples/local_ratelimit/Dockerfile-nginx index bd3de4728444..961a664e53f7 100644 --- a/examples/local_ratelimit/Dockerfile-nginx +++ b/examples/local_ratelimit/Dockerfile-nginx @@ -1 +1 @@ -FROM nginx@sha256:32da30332506740a2f7c34d5dc70467b7f14ec67d912703568daff790ab3f755 +FROM nginx@sha256:b4af4f8b6470febf45dc10f564551af682a802eda1743055a7dfc8332dffa595 diff --git a/examples/mysql/Dockerfile-mysql b/examples/mysql/Dockerfile-mysql index 57e78b33da18..7dbb1cc059dc 100644 --- a/examples/mysql/Dockerfile-mysql +++ b/examples/mysql/Dockerfile-mysql @@ -1 +1 @@ -FROM mysql:8.1.0@sha256:44056c45e214c26c37b6f244534c6fb5f8a40eacbc28e870a2652b19d7a8a814 +FROM mysql:8.1.0@sha256:1ee299bf9eb8d2218fcb4fad666a090c92caef48ce524e6edce35f2e2d55170d diff --git a/examples/redis/Dockerfile-redis b/examples/redis/Dockerfile-redis index e5c869b218dd..0d311cb43817 100644 --- a/examples/redis/Dockerfile-redis +++ b/examples/redis/Dockerfile-redis @@ -1 +1 @@ -FROM redis@sha256:b68c6efe2c5f2d7d7d14a2749f66d6d81645ec0cacb92572b2fb7d5c42c82031 +FROM redis@sha256:4ca2a277f1dc3ddd0da33a258096de9a1cf5b9d9bd96b27ee78763ee2248c28c diff --git a/examples/shared/build/Dockerfile b/examples/shared/build/Dockerfile index 8fed6f57e6ce..1b0994c43501 100644 --- a/examples/shared/build/Dockerfile +++ b/examples/shared/build/Dockerfile @@ -1,4 +1,4 @@ -FROM envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +FROM envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index 376607d15875..197aaf408547 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -1,9 +1,9 @@ -FROM debian:bullseye-slim@sha256:c618be84fc82aa8ba203abbb07218410b0f5b3c7cb6b4e7248fda7785d4f9946 as os-base +FROM debian:bullseye-slim@sha256:9bec46ecd98ce4bf8305840b021dda9b3e1f8494a0768c407e2b233180fa1466 as os-base RUN rm -f /etc/apt/apt.conf.d/docker-clean \ && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache -FROM golang:1.21.3-bullseye@sha256:9bc6dcb86d0b13c6ecc41284c4ca4c940c7be322d18d9ab652c0f1af11ac9327 as golang-base +FROM golang:1.21.3-bullseye@sha256:26c7537d6ac3827eb4638034d16edc64de57bb011c8cc8fe301ac13a6568f6f4 as golang-base FROM golang-base as golang-control-plane-builder diff --git a/examples/shared/jaeger/Dockerfile b/examples/shared/jaeger/Dockerfile index 64639486fdab..4cfa4a1bbfe6 100644 --- a/examples/shared/jaeger/Dockerfile +++ b/examples/shared/jaeger/Dockerfile @@ -1,4 +1,4 @@ -FROM jaegertracing/all-in-one@sha256:8f090c17b7e5886d37395f8fc80726651d8adf6bfbcf5505b07adb0c6f1ffe0f +FROM jaegertracing/all-in-one@sha256:5c755d9eb00ba9487d18f301973f55e88e30eba3fcf32306d92fde025dd3fd37 HEALTHCHECK \ --interval=1s \ --timeout=1s \ diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index ec7f1899f102..bd7ff12a351f 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.8-bullseye-slim@sha256:ae31e40fdecf15751ee23055b60717e2ce6e03acc4ee7ffd8f87e76813d8010f as node-base +FROM node:20.8-bullseye-slim@sha256:d9730e4dd0f0ca135d2407592646252880089cd9ea2405f54da9c076e3fd8ce7 as node-base FROM node-base as node-http-auth diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index 63ac4fd3039d..02c1bf8e5e4d 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:f1aaf6f8be5552bef66c5580efbd2942c37d7277cd0416ef4939fa34bf0baf31 +FROM postgres:latest@sha256:3d9ed832906091d609cfd6f283e79492ace01ba15866b21d8a262e8fd1cdfb55 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] diff --git a/examples/shared/python/aiohttp/requirements.txt b/examples/shared/python/aiohttp/requirements.txt index c6727e7a1d1a..f918d02fc8a0 100644 --- a/examples/shared/python/aiohttp/requirements.txt +++ b/examples/shared/python/aiohttp/requirements.txt @@ -97,166 +97,168 @@ aiosignal==1.3.1 \ --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ --hash=sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17 # via aiohttp -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c +async-timeout==4.0.3 \ + --hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \ + --hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028 # via aiohttp -attrs==22.2.0 \ - --hash=sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836 \ - --hash=sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99 +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via aiohttp -charset-normalizer==3.1.0 \ - --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \ - --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \ - --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \ - --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \ - --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \ - --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \ - --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \ - --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \ - --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \ - --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \ - --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \ - --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \ - --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \ - --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \ - --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \ - --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \ - --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \ - --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \ - --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \ - --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \ - --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \ - --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \ - --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \ - --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \ - --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \ - --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \ - --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \ - --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \ - --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \ - --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \ - --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \ - --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \ - --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \ - --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \ - --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \ - --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \ - --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \ - --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \ - --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \ - --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \ - --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \ - --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \ - --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \ - --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \ - --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \ - --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \ - --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \ - --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \ - --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \ - --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \ - --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \ - --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \ - --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \ - --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \ - --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \ - --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \ - --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \ - --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \ - --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \ - --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \ - --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \ - --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \ - --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \ - --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \ - --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \ - --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \ - --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \ - --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \ - --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \ - --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \ - --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \ - --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \ - --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \ - --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ - --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab +charset-normalizer==3.3.0 \ + --hash=sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843 \ + --hash=sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786 \ + --hash=sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e \ + --hash=sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8 \ + --hash=sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4 \ + --hash=sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa \ + --hash=sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d \ + --hash=sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82 \ + --hash=sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7 \ + --hash=sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895 \ + --hash=sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d \ + --hash=sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a \ + --hash=sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382 \ + --hash=sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678 \ + --hash=sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b \ + --hash=sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e \ + --hash=sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741 \ + --hash=sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4 \ + --hash=sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596 \ + --hash=sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9 \ + --hash=sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69 \ + --hash=sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c \ + --hash=sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77 \ + --hash=sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13 \ + --hash=sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459 \ + --hash=sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e \ + --hash=sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7 \ + --hash=sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908 \ + --hash=sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a \ + --hash=sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f \ + --hash=sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8 \ + --hash=sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482 \ + --hash=sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d \ + --hash=sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d \ + --hash=sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545 \ + --hash=sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34 \ + --hash=sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86 \ + --hash=sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6 \ + --hash=sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe \ + --hash=sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e \ + --hash=sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc \ + --hash=sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7 \ + --hash=sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd \ + --hash=sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c \ + --hash=sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557 \ + --hash=sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a \ + --hash=sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89 \ + --hash=sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078 \ + --hash=sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e \ + --hash=sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4 \ + --hash=sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403 \ + --hash=sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0 \ + --hash=sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89 \ + --hash=sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115 \ + --hash=sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9 \ + --hash=sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05 \ + --hash=sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a \ + --hash=sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec \ + --hash=sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56 \ + --hash=sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38 \ + --hash=sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479 \ + --hash=sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c \ + --hash=sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e \ + --hash=sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd \ + --hash=sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186 \ + --hash=sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455 \ + --hash=sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c \ + --hash=sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65 \ + --hash=sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78 \ + --hash=sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287 \ + --hash=sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df \ + --hash=sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43 \ + --hash=sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1 \ + --hash=sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7 \ + --hash=sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989 \ + --hash=sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a \ + --hash=sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63 \ + --hash=sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884 \ + --hash=sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649 \ + --hash=sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810 \ + --hash=sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828 \ + --hash=sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4 \ + --hash=sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2 \ + --hash=sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd \ + --hash=sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5 \ + --hash=sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe \ + --hash=sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293 \ + --hash=sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e \ + --hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e \ + --hash=sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8 # via aiohttp -frozenlist==1.3.3 \ - --hash=sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c \ - --hash=sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f \ - --hash=sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a \ - --hash=sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784 \ - --hash=sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27 \ - --hash=sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d \ - --hash=sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3 \ - --hash=sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678 \ - --hash=sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a \ - --hash=sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483 \ - --hash=sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8 \ - --hash=sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf \ - --hash=sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99 \ - --hash=sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c \ - --hash=sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48 \ - --hash=sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5 \ - --hash=sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56 \ - --hash=sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e \ - --hash=sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1 \ - --hash=sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401 \ - --hash=sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4 \ - --hash=sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e \ - --hash=sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649 \ - --hash=sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a \ - --hash=sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d \ - --hash=sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0 \ - --hash=sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6 \ - --hash=sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d \ - --hash=sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b \ - --hash=sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6 \ - --hash=sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf \ - --hash=sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef \ - --hash=sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7 \ - --hash=sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842 \ - --hash=sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba \ - --hash=sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420 \ - --hash=sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b \ - --hash=sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d \ - --hash=sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332 \ - --hash=sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936 \ - --hash=sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816 \ - --hash=sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91 \ - --hash=sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420 \ - --hash=sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448 \ - --hash=sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411 \ - --hash=sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4 \ - --hash=sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32 \ - --hash=sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b \ - --hash=sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0 \ - --hash=sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530 \ - --hash=sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669 \ - --hash=sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7 \ - --hash=sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1 \ - --hash=sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5 \ - --hash=sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce \ - --hash=sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4 \ - --hash=sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e \ - --hash=sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2 \ - --hash=sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d \ - --hash=sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9 \ - --hash=sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642 \ - --hash=sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0 \ - --hash=sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703 \ - --hash=sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb \ - --hash=sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1 \ - --hash=sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13 \ - --hash=sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab \ - --hash=sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38 \ - --hash=sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb \ - --hash=sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb \ - --hash=sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81 \ - --hash=sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8 \ - --hash=sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd \ - --hash=sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4 +frozenlist==1.4.0 \ + --hash=sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6 \ + --hash=sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01 \ + --hash=sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251 \ + --hash=sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9 \ + --hash=sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b \ + --hash=sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87 \ + --hash=sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf \ + --hash=sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f \ + --hash=sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0 \ + --hash=sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2 \ + --hash=sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b \ + --hash=sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc \ + --hash=sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c \ + --hash=sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467 \ + --hash=sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9 \ + --hash=sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1 \ + --hash=sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a \ + --hash=sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79 \ + --hash=sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167 \ + --hash=sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300 \ + --hash=sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf \ + --hash=sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea \ + --hash=sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2 \ + --hash=sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab \ + --hash=sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3 \ + --hash=sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb \ + --hash=sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087 \ + --hash=sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc \ + --hash=sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8 \ + --hash=sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62 \ + --hash=sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f \ + --hash=sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326 \ + --hash=sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c \ + --hash=sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431 \ + --hash=sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963 \ + --hash=sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7 \ + --hash=sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef \ + --hash=sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3 \ + --hash=sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956 \ + --hash=sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781 \ + --hash=sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472 \ + --hash=sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc \ + --hash=sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839 \ + --hash=sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672 \ + --hash=sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3 \ + --hash=sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503 \ + --hash=sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d \ + --hash=sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8 \ + --hash=sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b \ + --hash=sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc \ + --hash=sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f \ + --hash=sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559 \ + --hash=sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b \ + --hash=sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95 \ + --hash=sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb \ + --hash=sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963 \ + --hash=sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919 \ + --hash=sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f \ + --hash=sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3 \ + --hash=sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1 \ + --hash=sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e # via # aiohttp # aiosignal @@ -343,7 +345,9 @@ multidict==6.0.4 \ # aiohttp # yarl pyyaml==6.0.1 \ + --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ @@ -351,7 +355,10 @@ pyyaml==6.0.1 \ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ + --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ @@ -359,9 +366,12 @@ pyyaml==6.0.1 \ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ + --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ @@ -376,7 +386,9 @@ pyyaml==6.0.1 \ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ @@ -384,79 +396,79 @@ pyyaml==6.0.1 \ --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f # via -r requirements.in -yarl==1.8.2 \ - --hash=sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87 \ - --hash=sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89 \ - --hash=sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a \ - --hash=sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08 \ - --hash=sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996 \ - --hash=sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077 \ - --hash=sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901 \ - --hash=sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e \ - --hash=sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee \ - --hash=sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574 \ - --hash=sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165 \ - --hash=sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634 \ - --hash=sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229 \ - --hash=sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b \ - --hash=sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f \ - --hash=sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7 \ - --hash=sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf \ - --hash=sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89 \ - --hash=sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0 \ - --hash=sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1 \ - --hash=sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe \ - --hash=sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf \ - --hash=sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76 \ - --hash=sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951 \ - --hash=sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863 \ - --hash=sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06 \ - --hash=sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562 \ - --hash=sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6 \ - --hash=sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c \ - --hash=sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e \ - --hash=sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1 \ - --hash=sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3 \ - --hash=sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3 \ - --hash=sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778 \ - --hash=sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8 \ - --hash=sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2 \ - --hash=sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b \ - --hash=sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d \ - --hash=sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f \ - --hash=sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c \ - --hash=sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581 \ - --hash=sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918 \ - --hash=sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c \ - --hash=sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e \ - --hash=sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220 \ - --hash=sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37 \ - --hash=sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739 \ - --hash=sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77 \ - --hash=sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6 \ - --hash=sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42 \ - --hash=sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946 \ - --hash=sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5 \ - --hash=sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d \ - --hash=sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146 \ - --hash=sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a \ - --hash=sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83 \ - --hash=sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef \ - --hash=sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80 \ - --hash=sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588 \ - --hash=sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5 \ - --hash=sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2 \ - --hash=sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef \ - --hash=sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826 \ - --hash=sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05 \ - --hash=sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516 \ - --hash=sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0 \ - --hash=sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4 \ - --hash=sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2 \ - --hash=sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0 \ - --hash=sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd \ - --hash=sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8 \ - --hash=sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b \ - --hash=sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1 \ - --hash=sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c +yarl==1.9.2 \ + --hash=sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571 \ + --hash=sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3 \ + --hash=sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3 \ + --hash=sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c \ + --hash=sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7 \ + --hash=sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04 \ + --hash=sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191 \ + --hash=sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea \ + --hash=sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4 \ + --hash=sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4 \ + --hash=sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095 \ + --hash=sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e \ + --hash=sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74 \ + --hash=sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef \ + --hash=sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33 \ + --hash=sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde \ + --hash=sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45 \ + --hash=sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf \ + --hash=sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b \ + --hash=sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac \ + --hash=sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0 \ + --hash=sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528 \ + --hash=sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716 \ + --hash=sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb \ + --hash=sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18 \ + --hash=sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72 \ + --hash=sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6 \ + --hash=sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582 \ + --hash=sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5 \ + --hash=sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368 \ + --hash=sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc \ + --hash=sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9 \ + --hash=sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be \ + --hash=sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a \ + --hash=sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80 \ + --hash=sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8 \ + --hash=sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6 \ + --hash=sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417 \ + --hash=sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574 \ + --hash=sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59 \ + --hash=sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608 \ + --hash=sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82 \ + --hash=sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1 \ + --hash=sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3 \ + --hash=sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d \ + --hash=sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8 \ + --hash=sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc \ + --hash=sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac \ + --hash=sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8 \ + --hash=sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955 \ + --hash=sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0 \ + --hash=sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367 \ + --hash=sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb \ + --hash=sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a \ + --hash=sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623 \ + --hash=sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2 \ + --hash=sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6 \ + --hash=sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7 \ + --hash=sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4 \ + --hash=sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051 \ + --hash=sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938 \ + --hash=sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8 \ + --hash=sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9 \ + --hash=sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3 \ + --hash=sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5 \ + --hash=sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9 \ + --hash=sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333 \ + --hash=sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185 \ + --hash=sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3 \ + --hash=sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560 \ + --hash=sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b \ + --hash=sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7 \ + --hash=sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78 \ + --hash=sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7 # via aiohttp diff --git a/examples/shared/websocket/Dockerfile b/examples/shared/websocket/Dockerfile index 59fa6a940dff..a72dd11675bf 100644 --- a/examples/shared/websocket/Dockerfile +++ b/examples/shared/websocket/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim@sha256:c618be84fc82aa8ba203abbb07218410b0f5b3c7cb6b4e7248fda7785d4f9946 as websocket-base +FROM debian:bullseye-slim@sha256:9bec46ecd98ce4bf8305840b021dda9b3e1f8494a0768c407e2b233180fa1466 as websocket-base ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ diff --git a/go.mod b/go.mod index 61b62bbe8bfe..0ed13eb7e160 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/envoyproxy/envoy -go 1.18 +go 1.20 require google.golang.org/protobuf v1.31.0 diff --git a/mobile/bazel/kotlin_test.bzl b/mobile/bazel/kotlin_test.bzl index d8666e7e014b..feee3044f55c 100644 --- a/mobile/bazel/kotlin_test.bzl +++ b/mobile/bazel/kotlin_test.bzl @@ -44,14 +44,13 @@ def jvm_flags(lib_name): # A basic macro to make it easier to declare and run kotlin tests which depend on a JNI lib # This will create the native .so binary (for linux) and a .jnilib (for macOS) look up -def envoy_mobile_jni_kt_test(name, srcs, native_deps = [], deps = [], repository = "", exec_properties = {}): - lib_name = native_lib_name(native_deps[0])[3:] +def envoy_mobile_jni_kt_test(name, srcs, native_lib_name, native_deps = [], deps = [], repository = "", exec_properties = {}): _internal_kt_test( name, srcs, deps, data = native_deps, - jvm_flags = jvm_flags(lib_name), + jvm_flags = jvm_flags(native_lib_name), repository = repository, exec_properties = exec_properties, ) @@ -76,8 +75,7 @@ def envoy_mobile_kt_test(name, srcs, deps = [], repository = "", exec_properties _internal_kt_test(name, srcs, deps, repository = repository, exec_properties = exec_properties) # A basic macro to run android based (robolectric) tests with native dependencies -def envoy_mobile_android_test(name, srcs, deps = [], native_deps = [], repository = "", exec_properties = {}): - lib_name = native_lib_name(native_deps[0])[3:] +def envoy_mobile_android_test(name, srcs, native_lib_name, deps = [], native_deps = [], repository = "", exec_properties = {}): android_library( name = name + "_test_lib", custom_package = "io.envoyproxy.envoymobile.test", @@ -114,6 +112,6 @@ def envoy_mobile_android_test(name, srcs, deps = [], native_deps = [], repositor manifest = repository + "//bazel:test_manifest.xml", custom_package = "io.envoyproxy.envoymobile.tests", test_class = "io.envoyproxy.envoymobile.bazel.EnvoyMobileTestSuite", - jvm_flags = jvm_flags(lib_name), + jvm_flags = jvm_flags(native_lib_name), exec_properties = exec_properties, ) diff --git a/mobile/envoy_build_config/extension_registry.cc b/mobile/envoy_build_config/extension_registry.cc index ee05200e0707..d07d5c93f6e8 100644 --- a/mobile/envoy_build_config/extension_registry.cc +++ b/mobile/envoy_build_config/extension_registry.cc @@ -208,6 +208,7 @@ void ExtensionRegistry::registerFactories() { Network::forceRegisterUdpDefaultWriterFactoryFactory(); Server::forceRegisterConnectionHandlerFactoryImpl(); Quic::forceRegisterQuicHttpServerConnectionFactoryImpl(); + Quic::forceRegisterEnvoyQuicCryptoServerStreamFactoryImpl(); Quic::forceRegisterQuicServerTransportSocketConfigFactory(); Quic::forceRegisterEnvoyQuicProofSourceFactoryImpl(); Quic::forceRegisterEnvoyDeterministicConnectionIdGeneratorConfigFactory(); diff --git a/mobile/library/cc/BUILD b/mobile/library/cc/BUILD index 193e6b40857b..ff53c0ae5576 100644 --- a/mobile/library/cc/BUILD +++ b/mobile/library/cc/BUILD @@ -1,4 +1,4 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", "envoy_select_envoy_mobile_request_compression") +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", "envoy_select_envoy_mobile_request_compression", "envoy_select_google_grpc") licenses(["notice"]) # Apache 2 @@ -49,6 +49,16 @@ envoy_cc_library( "@envoy_api//envoy/extensions/filters/http/compressor/v3:pkg_cc_proto", ], "@envoy", + ) + envoy_select_google_grpc( + [ + "@envoy//source/common/grpc:google_grpc_creds_lib", + "@envoy//source/extensions/config_subscription/grpc:grpc_collection_subscription_lib", + "@envoy//source/extensions/config_subscription/grpc:grpc_subscription_lib", + "@envoy//source/extensions/clusters/static:static_cluster_lib", + "@envoy//source/extensions/clusters/strict_dns:strict_dns_cluster_lib", + "@envoy//source/extensions/health_checkers/http:health_checker_lib", + ], + "@envoy", ), ) diff --git a/mobile/library/common/extensions/key_value/platform/config.cc b/mobile/library/common/extensions/key_value/platform/config.cc index aa9c7b26e683..8f4eb7f21e46 100644 --- a/mobile/library/common/extensions/key_value/platform/config.cc +++ b/mobile/library/common/extensions/key_value/platform/config.cc @@ -27,7 +27,9 @@ class PlatformInterfaceImpl : public PlatformInterface, std::string read(const std::string& key) const override { envoy_data bridged_key = Data::Utility::copyToBridgeData(key); envoy_data bridged_value = bridged_store_.read(bridged_key, bridged_store_.context); - return Data::Utility::copyToString(bridged_value); + std::string result = Data::Utility::copyToString(bridged_value); + release_envoy_data(bridged_value); + return result; } void save(const std::string& key, const std::string& contents) override { diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD b/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD index a4ea5c339633..6b15c61b29ba 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/BUILD @@ -81,9 +81,6 @@ envoy_mobile_kt_library( "RequestHeadersBuilderCompressionUtil.kt", ], "@envoy", - ) + envoy_select_enable_http3( - ["EngineBuilderHTTP3Util.kt"], - "@envoy", ), visibility = ["//visibility:public"], deps = [ diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilderHTTP3Util.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilderHTTP3Util.kt deleted file mode 100644 index 1d4c29d9b865..000000000000 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilderHTTP3Util.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.envoyproxy.envoymobile - -/** Utility to enable HTTP/3. */ -object EngineBuilderHTTP3Util { - /** - * Specify whether to enable experimental HTTP/3 (QUIC) support. Note the actual protocol will be - * negotiated with the upstream endpoint and so upstream support is still required for HTTP/3 to - * be utilized. - * - * @param enableHttp3 whether to enable HTTP/3. - * @return This builder. - */ - fun EngineBuilder.enableHttp3(enableHttp3: Boolean): EngineBuilder { - this.enableHttp3 = enableHttp3 - return this - } -} diff --git a/mobile/test/common/integration/BUILD b/mobile/test/common/integration/BUILD index 11c71efed0f1..052a73a748e9 100644 --- a/mobile/test/common/integration/BUILD +++ b/mobile/test/common/integration/BUILD @@ -22,6 +22,12 @@ envoy_cc_test( deps = [ ":base_client_integration_test_lib", "//test/common/mocks/common:common_mocks", + "@envoy//source/common/quic:active_quic_listener_lib", + "@envoy//source/common/quic:client_connection_factory_lib", + "@envoy//source/common/quic:quic_server_factory_lib", + "@envoy//source/common/quic:quic_transport_socket_factory_lib", + "@envoy//source/common/quic:udp_gso_batch_writer_lib", + "@envoy//source/extensions/udp_packet_writer/gso:config", ], ) diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index a92dfb19126d..cfd56325090f 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -1,9 +1,16 @@ +#include "source/common/quic/quic_transport_socket_factory.h" +#include "source/common/quic/server_codec_impl.h" #include "source/extensions/http/header_formatters/preserve_case/preserve_case_formatter.h" +#include "source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator_config.h" +#include "source/extensions/quic/crypto_stream/envoy_quic_crypto_server_stream.h" +#include "source/extensions/quic/proof_source/envoy_quic_proof_source_factory_impl.h" +#include "source/extensions/udp_packet_writer/default/config.h" #include "test/common/integration/base_client_integration_test.h" #include "test/common/mocks/common/mocks.h" #include "test/integration/autonomous_upstream.h" +#include "extension_registry.h" #include "library/common/data/utility.h" #include "library/common/main_interface.h" #include "library/common/network/proxy_settings.h" @@ -16,10 +23,40 @@ using testing::ReturnRef; namespace Envoy { namespace { +// The only thing this TestKeyValueStore does is return value_ when asked for +// initial loaded contents. +// In this case the TestKeyValueStore will be used for DNS and value will map +// www.lyft.com -> fake test upstream. +class TestKeyValueStore : public Envoy::Platform::KeyValueStore { +public: + absl::optional read(const std::string&) override { + ASSERT(!value_.empty()); + return value_; + } + void save(std::string, std::string) override {} + void remove(const std::string&) override {} + void addOrUpdate(absl::string_view, absl::string_view, absl::optional) {} + absl::optional get(absl::string_view) { return {}; } + void flush() {} + void iterate(::Envoy::KeyValueStore::ConstIterateCb) const {} + void setValue(std::string value) { value_ = value; } + +protected: + std::string value_; +}; + class ClientIntegrationTest : public BaseClientIntegrationTest, public testing::TestWithParam { public: - ClientIntegrationTest() : BaseClientIntegrationTest(/*ip_version=*/GetParam()) {} + ClientIntegrationTest() : BaseClientIntegrationTest(/*ip_version=*/GetParam()) { + // For H3 tests. + Network::forceRegisterUdpDefaultWriterFactoryFactory(); + Quic::forceRegisterEnvoyQuicCryptoServerStreamFactoryImpl(); + Quic::forceRegisterQuicHttpServerConnectionFactoryImpl(); + Quic::forceRegisterQuicServerTransportSocketConfigFactory(); + Quic::forceRegisterEnvoyQuicProofSourceFactoryImpl(); + Quic::forceRegisterEnvoyDeterministicConnectionIdGeneratorConfigFactory(); + } void SetUp() override { setUpstreamCount(config_helper_.bootstrap().static_resources().clusters_size()); @@ -30,13 +67,26 @@ class ClientIntegrationTest : public BaseClientIntegrationTest, } void createEnvoy() override { - BaseClientIntegrationTest::createEnvoy(); // Allow last minute addition of QUIC hints. This is done lazily as it must be done after // upstreams are created. if (add_quic_hints_) { auto address = fake_upstreams_[0]->localAddress(); - builder_.addQuicHint(address->ip()->addressAsString(), address->ip()->port()); + auto upstream_port = fake_upstreams_[0]->localAddress()->ip()->port(); + builder_.addQuicHint("www.lyft.com", upstream_port); + ASSERT(test_key_value_store_); + + // Force www.lyft.com to resolve to the fake upstream. It's the only domain + // name the certs work for so we want that in the request, but we need to + // fake resolution to not result in a request to the real www.lyft.com + std::string host = fmt::format("www.lyft.com:{}", upstream_port); + std::string cache_file_value_contents = + absl::StrCat(Network::Test::getLoopbackAddressUrlString(version_), ":", + fake_upstreams_[0]->localAddress()->ip()->port(), "|1000000|0"); + test_key_value_store_->setValue(absl::StrCat(host.length(), "\n", host, + cache_file_value_contents.length(), "\n", + cache_file_value_contents)); } + BaseClientIntegrationTest::createEnvoy(); } void TearDown() override { BaseClientIntegrationTest::TearDown(); } @@ -47,6 +97,7 @@ class ClientIntegrationTest : public BaseClientIntegrationTest, protected: std::unique_ptr helper_handle_; bool add_quic_hints_ = false; + std::shared_ptr test_key_value_store_; }; INSTANTIATE_TEST_SUITE_P(IpVersions, ClientIntegrationTest, @@ -80,7 +131,7 @@ void ClientIntegrationTest::basicTest() { ASSERT_EQ(cc_.on_headers_calls, 1); ASSERT_EQ(cc_.status, "200"); - ASSERT_EQ(cc_.on_data_calls, 2); + ASSERT_GE(cc_.on_data_calls, 1); ASSERT_EQ(cc_.on_complete_calls, 1); if (upstreamProtocol() == Http::CodecType::HTTP1) { ASSERT_EQ(cc_.on_header_consumed_bytes_from_response, 27); @@ -216,20 +267,38 @@ TEST_P(ClientIntegrationTest, BasicHttp2) { } // Do HTTP/3 without doing the alt-svc-over-HTTP/2 dance. -TEST_P(ClientIntegrationTest, DISABLED_Http3WithQuicHints) { +TEST_P(ClientIntegrationTest, Http3WithQuicHints) { + if (version_ != Network::Address::IpVersion::v4) { + // Loopback resolves to a v4 address. + return; + } EXPECT_CALL(helper_handle_->mock_helper(), isCleartextPermitted(_)).Times(0); EXPECT_CALL(helper_handle_->mock_helper(), validateCertificateChain(_, _)); EXPECT_CALL(helper_handle_->mock_helper(), cleanupAfterCertificateValidation()); setUpstreamProtocol(Http::CodecType::HTTP3); builder_.enablePlatformCertificatesValidation(true); + // Create a k-v store for DNS lookup which createEnvoy() will use to point + // www.lyft.com -> fake H3 backend. + test_key_value_store_ = std::make_shared(); + builder_.addKeyValueStore("reserved.platform_store", test_key_value_store_); + builder_.enableDnsCache(true, 1); upstream_tls_ = true; add_quic_hints_ = true; initialize(); + + auto address = fake_upstreams_[0]->localAddress(); + auto upstream_port = fake_upstreams_[0]->localAddress()->ip()->port(); + default_request_headers_.setHost(fmt::format("www.lyft.com:{}", upstream_port)); default_request_headers_.setScheme("https"); basicTest(); - // HTTP/3 + + // This verifies the H3 attempt was made due to the quic hints + std::string stats = engine_->dumpStats(); + EXPECT_TRUE((absl::StrContains(stats, "cluster.base.upstream_cx_http3_total: 1"))) << stats; + + // Make sure the client reported protocol was also HTTP/3. ASSERT_EQ(3, last_stream_final_intel_.upstream_protocol); } @@ -521,10 +590,6 @@ TEST_P(ClientIntegrationTest, Proxying) { TEST_P(ClientIntegrationTest, DirectResponse) { initialize(); - if (version_ == Network::Address::IpVersion::v6) { - // Localhost only resolves to an ipv4 address - alas no kernel happy eyeballs. - return; - } // Override to not validate stream intel. stream_prototype_->setOnComplete( diff --git a/mobile/test/java/integration/BUILD b/mobile/test/java/integration/BUILD index 73b0ea69f7bc..b908a5d7106f 100644 --- a/mobile/test/java/integration/BUILD +++ b/mobile/test/java/integration/BUILD @@ -16,8 +16,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", ], @@ -34,8 +39,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", "//test/java/io/envoyproxy/envoymobile/engine/testing", @@ -53,8 +63,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", "//test/java/io/envoyproxy/envoymobile/engine/testing", @@ -72,8 +87,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", "//test/java/io/envoyproxy/envoymobile/engine/testing", diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/BUILD b/mobile/test/java/io/envoyproxy/envoymobile/engine/BUILD index 071068ca711e..69211f0c623e 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/BUILD +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/BUILD @@ -9,8 +9,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//test/java/io/envoyproxy/envoymobile/engine/testing", @@ -44,8 +49,13 @@ envoy_mobile_android_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD index c25d9390608c..32e1afd35d2c 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/testing/BUILD @@ -32,8 +32,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ ":testing", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", diff --git a/mobile/test/java/io/envoyproxy/envoymobile/utilities/BUILD b/mobile/test/java/io/envoyproxy/envoymobile/utilities/BUILD index 67bbba18c17e..6447ebe5664e 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/utilities/BUILD +++ b/mobile/test/java/io/envoyproxy/envoymobile/utilities/BUILD @@ -12,8 +12,13 @@ envoy_mobile_android_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", diff --git a/mobile/test/java/org/chromium/net/BUILD b/mobile/test/java/org/chromium/net/BUILD index 6f67363de3dd..0fc8c35c119e 100644 --- a/mobile/test/java/org/chromium/net/BUILD +++ b/mobile/test/java/org/chromium/net/BUILD @@ -23,8 +23,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", @@ -48,8 +53,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", @@ -72,8 +82,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", @@ -96,8 +111,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", diff --git a/mobile/test/java/org/chromium/net/CronetUrlRequestTest.java b/mobile/test/java/org/chromium/net/CronetUrlRequestTest.java index 99d7de35ce27..1a32ccb6ba2d 100644 --- a/mobile/test/java/org/chromium/net/CronetUrlRequestTest.java +++ b/mobile/test/java/org/chromium/net/CronetUrlRequestTest.java @@ -1769,8 +1769,8 @@ public void testUploadFailsWithoutInitializingStream() throws Exception { callback.mError.getMessage()); } - private void throwOrCancel(FailureType failureType, ResponseStep failureStep, - boolean expectResponseInfo, boolean expectError) { + private TestUrlRequestCallback throwOrCancel(FailureType failureType, ResponseStep failureStep, + boolean expectError) { if (Log.isLoggable("TESTING", Log.VERBOSE)) { Log.v("TESTING", "Testing " + failureType + " during " + failureStep); } @@ -1790,7 +1790,6 @@ private void throwOrCancel(FailureType failureType, ResponseStep failureStep, assertEquals(ResponseStep.ON_FAILED, callback.mResponseStep); } assertTrue(urlRequest.isDone()); - assertEquals(expectResponseInfo, callback.mResponseInfo != null); assertEquals(expectError, callback.mError != null); assertEquals(expectError, callback.mOnErrorCalled); // When failureType is FailureType.CANCEL_ASYNC_WITHOUT_PAUSE and failureStep is @@ -1803,6 +1802,13 @@ private void throwOrCancel(FailureType failureType, ResponseStep failureStep, failureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE, callback.mOnCanceledCalled); } + return callback; + } + + private void throwOrCancel(FailureType failureType, ResponseStep failureStep, + boolean expectResponseInfo, boolean expectError) { + TestUrlRequestCallback callback = throwOrCancel(failureType, failureStep, expectError); + assertEquals(expectResponseInfo, callback.mResponseInfo != null); } @Test @@ -1811,20 +1817,17 @@ private void throwOrCancel(FailureType failureType, ResponseStep failureStep, public void testFailures() throws Exception { throwOrCancel(FailureType.CANCEL_SYNC, ResponseStep.ON_RECEIVED_REDIRECT, false, false); throwOrCancel(FailureType.CANCEL_ASYNC, ResponseStep.ON_RECEIVED_REDIRECT, false, false); - throwOrCancel(FailureType.CANCEL_ASYNC_WITHOUT_PAUSE, ResponseStep.ON_RECEIVED_REDIRECT, false, - false); + throwOrCancel(FailureType.CANCEL_ASYNC_WITHOUT_PAUSE, ResponseStep.ON_RECEIVED_REDIRECT, false); throwOrCancel(FailureType.THROW_SYNC, ResponseStep.ON_RECEIVED_REDIRECT, false, true); throwOrCancel(FailureType.CANCEL_SYNC, ResponseStep.ON_RESPONSE_STARTED, true, false); throwOrCancel(FailureType.CANCEL_ASYNC, ResponseStep.ON_RESPONSE_STARTED, true, false); - throwOrCancel(FailureType.CANCEL_ASYNC_WITHOUT_PAUSE, ResponseStep.ON_RESPONSE_STARTED, true, - false); + throwOrCancel(FailureType.CANCEL_ASYNC_WITHOUT_PAUSE, ResponseStep.ON_RESPONSE_STARTED, false); throwOrCancel(FailureType.THROW_SYNC, ResponseStep.ON_RESPONSE_STARTED, true, true); throwOrCancel(FailureType.CANCEL_SYNC, ResponseStep.ON_READ_COMPLETED, true, false); throwOrCancel(FailureType.CANCEL_ASYNC, ResponseStep.ON_READ_COMPLETED, true, false); - throwOrCancel(FailureType.CANCEL_ASYNC_WITHOUT_PAUSE, ResponseStep.ON_READ_COMPLETED, true, - false); + throwOrCancel(FailureType.CANCEL_ASYNC_WITHOUT_PAUSE, ResponseStep.ON_READ_COMPLETED, false); throwOrCancel(FailureType.THROW_SYNC, ResponseStep.ON_READ_COMPLETED, true, true); } diff --git a/mobile/test/java/org/chromium/net/impl/BUILD b/mobile/test/java/org/chromium/net/impl/BUILD index 51189b5a5091..c728a40003c3 100644 --- a/mobile/test/java/org/chromium/net/impl/BUILD +++ b/mobile/test/java/org/chromium/net/impl/BUILD @@ -21,8 +21,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", diff --git a/mobile/test/java/org/chromium/net/testing/BUILD b/mobile/test/java/org/chromium/net/testing/BUILD index e17fc3b4c738..88d9eb76769a 100644 --- a/mobile/test/java/org/chromium/net/testing/BUILD +++ b/mobile/test/java/org/chromium/net/testing/BUILD @@ -1,5 +1,5 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") load("@build_bazel_rules_android//android:rules.bzl", "android_library") +load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") load("@envoy_mobile//bazel:kotlin_test.bzl", "envoy_mobile_android_test") licenses(["notice"]) # Apache 2 @@ -62,8 +62,13 @@ envoy_mobile_android_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ ":testing", "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", @@ -85,8 +90,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ ":testing", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", @@ -104,8 +114,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ ":testing", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", diff --git a/mobile/test/java/org/chromium/net/testing/Http2TestServerTest.java b/mobile/test/java/org/chromium/net/testing/Http2TestServerTest.java index 411a17f4a9b9..cd79c3515d1e 100644 --- a/mobile/test/java/org/chromium/net/testing/Http2TestServerTest.java +++ b/mobile/test/java/org/chromium/net/testing/Http2TestServerTest.java @@ -8,7 +8,6 @@ import static org.junit.Assert.assertNotNull; import android.content.Context; import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; import io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary; import io.envoyproxy.envoymobile.AndroidEngineBuilder; import io.envoyproxy.envoymobile.Engine; @@ -29,17 +28,13 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import java.nio.charset.StandardCharsets; -import org.chromium.net.testing.CertTestUtil; import io.envoyproxy.envoymobile.utilities.FakeX509Util; @RunWith(RobolectricTestRunner.class) @@ -96,10 +91,10 @@ private void getSchemeIsHttps(boolean enablePlatformCertificatesValidation, Response response = sendRequest(requestScenario); + assertThat(response.getEnvoyError()).isNull(); assertThat(response.getHeaders().getHttpStatus()).isEqualTo(200); assertThat(response.getBodyAsString()).contains(":scheme: https"); assertThat(response.getHeaders().value("x-envoy-upstream-alpn")).containsExactly("h2"); - assertThat(response.getEnvoyError()).isNull(); } @Test @@ -107,6 +102,7 @@ public void testGetRequest() throws Exception { getSchemeIsHttps(false, TrustChainVerification.ACCEPT_UNTRUSTED); } + @Ignore @Test public void testGetRequestWithPlatformCertValidatorSuccess() throws Exception { getSchemeIsHttps(true, TrustChainVerification.VERIFY_TRUST_CHAIN); diff --git a/mobile/test/java/org/chromium/net/urlconnection/BUILD b/mobile/test/java/org/chromium/net/urlconnection/BUILD index 8d17d77fe40e..9766dcb403d9 100644 --- a/mobile/test/java/org/chromium/net/urlconnection/BUILD +++ b/mobile/test/java/org/chromium/net/urlconnection/BUILD @@ -23,8 +23,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", diff --git a/mobile/test/kotlin/integration/BUILD b/mobile/test/kotlin/integration/BUILD index 134f98dd18d1..cc504cea3408 100644 --- a/mobile/test/kotlin/integration/BUILD +++ b/mobile/test/kotlin/integration/BUILD @@ -1,5 +1,5 @@ -load("@envoy_mobile//bazel:kotlin_test.bzl", "envoy_mobile_android_test", "envoy_mobile_jni_kt_test") load("@envoy_mobile//bazel:kotlin_lib.bzl", "envoy_mobile_kt_library") +load("@envoy_mobile//bazel:kotlin_test.bzl", "envoy_mobile_android_test", "envoy_mobile_jni_kt_test") envoy_mobile_jni_kt_test( name = "engine_start_test", @@ -8,8 +8,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -22,8 +27,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -36,8 +46,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -50,8 +65,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -64,8 +84,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -79,8 +104,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -93,8 +123,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -107,8 +142,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -121,8 +161,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -135,8 +180,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -149,8 +199,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -167,8 +222,13 @@ envoy_mobile_jni_kt_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -185,8 +245,13 @@ envoy_mobile_jni_kt_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -199,8 +264,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -213,8 +283,13 @@ envoy_mobile_android_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//test/java/io/envoyproxy/envoymobile/engine/testing", @@ -228,8 +303,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -242,8 +322,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -256,8 +341,13 @@ envoy_mobile_jni_kt_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], @@ -275,8 +365,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", @@ -290,8 +385,13 @@ envoy_mobile_android_test( ], native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ ":test_utilities", "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", diff --git a/mobile/test/kotlin/integration/proxying/BUILD b/mobile/test/kotlin/integration/proxying/BUILD index a19133bdbce3..9579c256cc41 100644 --- a/mobile/test/kotlin/integration/proxying/BUILD +++ b/mobile/test/kotlin/integration/proxying/BUILD @@ -23,8 +23,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_and_listener_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", @@ -44,8 +49,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_and_listener_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", @@ -65,8 +75,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_and_listener_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", @@ -86,8 +101,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_and_listener_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", @@ -107,8 +127,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_and_listener_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", @@ -128,8 +153,13 @@ envoy_mobile_android_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_and_listener_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_and_listener_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", "//library/kotlin/io/envoyproxy/envoymobile:envoy_lib", diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/BUILD b/mobile/test/kotlin/io/envoyproxy/envoymobile/BUILD index 4f052d6aceef..05d87d1b14be 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/BUILD +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/BUILD @@ -13,8 +13,13 @@ envoy_mobile_jni_kt_test( }, native_deps = [ "//test/common/jni:libenvoy_jni_with_test_extensions.so", - "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", - ], + ] + select({ + "@platforms//os:macos": [ + "//test/common/jni:libenvoy_jni_with_test_extensions_jnilib", + ], + "//conditions:default": [], + }), + native_lib_name = "envoy_jni_with_test_extensions", deps = [ "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], diff --git a/mobile/third_party/rbe_configs/config/BUILD b/mobile/third_party/rbe_configs/config/BUILD index 558a7ce5f01b..77ce2843c8ac 100644 --- a/mobile/third_party/rbe_configs/config/BUILD +++ b/mobile/third_party/rbe_configs/config/BUILD @@ -42,7 +42,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e", "OSFamily": "Linux", "Pool": "linux", }, @@ -57,7 +57,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e", "OSFamily": "Linux", "Pool": "linux", # Necessary to workaround https://github.com/google/sanitizers/issues/916, otherwise, dangling threads in the diff --git a/mobile/tools/what_to_run.sh b/mobile/tools/what_to_run.sh index 8daacd10892e..e50ac44bbd85 100755 --- a/mobile/tools/what_to_run.sh +++ b/mobile/tools/what_to_run.sh @@ -5,7 +5,7 @@ set -euo pipefail BRANCH_NAME="$GITHUB_REF_NAME" BASE_COMMIT="$(git merge-base origin/main HEAD)" CHANGED_FILES="$(git diff "${BASE_COMMIT}" --name-only)" -CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml^tools/code_format/check_format.py' +CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml|^tools/code_format/check_format.py|bazel/external/quiche.BUILD' # The logic in this file is roughly: # diff --git a/source/common/common/fine_grain_logger.cc b/source/common/common/fine_grain_logger.cc index 3d9f53eb62d0..5fd65e845740 100644 --- a/source/common/common/fine_grain_logger.cc +++ b/source/common/common/fine_grain_logger.cc @@ -93,14 +93,9 @@ std::string FineGrainLogContext::listFineGrainLoggers() ABSL_LOCKS_EXCLUDED(fine void FineGrainLogContext::setAllFineGrainLoggers(spdlog::level::level_enum level) ABSL_LOCKS_EXCLUDED(fine_grain_log_lock_) { absl::ReaderMutexLock l(&fine_grain_log_lock_); - if (verbosity_update_info_.empty()) { - for (const auto& it : *fine_grain_log_map_) { - it.second->set_level(level); - } - } else { - for (const auto& [key, logger] : *fine_grain_log_map_) { - logger->set_level(getLogLevel(key)); - } + verbosity_update_info_.clear(); + for (const auto& it : *fine_grain_log_map_) { + it.second->set_level(level); } } @@ -174,9 +169,6 @@ spdlog::logger* FineGrainLogContext::createLogger(const std::string& key) void FineGrainLogContext::updateVerbosityDefaultLevel(level_enum level) { { absl::WriterMutexLock wl(&fine_grain_log_lock_); - if (level == verbosity_default_level_) { - return; - } verbosity_default_level_ = level; } @@ -229,18 +221,17 @@ level_enum FineGrainLogContext::getLogLevel(absl::string_view file) const { } } - absl::string_view stem = file, stem_basename = basename; + absl::string_view stem_basename = basename; { const size_t sep = stem_basename.find('.'); if (sep != stem_basename.npos) { - stem.remove_suffix(stem_basename.size() - sep); stem_basename.remove_suffix(stem_basename.size() - sep); } } for (const auto& info : verbosity_update_info_) { if (info.update_is_path) { // If there are any slashes in the pattern, try to match the full path name. - if (safeFileNameMatch(info.update_pattern, stem)) { + if (safeFileNameMatch(info.update_pattern, file)) { return info.log_level; } } else if (safeFileNameMatch(info.update_pattern, stem_basename)) { diff --git a/source/common/common/logger.cc b/source/common/common/logger.cc index 77e499c9e702..ebbd651f7cdb 100644 --- a/source/common/common/logger.cc +++ b/source/common/common/logger.cc @@ -209,9 +209,11 @@ void Context::changeAllLogLevels(spdlog::level::level_enum level) { Registry::setLogLevel(level); } else { // Level setting with Fine-Grain Logger. - FINE_GRAIN_LOG(info, "change all log levels: level='{}'", - spdlog::level::level_string_views[level]); - getFineGrainLogContext().setAllFineGrainLoggers(level); + FINE_GRAIN_LOG( + info, + "change all log levels and default verbosity level for fine grain loggers: level='{}'", + spdlog::level::level_string_views[level]); + getFineGrainLogContext().updateVerbosityDefaultLevel(level); } } diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 756c3f942a5a..f3ad2bc01bc7 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -54,6 +54,7 @@ const static bool should_log = true; FUNCTION(file) \ FUNCTION(filter) \ FUNCTION(forward_proxy) \ + FUNCTION(geolocation) \ FUNCTION(grpc) \ FUNCTION(happy_eyeballs) \ FUNCTION(hc) \ diff --git a/source/common/config/metadata.h b/source/common/config/metadata.h index 25978f51e188..ffa24cb19711 100644 --- a/source/common/config/metadata.h +++ b/source/common/config/metadata.h @@ -141,5 +141,19 @@ template class TypedMetadataImpl : public TypedMetadata absl::node_hash_map> data_; }; +// MetadataPack is struct that contains both the proto and typed metadata. +template struct MetadataPack { + MetadataPack(const envoy::config::core::v3::Metadata& metadata) + : proto_metadata_(metadata), typed_metadata_(proto_metadata_) {} + MetadataPack() : proto_metadata_(), typed_metadata_(proto_metadata_) {} + + const envoy::config::core::v3::Metadata proto_metadata_; + const TypedMetadataImpl typed_metadata_; +}; + +template using MetadataPackPtr = std::unique_ptr>; +template +using MetadataPackSharedPtr = std::shared_ptr>; + } // namespace Config } // namespace Envoy diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 547817a1411d..d7c47148a114 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -679,13 +680,16 @@ void ConnectionManagerImpl::maybeDrainDueToPrematureResets() { runtime_.snapshot().getInteger(ConnectionManagerImpl::PrematureResetTotalStreamCountKey, 500); if (closed_non_internally_destroyed_requests_ < limit) { - return; - } - - if (static_cast(number_premature_stream_resets_) / - closed_non_internally_destroyed_requests_ < - .5) { - return; + // Even though the total number of streams have not reached `limit`, check if the number of bad + // streams is high enough that even if every subsequent stream is good, the connection + // would be closed once the limit is reached, and if so close the connection now. + if (number_premature_stream_resets_ * 2 < limit) { + return; + } + } else { + if (number_premature_stream_resets_ * 2 < closed_non_internally_destroyed_requests_) { + return; + } } if (drain_state_ == DrainState::NotDraining) { @@ -2240,6 +2244,8 @@ bool ConnectionManagerImpl::ActiveStream::onDeferredRequestProcessing() { if (end_stream) { return true; } + // Filter manager will return early from decodeData and decodeTrailers if + // request has completed. if (deferred_data_ != nullptr) { end_stream = state_.deferred_end_stream_ && request_trailers_ == nullptr; filter_manager_.decodeData(*deferred_data_, end_stream); @@ -2269,19 +2275,26 @@ bool ConnectionManagerImpl::shouldDeferRequestProxyingToNextIoCycle() { } void ConnectionManagerImpl::onDeferredRequestProcessing() { + if (streams_.empty()) { + return; + } requests_during_dispatch_count_ = 1; // 1 stream is always let through // Streams are inserted at the head of the list. As such process deferred - // streams at the back of the list first. - for (auto reverse_iter = streams_.rbegin(); reverse_iter != streams_.rend();) { - auto& stream_ptr = *reverse_iter; - // Move the iterator to the next item in case the `onDeferredRequestProcessing` call removes the - // stream from the list. - ++reverse_iter; - bool was_deferred = stream_ptr->onDeferredRequestProcessing(); + // streams in the reverse order. + auto reverse_iter = std::prev(streams_.end()); + bool at_first_element = false; + do { + at_first_element = reverse_iter == streams_.begin(); + // Move the iterator to the previous item in case the `onDeferredRequestProcessing` call removes + // the stream from the list. + auto previous_element = std::prev(reverse_iter); + bool was_deferred = (*reverse_iter)->onDeferredRequestProcessing(); if (was_deferred && shouldDeferRequestProxyingToNextIoCycle()) { break; } - } + reverse_iter = previous_element; + // TODO(yanavlasov): see if `rend` can be used. + } while (!at_first_element); } } // namespace Http diff --git a/source/common/http/http_server_properties_cache_manager_impl.cc b/source/common/http/http_server_properties_cache_manager_impl.cc index 307e8991eb55..a640fcd3be6a 100644 --- a/source/common/http/http_server_properties_cache_manager_impl.cc +++ b/source/common/http/http_server_properties_cache_manager_impl.cc @@ -61,7 +61,7 @@ HttpServerPropertiesCacheSharedPtr HttpServerPropertiesCacheManagerImpl::getCach entry : options.prepopulated_entries()) { const HttpServerPropertiesCacheImpl::Origin origin = {"https", entry.hostname(), entry.port()}; std::vector protocol = { - {"h3", entry.hostname(), entry.port(), + {"h3", "", entry.port(), dispatcher.timeSource().monotonicTime() + std::chrono::hours(168)}}; OptRef> existing_protocols = new_cache->findAlternatives(origin); diff --git a/source/common/http/null_route_impl.h b/source/common/http/null_route_impl.h index 8caef5df4d70..d8d4b31bd4b2 100644 --- a/source/common/http/null_route_impl.h +++ b/source/common/http/null_route_impl.h @@ -43,6 +43,12 @@ struct NullCommonConfig : public Router::CommonConfig { bool usesVhds() const override { return false; } bool mostSpecificHeaderMutationsWins() const override { return false; } uint32_t maxDirectResponseBodySizeBytes() const override { return 0; } + const envoy::config::core::v3::Metadata& metadata() const override { + return Router::DefaultRouteMetadataPack::get().proto_metadata_; + } + const Envoy::Config::TypedMetadata& typedMetadata() const override { + return Router::DefaultRouteMetadataPack::get().typed_metadata_; + } static const std::list internal_only_headers_; }; @@ -64,6 +70,13 @@ struct NullVirtualHost : public Router::VirtualHost { void traversePerFilterConfig( const std::string&, std::function) const override {} + const envoy::config::core::v3::Metadata& metadata() const override { + return Router::DefaultRouteMetadataPack::get().proto_metadata_; + } + const Envoy::Config::TypedMetadata& typedMetadata() const override { + return Router::DefaultRouteMetadataPack::get().typed_metadata_; + } + static const NullRateLimitPolicy rate_limit_policy_; static const NullCommonConfig route_configuration_; }; @@ -212,8 +225,7 @@ struct NullRouteImpl : public Router::Route { const Protobuf::RepeatedPtrField& hash_policy = {}, const absl::optional& retry_policy = {}) - : route_entry_(cluster_name, singleton_manager, timeout, hash_policy, retry_policy), - typed_metadata_({}) {} + : route_entry_(cluster_name, singleton_manager, timeout, hash_policy, retry_policy) {} // Router::Route const Router::DirectResponseEntry* directResponseEntry() const override { return nullptr; } @@ -227,14 +239,16 @@ struct NullRouteImpl : public Router::Route { void traversePerFilterConfig( const std::string&, std::function) const override {} - const envoy::config::core::v3::Metadata& metadata() const override { return metadata_; } - const Envoy::Config::TypedMetadata& typedMetadata() const override { return typed_metadata_; } + const envoy::config::core::v3::Metadata& metadata() const override { + return Router::DefaultRouteMetadataPack::get().proto_metadata_; + } + const Envoy::Config::TypedMetadata& typedMetadata() const override { + return Router::DefaultRouteMetadataPack::get().typed_metadata_; + } bool filterDisabled(absl::string_view) const override { return false; } const std::string& routeName() const override { return EMPTY_STRING; } RouteEntryImpl route_entry_; - const envoy::config::core::v3::Metadata metadata_; - const Envoy::Config::TypedMetadataImpl typed_metadata_; }; } // namespace Http diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index b4aa1262cacf..ef15d3436155 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -142,16 +142,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "spdy_server_push_utils_for_envoy_lib", - srcs = ["spdy_server_push_utils_for_envoy.cc"], - tags = ["nofips"], - deps = [ - "//source/common/common:assert_lib", - "@com_github_google_quiche//:quic_core_http_spdy_server_push_utils_header", - ], -) - envoy_cc_library( name = "envoy_quic_stream_lib", hdrs = ["envoy_quic_stream.h"], diff --git a/source/common/quic/client_connection_factory_impl.cc b/source/common/quic/client_connection_factory_impl.cc index 269dbbdf078f..d3efeba84e51 100644 --- a/source/common/quic/client_connection_factory_impl.cc +++ b/source/common/quic/client_connection_factory_impl.cc @@ -59,10 +59,9 @@ std::unique_ptr createQuicNetworkConnection( // QUICHE client session always use the 1st version to start handshake. return std::make_unique( - config, quic_versions, std::move(connection), server_id, std::move(crypto_config), - &info_impl->push_promise_index_, dispatcher, info_impl->buffer_limit_, - info_impl->crypto_stream_factory_, quic_stat_names, rtt_cache, scope, - transport_socket_options); + config, quic_versions, std::move(connection), server_id, std::move(crypto_config), dispatcher, + info_impl->buffer_limit_, info_impl->crypto_stream_factory_, quic_stat_names, rtt_cache, + scope, transport_socket_options); } } // namespace Quic diff --git a/source/common/quic/client_connection_factory_impl.h b/source/common/quic/client_connection_factory_impl.h index 4f21edb3038b..e2b8eede0e78 100644 --- a/source/common/quic/client_connection_factory_impl.h +++ b/source/common/quic/client_connection_factory_impl.h @@ -12,7 +12,6 @@ #include "source/extensions/quic/crypto_stream/envoy_quic_crypto_client_stream.h" #include "source/extensions/transport_sockets/tls/ssl_socket.h" -#include "quiche/quic/core/http/quic_client_push_promise_index.h" #include "quiche/quic/core/quic_utils.h" namespace Envoy { @@ -29,9 +28,6 @@ struct PersistentQuicInfoImpl : public Http::PersistentQuicInfo { quic::QuicConfig quic_config_; // The connection send buffer limits from cluster config. const uint32_t buffer_limit_; - // This arguably should not be shared across connections but as Envoy doesn't - // support push promise it's really moot point. - quic::QuicClientPushPromiseIndex push_promise_index_; // Hard code with the default crypto stream as there's no pluggable crypto for upstream Envoy. EnvoyQuicCryptoClientStreamFactoryImpl crypto_stream_factory_; }; diff --git a/source/common/quic/envoy_quic_client_connection.cc b/source/common/quic/envoy_quic_client_connection.cc index 8e2ba1aa49b2..09a8745572bd 100644 --- a/source/common/quic/envoy_quic_client_connection.cc +++ b/source/common/quic/envoy_quic_client_connection.cc @@ -106,6 +106,7 @@ void EnvoyQuicClientConnection::switchConnectionSocket( connection_socket->connectionInfoProvider().remoteAddress()->ip()); // The old socket is not closed in this call, because it could still receive useful packets. + num_socket_switches_++; setConnectionSocket(std::move(connection_socket)); setUpConnectionSocket(*connectionSocket(), delegate_); MigratePath(self_address, peer_address, writer.release(), true); @@ -117,7 +118,8 @@ void EnvoyQuicClientConnection::OnPathDegradingDetected() { } void EnvoyQuicClientConnection::maybeMigratePort() { - if (!IsHandshakeConfirmed() || HasPendingPathValidation() || !migrate_port_on_path_degrading_) { + if (!IsHandshakeConfirmed() || HasPendingPathValidation() || !migrate_port_on_path_degrading_ || + num_socket_switches_ >= kMaxNumSocketSwitches) { return; } @@ -172,6 +174,7 @@ void EnvoyQuicClientConnection::onPathValidationSuccess( peer_address() == envoy_context->peer_address()) { // probing_socket will be set as the new default socket. But old sockets are still able to // receive packets. + num_socket_switches_++; setConnectionSocket(std::move(probing_socket)); return; } diff --git a/source/common/quic/envoy_quic_client_connection.h b/source/common/quic/envoy_quic_client_connection.h index 5dc469049fd7..2ddd3e523e76 100644 --- a/source/common/quic/envoy_quic_client_connection.h +++ b/source/common/quic/envoy_quic_client_connection.h @@ -12,6 +12,9 @@ namespace Envoy { namespace Quic { +// Limits the max number of sockets created. +constexpr uint8_t kMaxNumSocketSwitches = 5; + class PacketsToReadDelegate { public: virtual ~PacketsToReadDelegate() = default; @@ -143,6 +146,7 @@ class EnvoyQuicClientConnection : public quic::QuicConnection, uint32_t packets_dropped_{0}; Event::Dispatcher& dispatcher_; bool migrate_port_on_path_degrading_{false}; + uint8_t num_socket_switches_{0}; }; } // namespace Quic diff --git a/source/common/quic/envoy_quic_client_session.cc b/source/common/quic/envoy_quic_client_session.cc index 45e6f620de00..d345a4fde0b9 100644 --- a/source/common/quic/envoy_quic_client_session.cc +++ b/source/common/quic/envoy_quic_client_session.cc @@ -46,8 +46,7 @@ class EnvoyQuicProofVerifyContextImpl : public EnvoyQuicProofVerifyContext { EnvoyQuicClientSession::EnvoyQuicClientSession( const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, std::unique_ptr connection, const quic::QuicServerId& server_id, - std::shared_ptr crypto_config, - quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, + std::shared_ptr crypto_config, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit, EnvoyQuicCryptoClientStreamFactoryInterface& crypto_stream_factory, QuicStatNames& quic_stat_names, OptRef rtt_cache, Stats::Scope& scope, @@ -59,7 +58,7 @@ EnvoyQuicClientSession::EnvoyQuicClientSession( dispatcher.timeSource(), connection->connectionSocket()->connectionInfoProviderSharedPtr())), quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, - crypto_config.get(), push_promise_index), + crypto_config.get()), crypto_config_(crypto_config), crypto_stream_factory_(crypto_stream_factory), quic_stat_names_(quic_stat_names), rtt_cache_(rtt_cache), scope_(scope), transport_socket_options_(transport_socket_options) { diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h index a5dff63fb883..4e615fc5daf2 100644 --- a/source/common/quic/envoy_quic_client_session.h +++ b/source/common/quic/envoy_quic_client_session.h @@ -26,8 +26,7 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, EnvoyQuicClientSession( const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, std::unique_ptr connection, const quic::QuicServerId& server_id, - std::shared_ptr crypto_config, - quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, + std::shared_ptr crypto_config, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit, EnvoyQuicCryptoClientStreamFactoryInterface& crypto_stream_factory, QuicStatNames& quic_stat_names, OptRef rtt_cache, diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc index 187ce518720d..39f16f4cca4a 100644 --- a/source/common/quic/platform/quiche_flags_impl.cc +++ b/source/common/quic/platform/quiche_flags_impl.cc @@ -63,7 +63,6 @@ template <> constexpr int32_t maybeOverride(absl::string_view name, int // Flag definitions #define QUIC_FLAG(flag, value) ABSL_FLAG(bool, envoy_##flag, maybeOverride(#flag, value), ""); #include "quiche/quic/core/quic_flags_list.h" -QUIC_FLAG(quic_reloadable_flag_spdy_testonly_default_false, false) // NOLINT QUIC_FLAG(quic_reloadable_flag_spdy_testonly_default_true, true) // NOLINT QUIC_FLAG(quic_restart_flag_spdy_testonly_default_false, false) // NOLINT QUIC_FLAG(quic_restart_flag_spdy_testonly_default_true, true) // NOLINT diff --git a/source/common/quic/quic_network_connection.h b/source/common/quic/quic_network_connection.h index 3a6783d75ae1..828f3eb1f761 100644 --- a/source/common/quic/quic_network_connection.h +++ b/source/common/quic/quic_network_connection.h @@ -61,7 +61,6 @@ class QuicNetworkConnection : protected Logger::Loggable // Hosts a list of active sockets, while only the last one is used for writing data. // Hosts a single default socket upon construction. New sockets can be pushed in later as a result // of QUIC connection migration. - // TODO(renjietang): Impose an upper limit. std::vector connection_sockets_; // Points to an instance of EnvoyQuicServerSession or EnvoyQuicClientSession. Network::Connection* envoy_connection_{nullptr}; diff --git a/source/common/quic/spdy_server_push_utils_for_envoy.cc b/source/common/quic/spdy_server_push_utils_for_envoy.cc deleted file mode 100644 index 70029bdef076..000000000000 --- a/source/common/quic/spdy_server_push_utils_for_envoy.cc +++ /dev/null @@ -1,39 +0,0 @@ -#include "quiche/quic/core/http/spdy_server_push_utils.h" - -// NOLINT(namespace-envoy) - -// This file has a substitute definition for -// quiche/quic/core/http/spdy_server_push_utils.cc which depends on GURL. -// Since Envoy doesn't support server push, these functions shouldn't be -// executed at all. - -using spdy::Http2HeaderBlock; - -namespace quic { - -// static -// NOLINTNEXTLINE(readability-identifier-naming) -std::string SpdyServerPushUtils::GetPromisedUrlFromHeaders(const Http2HeaderBlock& /*headers*/) { - return ""; -} - -// static -std::string -// NOLINTNEXTLINE(readability-identifier-naming) -SpdyServerPushUtils::GetPromisedHostNameFromHeaders(const Http2HeaderBlock& /*headers*/) { - return ""; -} - -// static -// NOLINTNEXTLINE(readability-identifier-naming) -bool SpdyServerPushUtils::PromisedUrlIsValid(const Http2HeaderBlock& /*headers*/) { return false; } - -// static -// NOLINTNEXTLINE(readability-identifier-naming) -std::string SpdyServerPushUtils::GetPushPromiseUrl(absl::string_view /*scheme*/, - absl::string_view /*authority*/, - absl::string_view /*path*/) { - return ""; -} - -} // namespace quic diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 59b8747de748..7ca8f9edfb95 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -543,8 +543,7 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, route.response_headers_to_remove()); } if (route.has_metadata()) { - metadata_ = std::make_unique(route.metadata()); - typed_metadata_ = std::make_unique(route.metadata()); + metadata_ = std::make_unique(route.metadata()); } if (route.route().has_metadata_match()) { const auto filter_it = route.route().metadata_match().filter_metadata().find( @@ -1386,6 +1385,15 @@ void RouteEntryImplBase::traversePerFilterConfig( } } +const envoy::config::core::v3::Metadata& RouteEntryImplBase::metadata() const { + return metadata_ != nullptr ? metadata_->proto_metadata_ + : DefaultRouteMetadataPack::get().proto_metadata_; +} +const Envoy::Config::TypedMetadata& RouteEntryImplBase::typedMetadata() const { + return metadata_ != nullptr ? metadata_->typed_metadata_ + : DefaultRouteMetadataPack::get().typed_metadata_; +} + RouteEntryImplBase::WeightedClusterEntry::WeightedClusterEntry( const RouteEntryImplBase* parent, const std::string& runtime_key, Server::Configuration::ServerFactoryContext& factory_context, @@ -1713,6 +1721,10 @@ CommonVirtualHostImpl::CommonVirtualHostImpl( if (virtual_host.has_cors()) { cors_policy_ = std::make_unique(virtual_host.cors(), factory_context.runtime()); } + + if (virtual_host.has_metadata()) { + metadata_ = std::make_unique(virtual_host.metadata()); + } } CommonVirtualHostImpl::VirtualClusterEntry::VirtualClusterEntry( @@ -1759,6 +1771,15 @@ void CommonVirtualHostImpl::traversePerFilterConfig( } } +const envoy::config::core::v3::Metadata& CommonVirtualHostImpl::metadata() const { + return metadata_ != nullptr ? metadata_->proto_metadata_ + : DefaultRouteMetadataPack::get().proto_metadata_; +} +const Envoy::Config::TypedMetadata& CommonVirtualHostImpl::typedMetadata() const { + return metadata_ != nullptr ? metadata_->typed_metadata_ + : DefaultRouteMetadataPack::get().typed_metadata_; +} + VirtualHostImpl::VirtualHostImpl( const envoy::config::route::v3::VirtualHost& virtual_host, const OptionalHttpFilters& optional_http_filters, @@ -2084,6 +2105,10 @@ CommonConfigImpl::CommonConfigImpl(const envoy::config::route::v3::RouteConfigur response_headers_parser_ = HeaderParser::configure(config.response_headers_to_add(), config.response_headers_to_remove()); } + + if (config.has_metadata()) { + metadata_ = std::make_unique(config.metadata()); + } } ClusterSpecifierPluginSharedPtr @@ -2096,6 +2121,15 @@ CommonConfigImpl::clusterSpecifierPlugin(absl::string_view provider) const { return iter->second; } +const envoy::config::core::v3::Metadata& CommonConfigImpl::metadata() const { + return metadata_ != nullptr ? metadata_->proto_metadata_ + : DefaultRouteMetadataPack::get().proto_metadata_; +} +const Envoy::Config::TypedMetadata& CommonConfigImpl::typedMetadata() const { + return metadata_ != nullptr ? metadata_->typed_metadata_ + : DefaultRouteMetadataPack::get().typed_metadata_; +} + ConfigImpl::ConfigImpl(const envoy::config::route::v3::RouteConfiguration& config, const OptionalHttpFilters& optional_http_filters, Server::Configuration::ServerFactoryContext& factory_context, @@ -2117,6 +2151,13 @@ RouteConstSharedPtr ConfigImpl::route(const RouteCallback& cb, return route_matcher_->route(cb, headers, stream_info, random_value); } +const envoy::config::core::v3::Metadata& NullConfigImpl::metadata() const { + return DefaultRouteMetadataPack::get().proto_metadata_; +} +const Envoy::Config::TypedMetadata& NullConfigImpl::typedMetadata() const { + return DefaultRouteMetadataPack::get().typed_metadata_; +} + RouteSpecificFilterConfigConstSharedPtr PerFilterConfigs::createRouteSpecificFilterConfig( const std::string& name, const ProtobufWkt::Any& typed_config, bool is_optional, Server::Configuration::ServerFactoryContext& factory_context, diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index ed9c0cd0acdd..edb0eb4890b8 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -42,6 +42,10 @@ namespace Envoy { namespace Router { +using RouteMetadataPack = Envoy::Config::MetadataPack; +using RouteMetadataPackPtr = Envoy::Config::MetadataPackPtr; +using DefaultRouteMetadataPack = ConstSingleton; + /** * Original port from the authority header. */ @@ -288,6 +292,8 @@ class CommonVirtualHostImpl : public VirtualHost, Logger::Loggable cb) const override; + const envoy::config::core::v3::Metadata& metadata() const override; + const Envoy::Config::TypedMetadata& typedMetadata() const override; private: struct StatNameProvider { @@ -344,6 +350,7 @@ class CommonVirtualHostImpl : public VirtualHost, Logger::Loggable retry_policy_; std::unique_ptr hedge_policy_; std::unique_ptr virtual_cluster_catch_all_; + RouteMetadataPackPtr metadata_; // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. uint32_t retry_shadow_buffer_limit_{std::numeric_limits::max()}; const bool include_attempt_count_in_request_ : 1; @@ -748,22 +755,8 @@ class RouteEntryImplBase : public RouteEntryAndRoute, return opaque_config_; } bool includeVirtualHostRateLimits() const override { return include_vh_rate_limits_; } - using DefaultMetadata = ConstSingleton; - const envoy::config::core::v3::Metadata& metadata() const override { - if (metadata_ != nullptr) { - return *metadata_; - } - return DefaultMetadata::get(); - } - using RouteTypedMetadata = Envoy::Config::TypedMetadataImpl; - const Envoy::Config::TypedMetadata& typedMetadata() const override { - if (typed_metadata_ != nullptr) { - return *typed_metadata_; - } - static const RouteTypedMetadata* defaultTypedMetadata = - new RouteTypedMetadata(DefaultMetadata::get()); - return *defaultTypedMetadata; - } + const envoy::config::core::v3::Metadata& metadata() const override; + const Envoy::Config::TypedMetadata& typedMetadata() const override; const PathMatchCriterion& pathMatchCriterion() const override { return *this; } bool includeAttemptCountInRequest() const override { return vhost_->includeAttemptCountInRequest(); @@ -1204,8 +1197,7 @@ class RouteEntryImplBase : public RouteEntryAndRoute, TlsContextMatchCriteriaConstPtr tls_context_match_criteria_; HeaderParserPtr request_headers_parser_; HeaderParserPtr response_headers_parser_; - std::unique_ptr metadata_; - std::unique_ptr typed_metadata_; + RouteMetadataPackPtr metadata_; const std::vector dynamic_metadata_; // TODO(danielhochman): refactor multimap into unordered_map since JSON is unordered map. @@ -1580,6 +1572,8 @@ class CommonConfigImpl : public CommonConfig { bool ignorePathParametersInPathMatching() const { return ignore_path_parameters_in_path_matching_; } + const envoy::config::core::v3::Metadata& metadata() const override; + const Envoy::Config::TypedMetadata& typedMetadata() const override; private: std::list internal_only_headers_; @@ -1591,6 +1585,7 @@ class CommonConfigImpl : public CommonConfig { // Cluster specifier plugins/providers. absl::flat_hash_map cluster_specifier_plugins_; PerFilterConfigs per_filter_configs_; + RouteMetadataPackPtr metadata_; // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. const uint32_t max_direct_response_body_size_bytes_; const bool uses_vhds_ : 1; @@ -1638,6 +1633,12 @@ class ConfigImpl : public Config { bool ignorePathParametersInPathMatching() const { return shared_config_->ignorePathParametersInPathMatching(); } + const envoy::config::core::v3::Metadata& metadata() const override { + return shared_config_->metadata(); + } + const Envoy::Config::TypedMetadata& typedMetadata() const override { + return shared_config_->typedMetadata(); + } private: CommonConfigSharedPtr shared_config_; @@ -1668,6 +1669,8 @@ class NullConfigImpl : public Config { bool usesVhds() const override { return false; } bool mostSpecificHeaderMutationsWins() const override { return false; } uint32_t maxDirectResponseBodySizeBytes() const override { return 0; } + const envoy::config::core::v3::Metadata& metadata() const override; + const Envoy::Config::TypedMetadata& typedMetadata() const override; private: std::list internal_only_headers_; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index e83eb395f69c..65e733051d22 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -55,7 +55,6 @@ RUNTIME_GUARD(envoy_reloadable_features_http1_use_balsa_parser); RUNTIME_GUARD(envoy_reloadable_features_http2_decode_metadata_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http_allow_partial_urls_in_referer); -RUNTIME_GUARD(envoy_reloadable_features_http_ext_auth_failure_mode_allow_header_add); RUNTIME_GUARD(envoy_reloadable_features_http_filter_avoid_reentrant_local_reply); // Delay deprecation and decommission until UHV is enabled. RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); diff --git a/source/common/tracing/tracer_impl.h b/source/common/tracing/tracer_impl.h index 8df1676676bd..70354ef4ba8c 100644 --- a/source/common/tracing/tracer_impl.h +++ b/source/common/tracing/tracer_impl.h @@ -65,7 +65,7 @@ class NullTracer : public Tracer { public: // Tracing::Tracer SpanPtr startSpan(const Config&, TraceContext&, const StreamInfo::StreamInfo&, - const Tracing::Decision) override { + Tracing::Decision) override { return SpanPtr{new NullSpan()}; } }; @@ -77,7 +77,7 @@ class TracerImpl : public Tracer { // Tracing::Tracer SpanPtr startSpan(const Config& config, TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, - const Tracing::Decision tracing_decision) override; + Tracing::Decision tracing_decision) override; DriverSharedPtr driverForTest() const { return driver_; } diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc index 7155a13ee5fe..9a25f65dadfb 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc @@ -2,6 +2,7 @@ #include "envoy/data/accesslog/v3/accesslog.pb.h" #include "envoy/extensions/access_loggers/grpc/v3/als.pb.h" +#include "envoy/stream_info/filter_state.h" #include "envoy/upstream/upstream.h" #include "source/common/network/utility.h" @@ -300,16 +301,11 @@ void Utility::extractCommonAccessLogProperties( } for (const auto& key : config.filter_state_objects_to_log()) { - if (auto state = stream_info.filterState().getDataReadOnlyGeneric(key); state != nullptr) { - ProtobufTypes::MessagePtr serialized_proto = state->serializeAsProto(); - if (serialized_proto != nullptr) { - auto& filter_state_objects = *common_access_log.mutable_filter_state_objects(); - ProtobufWkt::Any& any = filter_state_objects[key]; - if (dynamic_cast(serialized_proto.get()) != nullptr) { - any.Swap(dynamic_cast(serialized_proto.get())); - } else { - any.PackFrom(*serialized_proto); - } + if (!(extractFilterStateData(stream_info.filterState(), key, common_access_log))) { + if (stream_info.upstreamInfo().has_value() && + stream_info.upstreamInfo()->upstreamFilterState() != nullptr) { + extractFilterStateData(*(stream_info.upstreamInfo()->upstreamFilterState()), key, + common_access_log); } } } @@ -342,6 +338,24 @@ void Utility::extractCommonAccessLogProperties( common_access_log.set_access_log_type(access_log_type); } +bool extractFilterStateData(const StreamInfo::FilterState& filter_state, const std::string& key, + envoy::data::accesslog::v3::AccessLogCommon& common_access_log) { + if (auto state = filter_state.getDataReadOnlyGeneric(key); state != nullptr) { + ProtobufTypes::MessagePtr serialized_proto = state->serializeAsProto(); + if (serialized_proto != nullptr) { + auto& filter_state_objects = *common_access_log.mutable_filter_state_objects(); + ProtobufWkt::Any& any = filter_state_objects[key]; + if (dynamic_cast(serialized_proto.get()) != nullptr) { + any.Swap(dynamic_cast(serialized_proto.get())); + } else { + any.PackFrom(*serialized_proto); + } + } + return true; + } + return false; +} + } // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h index 9f4f1e07fbd5..beec1e719a8f 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h @@ -24,6 +24,9 @@ class Utility { const StreamInfo::StreamInfo& stream_info); }; +bool extractFilterStateData(const StreamInfo::FilterState& filter_state, const std::string& key, + envoy::data::accesslog::v3::AccessLogCommon& common_access_log); + } // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc index e60823fc9f7b..cdde09528a64 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc @@ -137,6 +137,15 @@ void GrpcMuxImpl::sendDiscoveryRequest(absl::string_view type_url) { } } +void GrpcMuxImpl::clearNonce() { + // Iterate over all api_states (for each type_url), and clear its nonce. + for (auto& [type_url, api_state] : api_state_) { + if (api_state) { + api_state->request_.clear_response_nonce(); + } + } +} + void GrpcMuxImpl::loadConfigFromDelegate(const std::string& type_url, const absl::flat_hash_set& resource_names) { if (!xds_resources_delegate_.has_value()) { @@ -458,6 +467,7 @@ void GrpcMuxImpl::onWriteable() { drainRequests(); } void GrpcMuxImpl::onStreamEstablished() { first_stream_request_ = true; grpc_stream_.maybeUpdateQueueSizeStat(0); + clearNonce(); request_queue_ = std::make_unique>(); for (const auto& type_url : subscriptions_) { queueDiscoveryRequest(type_url); diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.h b/source/extensions/config_subscription/grpc/grpc_mux_impl.h index 1bfbfd8daf65..3e5e7bfdf54f 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.h @@ -94,6 +94,8 @@ class GrpcMuxImpl : public GrpcMux, void drainRequests(); void setRetryTimer(); void sendDiscoveryRequest(absl::string_view type_url); + // Clears the nonces of all subscribed types in this gRPC mux. + void clearNonce(); struct GrpcMuxWatchImpl : public GrpcMuxWatch { GrpcMuxWatchImpl(const absl::flat_hash_set& resources, diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 45712f691cf1..f73bf64356c9 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -140,7 +140,7 @@ EXTENSIONS = { "envoy.filters.http.fault": "//source/extensions/filters/http/fault:config", "envoy.filters.http.file_system_buffer": "//source/extensions/filters/http/file_system_buffer:config", "envoy.filters.http.gcp_authn": "//source/extensions/filters/http/gcp_authn:config", - "envoy.filters.http.geoip": "//source/extensions/filters/http/geoip:config", + "envoy.filters.http.geoip": "//source/extensions/filters/http/geoip:config", "envoy.filters.http.grpc_field_extraction": "//source/extensions/filters/http/grpc_field_extraction:config", "envoy.filters.http.grpc_http1_bridge": "//source/extensions/filters/http/grpc_http1_bridge:config", "envoy.filters.http.grpc_http1_reverse_bridge": "//source/extensions/filters/http/grpc_http1_reverse_bridge:config", @@ -222,6 +222,7 @@ EXTENSIONS = { # "envoy.filters.udp.session.http_capsule": "//source/extensions/filters/udp/udp_proxy/session_filters/http_capsule:config", + "envoy.filters.udp.session.dynamic_forward_proxy": "//source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy:config", # # Resource monitors @@ -474,6 +475,11 @@ EXTENSIONS = { "envoy.config_subscription.ads_collection": "//source/extensions/config_subscription/grpc:grpc_collection_subscription_lib", "envoy.config_mux.delta_grpc_mux_factory": "//source/extensions/config_subscription/grpc/xds_mux:grpc_mux_lib", "envoy.config_mux.sotw_grpc_mux_factory": "//source/extensions/config_subscription/grpc/xds_mux:grpc_mux_lib", + + # + # Geolocation Provider + # + "envoy.geoip_providers.maxmind": "//source/extensions/geoip_providers/maxmind:config", } # These can be changed to ["//visibility:public"], for downstream builds which diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 1826f7cdd06d..3257bdce7069 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -754,6 +754,13 @@ envoy.filters.udp.session.http_capsule: status: alpha type_urls: - envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig +envoy.filters.udp.session.dynamic_forward_proxy: + categories: + - envoy.filters.udp.session + security_posture: robust_to_untrusted_downstream + status: alpha + type_urls: + - envoy.extensions.filters.udp.udp_proxy.session.dynamic_forward_proxy.v3.FilterConfig envoy.formatter.cel: categories: - envoy.formatter @@ -775,6 +782,13 @@ envoy.formatter.req_without_query: status: alpha type_urls: - envoy.extensions.formatter.req_without_query.v3.ReqWithoutQuery +envoy.geoip_providers.maxmind: + categories: + - envoy.geoip_providers + security_posture: unknown + status: wip + type_urls: + - envoy.extensions.geoip_providers.maxmind.v3.MaxMindConfig envoy.grpc_credentials.aws_iam: categories: - envoy.grpc_credentials diff --git a/source/extensions/filters/http/compressor/compressor_filter.cc b/source/extensions/filters/http/compressor/compressor_filter.cc index 5c068872b2c8..09a1d0c50493 100644 --- a/source/extensions/filters/http/compressor/compressor_filter.cc +++ b/source/extensions/filters/http/compressor/compressor_filter.cc @@ -179,6 +179,10 @@ CompressorPerRouteFilterConfig::CompressorPerRouteFilterConfig( // Consequently, if `response_direction_config.common_direction_config.enabled` ever gets // added, its absence must enable compression. response_compression_enabled_ = true; + if (config.overrides().response_direction_config().has_remove_accept_encoding_header()) { + remove_accept_encoding_header_ = + config.overrides().response_direction_config().remove_accept_encoding_header().value(); + } } break; case CompressorPerRoute::OVERRIDE_NOT_SET: @@ -198,7 +202,11 @@ Http::FilterHeadersStatus CompressorFilter::decodeHeaders(Http::RequestHeaderMap } const auto& response_config = config_->responseDirectionConfig(); - if (compressionEnabled(response_config) && response_config.removeAcceptEncodingHeader()) { + const auto* per_route_config = + Http::Utility::resolveMostSpecificPerFilterConfig( + decoder_callbacks_); + if (compressionEnabled(response_config, per_route_config) && + removeAcceptEncodingHeader(response_config, per_route_config)) { headers.removeInline(accept_encoding_handle.handle()); } @@ -266,10 +274,13 @@ void CompressorFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallba Http::FilterHeadersStatus CompressorFilter::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { const auto& config = config_->responseDirectionConfig(); + const auto* per_route_config = + Http::Utility::resolveMostSpecificPerFilterConfig( + decoder_callbacks_); // This is used to decide whether stats for accept-encoding header should be touched. const bool isEnabledAndContentLengthBigEnough = - compressionEnabled(config) && config.isMinimumContentLength(headers); + compressionEnabled(config, per_route_config) && config.isMinimumContentLength(headers); const bool isCompressible = isEnabledAndContentLengthBigEnough && !Http::Utility::isUpgrade(headers) && @@ -630,15 +641,21 @@ void CompressorFilter::sanitizeEtagHeader(Http::ResponseHeaderMap& headers) { // True if response compression is enabled. bool CompressorFilter::compressionEnabled( - const CompressorFilterConfig::ResponseDirectionConfig& config) const { - const CompressorPerRouteFilterConfig* per_route_config = - Http::Utility::resolveMostSpecificPerFilterConfig( - decoder_callbacks_); + const CompressorFilterConfig::ResponseDirectionConfig& config, + const CompressorPerRouteFilterConfig* per_route_config) const { return per_route_config && per_route_config->responseCompressionEnabled().has_value() ? *per_route_config->responseCompressionEnabled() : config.compressionEnabled(); } +bool CompressorFilter::removeAcceptEncodingHeader( + const CompressorFilterConfig::ResponseDirectionConfig& config, + const CompressorPerRouteFilterConfig* per_route_config) const { + return per_route_config && per_route_config->removeAcceptEncodingHeader().has_value() + ? *per_route_config->removeAcceptEncodingHeader() + : config.removeAcceptEncodingHeader(); +} + } // namespace Compressor } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/compressor/compressor_filter.h b/source/extensions/filters/http/compressor/compressor_filter.h index ce5634b9d13d..f63903195606 100644 --- a/source/extensions/filters/http/compressor/compressor_filter.h +++ b/source/extensions/filters/http/compressor/compressor_filter.h @@ -170,9 +170,11 @@ class CompressorPerRouteFilterConfig : public Router::RouteSpecificFilterConfig // If a value is present, that value overrides // ResponseDirectionConfig::compressionEnabled. absl::optional responseCompressionEnabled() const { return response_compression_enabled_; } + absl::optional removeAcceptEncodingHeader() const { return remove_accept_encoding_header_; } private: absl::optional response_compression_enabled_; + absl::optional remove_accept_encoding_header_; }; /** @@ -196,7 +198,10 @@ class CompressorFilter : public Http::PassThroughFilter { Http::FilterTrailersStatus encodeTrailers(Http::ResponseTrailerMap&) override; private: - bool compressionEnabled(const CompressorFilterConfig::ResponseDirectionConfig& config) const; + bool compressionEnabled(const CompressorFilterConfig::ResponseDirectionConfig& config, + const CompressorPerRouteFilterConfig* per_route_config) const; + bool removeAcceptEncodingHeader(const CompressorFilterConfig::ResponseDirectionConfig& config, + const CompressorPerRouteFilterConfig* per_route_config) const; bool hasCacheControlNoTransform(Http::ResponseHeaderMap& headers) const; bool isAcceptEncodingAllowed(bool maybe_compress, const Http::ResponseHeaderMap& headers) const; bool isEtagAllowed(Http::ResponseHeaderMap& headers) const; diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 68f327e9774d..3de1424b64ec 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -421,8 +421,7 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { if (cluster_) { config_->incCounter(cluster_->statsScope(), config_->ext_authz_failure_mode_allowed_); } - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.http_ext_auth_failure_mode_allow_header_add")) { + if (config_->failureModeAllowHeaderAdd()) { request_headers_->addReferenceKey( Filters::Common::ExtAuthz::Headers::get().EnvoyAuthFailureModeAllowed, "true"); } diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 91482a93eca7..0175cbc1c48a 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -60,6 +60,7 @@ class FilterConfig { const std::string& stats_prefix, envoy::config::bootstrap::v3::Bootstrap& bootstrap) : allow_partial_message_(config.with_request_body().allow_partial_message()), failure_mode_allow_(config.failure_mode_allow()), + failure_mode_allow_header_add_(config.failure_mode_allow_header_add()), clear_route_cache_(config.clear_route_cache()), max_request_bytes_(config.with_request_body().max_request_bytes()), @@ -133,6 +134,8 @@ class FilterConfig { bool failureModeAllow() const { return failure_mode_allow_; } + bool failureModeAllowHeaderAdd() const { return failure_mode_allow_header_add_; } + bool clearRouteCache() const { return clear_route_cache_; } uint32_t maxRequestBytes() const { return max_request_bytes_; } @@ -205,6 +208,7 @@ class FilterConfig { const bool allow_partial_message_; const bool failure_mode_allow_; + const bool failure_mode_allow_header_add_; const bool clear_route_cache_; const uint32_t max_request_bytes_; const bool pack_as_bytes_; diff --git a/source/extensions/filters/http/geoip/BUILD b/source/extensions/filters/http/geoip/BUILD index f12f88882178..7d47565f71d1 100644 --- a/source/extensions/filters/http/geoip/BUILD +++ b/source/extensions/filters/http/geoip/BUILD @@ -16,8 +16,9 @@ envoy_cc_library( name = "geoip_filter_lib", srcs = ["geoip_filter.cc"], hdrs = ["geoip_filter.h"], + tags = ["skip_on_windows"], deps = [ - ":provider_config", + "//envoy/geoip:geoip_provider_driver_interface", "//envoy/http:filter_interface", "//envoy/runtime:runtime_interface", "//source/common/common:assert_lib", @@ -29,28 +30,13 @@ envoy_cc_library( ], ) -#todo(nezdolik) may need to split into interface and impl -envoy_cc_extension( - name = "provider_config", - hdrs = [ - "geoip_provider_config.h", - "geoip_provider_config_impl.h", - ], - deps = [ - "//envoy/config:typed_config_interface", - "//envoy/network:address_interface", - "//envoy/protobuf:message_validator_interface", - "//source/common/common:hash_lib", - "@envoy_api//envoy/extensions/filters/http/geoip/v3:pkg_cc_proto", - ], -) - envoy_cc_extension( name = "config", srcs = ["config.cc"], hdrs = ["config.h"], + tags = ["skip_on_windows"], deps = [ - ":provider_config", + "//envoy/geoip:geoip_provider_driver_interface", "//source/common/config:utility_lib", "//source/common/protobuf:utility_lib", "//source/extensions/filters/http/common:factory_base_lib", diff --git a/source/extensions/filters/http/geoip/config.cc b/source/extensions/filters/http/geoip/config.cc index b88138abbec2..86c83472dc91 100644 --- a/source/extensions/filters/http/geoip/config.cc +++ b/source/extensions/filters/http/geoip/config.cc @@ -5,7 +5,6 @@ #include "source/common/config/utility.h" #include "source/common/protobuf/utility.h" #include "source/extensions/filters/http/geoip/geoip_filter.h" -#include "source/extensions/filters/http/geoip/geoip_provider_config_impl.h" namespace Envoy { namespace Extensions { @@ -15,19 +14,16 @@ namespace Geoip { Http::FilterFactoryCb GeoipFilterFactory::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::geoip::v3::Geoip& proto_config, const std::string& stat_prefix, Server::Configuration::FactoryContext& context) { - if (!provider_context_) { - provider_context_ = - std::make_unique(context.messageValidationVisitor()); - } GeoipFilterConfigSharedPtr filter_config( std::make_shared(proto_config, stat_prefix, context.scope())); const auto& provider_config = proto_config.provider(); auto& geo_provider_factory = - Envoy::Config::Utility::getAndCheckFactory(provider_config); + Envoy::Config::Utility::getAndCheckFactory( + provider_config); ProtobufTypes::MessagePtr message = Envoy::Config::Utility::translateToFactoryConfig( - provider_config, provider_context_->messageValidationVisitor(), geo_provider_factory); - auto driver = geo_provider_factory.createGeoipProviderDriver(*message, provider_context_); + provider_config, context.messageValidationVisitor(), geo_provider_factory); + auto driver = geo_provider_factory.createGeoipProviderDriver(*message, stat_prefix, context); return [filter_config, driver](Http::FilterChainFactoryCallbacks& callbacks) -> void { callbacks.addStreamDecoderFilter(std::make_shared(filter_config, driver)); }; diff --git a/source/extensions/filters/http/geoip/config.h b/source/extensions/filters/http/geoip/config.h index 8b6d782b8582..677cff6be55b 100644 --- a/source/extensions/filters/http/geoip/config.h +++ b/source/extensions/filters/http/geoip/config.h @@ -2,9 +2,9 @@ #include "envoy/extensions/filters/http/geoip/v3/geoip.pb.h" #include "envoy/extensions/filters/http/geoip/v3/geoip.pb.validate.h" +#include "envoy/geoip/geoip_provider_driver.h" #include "source/extensions/filters/http/common/factory_base.h" -#include "source/extensions/filters/http/geoip/geoip_provider_config.h" namespace Envoy { namespace Extensions { @@ -22,9 +22,6 @@ class GeoipFilterFactory Http::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::geoip::v3::Geoip& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; - -private: - GeoipProviderFactoryContextPtr provider_context_; }; } // namespace Geoip diff --git a/source/extensions/filters/http/geoip/geoip_filter.cc b/source/extensions/filters/http/geoip/geoip_filter.cc index cd3b2a423e35..2d197bb158fc 100644 --- a/source/extensions/filters/http/geoip/geoip_filter.cc +++ b/source/extensions/filters/http/geoip/geoip_filter.cc @@ -15,33 +15,10 @@ GeoipFilterConfig::GeoipFilterConfig( const envoy::extensions::filters::http::geoip::v3::Geoip& config, const std::string& stat_prefix, Stats::Scope& scope) : scope_(scope), stat_name_set_(scope.symbolTable().makeSet("Geoip")), - stats_prefix_(stat_name_set_->add(stat_prefix + "geoip")), - total_(stat_name_set_->add("total")), use_xff_(config.has_xff_config()), + stats_prefix_(stat_name_set_->add(stat_prefix + "geoip")), use_xff_(config.has_xff_config()), xff_num_trusted_hops_(config.has_xff_config() ? config.xff_config().xff_num_trusted_hops() : 0) { - const auto& geo_headers_to_add = config.geo_headers_to_add(); - geo_headers_ = processGeoHeaders({geo_headers_to_add.country(), geo_headers_to_add.city(), - geo_headers_to_add.region(), geo_headers_to_add.asn()}); - geo_anon_headers_ = - processGeoHeaders({geo_headers_to_add.is_anon(), geo_headers_to_add.anon_vpn(), - geo_headers_to_add.anon_hosting(), geo_headers_to_add.anon_tor(), - geo_headers_to_add.anon_proxy()}); - if (geo_headers_.empty() && geo_anon_headers_.empty()) { - throw EnvoyException("No geolocation headers configured"); - } -} - -absl::flat_hash_set -GeoipFilterConfig::processGeoHeaders(const absl::flat_hash_set& headers) const { - absl::flat_hash_set geo_headers; - for (auto header : headers) { - if (!header.empty()) { - stat_name_set_->rememberBuiltin(absl::StrCat(header, ".hit")); - stat_name_set_->rememberBuiltin(absl::StrCat(header, ".total")); - geo_headers.insert(std::string(header)); - } - } - return geo_headers; + stat_name_set_->rememberBuiltin("total"); } void GeoipFilterConfig::incCounter(Stats::StatName name) { @@ -49,7 +26,7 @@ void GeoipFilterConfig::incCounter(Stats::StatName name) { scope_.counterFromStatName(Stats::StatName(storage.get())).inc(); } -GeoipFilter::GeoipFilter(GeoipFilterConfigSharedPtr config, DriverSharedPtr driver) +GeoipFilter::GeoipFilter(GeoipFilterConfigSharedPtr config, Geolocation::DriverSharedPtr driver) : config_(config), driver_(std::move(driver)) {} GeoipFilter::~GeoipFilter() = default; @@ -76,13 +53,9 @@ Http::FilterHeadersStatus GeoipFilter::decodeHeaders(Http::RequestHeaderMap& hea // This is a safe measure to protect against the case when filter gets deleted before the callback // is run. GeoipFilterWeakPtr self = weak_from_this(); - // Copy header values to pass to the driver lookup function (in case filter gets destroyed before - // lookup completes). - absl::flat_hash_set geo_headers = config_->geoHeaders(); - absl::flat_hash_set geo_anon_headers = config_->geoAnonHeaders(); driver_->lookup( - LookupRequest{std::move(remote_address), std::move(geo_headers), std::move(geo_anon_headers)}, - [self, &dispatcher = decoder_callbacks_->dispatcher()](LookupResult&& result) { + Geolocation::LookupRequest{std::move(remote_address)}, + [self, &dispatcher = decoder_callbacks_->dispatcher()](Geolocation::LookupResult&& result) { dispatcher.post([self, result]() { if (GeoipFilterSharedPtr filter = self.lock()) { filter->onLookupComplete(std::move(result)); @@ -106,18 +79,16 @@ void GeoipFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& decoder_callbacks_ = &callbacks; } -void GeoipFilter::onLookupComplete(LookupResult&& result) { +void GeoipFilter::onLookupComplete(Geolocation::LookupResult&& result) { ASSERT(request_headers_); for (auto it = result.cbegin(); it != result.cend();) { const auto& geo_header = it->first; const auto& lookup_result = it++->second; - if (lookup_result) { - request_headers_->setCopy(Http::LowerCaseString(geo_header), lookup_result.value()); - config_->incHit(geo_header); + if (!lookup_result.empty()) { + request_headers_->setCopy(Http::LowerCaseString(geo_header), lookup_result); } - config_->incTotal(geo_header); } - + config_->incTotal(); ENVOY_LOG(debug, "Geoip filter: finished decoding geolocation headers"); decoder_callbacks_->continueDecoding(); } diff --git a/source/extensions/filters/http/geoip/geoip_filter.h b/source/extensions/filters/http/geoip/geoip_filter.h index 254ab7c9a3d1..bbc2345b311e 100644 --- a/source/extensions/filters/http/geoip/geoip_filter.h +++ b/source/extensions/filters/http/geoip/geoip_filter.h @@ -3,19 +3,15 @@ #include "envoy/common/exception.h" #include "envoy/common/optref.h" #include "envoy/extensions/filters/http/geoip/v3/geoip.pb.h" +#include "envoy/geoip/geoip_provider_driver.h" #include "envoy/http/filter.h" #include "envoy/stats/scope.h" -#include "source/extensions/filters/http/geoip/geoip_provider_config.h" - namespace Envoy { namespace Extensions { namespace HttpFilters { namespace Geoip { -using GeolocationHeadersToAdd = - envoy::extensions::filters::http::geoip::v3::Geoip_GeolocationHeadersToAdd; - /** * Configuration for the Geoip filter. */ @@ -24,32 +20,20 @@ class GeoipFilterConfig { GeoipFilterConfig(const envoy::extensions::filters::http::geoip::v3::Geoip& config, const std::string& stat_prefix, Stats::Scope& scope); - void incHit(absl::string_view geo_header) { - incCounter(stat_name_set_->getBuiltin(absl::StrCat(geo_header, ".hit"), unknown_hit_)); - } - void incTotal(absl::string_view geo_header) { - incCounter(stat_name_set_->getBuiltin(absl::StrCat(geo_header, ".total"), unknown_hit_)); - } + void incTotal() { incCounter(stat_name_set_->getBuiltin("total", unknown_hit_)); } bool useXff() const { return use_xff_; } uint32_t xffNumTrustedHops() const { return xff_num_trusted_hops_; } - const absl::flat_hash_set& geoHeaders() const { return geo_headers_; } - const absl::flat_hash_set& geoAnonHeaders() const { return geo_anon_headers_; } private: void incCounter(Stats::StatName name); - absl::flat_hash_set - processGeoHeaders(const absl::flat_hash_set& headers) const; Stats::Scope& scope_; Stats::StatNameSetPtr stat_name_set_; const Stats::StatName stats_prefix_; - const Stats::StatName total_; const Stats::StatName unknown_hit_; bool use_xff_; const uint32_t xff_num_trusted_hops_; - absl::flat_hash_set geo_headers_; - absl::flat_hash_set geo_anon_headers_; }; using GeoipFilterConfigSharedPtr = std::shared_ptr; @@ -58,7 +42,7 @@ class GeoipFilter : public Http::StreamDecoderFilter, public Logger::Loggable, public std::enable_shared_from_this { public: - GeoipFilter(GeoipFilterConfigSharedPtr config, DriverSharedPtr driver); + GeoipFilter(GeoipFilterConfigSharedPtr config, Geolocation::DriverSharedPtr driver); ~GeoipFilter() override; // Http::StreamFilterBase @@ -71,14 +55,14 @@ class GeoipFilter : public Http::StreamDecoderFilter, Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap& trailers) override; void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override; // Callbacks for geolocation filter when lookup is complete. - void onLookupComplete(LookupResult&& result); + void onLookupComplete(Geolocation::LookupResult&& result); private: // Allow the unit test to have access to private members. friend class GeoipFilterPeer; GeoipFilterConfigSharedPtr config_; Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; - DriverSharedPtr driver_; + Geolocation::DriverSharedPtr driver_; OptRef request_headers_; }; diff --git a/source/extensions/filters/http/geoip/geoip_provider_config_impl.h b/source/extensions/filters/http/geoip/geoip_provider_config_impl.h deleted file mode 100644 index 4b67084b0a13..000000000000 --- a/source/extensions/filters/http/geoip/geoip_provider_config_impl.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "source/common/protobuf/utility.h" -#include "source/extensions/filters/http/geoip/geoip_provider_config.h" - -namespace Envoy { -namespace Extensions { -namespace HttpFilters { -namespace Geoip { - -class GeoipProviderFactoryContextImpl : public GeoipProviderFactoryContext { -public: - GeoipProviderFactoryContextImpl(ProtobufMessage::ValidationVisitor& validation_visitor) - : validation_visitor_(validation_visitor) {} - ProtobufMessage::ValidationVisitor& messageValidationVisitor() override { - return validation_visitor_; - } - -private: - ProtobufMessage::ValidationVisitor& validation_visitor_; -}; - -/** - * Common base class for geolocation provider registrations. - */ -template class GeoipProviderFactoryBase : public GeoipProviderFactory { -public: - // Server::Configuration::GeoipProviderFactory - DriverSharedPtr createGeoipProviderDriver(const Protobuf::Message& config, - GeoipProviderFactoryContextPtr& context) override { - return createGeoipProviderDriverTyped(MessageUtil::downcastAndValidate( - config, context->messageValidationVisitor()), - context); - } - - ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return std::make_unique(); - } - - std::string name() const override { return name_; } - -protected: - GeoipProviderFactoryBase(const std::string& name) : name_(name) {} - -private: - virtual DriverSharedPtr - createGeoipProviderDriverTyped(const ConfigProto& proto_config, - GeoipProviderFactoryContextPtr& context) PURE; - - const std::string name_; -}; - -} // namespace Geoip -} // namespace HttpFilters -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/filters/http/well_known_names.h b/source/extensions/filters/http/well_known_names.h index 0049279f0300..cfd1783b55b4 100644 --- a/source/extensions/filters/http/well_known_names.h +++ b/source/extensions/filters/http/well_known_names.h @@ -44,6 +44,8 @@ class HttpFilterNameValues { const std::string GrpcJsonTranscoder = "envoy.filters.http.grpc_json_transcoder"; // GRPC web filter const std::string GrpcWeb = "envoy.filters.http.grpc_web"; + // GRPC Field Extraction filter + const std::string GrpcFieldExtraction = "envoy.filters.http.grpc_field_extraction"; // GRPC http1 reverse bridge filter const std::string GrpcHttp1ReverseBridge = "envoy.filters.http.grpc_http1_reverse_bridge"; // GRPC telemetry diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD new file mode 100644 index 000000000000..2ddede80431e --- /dev/null +++ b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD @@ -0,0 +1,37 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "proxy_filter_lib", + srcs = ["proxy_filter.cc"], + hdrs = ["proxy_filter.h"], + deps = [ + "//envoy/router:string_accessor_interface", + "//source/common/common:assert_lib", + "//source/common/http:header_utility_lib", + "//source/common/stream_info:uint32_accessor_lib", + "//source/extensions/common/dynamic_forward_proxy:dns_cache_interface", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", + "@envoy_api//envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":proxy_filter_lib", + "//source/extensions/common/dynamic_forward_proxy:dns_cache_manager_impl", + "//source/extensions/filters/udp/udp_proxy/session_filters:factory_base_lib", + "@envoy_api//envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.cc b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.cc new file mode 100644 index 000000000000..0a5062b4a2a0 --- /dev/null +++ b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.cc @@ -0,0 +1,42 @@ +#include "source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.h" + +#include "envoy/registry/registry.h" +#include "envoy/server/filter_config.h" + +#include "source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { +namespace DynamicForwardProxy { + +DynamicForwardProxyNetworkFilterConfigFactory::DynamicForwardProxyNetworkFilterConfigFactory() + : FactoryBase("envoy.filters.udp.session.dynamic_forward_proxy") {} + +FilterFactoryCb DynamicForwardProxyNetworkFilterConfigFactory::createFilterFactoryFromProtoTyped( + const FilterConfig& proto_config, Server::Configuration::FactoryContext& context) { + + Extensions::Common::DynamicForwardProxy::DnsCacheManagerFactoryImpl cache_manager_factory( + context); + ProxyFilterConfigSharedPtr filter_config( + std::make_shared(proto_config, cache_manager_factory, context)); + + return [filter_config](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addReadFilter(std::make_shared(filter_config)); + }; +} + +/** + * Static registration for the dynamic_forward_proxy filter. @see RegisterFactory. + */ +REGISTER_FACTORY(DynamicForwardProxyNetworkFilterConfigFactory, NamedUdpSessionFilterConfigFactory); + +} // namespace DynamicForwardProxy +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.h b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.h new file mode 100644 index 000000000000..11d98c0eee97 --- /dev/null +++ b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.h" +#include "envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.validate.h" + +#include "source/extensions/filters/udp/udp_proxy/session_filters/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { +namespace DynamicForwardProxy { + +using FilterConfig = + envoy::extensions::filters::udp::udp_proxy::session::dynamic_forward_proxy::v3::FilterConfig; + +/** + * Config registration for the dynamic_forward_proxy filter. @see + * NamedNetworkFilterConfigFactory. + */ +class DynamicForwardProxyNetworkFilterConfigFactory : public FactoryBase { +public: + DynamicForwardProxyNetworkFilterConfigFactory(); + +private: + FilterFactoryCb + createFilterFactoryFromProtoTyped(const FilterConfig& proto_config, + Server::Configuration::FactoryContext& context) override; +}; + +} // namespace DynamicForwardProxy +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.cc new file mode 100644 index 000000000000..fdae4b4b5e83 --- /dev/null +++ b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.cc @@ -0,0 +1,152 @@ +#include "source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.h" + +#include "envoy/router/string_accessor.h" +#include "envoy/stream_info/uint32_accessor.h" +#include "envoy/upstream/thread_local_cluster.h" + +#include "source/common/common/assert.h" +#include "source/common/stream_info/uint32_accessor_impl.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { +namespace DynamicForwardProxy { + +constexpr uint32_t DefaultMaxBufferedDatagrams = 1024; +constexpr uint64_t DefaultMaxBufferedBytes = 16384; + +ProxyFilterConfig::ProxyFilterConfig( + const FilterConfig& config, + Extensions::Common::DynamicForwardProxy::DnsCacheManagerFactory& cache_manager_factory, + Server::Configuration::FactoryContext& context) + : dns_cache_manager_(cache_manager_factory.get()), + dns_cache_(dns_cache_manager_->getCache(config.dns_cache_config())), + stats_scope_(context.scope().createScope( + absl::StrCat("udp.session.dynamic_forward_proxy.", config.stat_prefix(), "."))), + filter_stats_(generateStats(*stats_scope_)), buffer_enabled_(config.has_buffer_options()), + max_buffered_datagrams_(config.has_buffer_options() + ? PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.buffer_options(), + max_buffered_datagrams, + DefaultMaxBufferedDatagrams) + : DefaultMaxBufferedDatagrams), + max_buffered_bytes_(config.has_buffer_options() + ? PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.buffer_options(), + max_buffered_bytes, + DefaultMaxBufferedBytes) + : DefaultMaxBufferedBytes) {} + +ReadFilterStatus ProxyFilter::onNewSession() { + absl::string_view host; + const auto* host_filter_state = + read_callbacks_->streamInfo().filterState()->getDataReadOnly( + "envoy.upstream.dynamic_host"); + if (host_filter_state != nullptr) { + host = host_filter_state->asString(); + } + + absl::optional port; + const auto* port_filter_state = + read_callbacks_->streamInfo().filterState()->getDataReadOnly( + "envoy.upstream.dynamic_port"); + if (port_filter_state != nullptr && port_filter_state->value() > 0 && + port_filter_state->value() <= 65535) { + port = port_filter_state->value(); + } + + if (host.empty() || !port.has_value()) { + ENVOY_LOG(trace, "new session missing host or port"); + // TODO(ohadvano): add callback to remove session. + return ReadFilterStatus::StopIteration; + } + + ENVOY_LOG(trace, "new session with host '{}', port '{}'", host, port.value()); + + circuit_breaker_ = config_->cache().canCreateDnsRequest(); + if (circuit_breaker_ == nullptr) { + ENVOY_LOG(debug, "pending request overflow"); + // TODO(ohadvano): add callback to remove session. + return ReadFilterStatus::StopIteration; + } + + auto result = config_->cache().loadDnsCacheEntry(host, port.value(), false, *this); + + cache_load_handle_ = std::move(result.handle_); + if (cache_load_handle_ == nullptr) { + circuit_breaker_.reset(); + } + + switch (result.status_) { + case LoadDnsCacheEntryStatus::InCache: + ASSERT(cache_load_handle_ == nullptr); + ENVOY_LOG(debug, "DNS cache entry already loaded, continuing"); + load_dns_cache_completed_ = true; + return ReadFilterStatus::Continue; + case LoadDnsCacheEntryStatus::Loading: + ASSERT(cache_load_handle_ != nullptr); + ENVOY_LOG(debug, "waiting to load DNS cache entry"); + return ReadFilterStatus::StopIteration; + case LoadDnsCacheEntryStatus::Overflow: + ASSERT(cache_load_handle_ == nullptr); + ENVOY_LOG(debug, "DNS cache overflow"); + // TODO(ohadvano): add callback to remove session. + return ReadFilterStatus::StopIteration; + } + + PANIC_DUE_TO_CORRUPT_ENUM; +} + +ReadFilterStatus ProxyFilter::onData(Network::UdpRecvData& data) { + if (load_dns_cache_completed_) { + return ReadFilterStatus::Continue; + } + + maybeBufferDatagram(data); + return ReadFilterStatus::StopIteration; +} + +void ProxyFilter::onLoadDnsCacheComplete(const Common::DynamicForwardProxy::DnsHostInfoSharedPtr&) { + ENVOY_LOG(debug, "load DNS cache complete, continuing"); + ASSERT(circuit_breaker_ != nullptr); + circuit_breaker_.reset(); + + load_dns_cache_completed_ = true; + read_callbacks_->continueFilterChain(); + + while (!datagrams_buffer_.empty()) { + BufferedDatagramPtr buffered_datagram = std::move(datagrams_buffer_.front()); + datagrams_buffer_.pop(); + read_callbacks_->injectDatagramToFilterChain(*buffered_datagram); + } + + config_->disableBuffer(); + buffered_bytes_ = 0; +} + +void ProxyFilter::maybeBufferDatagram(Network::UdpRecvData& data) { + if (!config_->bufferEnabled()) { + return; + } + + if (datagrams_buffer_.size() == config_->maxBufferedDatagrams() || + buffered_bytes_ + data.buffer_->length() > config_->maxBufferedBytes()) { + config_->filterStats().buffer_overflow_.inc(); + return; + } + + auto buffered_datagram = std::make_unique(); + buffered_datagram->addresses_ = {std::move(data.addresses_.local_), + std::move(data.addresses_.peer_)}; + buffered_datagram->buffer_ = std::move(data.buffer_); + buffered_datagram->receive_time_ = data.receive_time_; + buffered_bytes_ += buffered_datagram->buffer_->length(); + datagrams_buffer_.push(std::move(buffered_datagram)); +} + +} // namespace DynamicForwardProxy +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.h b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.h new file mode 100644 index 000000000000..d1af2fac62c3 --- /dev/null +++ b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.h @@ -0,0 +1,102 @@ +#pragma once + +#include + +#include "envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.h" + +#include "source/common/common/logger.h" +#include "source/common/http/header_utility.h" +#include "source/extensions/common/dynamic_forward_proxy/dns_cache.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/filter.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { +namespace DynamicForwardProxy { + +using FilterConfig = + envoy::extensions::filters::udp::udp_proxy::session::dynamic_forward_proxy::v3::FilterConfig; + +/** + * All filter state dynamic forward proxy stats. @see stats_macros.h + */ +#define ALL_DYNAMIC_FORWARD_PROXY_STATS(COUNTER) COUNTER(buffer_overflow) + +/** + * Struct definition for all filter state dynamic forward proxy stats. @see stats_macros.h + */ +struct DynamicForwardProxyStats { + ALL_DYNAMIC_FORWARD_PROXY_STATS(GENERATE_COUNTER_STRUCT) +}; + +class ProxyFilterConfig { +public: + ProxyFilterConfig( + const FilterConfig& config, + Extensions::Common::DynamicForwardProxy::DnsCacheManagerFactory& cache_manager_factory, + Server::Configuration::FactoryContext& context); + + Extensions::Common::DynamicForwardProxy::DnsCache& cache() { return *dns_cache_; } + DynamicForwardProxyStats& filterStats() { return filter_stats_; } + bool bufferEnabled() const { return buffer_enabled_; }; + void disableBuffer() { buffer_enabled_ = false; } + uint32_t maxBufferedDatagrams() const { return max_buffered_datagrams_; }; + uint64_t maxBufferedBytes() const { return max_buffered_bytes_; }; + +private: + static DynamicForwardProxyStats generateStats(Stats::Scope& scope) { + return {ALL_DYNAMIC_FORWARD_PROXY_STATS(POOL_COUNTER(scope))}; + } + + const Extensions::Common::DynamicForwardProxy::DnsCacheManagerSharedPtr dns_cache_manager_; + const Extensions::Common::DynamicForwardProxy::DnsCacheSharedPtr dns_cache_; + const Stats::ScopeSharedPtr stats_scope_; + DynamicForwardProxyStats filter_stats_; + bool buffer_enabled_; + uint32_t max_buffered_datagrams_; + uint64_t max_buffered_bytes_; +}; + +using ProxyFilterConfigSharedPtr = std::shared_ptr; +using BufferedDatagramPtr = std::unique_ptr; +using LoadDnsCacheEntryStatus = Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryStatus; + +class ProxyFilter + : public ReadFilter, + public Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryCallbacks, + Logger::Loggable { +public: + ProxyFilter(ProxyFilterConfigSharedPtr config) : config_(std::move(config)){}; + + // Network::ReadFilter + ReadFilterStatus onNewSession() override; + ReadFilterStatus onData(Network::UdpRecvData& data) override; + + void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + + // Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryCallbacks + void onLoadDnsCacheComplete( + const Extensions::Common::DynamicForwardProxy::DnsHostInfoSharedPtr&) override; + +private: + void maybeBufferDatagram(Network::UdpRecvData& data); + + const ProxyFilterConfigSharedPtr config_; + Upstream::ResourceAutoIncDecPtr circuit_breaker_; + Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryHandlePtr cache_load_handle_; + ReadFilterCallbacks* read_callbacks_{}; + bool load_dns_cache_completed_{false}; + uint64_t buffered_bytes_{0}; + std::queue datagrams_buffer_; +}; + +} // namespace DynamicForwardProxy +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc index 7725d50623af..935480811d1b 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc @@ -151,7 +151,7 @@ UdpProxyFilter::ClusterInfo::createSession(Network::UdpRecvData::LocalPeerAddres return createSessionWithOptionalHost(std::move(addresses), optional_host); } - auto host = chooseHost(addresses.peer_); + auto host = chooseHost(addresses.peer_, nullptr); if (host == nullptr) { ENVOY_LOG(debug, "cannot find any valid host."); cluster_.info()->trafficStats()->upstream_cx_none_healthy_.inc(); @@ -181,8 +181,9 @@ UdpProxyFilter::ActiveSession* UdpProxyFilter::ClusterInfo::createSessionWithOpt } Upstream::HostConstSharedPtr UdpProxyFilter::ClusterInfo::chooseHost( - const Network::Address::InstanceConstSharedPtr& peer_address) const { - UdpLoadBalancerContext context(filter_.config_->hashPolicy(), peer_address); + const Network::Address::InstanceConstSharedPtr& peer_address, + const StreamInfo::StreamInfo* stream_info) const { + UdpLoadBalancerContext context(filter_.config_->hashPolicy(), peer_address, stream_info); Upstream::HostConstSharedPtr host = cluster_.loadBalancer().chooseHost(&context); return host; } @@ -214,7 +215,7 @@ Network::FilterStatus UdpProxyFilter::StickySessionClusterInfo::onData(Network:: // If a host becomes unhealthy, we optimally would like to replace it with a new session // to a healthy host. We may eventually want to make this behavior configurable, but for now // this will be the universal behavior. - auto host = chooseHost(data.addresses_.peer_); + auto host = chooseHost(data.addresses_.peer_, nullptr); if (host != nullptr && host->coarseHealth() != Upstream::Host::Health::Unhealthy && host.get() != &active_session->host().value().get()) { ENVOY_LOG(debug, "upstream session unhealthy, recreating the session"); @@ -240,7 +241,7 @@ UdpProxyFilter::PerPacketLoadBalancingClusterInfo::PerPacketLoadBalancingCluster Network::FilterStatus UdpProxyFilter::PerPacketLoadBalancingClusterInfo::onData(Network::UdpRecvData& data) { - auto host = chooseHost(data.addresses_.peer_); + auto host = chooseHost(data.addresses_.peer_, nullptr); if (host == nullptr) { ENVOY_LOG(debug, "cannot find any valid host."); cluster_.info()->trafficStats()->upstream_cx_none_healthy_.inc(); @@ -493,7 +494,7 @@ void UdpProxyFilter::UdpActiveSession::createUpstream() { } if (!host_) { - host_ = cluster_.chooseHost(addresses_.peer_); + host_ = cluster_.chooseHost(addresses_.peer_, &udp_session_info_); if (host_ == nullptr) { ENVOY_LOG(debug, "cannot find any valid host."); cluster_.cluster_.info()->trafficStats()->upstream_cx_none_healthy_.inc(); @@ -801,7 +802,7 @@ void UdpProxyFilter::TunnelingActiveSession::createUpstream() { conn_pool_factory_ = std::make_unique(); load_balancer_context_ = std::make_unique( - cluster_.filter_.config_->hashPolicy(), addresses_.peer_); + cluster_.filter_.config_->hashPolicy(), addresses_.peer_, &udp_session_info_); establishUpstreamConnection(); } diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h index ddbb6ee6ea5f..8bf19ef77428 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -136,16 +136,20 @@ using UdpProxyFilterConfigSharedPtr = std::shared_ptrgenerateHash(*peer_address); } } absl::optional computeHashKey() override { return hash_; } + const StreamInfo::StreamInfo* requestStreamInfo() const override { return stream_info_; } private: absl::optional hash_; + const StreamInfo::StreamInfo* stream_info_; }; /** @@ -761,7 +765,8 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, } Upstream::HostConstSharedPtr - chooseHost(const Network::Address::InstanceConstSharedPtr& peer_address) const; + chooseHost(const Network::Address::InstanceConstSharedPtr& peer_address, + const StreamInfo::StreamInfo* stream_info) const; UdpProxyFilter& filter_; Upstream::ThreadLocalCluster& cluster_; diff --git a/source/extensions/geoip_providers/common/BUILD b/source/extensions/geoip_providers/common/BUILD new file mode 100644 index 000000000000..2f68534edc7a --- /dev/null +++ b/source/extensions/geoip_providers/common/BUILD @@ -0,0 +1,20 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "factory_base_lib", + hdrs = ["factory_base.h"], + tags = ["skip_on_windows"], + deps = [ + "//envoy/geoip:geoip_provider_driver_interface", + "//source/common/config:utility_lib", + "//source/common/protobuf:utility_lib", + ], +) diff --git a/source/extensions/geoip_providers/common/factory_base.h b/source/extensions/geoip_providers/common/factory_base.h new file mode 100644 index 000000000000..9376192e8d66 --- /dev/null +++ b/source/extensions/geoip_providers/common/factory_base.h @@ -0,0 +1,46 @@ +#pragma once + +#include "envoy/geoip/geoip_provider_driver.h" + +#include "source/common/protobuf/protobuf.h" +#include "source/common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace GeoipProviders { +namespace Common { + +/** + * Common base class for geoip provider factory registrations. Removes a substantial amount of + * boilerplate. + */ +template class FactoryBase : public Geolocation::GeoipProviderFactory { +public: + // GeoipProviderFactory + Geolocation::DriverSharedPtr + createGeoipProviderDriver(const Protobuf::Message& config, const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) override { + return createGeoipProviderDriverTyped(MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()), + stat_prefix, context); + } + + std::string name() const override { return name_; } + + std::string category() const override { return "envoy.geoip_providers"; } + +protected: + FactoryBase(const std::string& name) : name_(name) {} + +private: + virtual Geolocation::DriverSharedPtr + createGeoipProviderDriverTyped(const ConfigProto& proto_config, const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) PURE; + + const std::string name_; +}; + +} // namespace Common +} // namespace GeoipProviders +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/geoip_providers/maxmind/BUILD b/source/extensions/geoip_providers/maxmind/BUILD new file mode 100644 index 000000000000..5d9f368bf64f --- /dev/null +++ b/source/extensions/geoip_providers/maxmind/BUILD @@ -0,0 +1,46 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +# HTTP L7 filter that decorates request with geolocation data +# Public docs: https://envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/geoip_filter + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = select({ + "//bazel:linux": ["config.cc"], + "//conditions:default": [], + }), + hdrs = ["config.h"], + tags = ["skip_on_windows"], + deps = [ + ":provider_impl", + "//envoy/geoip:geoip_provider_driver_interface", + "//source/common/config:utility_lib", + "//source/common/protobuf:utility_lib", + "//source/extensions/geoip_providers/common:factory_base_lib", + "@envoy_api//envoy/extensions/geoip_providers/maxmind/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "provider_impl", + srcs = select({ + "//bazel:linux": ["geoip_provider.cc"], + "//conditions:default": [], + }), + hdrs = ["geoip_provider.h"], + external_deps = ["maxmind"], + tags = ["skip_on_windows"], + deps = [ + "//envoy/geoip:geoip_provider_driver_interface", + "@envoy_api//envoy/extensions/geoip_providers/maxmind/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/geoip_providers/maxmind/config.cc b/source/extensions/geoip_providers/maxmind/config.cc new file mode 100644 index 000000000000..df7575436b9f --- /dev/null +++ b/source/extensions/geoip_providers/maxmind/config.cc @@ -0,0 +1,75 @@ +#include "source/extensions/geoip_providers/maxmind/config.h" + +#include "envoy/extensions/geoip_providers/maxmind/v3/maxmind.pb.h" +#include "envoy/registry/registry.h" + +#include "source/common/common/utility.h" +#include "source/common/protobuf/utility.h" + +#include "geoip_provider.h" + +namespace Envoy { +namespace Extensions { +namespace GeoipProviders { +namespace Maxmind { + +using ConfigProto = envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig; + +/** + * A singleton that acts as a factory for generating and looking up GeoipProviders. + * When given equivalent provider configs, the singleton returns pointers to the same + * driver/provider. When given different configs, the singleton returns different provider + * instances. + */ +class DriverSingleton : public Envoy::Singleton::Instance { +public: + std::shared_ptr get(std::shared_ptr singleton, + const ConfigProto& proto_config, + const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) { + std::shared_ptr driver; + const uint64_t key = MessageUtil::hash(proto_config); + absl::MutexLock lock(&mu_); + auto it = drivers_.find(key); + if (it != drivers_.end()) { + driver = it->second.lock(); + } else { + const auto& provider_config = + std::make_shared(proto_config, stat_prefix, context.scope()); + driver = std::make_shared(singleton, provider_config); + drivers_[key] = driver; + } + return driver; + } + +private: + absl::Mutex mu_; + // We keep weak_ptr here so the providers can be destroyed if the config is updated to stop using + // that config of the provider. Each provider stores shared_ptrs to this singleton, which keeps + // the singleton from being destroyed unless it's no longer keeping track of any providers. (The + // singleton shared_ptr is *only* held by driver instances.) + absl::flat_hash_map> drivers_ ABSL_GUARDED_BY(mu_); +}; + +SINGLETON_MANAGER_REGISTRATION(maxmind_geolocation_provider_singleton); + +MaxmindProviderFactory::MaxmindProviderFactory() : FactoryBase("envoy.geoip_providers.maxmind") {} + +DriverSharedPtr MaxmindProviderFactory::createGeoipProviderDriverTyped( + const ConfigProto& proto_config, const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) { + std::shared_ptr drivers = context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(maxmind_geolocation_provider_singleton), + [] { return std::make_shared(); }); + return drivers->get(drivers, proto_config, stat_prefix, context); +} + +/** + * Static registration for the Maxmind provider. @see RegisterFactory. + */ +REGISTER_FACTORY(MaxmindProviderFactory, Geolocation::GeoipProviderFactory); + +} // namespace Maxmind +} // namespace GeoipProviders +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/geoip_providers/maxmind/config.h b/source/extensions/geoip_providers/maxmind/config.h new file mode 100644 index 000000000000..007bc90936ee --- /dev/null +++ b/source/extensions/geoip_providers/maxmind/config.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/extensions/geoip_providers/maxmind/v3/maxmind.pb.h" +#include "envoy/extensions/geoip_providers/maxmind/v3/maxmind.pb.validate.h" + +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/geoip_providers/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace GeoipProviders { +namespace Maxmind { + +using DriverSharedPtr = Envoy::Geolocation::DriverSharedPtr; + +class MaxmindProviderFactory + : public Common::FactoryBase { +public: + MaxmindProviderFactory(); + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + +private: + // FactoryBase + DriverSharedPtr createGeoipProviderDriverTyped( + const envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig& proto_config, + const std::string& stat_prefix, Server::Configuration::FactoryContext& context) override; +}; + +DECLARE_FACTORY(MaxmindProviderFactory); + +} // namespace Maxmind +} // namespace GeoipProviders +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/geoip_providers/maxmind/geoip_provider.cc b/source/extensions/geoip_providers/maxmind/geoip_provider.cc new file mode 100644 index 000000000000..8b10ecefdda0 --- /dev/null +++ b/source/extensions/geoip_providers/maxmind/geoip_provider.cc @@ -0,0 +1,267 @@ +#include "source/extensions/geoip_providers/maxmind/geoip_provider.h" + +#include "source/common/common/assert.h" +#include "source/common/protobuf/protobuf.h" + +namespace Envoy { +namespace Extensions { +namespace GeoipProviders { +namespace Maxmind { + +namespace { +static constexpr const char* MMDB_CITY_LOOKUP_ARGS[] = {"city", "names", "en"}; +static constexpr const char* MMDB_REGION_LOOKUP_ARGS[] = {"subdivisions", "0", "names", "en"}; +static constexpr const char* MMDB_COUNTRY_LOOKUP_ARGS[] = {"country", "names", "en"}; +static constexpr const char* MMDB_ASN_LOOKUP_ARGS[] = {"autonomous_system_number"}; +static constexpr const char* MMDB_ANON_LOOKUP_ARGS[] = {"is_anonymous", "is_anonymous_vpn", + "is_hosting_provider", "is_tor_exit_node", + "is_public_proxy"}; +} // namespace + +GeoipProviderConfig::GeoipProviderConfig( + const envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig& config, + const std::string& stat_prefix, Stats::Scope& scope) + : city_db_path_(!config.city_db_path().empty() ? absl::make_optional(config.city_db_path()) + : absl::nullopt), + isp_db_path_(!config.isp_db_path().empty() ? absl::make_optional(config.isp_db_path()) + : absl::nullopt), + anon_db_path_(!config.anon_db_path().empty() ? absl::make_optional(config.anon_db_path()) + : absl::nullopt), + scope_(scope), stat_name_set_(scope.symbolTable().makeSet("Maxmind")), + stats_prefix_(stat_name_set_->add(stat_prefix + "maxmind")) { + auto geo_headers_to_add = config.common_provider_config().geo_headers_to_add(); + country_header_ = !geo_headers_to_add.country().empty() + ? absl::make_optional(geo_headers_to_add.country()) + : absl::nullopt; + city_header_ = !geo_headers_to_add.city().empty() ? absl::make_optional(geo_headers_to_add.city()) + : absl::nullopt; + region_header_ = !geo_headers_to_add.region().empty() + ? absl::make_optional(geo_headers_to_add.region()) + : absl::nullopt; + asn_header_ = !geo_headers_to_add.asn().empty() ? absl::make_optional(geo_headers_to_add.asn()) + : absl::nullopt; + anon_header_ = !geo_headers_to_add.is_anon().empty() + ? absl::make_optional(geo_headers_to_add.is_anon()) + : absl::nullopt; + anon_vpn_header_ = !geo_headers_to_add.anon_vpn().empty() + ? absl::make_optional(geo_headers_to_add.anon_vpn()) + : absl::nullopt; + anon_hosting_header_ = !geo_headers_to_add.anon_hosting().empty() + ? absl::make_optional(geo_headers_to_add.anon_hosting()) + : absl::nullopt; + anon_tor_header_ = !geo_headers_to_add.anon_tor().empty() + ? absl::make_optional(geo_headers_to_add.anon_tor()) + : absl::nullopt; + anon_proxy_header_ = !geo_headers_to_add.anon_proxy().empty() + ? absl::make_optional(geo_headers_to_add.anon_proxy()) + : absl::nullopt; + if (!city_db_path_ && !isp_db_path_ && !anon_db_path_) { + throw EnvoyException("At least one geolocation database path needs to be configured: " + "city_db_path, isp_db_path or anon_db_path"); + } + if (city_db_path_) { + registerGeoDbStats("city_db"); + } + if (isp_db_path_) { + registerGeoDbStats("isp_db"); + } + if (anon_db_path_) { + registerGeoDbStats("anon_db"); + } +}; + +void GeoipProviderConfig::registerGeoDbStats(const std::string& db_type) { + stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".total")); + stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".hit")); + stat_name_set_->rememberBuiltin(absl::StrCat(db_type, ".lookup_error")); +} + +bool GeoipProviderConfig::isLookupEnabledForHeader(const absl::optional& header) { + return (header && !header.value().empty()); +} + +void GeoipProviderConfig::incCounter(Stats::StatName name) { + Stats::SymbolTable::StoragePtr storage = scope_.symbolTable().join({stats_prefix_, name}); + scope_.counterFromStatName(Stats::StatName(storage.get())).inc(); +} + +GeoipProvider::~GeoipProvider() { + ENVOY_LOG(debug, "Shutting down Maxmind geolocation provider"); + if (city_db_) { + MMDB_close(city_db_.get()); + } + if (isp_db_) { + MMDB_close(isp_db_.get()); + } + if (anon_db_) { + MMDB_close(anon_db_.get()); + } +} + +MaxmindDbPtr GeoipProvider::initMaxMindDb(const absl::optional& db_path) { + if (db_path) { + MMDB_s maxmind_db; + int result_code = MMDB_open(db_path.value().c_str(), MMDB_MODE_MMAP, &maxmind_db); + RELEASE_ASSERT(MMDB_SUCCESS == result_code, + fmt::format("Unable to open Maxmind database file {}. Error {}", db_path.value(), + std::string(MMDB_strerror(result_code)))); + return std::make_unique(maxmind_db); + } else { + ENVOY_LOG(debug, "Geolocation database path is empty, skipping database creation"); + return nullptr; + } +} + +void GeoipProvider::lookup(Geolocation::LookupRequest&& request, + Geolocation::LookupGeoHeadersCallback&& cb) const { + auto& remote_address = request.remoteAddress(); + auto lookup_result = absl::flat_hash_map{}; + lookupInCityDb(remote_address, lookup_result); + lookupInAsnDb(remote_address, lookup_result); + lookupInAnonDb(remote_address, lookup_result); + cb(std::move(lookup_result)); +} + +void GeoipProvider::lookupInCityDb( + const Network::Address::InstanceConstSharedPtr& remote_address, + absl::flat_hash_map& lookup_result) const { + if (config_->isLookupEnabledForHeader(config_->cityHeader()) || + config_->isLookupEnabledForHeader(config_->regionHeader()) || + config_->isLookupEnabledForHeader(config_->countryHeader())) { + ASSERT(city_db_, "Maxmind city database is not initialised for performing lookups"); + int mmdb_error; + const uint32_t n_prev_hits = lookup_result.size(); + MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr( + city_db_.get(), reinterpret_cast(remote_address->sockAddr()), &mmdb_error); + if (!mmdb_error) { + MMDB_entry_data_list_s* entry_data_list; + int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list); + if (status == MMDB_SUCCESS) { + if (config_->isLookupEnabledForHeader(config_->cityHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, config_->cityHeader().value(), + MMDB_CITY_LOOKUP_ARGS[0], MMDB_CITY_LOOKUP_ARGS[1], + MMDB_CITY_LOOKUP_ARGS[2]); + } + if (config_->isLookupEnabledForHeader(config_->regionHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, + config_->regionHeader().value(), MMDB_REGION_LOOKUP_ARGS[0], + MMDB_REGION_LOOKUP_ARGS[1], MMDB_REGION_LOOKUP_ARGS[2], + MMDB_REGION_LOOKUP_ARGS[3]); + } + if (config_->isLookupEnabledForHeader(config_->countryHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, + config_->countryHeader().value(), MMDB_COUNTRY_LOOKUP_ARGS[0], + MMDB_COUNTRY_LOOKUP_ARGS[1], MMDB_COUNTRY_LOOKUP_ARGS[2]); + } + if (lookup_result.size() > n_prev_hits) { + config_->incHit("city_db"); + } + MMDB_free_entry_data_list(entry_data_list); + } + + } else { + config_->incLookupError("city_db"); + } + config_->incTotal("city_db"); + } +} + +void GeoipProvider::lookupInAsnDb( + const Network::Address::InstanceConstSharedPtr& remote_address, + absl::flat_hash_map& lookup_result) const { + if (config_->isLookupEnabledForHeader(config_->asnHeader())) { + RELEASE_ASSERT(isp_db_, "Maxmind asn database is not initialized for performing lookups"); + int mmdb_error; + const uint32_t n_prev_hits = lookup_result.size(); + MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr( + isp_db_.get(), reinterpret_cast(remote_address->sockAddr()), &mmdb_error); + if (!mmdb_error) { + MMDB_entry_data_list_s* entry_data_list; + int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list); + if (status == MMDB_SUCCESS && entry_data_list) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, config_->asnHeader().value(), + MMDB_ASN_LOOKUP_ARGS[0]); + MMDB_free_entry_data_list(entry_data_list); + if (lookup_result.size() > n_prev_hits) { + config_->incHit("isp_db"); + } + } else { + config_->incLookupError("isp_db"); + } + } + config_->incTotal("isp_db"); + } +} + +void GeoipProvider::lookupInAnonDb( + const Network::Address::InstanceConstSharedPtr& remote_address, + absl::flat_hash_map& lookup_result) const { + if (config_->isLookupEnabledForHeader(config_->anonHeader()) || config_->anonVpnHeader()) { + ASSERT(anon_db_, "Maxmind city database is not initialised for performing lookups"); + int mmdb_error; + const uint32_t n_prev_hits = lookup_result.size(); + MMDB_lookup_result_s mmdb_lookup_result = MMDB_lookup_sockaddr( + anon_db_.get(), reinterpret_cast(remote_address->sockAddr()), &mmdb_error); + if (!mmdb_error) { + MMDB_entry_data_list_s* entry_data_list; + int status = MMDB_get_entry_data_list(&mmdb_lookup_result.entry, &entry_data_list); + if (status == MMDB_SUCCESS) { + if (config_->isLookupEnabledForHeader(config_->anonHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, config_->anonHeader().value(), + MMDB_ANON_LOOKUP_ARGS[0]); + } + if (config_->isLookupEnabledForHeader(config_->anonVpnHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, + config_->anonVpnHeader().value(), MMDB_ANON_LOOKUP_ARGS[1]); + } + if (config_->isLookupEnabledForHeader(config_->anonHostingHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, + config_->anonHostingHeader().value(), MMDB_ANON_LOOKUP_ARGS[2]); + } + if (config_->isLookupEnabledForHeader(config_->anonTorHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, + config_->anonTorHeader().value(), MMDB_ANON_LOOKUP_ARGS[3]); + } + if (config_->isLookupEnabledForHeader(config_->anonProxyHeader())) { + populateGeoLookupResult(mmdb_lookup_result, lookup_result, + config_->anonProxyHeader().value(), MMDB_ANON_LOOKUP_ARGS[4]); + } + if (lookup_result.size() > n_prev_hits) { + config_->incHit("anon_db"); + } + MMDB_free_entry_data_list(entry_data_list); + } else { + config_->incLookupError("anon_db"); + } + } + config_->incTotal("anon_db"); + } +} + +template +void GeoipProvider::populateGeoLookupResult( + MMDB_lookup_result_s& mmdb_lookup_result, + absl::flat_hash_map& lookup_result, const std::string& result_key, + Params... lookup_params) const { + MMDB_entry_data_s entry_data; + if ((MMDB_get_value(&mmdb_lookup_result.entry, &entry_data, lookup_params..., NULL)) == + MMDB_SUCCESS) { + std::string result_value; + if (entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) { + result_value = std::string(entry_data.utf8_string, entry_data.data_size); + } else if (entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UINT32 && + entry_data.uint32 > 0) { + result_value = std::to_string(entry_data.uint32); + } else if (entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_BOOLEAN) { + result_value = entry_data.boolean ? "true" : "false"; + } + if (!result_value.empty()) { + lookup_result.insert(std::make_pair(result_key, result_value)); + } + } +} + +} // namespace Maxmind +} // namespace GeoipProviders +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/geoip_providers/maxmind/geoip_provider.h b/source/extensions/geoip_providers/maxmind/geoip_provider.h new file mode 100644 index 000000000000..29fb57285a5e --- /dev/null +++ b/source/extensions/geoip_providers/maxmind/geoip_provider.h @@ -0,0 +1,122 @@ +#pragma once + +#include "envoy/common/platform.h" +#include "envoy/extensions/geoip_providers/maxmind/v3/maxmind.pb.h" +#include "envoy/geoip/geoip_provider_driver.h" + +#include "source/common/common/logger.h" + +#include "maxminddb.h" + +namespace Envoy { +namespace Extensions { +namespace GeoipProviders { +namespace Maxmind { + +class GeoipProviderConfig { +public: + GeoipProviderConfig(const envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig& config, + const std::string& stat_prefix, Stats::Scope& scope); + + const absl::optional& cityDbPath() const { return city_db_path_; } + const absl::optional& ispDbPath() const { return isp_db_path_; } + const absl::optional& anonDbPath() const { return anon_db_path_; } + + bool isLookupEnabledForHeader(const absl::optional& header); + + const absl::optional& countryHeader() const { return country_header_; } + const absl::optional& cityHeader() const { return city_header_; } + const absl::optional& regionHeader() const { return region_header_; } + const absl::optional& asnHeader() const { return asn_header_; } + + const absl::optional& anonHeader() const { return anon_header_; } + const absl::optional& anonVpnHeader() const { return anon_vpn_header_; } + const absl::optional& anonHostingHeader() const { return anon_hosting_header_; } + const absl::optional& anonTorHeader() const { return anon_tor_header_; } + const absl::optional& anonProxyHeader() const { return anon_proxy_header_; } + + void incLookupError(absl::string_view maxmind_db_type) { + incCounter( + stat_name_set_->getBuiltin(absl::StrCat(maxmind_db_type, ".lookup_error"), unknown_hit_)); + } + + void incTotal(absl::string_view maxmind_db_type) { + incCounter(stat_name_set_->getBuiltin(absl::StrCat(maxmind_db_type, ".total"), unknown_hit_)); + } + + void incHit(absl::string_view maxmind_db_type) { + incCounter(stat_name_set_->getBuiltin(absl::StrCat(maxmind_db_type, ".hit"), unknown_hit_)); + } + + void registerGeoDbStats(const std::string& db_type); + +private: + absl::optional city_db_path_; + absl::optional isp_db_path_; + absl::optional anon_db_path_; + + absl::optional country_header_; + absl::optional city_header_; + absl::optional region_header_; + absl::optional asn_header_; + + absl::optional anon_header_; + absl::optional anon_vpn_header_; + absl::optional anon_hosting_header_; + absl::optional anon_tor_header_; + absl::optional anon_proxy_header_; + + Stats::Scope& scope_; + Stats::StatNameSetPtr stat_name_set_; + const Stats::StatName stats_prefix_; + const Stats::StatName unknown_hit_; + void incCounter(Stats::StatName name); +}; + +using GeoipProviderConfigSharedPtr = std::shared_ptr; + +using MaxmindDbPtr = std::unique_ptr; +class GeoipProvider : public Envoy::Geolocation::Driver, + public Logger::Loggable { + +public: + GeoipProvider(Singleton::InstanceSharedPtr owner, GeoipProviderConfigSharedPtr config) + : config_(config), owner_(owner) { + city_db_ = initMaxMindDb(config_->cityDbPath()); + isp_db_ = initMaxMindDb(config_->ispDbPath()); + anon_db_ = initMaxMindDb(config_->anonDbPath()); + }; + + ~GeoipProvider(); + + // Envoy::Geolocation::Driver + void lookup(Geolocation::LookupRequest&&, Geolocation::LookupGeoHeadersCallback&&) const override; + +private: + // Allow the unit test to have access to private members. + friend class GeoipProviderPeer; + GeoipProviderConfigSharedPtr config_; + MaxmindDbPtr city_db_; + MaxmindDbPtr isp_db_; + MaxmindDbPtr anon_db_; + MaxmindDbPtr initMaxMindDb(const absl::optional& db_path); + void lookupInCityDb(const Network::Address::InstanceConstSharedPtr& remote_address, + absl::flat_hash_map& lookup_result) const; + void lookupInAsnDb(const Network::Address::InstanceConstSharedPtr& remote_address, + absl::flat_hash_map& lookup_result) const; + void lookupInAnonDb(const Network::Address::InstanceConstSharedPtr& remote_address, + absl::flat_hash_map& lookup_result) const; + template + void populateGeoLookupResult(MMDB_lookup_result_s& mmdb_lookup_result, + absl::flat_hash_map& lookup_result, + const std::string& result_key, Params... lookup_params) const; + // A shared_ptr to keep the provider singleton alive as long as any of its providers are in use. + const Singleton::InstanceSharedPtr owner_; +}; + +using GeoipProviderSharedPtr = std::shared_ptr; + +} // namespace Maxmind +} // namespace GeoipProviders +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/path/uri_template_lib/uri_template_internal.cc b/source/extensions/path/uri_template_lib/uri_template_internal.cc index 0b636fb2f75d..7cec7fed99c0 100644 --- a/source/extensions/path/uri_template_lib/uri_template_internal.cc +++ b/source/extensions/path/uri_template_lib/uri_template_internal.cc @@ -41,7 +41,8 @@ constexpr unsigned long kPatternMatchingMinVariableNameLen = 1; constexpr absl::string_view kLiteral = "a-zA-Z0-9-._~" // Unreserved "%" // pct-encoded "!$&'()+,;" // sub-delims excluding *= - ":@"; + ":@" + "="; // user included "=" allowed // Default operator used for the variable when none specified. constexpr Operator kDefaultVariableOperator = Operator::PathGlob; diff --git a/source/extensions/tracers/common/ot/opentracing_driver_impl.cc b/source/extensions/tracers/common/ot/opentracing_driver_impl.cc index 387be89f9d2b..e802f29e1295 100644 --- a/source/extensions/tracers/common/ot/opentracing_driver_impl.cc +++ b/source/extensions/tracers/common/ot/opentracing_driver_impl.cc @@ -155,7 +155,7 @@ Tracing::SpanPtr OpenTracingDriver::startSpan(const Tracing::Config& config, Tracing::TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision) { + Tracing::Decision tracing_decision) { const PropagationMode propagation_mode = this->propagationMode(); const opentracing::Tracer& tracer = this->tracer(); std::unique_ptr active_span; diff --git a/source/extensions/tracers/common/ot/opentracing_driver_impl.h b/source/extensions/tracers/common/ot/opentracing_driver_impl.h index 94c58b44cf4f..360b1a15e7d6 100644 --- a/source/extensions/tracers/common/ot/opentracing_driver_impl.h +++ b/source/extensions/tracers/common/ot/opentracing_driver_impl.h @@ -68,7 +68,7 @@ class OpenTracingDriver : public Tracing::Driver, protected Logger::Loggable(); @@ -86,7 +86,12 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext // The OpenTracing implementation ignored the `Tracing::Config` argument, // so we will as well. datadog::tracing::SpanConfig span_config; - span_config.name = operation_name; + // The `operation_name` parameter to this function more closely matches + // Datadog's concept of "resource name." Datadog's "span name," or "operation + // name," instead describes the category of operation being performed, which + // here we hard-code. + span_config.name = "envoy.proxy"; + span_config.resource = operation_name; span_config.start = estimateTime(stream_info.startTime()); datadog::tracing::Tracer& tracer = *thread_local_tracer.tracer; diff --git a/source/extensions/tracers/datadog/tracer.h b/source/extensions/tracers/datadog/tracer.h index 9e822cb9a0df..0e433760a304 100644 --- a/source/extensions/tracers/datadog/tracer.h +++ b/source/extensions/tracers/datadog/tracer.h @@ -83,7 +83,7 @@ class Tracer : public Tracing::Driver, private Logger::Loggable(config, oc_config_, trace_context, operation_name, stream_info.startTime(), tracing_decision); } diff --git a/source/extensions/tracers/opencensus/opencensus_tracer_impl.h b/source/extensions/tracers/opencensus/opencensus_tracer_impl.h index 1a392ce9f59d..db3da7856a86 100644 --- a/source/extensions/tracers/opencensus/opencensus_tracer_impl.h +++ b/source/extensions/tracers/opencensus/opencensus_tracer_impl.h @@ -24,7 +24,7 @@ class Driver : public Tracing::Driver, Logger::Loggable { Tracing::SpanPtr startSpan(const Tracing::Config& config, Tracing::TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision) override; + Tracing::Decision tracing_decision) override; private: void applyTraceConfig(const opencensus::proto::trace::v1::TraceConfig& config); diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc index a3119c187e83..4ff15757b468 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc @@ -49,7 +49,7 @@ Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config, Tracing::TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision) { + Tracing::Decision tracing_decision) { // Get tracer from TLS and start span. auto& tracer = tls_slot_ptr_->getTyped().tracer(); SpanContextExtractor extractor(trace_context); diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h index 35f734c87b82..bbdc94b43f16 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h @@ -36,7 +36,7 @@ class Driver : Logger::Loggable, public Tracing::Driver { Tracing::SpanPtr startSpan(const Tracing::Config& config, Tracing::TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision) override; + Tracing::Decision tracing_decision) override; private: class TlsTracer : public ThreadLocal::ThreadLocalObject { diff --git a/source/extensions/tracers/opentelemetry/tracer.cc b/source/extensions/tracers/opentelemetry/tracer.cc index cc613fa5ddc5..ca52280ed359 100644 --- a/source/extensions/tracers/opentelemetry/tracer.cc +++ b/source/extensions/tracers/opentelemetry/tracer.cc @@ -168,7 +168,7 @@ void Tracer::sendSpan(::opentelemetry::proto::trace::v1::Span& span) { } Tracing::SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& operation_name, - SystemTime start_time, const Tracing::Decision tracing_decision, + SystemTime start_time, Tracing::Decision tracing_decision, bool downstream_span) { // Create an Tracers::OpenTelemetry::Span class that will contain the OTel span. Span new_span(config, operation_name, start_time, time_source_, *this, downstream_span); diff --git a/source/extensions/tracers/opentelemetry/tracer.h b/source/extensions/tracers/opentelemetry/tracer.h index 7f080278161b..7f0d547e356b 100644 --- a/source/extensions/tracers/opentelemetry/tracer.h +++ b/source/extensions/tracers/opentelemetry/tracer.h @@ -40,7 +40,7 @@ class Tracer : Logger::Loggable { void sendSpan(::opentelemetry::proto::trace::v1::Span& span); Tracing::SpanPtr startSpan(const Tracing::Config& config, const std::string& operation_name, - SystemTime start_time, const Tracing::Decision tracing_decision, + SystemTime start_time, Tracing::Decision tracing_decision, bool downstream_span = true); Tracing::SpanPtr startSpan(const Tracing::Config& config, const std::string& operation_name, diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc index 21671c8cfe26..932799a5c90a 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc @@ -46,7 +46,7 @@ Driver::Driver(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, Tracing::SpanPtr Driver::startSpan(const Tracing::Config&, Tracing::TraceContext& trace_context, const StreamInfo::StreamInfo&, const std::string&, - const Tracing::Decision decision) { + Tracing::Decision decision) { auto& tracer = tls_slot_ptr_->getTyped().tracer(); TracingContextPtr tracing_context; // TODO(shikugawa): support extension span header. diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h index 0a97052c624e..f99cb8aa2345 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h @@ -28,7 +28,7 @@ class Driver : public Tracing::Driver, public Logger::LoggablegetTyped().tracer_; SpanPtr new_zipkin_span; SpanContextExtractor extractor(trace_context); diff --git a/source/extensions/tracers/zipkin/zipkin_tracer_impl.h b/source/extensions/tracers/zipkin/zipkin_tracer_impl.h index bc7a9fa95826..9385e6d0280b 100644 --- a/source/extensions/tracers/zipkin/zipkin_tracer_impl.h +++ b/source/extensions/tracers/zipkin/zipkin_tracer_impl.h @@ -126,7 +126,7 @@ class Driver : public Tracing::Driver { Tracing::SpanPtr startSpan(const Tracing::Config& config, Tracing::TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision) override; + Tracing::Decision tracing_decision) override; // Getters to return the ZipkinDriver's key members. Upstream::ClusterManager& clusterManager() { return cm_; } diff --git a/source/server/admin/admin.cc b/source/server/admin/admin.cc index 107165130792..0da4500b8331 100644 --- a/source/server/admin/admin.cc +++ b/source/server/admin/admin.cc @@ -201,6 +201,9 @@ AdminImpl::AdminImpl(const std::string& profile_path, Server::Instance& server, "When draining listeners, enter a graceful drain period prior to closing " "listeners. This behaviour and duration is configurable via server options " "or CLI"}, + {ParamDescriptor::Type::Boolean, "skip_exit", + "When draining listeners, do not exit after the drain period. " + "This must be used with graceful"}, {ParamDescriptor::Type::Boolean, "inboundonly", "Drains all inbound listeners. traffic_direction field in " "envoy_v3_api_msg_config.listener.v3.Listener is used to determine whether a " @@ -296,7 +299,6 @@ bool AdminImpl::createFilterChain(Http::FilterChainManager& manager, bool, } namespace { - // Implements a chunked request for static text. class StaticTextRequest : public Admin::Request { public: diff --git a/source/server/admin/listeners_handler.cc b/source/server/admin/listeners_handler.cc index 127de3e3c6f5..b04302331a98 100644 --- a/source/server/admin/listeners_handler.cc +++ b/source/server/admin/listeners_handler.cc @@ -22,12 +22,19 @@ Http::Code ListenersHandler::handlerDrainListeners(Http::ResponseHeaderMap&, : ListenerManager::StopListenersType::All; const bool graceful = params.find("graceful") != params.end(); + const bool skip_exit = params.find("skip_exit") != params.end(); + if (skip_exit && !graceful) { + response.add("skip_exit requires graceful\n"); + return Http::Code::BadRequest; + } if (graceful) { // Ignore calls to /drain_listeners?graceful if the drain sequence has // already started. if (!server_.drainManager().draining()) { - server_.drainManager().startDrainSequence([this, stop_listeners_type]() { - server_.listenerManager().stopListeners(stop_listeners_type, {}); + server_.drainManager().startDrainSequence([this, stop_listeners_type, skip_exit]() { + if (!skip_exit) { + server_.listenerManager().stopListeners(stop_listeners_type, {}); + } }); } } else { diff --git a/source/server/admin/logs_handler.cc b/source/server/admin/logs_handler.cc index 6ae59c25f5bd..db84851d5bbb 100644 --- a/source/server/admin/logs_handler.cc +++ b/source/server/admin/logs_handler.cc @@ -118,6 +118,8 @@ absl::Status LogsHandler::changeLogLevel(Http::Utility::QueryParams& params) { // Build a map of name:level pairs, a few allocations is ok here since it's // not common to call this function at a high rate. absl::flat_hash_map name_levels; + std::vector> glob_levels; + const bool use_fine_grain_logger = Logger::Context::useFineGrainLogger(); if (paths != params.end()) { // Bulk change log level by name:level pairs, separated by comma. @@ -136,7 +138,13 @@ absl::Status LogsHandler::changeLogLevel(Http::Utility::QueryParams& params) { return level_to_use.status(); } - name_levels[name] = *level_to_use; + if (use_fine_grain_logger) { + ENVOY_LOG(info, "adding fine-grain log update, glob='{}' level='{}'", name, + spdlog::level::level_string_views[*level_to_use]); + glob_levels.emplace_back(name, *level_to_use); + } else { + name_levels[name] = *level_to_use; + } } } else { // The HTML admin interface will always populate "level" and "paths" though @@ -157,57 +165,47 @@ absl::Status LogsHandler::changeLogLevel(Http::Utility::QueryParams& params) { return level_to_use.status(); } - name_levels[key] = *level_to_use; + if (use_fine_grain_logger) { + ENVOY_LOG(info, "adding fine-grain log update, glob='{}' level='{}'", key, + spdlog::level::level_string_views[*level_to_use]); + glob_levels.emplace_back(key, *level_to_use); + } else { + name_levels[key] = *level_to_use; + } } - return changeLogLevels(name_levels); + if (!use_fine_grain_logger) { + return changeLogLevelsForComponentLoggers(name_levels); + } + getFineGrainLogContext().updateVerbositySetting(glob_levels); + + return absl::OkStatus(); } -absl::Status LogsHandler::changeLogLevels( +absl::Status LogsHandler::changeLogLevelsForComponentLoggers( const absl::flat_hash_map& changes) { - if (!Logger::Context::useFineGrainLogger()) { - std::vector> loggers_to_change; - for (Logger::Logger& logger : Logger::Registry::loggers()) { - auto name_level_itr = changes.find(logger.name()); - if (name_level_itr == changes.end()) { - continue; - } - - loggers_to_change.emplace_back(std::make_pair(&logger, name_level_itr->second)); - } - - // Check if we have any invalid logger in changes. - if (loggers_to_change.size() != changes.size()) { - return absl::InvalidArgumentError("unknown logger name"); + std::vector> loggers_to_change; + for (Logger::Logger& logger : Logger::Registry::loggers()) { + auto name_level_itr = changes.find(logger.name()); + if (name_level_itr == changes.end()) { + continue; } - for (auto& it : loggers_to_change) { - Logger::Logger* logger = it.first; - spdlog::level::level_enum level = it.second; - - ENVOY_LOG(info, "change log level: name='{}' level='{}'", logger->name(), - spdlog::level::level_string_views[level]); - logger->setLevel(level); - } - } else { - std::vector> loggers_to_change; - for (auto& it : changes) { - SpdLoggerSharedPtr logger = getFineGrainLogContext().getFineGrainLogEntry(it.first); - if (!logger) { - return absl::InvalidArgumentError("unknown logger name"); - } + loggers_to_change.emplace_back(std::make_pair(&logger, name_level_itr->second)); + } - loggers_to_change.emplace_back(std::make_pair(logger, it.second)); - } + // Check if we have any invalid logger in changes. + if (loggers_to_change.size() != changes.size()) { + return absl::InvalidArgumentError("unknown logger name"); + } - for (auto& it : loggers_to_change) { - SpdLoggerSharedPtr logger = it.first; - spdlog::level::level_enum level = it.second; + for (auto& it : loggers_to_change) { + Logger::Logger* logger = it.first; + spdlog::level::level_enum level = it.second; - FINE_GRAIN_LOG(info, "change log level: name='{}' level='{}'", logger->name(), - spdlog::level::level_string_views[level]); - logger->set_level(level); - } + ENVOY_LOG(info, "change log level: name='{}' level='{}'", logger->name(), + spdlog::level::level_string_views[level]); + logger->setLevel(level); } return absl::OkStatus(); diff --git a/source/server/admin/logs_handler.h b/source/server/admin/logs_handler.h index 317a854b5230..5476b8e5d223 100644 --- a/source/server/admin/logs_handler.h +++ b/source/server/admin/logs_handler.h @@ -43,8 +43,8 @@ class LogsHandler : public HandlerContextBase, Logger::Loggable& changes); + absl::Status changeLogLevelsForComponentLoggers( + const absl::flat_hash_map& changes); inline absl::StatusOr parseLogLevel(absl::string_view level_string) { auto level_it = log_levels_.find(level_string); diff --git a/test/common/common/log_verbosity_update_test.cc b/test/common/common/log_verbosity_update_test.cc index 72a105c43229..db2609252c1a 100644 --- a/test/common/common/log_verbosity_update_test.cc +++ b/test/common/common/log_verbosity_update_test.cc @@ -41,10 +41,8 @@ TEST(FineGrainLog, updateCurrentFilePath) { getFineGrainLogContext().setFineGrainLogger(__FILE__, spdlog::level::info); FINE_GRAIN_LOG(debug, "Debug: you shouldn't see this message!"); - absl::string_view file_path = __FILE__; - file_path.remove_suffix(3); const std::pair update = - std::make_pair(file_path, static_cast(spdlog::level::debug)); + std::make_pair(__FILE__, static_cast(spdlog::level::debug)); getFineGrainLogContext().updateVerbositySetting({update}); FINE_GRAIN_LOG(debug, "Debug: now level is debug"); @@ -86,13 +84,13 @@ TEST(FineGrainLog, verbosityUpdatePriority) { const std::pair update = std::make_pair(file_path, static_cast(spdlog::level::debug)); getFineGrainLogContext().updateVerbositySetting({update}); + // This will also try to clear the verbosity update by changing the default level. getFineGrainLogContext().updateVerbosityDefaultLevel(spdlog::level::trace); - FINE_GRAIN_LOG(debug, "Debug: now level is debug"); - FINE_GRAIN_LOG(trace, "Trace: you should not see this message"); + FINE_GRAIN_LOG(trace, "Trace: you should see this message"); SpdLoggerSharedPtr p = getFineGrainLogContext().getFineGrainLogEntry(__FILE__); ASSERT_NE(p, nullptr); - EXPECT_EQ(p->level(), spdlog::level::debug); + EXPECT_EQ(p->level(), spdlog::level::trace); } TEST(FineGrainLog, updateBasename) { diff --git a/test/common/http/http2/http2_frame.cc b/test/common/http/http2/http2_frame.cc index 46ba3751ba24..319b3fc87380 100644 --- a/test/common/http/http2/http2_frame.cc +++ b/test/common/http/http2/http2_frame.cc @@ -51,12 +51,22 @@ Http2Frame::ResponseStatus Http2Frame::responseStatus() const { return ResponseStatus::Ok; case StaticHeaderIndex::Status404: return ResponseStatus::NotFound; + case StaticHeaderIndex::Status500: + return ResponseStatus::InternalServerError; default: break; } return ResponseStatus::Unknown; } +uint32_t Http2Frame::streamId() const { + if (empty() || size() <= HeaderSize) { + return 0; + } + return (uint32_t(data_[5]) << 24) + (uint32_t(data_[6]) << 16) + (uint32_t(data_[7]) << 8) + + uint32_t(data_[8]); +} + void Http2Frame::buildHeader(Type type, uint32_t payload_size, uint8_t flags, uint32_t stream_id) { data_.assign(payload_size + HeaderSize, 0); setPayloadSize(payload_size); diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index ea1613d4635a..e6a2a3372253 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -121,7 +121,7 @@ class Http2Frame { Http11Required }; - enum class ResponseStatus { Unknown, Ok, NotFound }; + enum class ResponseStatus { Unknown, Ok, NotFound, InternalServerError }; struct Header { Header(absl::string_view key, absl::string_view value) : key_(key), value_(value) {} @@ -226,6 +226,7 @@ class Http2Frame { return false; } ResponseStatus responseStatus() const; + uint32_t streamId() const; // Copy HTTP2 header. The `header` parameter must at least be HeaderSize long. // Allocates payload size based on the value in the header. diff --git a/test/common/quic/envoy_quic_client_session_test.cc b/test/common/quic/envoy_quic_client_session_test.cc index 921c0237f117..357519bef002 100644 --- a/test/common/quic/envoy_quic_client_session_test.cc +++ b/test/common/quic/envoy_quic_client_session_test.cc @@ -78,7 +78,7 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam()), envoy_quic_session_(quic_config_, quic_version_, std::unique_ptr(quic_connection_), - quic::QuicServerId("example.com", 443, false), crypto_config_, nullptr, + quic::QuicServerId("example.com", 443, false), crypto_config_, *dispatcher_, /*send_buffer_limit*/ 1024 * 1024, crypto_stream_factory_, quic_stat_names_, {}, *store_.rootScope(), transport_socket_options_), diff --git a/test/common/quic/platform/BUILD b/test/common/quic/platform/BUILD index 03b4fed648e5..3c500de5c7bf 100644 --- a/test/common/quic/platform/BUILD +++ b/test/common/quic/platform/BUILD @@ -27,7 +27,6 @@ envoy_cc_test( tags = ["nofips"], deps = [ "//source/common/memory:stats_lib", - "//source/common/quic:spdy_server_push_utils_for_envoy_lib", "//source/common/quic/platform:quiche_flags_impl_lib", "//test/common/buffer:utility_lib", "//test/common/stats:stat_test_utility_lib", diff --git a/test/common/quic/test_utils.h b/test/common/quic/test_utils.h index ebbf5e70306f..8a5a46971f89 100644 --- a/test/common/quic/test_utils.h +++ b/test/common/quic/test_utils.h @@ -200,7 +200,7 @@ class MockEnvoyQuicClientSession : public IsolatedStoreProvider, public EnvoyQui quic::QuicServerId("example.com", 443, false), std::make_shared( quic::test::crypto_test_utils::ProofVerifierForTesting()), - nullptr, dispatcher, send_buffer_limit, crypto_stream_factory, + dispatcher, send_buffer_limit, crypto_stream_factory, quic_stat_names_, {}, *stats_store_.rootScope(), nullptr) {} void Initialize() override { diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 9d0cb58403aa..dfd446290a23 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -6694,6 +6694,8 @@ TEST(NullConfigImplTest, All) { EXPECT_FALSE(config.usesVhds()); EXPECT_FALSE(config.mostSpecificHeaderMutationsWins()); EXPECT_EQ(0Ul, config.maxDirectResponseBodySizeBytes()); + config.metadata(); + config.typedMetadata(); } class BadHttpRouteConfigurationsTest : public testing::Test, public ConfigImplTestBase {}; @@ -7718,9 +7720,11 @@ TEST_F(RouteConfigurationV2, BrokenTypedMetadata) { TEST_F(RouteConfigurationV2, RouteConfigGetters) { const std::string yaml = R"EOF( name: foo +metadata: { filter_metadata: { com.bar.foo: { baz: test_config_value }, baz: {name: config_bluh} } } virtual_hosts: - name: bar domains: ["*"] + metadata: { filter_metadata: { com.bar.foo: { baz: test_vh_value }, baz: {name: vh_bluh} } } routes: - match: safe_regex: @@ -7760,6 +7764,26 @@ name: foo EXPECT_EQ("bar", symbol_table_->toString(route_entry->virtualHost().statName())); EXPECT_EQ("foo", route_entry->virtualHost().routeConfig().name()); + + // Get metadata of virtual host. + const auto& vh_metadata = route_entry->virtualHost().metadata(); + const auto& vh_typed_metadata = route_entry->virtualHost().typedMetadata(); + + EXPECT_EQ( + "test_vh_value", + Envoy::Config::Metadata::metadataValue(&vh_metadata, "com.bar.foo", "baz").string_value()); + EXPECT_NE(nullptr, vh_typed_metadata.get(baz_factory.name())); + EXPECT_EQ("vh_bluh", vh_typed_metadata.get(baz_factory.name())->name); + + // Get metadata of route configuration. + const auto& config_metadata = route_entry->virtualHost().routeConfig().metadata(); + const auto& config_typed_metadata = route_entry->virtualHost().routeConfig().typedMetadata(); + + EXPECT_EQ("test_config_value", + Envoy::Config::Metadata::metadataValue(&config_metadata, "com.bar.foo", "baz") + .string_value()); + EXPECT_NE(nullptr, config_typed_metadata.get(baz_factory.name())); + EXPECT_EQ("config_bluh", config_typed_metadata.get(baz_factory.name())->name); } TEST_F(RouteConfigurationV2, RouteTracingConfig) { @@ -9399,7 +9423,6 @@ TEST_F(RouteMatcherTest, MixedPathPatternMatch) { } TEST_F(RouteMatcherTest, PatternMatchRewriteSimple) { - const std::string yaml = R"EOF( virtual_hosts: - name: path_pattern @@ -9432,6 +9455,145 @@ TEST_F(RouteMatcherTest, PatternMatchRewriteSimple) { EXPECT_EQ("path.prefix.com", headers.get_(Http::Headers::get().Host)); } +TEST_F(RouteMatcherTest, PatternMatchRewriteDoubleEqual) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: path_pattern + domains: ["*"] + routes: + - match: + path_match_policy: + name: envoy.path.match.uri_template.uri_template_matcher + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig + path_template: "/one/two/{code=*}/{loc=*}/{curr=*}/{tri=**}" + case_sensitive: false + route: + cluster: "path-pattern-cluster-one" + path_rewrite_policy: + name: envoy.path.rewrite.uri_template.uri_template_rewriter + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.rewrite.uri_template.v3.UriTemplateRewriteConfig + path_template_rewrite: "/{code}/{loc}/{curr}/{tri}" + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"path-pattern-cluster-one"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + + Http::TestRequestHeaderMapImpl headers = + genHeaders("path.prefix.com", "/one/two/en/gb/ilp==/dGasdA/?key1=test1&key2=test2", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + EXPECT_EQ("/en/gb/ilp==/dGasdA/?key1=test1&key2=test2", + route->currentUrlPathAfterRewrite(headers)); + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("/en/gb/ilp==/dGasdA/?key1=test1&key2=test2", headers.get_(Http::Headers::get().Path)); + EXPECT_EQ("path.prefix.com", headers.get_(Http::Headers::get().Host)); +} + +TEST_F(RouteMatcherTest, PatternMatchRewriteTripleEqualVariable) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: path_pattern + domains: ["*"] + routes: + - match: + path_match_policy: + name: envoy.path.match.uri_template.uri_template_matcher + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig + path_template: "/one/{two}/{code===na/*}" + case_sensitive: false + route: + cluster: "path-pattern-cluster-one" + path_rewrite_policy: + name: envoy.path.rewrite.uri_template.uri_template_rewriter + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.rewrite.uri_template.v3.UriTemplateRewriteConfig + path_template_rewrite: "/{code}/{two}" + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"path-pattern-cluster-one"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + + Http::TestRequestHeaderMapImpl headers = + genHeaders("path.prefix.com", "/one/two/==na/three", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + EXPECT_EQ("/==na/three/two", route->currentUrlPathAfterRewrite(headers)); + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("/==na/three/two", headers.get_(Http::Headers::get().Path)); + EXPECT_EQ("path.prefix.com", headers.get_(Http::Headers::get().Host)); +} + +TEST_F(RouteMatcherTest, PatternMatchRewriteDoubleEqualVariable) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: path_pattern + domains: ["*"] + routes: + - match: + path_match_policy: + name: envoy.path.match.uri_template.uri_template_matcher + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig + path_template: "/one/{two}/{code==na/*}" + case_sensitive: false + route: + cluster: "path-pattern-cluster-one" + path_rewrite_policy: + name: envoy.path.rewrite.uri_template.uri_template_rewriter + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.rewrite.uri_template.v3.UriTemplateRewriteConfig + path_template_rewrite: "/{code}/{two}" + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"path-pattern-cluster-one"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + + Http::TestRequestHeaderMapImpl headers = + genHeaders("path.prefix.com", "/one/two/=na/three", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + EXPECT_EQ("/=na/three/two", route->currentUrlPathAfterRewrite(headers)); + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("/=na/three/two", headers.get_(Http::Headers::get().Path)); + EXPECT_EQ("path.prefix.com", headers.get_(Http::Headers::get().Host)); +} + +TEST_F(RouteMatcherTest, PatternMatchRewriteDoubleEqualInWildcard) { + + const std::string yaml = R"EOF( +virtual_hosts: + - name: path_pattern + domains: ["*"] + routes: + - match: + path_match_policy: + name: envoy.path.match.uri_template.uri_template_matcher + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig + path_template: "/one/two/{code=*}/{loc=*}/{curr=*}/{tri=**}" + case_sensitive: false + route: + cluster: "path-pattern-cluster-one" + path_rewrite_policy: + name: envoy.path.rewrite.uri_template.uri_template_rewriter + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.rewrite.uri_template.v3.UriTemplateRewriteConfig + path_template_rewrite: "/{code}/{loc}/{curr}/{tri}" + )EOF"; + NiceMock stream_info; + factory_context_.cluster_manager_.initializeClusters({"path-pattern-cluster-one"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true); + + Http::TestRequestHeaderMapImpl headers = + genHeaders("path.prefix.com", "/one/two/en/gb/ilp/dGasdA==/?key1=test1&key2=test2", "GET"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + EXPECT_EQ("/en/gb/ilp/dGasdA==/?key1=test1&key2=test2", + route->currentUrlPathAfterRewrite(headers)); + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("/en/gb/ilp/dGasdA==/?key1=test1&key2=test2", headers.get_(Http::Headers::get().Path)); + EXPECT_EQ("path.prefix.com", headers.get_(Http::Headers::get().Host)); +} + TEST_F(RouteMatcherTest, PatternMatchWildcardFilenameUnnamed) { const std::string yaml = R"EOF( @@ -9769,35 +9931,6 @@ TEST_F(RouteMatcherTest, PatternMatchConfigMissingVariable) { "/rest/{one}/{two}/{missing}"); } -TEST_F(RouteMatcherTest, PatternMatchInvalidVariableName) { - const std::string yaml = R"EOF( -virtual_hosts: - - name: path_pattern - domains: ["*"] - routes: - - match: - path_match_policy: - name: envoy.path.match.uri_template.uri_template_matcher - typed_config: - "@type": type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig - path_template: "/rest/{on==e}/{two}" - case_sensitive: false - route: - cluster: "path-pattern-cluster-one" - path_rewrite_policy: - name: envoy.path.rewrite.uri_template.uri_template_rewriter - typed_config: - "@type": type.googleapis.com/envoy.extensions.path.rewrite.uri_template.v3.UriTemplateRewriteConfig - path_template_rewrite: "/rest/{one}/{two}/{missing}" - )EOF"; - NiceMock stream_info; - factory_context_.cluster_manager_.initializeClusters({"path-pattern-cluster-one"}, {}); - - EXPECT_THROW_WITH_MESSAGE( - TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true), - EnvoyException, "path_match_policy.path_template /rest/{on==e}/{two} is invalid"); -} - TEST_F(RouteMatcherTest, PatternMatchWildcardMiddleThreePartVariableNamed) { const std::string yaml = R"EOF( virtual_hosts: diff --git a/test/common/tracing/tracer_manager_impl_test.cc b/test/common/tracing/tracer_manager_impl_test.cc index e9eeb4ec6da2..6520fc2919d0 100644 --- a/test/common/tracing/tracer_manager_impl_test.cc +++ b/test/common/tracing/tracer_manager_impl_test.cc @@ -24,7 +24,7 @@ namespace { class SampleDriver : public Driver { public: SpanPtr startSpan(const Config&, Tracing::TraceContext&, const StreamInfo::StreamInfo&, - const std::string&, const Tracing::Decision) override { + const std::string&, Tracing::Decision) override { return nullptr; } }; diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index c7f2b8219186..56bbbbb72114 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -481,7 +481,6 @@ envoy_cc_benchmark_binary( "//source/common/upstream:upstream_lib", "//source/extensions/load_balancing_policies/maglev:config", "//source/extensions/load_balancing_policies/ring_hash:config", - "//source/extensions/load_balancing_policies/subset:config", "//test/common/upstream:utility_lib", "//test/mocks/upstream:cluster_info_mocks", "//test/test_common:printers_lib", @@ -496,33 +495,6 @@ envoy_benchmark_test( benchmark_binary = "load_balancer_benchmark", ) -envoy_cc_test( - name = "subset_lb_test", - srcs = ["subset_lb_test.cc"], - deps = [ - ":utility_lib", - "//source/common/common:minimal_logger_lib", - "//source/common/network:utility_lib", - "//source/common/upstream:load_balancer_lib", - "//source/common/upstream:upstream_includes", - "//source/common/upstream:upstream_lib", - "//source/extensions/load_balancing_policies/subset:config", - "//test/mocks:common_lib", - "//test/mocks/access_log:access_log_mocks", - "//test/mocks/filesystem:filesystem_mocks", - "//test/mocks/runtime:runtime_mocks", - "//test/mocks/upstream:cluster_info_mocks", - "//test/mocks/upstream:host_mocks", - "//test/mocks/upstream:host_set_mocks", - "//test/mocks/upstream:load_balancer_context_mock", - "//test/mocks/upstream:load_balancer_mocks", - "//test/mocks/upstream:priority_set_mocks", - "//test/test_common:simulated_time_system_lib", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - ], -) - envoy_cc_test( name = "transport_socket_matcher_test", srcs = ["transport_socket_matcher_test.cc"], @@ -632,7 +604,6 @@ envoy_cc_test_library( "//source/common/stats:stats_lib", "//source/common/upstream:cluster_factory_lib", "//source/common/upstream:cluster_manager_lib", - "//source/extensions/load_balancing_policies/subset:config", "//source/extensions/transport_sockets/raw_buffer:config", "//source/extensions/transport_sockets/tls:context_lib", "//test/common/stats:stat_test_utility_lib", diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 34b11ce4af93..ac4777e53bbc 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -9,6 +9,7 @@ #include "source/common/network/resolver_impl.h" #include "source/common/router/context_impl.h" #include "source/common/upstream/load_balancer_factory_base.h" +#include "source/extensions/load_balancing_policies/subset/subset_lb.h" #include "source/extensions/transport_sockets/raw_buffer/config.h" #include "test/common/upstream/test_cluster_manager.h" diff --git a/test/common/upstream/load_balancer_benchmark.cc b/test/common/upstream/load_balancer_benchmark.cc index c52d7b7a2c12..cc49c23f5594 100644 --- a/test/common/upstream/load_balancer_benchmark.cc +++ b/test/common/upstream/load_balancer_benchmark.cc @@ -9,7 +9,6 @@ #include "source/common/upstream/upstream_impl.h" #include "source/extensions/load_balancing_policies/maglev/maglev_lb.h" #include "source/extensions/load_balancing_policies/ring_hash/ring_hash_lb.h" -#include "source/extensions/load_balancing_policies/subset/subset_lb.h" #include "test/benchmark/main.h" #include "test/common/upstream/utility.h" @@ -546,88 +545,6 @@ BENCHMARK(benchmarkMaglevLoadBalancerWeighted) ->Args({500, 95, 75, 25, 10000}) ->Unit(::benchmark::kMillisecond); -class SubsetLbTester : public BaseTester { -public: - SubsetLbTester(uint64_t num_hosts, bool single_host_per_subset) - : BaseTester(num_hosts, 0, 0, true /* attach metadata */) { - envoy::config::cluster::v3::Cluster::LbSubsetConfig subset_config; - subset_config.set_fallback_policy( - envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT); - auto* selector = subset_config.mutable_subset_selectors()->Add(); - selector->set_single_host_per_subset(single_host_per_subset); - *selector->mutable_keys()->Add() = std::string(metadata_key); - - subset_info_ = std::make_unique(subset_config); - auto child_lb_creator = std::make_unique( - LoadBalancerType::Random, absl::nullopt, absl::nullopt, absl::nullopt, absl::nullopt, - common_config_); - lb_ = std::make_unique(*subset_info_, std::move(child_lb_creator), - priority_set_, &local_priority_set_, stats_, - stats_scope_, runtime_, random_, simTime()); - - const HostVector& hosts = priority_set_.getOrCreateHostSet(0).hosts(); - ASSERT(hosts.size() == num_hosts); - orig_hosts_ = std::make_shared(hosts); - smaller_hosts_ = std::make_shared(hosts.begin() + 1, hosts.end()); - ASSERT(smaller_hosts_->size() + 1 == orig_hosts_->size()); - orig_locality_hosts_ = makeHostsPerLocality({*orig_hosts_}); - smaller_locality_hosts_ = makeHostsPerLocality({*smaller_hosts_}); - } - - // Remove a host and add it back. - void update() { - priority_set_.updateHosts(0, - HostSetImpl::partitionHosts(smaller_hosts_, smaller_locality_hosts_), - nullptr, {}, host_moved_, absl::nullopt); - priority_set_.updateHosts(0, HostSetImpl::partitionHosts(orig_hosts_, orig_locality_hosts_), - nullptr, host_moved_, {}, absl::nullopt); - } - - std::unique_ptr subset_info_; - std::unique_ptr lb_; - HostVectorConstSharedPtr orig_hosts_; - HostVectorConstSharedPtr smaller_hosts_; - HostsPerLocalitySharedPtr orig_locality_hosts_; - HostsPerLocalitySharedPtr smaller_locality_hosts_; - HostVector host_moved_; -}; - -void benchmarkSubsetLoadBalancerCreate(::benchmark::State& state) { - const bool single_host_per_subset = state.range(0); - const uint64_t num_hosts = state.range(1); - - if (benchmark::skipExpensiveBenchmarks() && num_hosts > 100) { - state.SkipWithError("Skipping expensive benchmark"); - return; - } - - for (auto _ : state) { // NOLINT: Silences warning about dead store - SubsetLbTester tester(num_hosts, single_host_per_subset); - } -} - -BENCHMARK(benchmarkSubsetLoadBalancerCreate) - ->Ranges({{false, true}, {50, 2500}}) - ->Unit(::benchmark::kMillisecond); - -void benchmarkSubsetLoadBalancerUpdate(::benchmark::State& state) { - const bool single_host_per_subset = state.range(0); - const uint64_t num_hosts = state.range(1); - if (benchmark::skipExpensiveBenchmarks() && num_hosts > 100) { - state.SkipWithError("Skipping expensive benchmark"); - return; - } - - SubsetLbTester tester(num_hosts, single_host_per_subset); - for (auto _ : state) { // NOLINT: Silences warning about dead store - tester.update(); - } -} - -BENCHMARK(benchmarkSubsetLoadBalancerUpdate) - ->Ranges({{false, true}, {50, 2500}}) - ->Unit(::benchmark::kMillisecond); - } // namespace } // namespace Upstream } // namespace Envoy diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc deleted file mode 100644 index 54e07a348cd6..000000000000 --- a/test/common/upstream/subset_lb_test.cc +++ /dev/null @@ -1,3198 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "envoy/config/cluster/v3/cluster.pb.h" -#include "envoy/config/core/v3/base.pb.h" - -#include "source/common/common/logger.h" -#include "source/common/config/metadata.h" -#include "source/common/upstream/upstream_impl.h" -#include "source/extensions/load_balancing_policies/subset/subset_lb.h" - -#include "test/common/upstream/utility.h" -#include "test/mocks/access_log/mocks.h" -#include "test/mocks/common.h" -#include "test/mocks/filesystem/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/upstream/cluster_info.h" -#include "test/mocks/upstream/host.h" -#include "test/mocks/upstream/host_set.h" -#include "test/mocks/upstream/load_balancer.h" -#include "test/mocks/upstream/load_balancer_context.h" -#include "test/mocks/upstream/priority_set.h" -#include "test/test_common/simulated_time_system.h" - -#include "absl/types/optional.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; - -namespace Envoy { -namespace Upstream { - -class SubsetLoadBalancerInternalStateTester { -public: - SubsetLoadBalancerInternalStateTester(std::shared_ptr lb) : lb_(lb) {} - - using MetadataVector = std::vector>; - - void testDescribeMetadata(std::string expected, const MetadataVector& metadata) { - const SubsetLoadBalancer::SubsetMetadata& subset_metadata(metadata); - EXPECT_EQ(expected, lb_.get()->describeMetadata(subset_metadata)); - } - - void validateLbTypeConfigs() const { - const auto* legacy_child_lb_creator = - dynamic_cast(lb_->child_lb_creator_.get()); - - if (legacy_child_lb_creator == nullptr) { - return; - } - - // Each of these expects that an lb_type is set to that type iff the - // returned config for that type exists. - EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::RingHash, - legacy_child_lb_creator->lbRingHashConfig() != absl::nullopt); - EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::Maglev, - legacy_child_lb_creator->lbMaglevConfig() != absl::nullopt); - EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::RoundRobin, - legacy_child_lb_creator->lbRoundRobinConfig() != absl::nullopt); - EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::LeastRequest, - legacy_child_lb_creator->lbLeastRequestConfig() != absl::nullopt); - } - -private: - std::shared_ptr lb_; -}; - -class TestMetadataMatchCriterion : public Router::MetadataMatchCriterion { -public: - TestMetadataMatchCriterion(const std::string& name, const HashedValue& value) - : name_(name), value_(value) {} - - const std::string& name() const override { return name_; } - const HashedValue& value() const override { return value_; } - -private: - std::string name_; - HashedValue value_; -}; - -class TestMetadataMatchCriteria : public Router::MetadataMatchCriteria { -public: - TestMetadataMatchCriteria(const std::map matches) { - for (const auto& it : matches) { - ProtobufWkt::Value v; - v.set_string_value(it.second); - - matches_.emplace_back( - std::make_shared(it.first, HashedValue(v))); - } - } - - TestMetadataMatchCriteria(const std::map matches) { - for (const auto& it : matches) { - matches_.emplace_back( - std::make_shared(it.first, HashedValue(it.second))); - } - } - - const std::vector& - metadataMatchCriteria() const override { - return matches_; - } - - Router::MetadataMatchCriteriaConstPtr - mergeMatchCriteria(const ProtobufWkt::Struct& override) const override { - auto new_criteria = std::make_unique(*this); - - // TODO: this is copied from MetadataMatchCriteriaImpl::extractMetadataMatchCriteria. - // should we start using real impl? - std::vector v; - absl::node_hash_map existing; - - for (const auto& it : matches_) { - existing.emplace(it->name(), v.size()); - v.emplace_back(it); - } - - // Add values from matches, replacing name/values copied from parent. - for (const auto& it : override.fields()) { - const auto index_it = existing.find(it.first); - if (index_it != existing.end()) { - v[index_it->second] = std::make_shared(it.first, it.second); - } else { - v.emplace_back(std::make_shared(it.first, it.second)); - } - } - std::sort(v.begin(), v.end(), - [](const Router::MetadataMatchCriterionConstSharedPtr& a, - const Router::MetadataMatchCriterionConstSharedPtr& b) -> bool { - return a->name() < b->name(); - }); - - new_criteria->matches_ = v; - return new_criteria; - } - - Router::MetadataMatchCriteriaConstPtr - filterMatchCriteria(const std::set& names) const override { - auto new_criteria = std::make_unique(*this); - for (auto it = new_criteria->matches_.begin(); it != new_criteria->matches_.end();) { - if (names.count(it->get()->name()) == 0) { - it = new_criteria->matches_.erase(it); - } else { - it++; - } - } - return new_criteria; - } - -private: - std::vector matches_; -}; - -namespace SubsetLoadBalancerTest { - -class TestLoadBalancerContext : public LoadBalancerContextBase { -public: - TestLoadBalancerContext( - std::initializer_list::value_type> metadata_matches) - : matches_( - new TestMetadataMatchCriteria(std::map(metadata_matches))) {} - - TestLoadBalancerContext( - std::initializer_list::value_type> metadata_matches) - : matches_(new TestMetadataMatchCriteria( - std::map(metadata_matches))) {} - - // Upstream::LoadBalancerContext - absl::optional computeHashKey() override { return {}; } - const Network::Connection* downstreamConnection() const override { return nullptr; } - const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return matches_.get(); } - const Http::RequestHeaderMap* downstreamHeaders() const override { return nullptr; } - - std::shared_ptr matches_; -}; - -enum class UpdateOrder { RemovesFirst, Simultaneous }; - -class SubsetLoadBalancerTest : public Event::TestUsingSimulatedTime, - public testing::TestWithParam { -public: - SubsetLoadBalancerTest() - : scope_(stats_store_.createScope("testprefix")), stat_names_(stats_store_.symbolTable()), - stats_(stat_names_, *stats_store_.rootScope()) { - least_request_lb_config_.mutable_choice_count()->set_value(2); - } - - using HostMetadata = std::map; - using HostListMetadata = std::map>; - using HostURLMetadataMap = std::map; - - void init() { - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, - }); - } - - void configureHostSet(const HostURLMetadataMap& host_metadata, MockHostSet& host_set) { - HostVector hosts; - for (const auto& it : host_metadata) { - hosts.emplace_back(makeHost(it.first, it.second)); - } - - host_set.hosts_ = hosts; - host_set.hosts_per_locality_ = makeHostsPerLocality({hosts}); - host_set.healthy_hosts_ = host_set.hosts_; - host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; - - host_set.runCallbacks({}, {}); - } - - void configureWeightedHostSet(const HostURLMetadataMap& first_locality_host_metadata, - const HostURLMetadataMap& second_locality_host_metadata, - MockHostSet& host_set, LocalityWeights locality_weights) { - HostVector all_hosts; - HostVector first_locality_hosts; - envoy::config::core::v3::Locality first_locality; - first_locality.set_zone("0"); - for (const auto& it : first_locality_host_metadata) { - auto host = makeHost(it.first, it.second, first_locality); - first_locality_hosts.emplace_back(host); - all_hosts.emplace_back(host); - } - - envoy::config::core::v3::Locality second_locality; - second_locality.set_zone("1"); - HostVector second_locality_hosts; - for (const auto& it : second_locality_host_metadata) { - auto host = makeHost(it.first, it.second, second_locality); - second_locality_hosts.emplace_back(host); - all_hosts.emplace_back(host); - } - - host_set.hosts_ = all_hosts; - host_set.hosts_per_locality_ = - makeHostsPerLocality({first_locality_hosts, second_locality_hosts}); - host_set.healthy_hosts_ = host_set.hosts_; - host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; - host_set.locality_weights_ = std::make_shared(locality_weights); - } - - void init(const HostURLMetadataMap& host_metadata) { - HostURLMetadataMap failover; - init(host_metadata, failover); - } - - void init(const HostURLMetadataMap& host_metadata, - const HostURLMetadataMap& failover_host_metadata, bool use_actual_subset_info = false) { - - if (!use_actual_subset_info) { - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - } - - configureHostSet(host_metadata, host_set_); - if (!failover_host_metadata.empty()) { - configureHostSet(failover_host_metadata, *priority_set_.getMockHostSet(1)); - } - - auto child_lb_creator = std::make_unique( - lb_type_, - lb_type_ == LoadBalancerType::RingHash - ? makeOptRef( - ring_hash_lb_config_) - : absl::nullopt, - lb_type_ == LoadBalancerType::Maglev - ? makeOptRef( - maglev_lb_config_) - : absl::nullopt, - lb_type_ == LoadBalancerType::RoundRobin - ? makeOptRef( - round_robin_lb_config_) - : absl::nullopt, - lb_type_ == LoadBalancerType::LeastRequest - ? makeOptRef( - least_request_lb_config_) - : absl::nullopt, - common_config_); - - lb_ = std::make_shared( - use_actual_subset_info ? static_cast(*actual_subset_info_) - : static_cast(subset_info_), - std::move(child_lb_creator), priority_set_, nullptr, stats_, *scope_, runtime_, random_, - simTime()); - } - - void zoneAwareInit(const std::vector& host_metadata_per_locality, - const std::vector& local_host_metadata_per_locality) { - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - - std::vector> localities; - for (uint32_t i = 0; i < 10; ++i) { - envoy::config::core::v3::Locality locality; - locality.set_zone(std::to_string(i)); - localities.emplace_back(std::make_shared(locality)); - } - ASSERT(host_metadata_per_locality.size() <= localities.size()); - ASSERT(local_host_metadata_per_locality.size() <= localities.size()); - - HostVector hosts; - std::vector hosts_per_locality; - for (uint32_t i = 0; i < host_metadata_per_locality.size(); ++i) { - const auto& host_metadata = host_metadata_per_locality[i]; - HostVector locality_hosts; - for (const auto& host_entry : host_metadata) { - HostSharedPtr host = makeHost(host_entry.first, host_entry.second, *localities[i]); - hosts.emplace_back(host); - locality_hosts.emplace_back(host); - } - hosts_per_locality.emplace_back(locality_hosts); - } - - host_set_.hosts_ = hosts; - host_set_.hosts_per_locality_ = makeHostsPerLocality(std::move(hosts_per_locality)); - - host_set_.healthy_hosts_ = host_set_.hosts_; - host_set_.healthy_hosts_per_locality_ = host_set_.hosts_per_locality_; - - local_hosts_ = std::make_shared(); - std::vector local_hosts_per_locality_vector; - for (uint32_t i = 0; i < local_host_metadata_per_locality.size(); ++i) { - const auto& local_host_metadata = local_host_metadata_per_locality[i]; - HostVector local_locality_hosts; - for (const auto& host_entry : local_host_metadata) { - HostSharedPtr host = makeHost(host_entry.first, host_entry.second, *localities[i]); - local_hosts_->emplace_back(host); - local_locality_hosts.emplace_back(host); - } - local_hosts_per_locality_vector.emplace_back(local_locality_hosts); - } - local_hosts_per_locality_ = makeHostsPerLocality(std::move(local_hosts_per_locality_vector)); - - local_priority_set_.updateHosts( - 0, - HostSetImpl::updateHostsParams( - local_hosts_, local_hosts_per_locality_, - std::make_shared(*local_hosts_), local_hosts_per_locality_, - std::make_shared(), HostsPerLocalityImpl::empty(), - std::make_shared(), HostsPerLocalityImpl::empty()), - {}, {}, {}, absl::nullopt); - - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - - lb_ = std::make_shared(subset_info_, std::move(child_lb_creator), - priority_set_, &local_priority_set_, stats_, *scope_, - runtime_, random_, simTime()); - } - - HostSharedPtr makeHost(const std::string& url, const HostMetadata& metadata) { - envoy::config::core::v3::Metadata m; - for (const auto& m_it : metadata) { - Config::Metadata::mutableMetadataValue(m, Config::MetadataFilters::get().ENVOY_LB, m_it.first) - .set_string_value(m_it.second); - } - - return makeTestHost(info_, url, m, simTime()); - } - - HostSharedPtr makeHost(const std::string& url, const HostMetadata& metadata, - const envoy::config::core::v3::Locality& locality) { - envoy::config::core::v3::Metadata m; - for (const auto& m_it : metadata) { - Config::Metadata::mutableMetadataValue(m, Config::MetadataFilters::get().ENVOY_LB, m_it.first) - .set_string_value(m_it.second); - } - - return makeTestHost(info_, url, m, locality, simTime()); - } - - HostSharedPtr makeHost(const std::string& url, const HostListMetadata& metadata) { - envoy::config::core::v3::Metadata m; - for (const auto& m_it : metadata) { - auto& metadata = Config::Metadata::mutableMetadataValue( - m, Config::MetadataFilters::get().ENVOY_LB, m_it.first); - for (const auto& value : m_it.second) { - metadata.mutable_list_value()->add_values()->set_string_value(value); - } - } - - return makeTestHost(info_, url, m, simTime()); - } - - ProtobufWkt::Struct makeDefaultSubset(HostMetadata metadata) { - ProtobufWkt::Struct default_subset; - - auto* fields = default_subset.mutable_fields(); - for (const auto& it : metadata) { - ProtobufWkt::Value v; - v.set_string_value(it.second); - fields->insert({it.first, v}); - } - - return default_subset; - } - - SubsetSelectorPtr - makeSelector(const std::set& selector_keys, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector:: - LbSubsetSelectorFallbackPolicy fallback_policy, - const std::set& fallback_keys_subset, - bool single_host_per_subset = false) { - Protobuf::RepeatedPtrField selector_keys_mapped; - for (const auto& it : selector_keys) { - selector_keys_mapped.Add(std::string(it)); - } - - Protobuf::RepeatedPtrField fallback_keys_subset_mapped; - for (const auto& it : fallback_keys_subset) { - fallback_keys_subset_mapped.Add(std::string(it)); - } - - return std::make_shared( - selector_keys_mapped, fallback_policy, fallback_keys_subset_mapped, single_host_per_subset); - } - - SubsetSelectorPtr makeSelector( - const std::set& selector_keys, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector:: - LbSubsetSelectorFallbackPolicy fallback_policy = - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED) { - return makeSelector(selector_keys, fallback_policy, {}); - } - - void modifyHosts(HostVector add, HostVector remove, absl::optional add_in_locality = {}, - uint32_t priority = 0) { - MockHostSet& host_set = *priority_set_.getMockHostSet(priority); - for (const auto& host : remove) { - auto it = std::find(host_set.hosts_.begin(), host_set.hosts_.end(), host); - if (it != host_set.hosts_.end()) { - host_set.hosts_.erase(it); - } - host_set.healthy_hosts_ = host_set.hosts_; - - std::vector locality_hosts_copy = host_set.hosts_per_locality_->get(); - for (auto& locality_hosts : locality_hosts_copy) { - auto it = std::find(locality_hosts.begin(), locality_hosts.end(), host); - if (it != locality_hosts.end()) { - locality_hosts.erase(it); - } - } - host_set.hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); - host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; - } - - if (GetParam() == UpdateOrder::RemovesFirst && !remove.empty()) { - host_set.runCallbacks({}, remove); - } - - for (const auto& host : add) { - host_set.hosts_.emplace_back(host); - host_set.healthy_hosts_ = host_set.hosts_; - - if (add_in_locality) { - std::vector locality_hosts_copy = host_set.hosts_per_locality_->get(); - locality_hosts_copy[add_in_locality.value()].emplace_back(host); - host_set.hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); - host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; - } - } - - if (GetParam() == UpdateOrder::RemovesFirst) { - if (!add.empty()) { - host_set.runCallbacks(add, {}); - } - } else if (!add.empty() || !remove.empty()) { - host_set.runCallbacks(add, remove); - } - } - - void modifyLocalHosts(HostVector add, HostVector remove, uint32_t add_in_locality) { - for (const auto& host : remove) { - auto it = std::find(local_hosts_->begin(), local_hosts_->end(), host); - if (it != local_hosts_->end()) { - local_hosts_->erase(it); - } - - std::vector locality_hosts_copy = local_hosts_per_locality_->get(); - for (auto& locality_hosts : locality_hosts_copy) { - auto it = std::find(locality_hosts.begin(), locality_hosts.end(), host); - if (it != locality_hosts.end()) { - locality_hosts.erase(it); - } - } - local_hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); - } - - if (GetParam() == UpdateOrder::RemovesFirst && !remove.empty()) { - local_priority_set_.updateHosts( - 0, - updateHostsParams(local_hosts_, local_hosts_per_locality_, - std::make_shared(*local_hosts_), - local_hosts_per_locality_), - {}, {}, remove, absl::nullopt); - } - - for (const auto& host : add) { - local_hosts_->emplace_back(host); - std::vector locality_hosts_copy = local_hosts_per_locality_->get(); - locality_hosts_copy[add_in_locality].emplace_back(host); - local_hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); - } - - if (GetParam() == UpdateOrder::RemovesFirst) { - if (!add.empty()) { - local_priority_set_.updateHosts( - 0, - updateHostsParams(local_hosts_, local_hosts_per_locality_, - std::make_shared(*local_hosts_), - local_hosts_per_locality_), - {}, add, {}, absl::nullopt); - } - } else if (!add.empty() || !remove.empty()) { - local_priority_set_.updateHosts( - 0, - updateHostsParams(local_hosts_, local_hosts_per_locality_, - std::make_shared(*local_hosts_), - local_hosts_per_locality_), - {}, add, remove, absl::nullopt); - } - } - - void doLbTypeTest(LoadBalancerType type) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - lb_type_ = type; - init({{"tcp://127.0.0.1:80", {{"version", "1.0"}}}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); - - HostSharedPtr added_host = makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}}); - modifyHosts({added_host}, {host_set_.hosts_.back()}); - - EXPECT_EQ(added_host, lb_->chooseHost(nullptr)); - } - - MetadataConstSharedPtr buildMetadata(const std::string& version, bool is_default = false) const { - envoy::config::core::v3::Metadata metadata; - - if (!version.empty()) { - Envoy::Config::Metadata::mutableMetadataValue( - metadata, Config::MetadataFilters::get().ENVOY_LB, "version") - .set_string_value(version); - } - - if (is_default) { - Envoy::Config::Metadata::mutableMetadataValue( - metadata, Config::MetadataFilters::get().ENVOY_LB, "default") - .set_string_value("true"); - } - - return std::make_shared(metadata); - } - - MetadataConstSharedPtr buildMetadataWithStage(const std::string& version, - const std::string& stage = "") const { - envoy::config::core::v3::Metadata metadata; - - if (!version.empty()) { - Envoy::Config::Metadata::mutableMetadataValue( - metadata, Config::MetadataFilters::get().ENVOY_LB, "version") - .set_string_value(version); - } - - if (!stage.empty()) { - Envoy::Config::Metadata::mutableMetadataValue( - metadata, Config::MetadataFilters::get().ENVOY_LB, "stage") - .set_string_value(stage); - } - - return std::make_shared(metadata); - } - - ProtobufWkt::Value valueFromJson(std::string json) { - ProtobufWkt::Value v; - TestUtility::loadFromJson(json, v); - return v; - } - - LoadBalancerType lb_type_{LoadBalancerType::RoundRobin}; - NiceMock priority_set_; - MockHostSet& host_set_ = *priority_set_.getMockHostSet(0); - // Mock subset info is used for testing most logic. - NiceMock subset_info_; - // Actual subset info is used for testing actual subset config parsing and behavior. - std::unique_ptr actual_subset_info_; - std::shared_ptr info_{new NiceMock()}; - envoy::config::cluster::v3::Cluster::RingHashLbConfig ring_hash_lb_config_; - envoy::config::cluster::v3::Cluster::MaglevLbConfig maglev_lb_config_; - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig least_request_lb_config_; - envoy::config::cluster::v3::Cluster::RoundRobinLbConfig round_robin_lb_config_; - envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; - NiceMock runtime_; - NiceMock random_; - Stats::IsolatedStoreImpl stats_store_; - Stats::ScopeSharedPtr scope_; - ClusterLbStatNames stat_names_; - ClusterLbStats stats_; - PrioritySetImpl local_priority_set_; - HostVectorSharedPtr local_hosts_; - HostsPerLocalitySharedPtr local_hosts_per_locality_; - std::shared_ptr lb_; -}; - -TEST_F(SubsetLoadBalancerTest, NoFallback) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - init(); - - EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); - EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); - - EXPECT_EQ(nullptr, lb_->peekAnotherHost(nullptr)); - EXPECT_FALSE(lb_->lifetimeCallbacks().has_value()); - std::vector hash_key; - auto mock_host = std::make_shared>(); - EXPECT_FALSE(lb_->selectExistingConnection(nullptr, *mock_host, hash_key).has_value()); -} - -// Validate that SubsetLoadBalancer unregisters its priority set member update -// callback. Regression for heap-use-after-free. -TEST_F(SubsetLoadBalancerTest, DeregisterCallbacks) { - init(); - lb_.reset(); - host_set_.runCallbacks({}, {}); -} - -TEST_P(SubsetLoadBalancerTest, NoFallbackAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - init(); - - EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); - - modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}})}, {host_set_.hosts_.back()}); - - EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); -} - -TEST_F(SubsetLoadBalancerTest, FallbackAnyEndpoint) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - init(); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); - EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); -} - -TEST_P(SubsetLoadBalancerTest, FallbackAnyEndpointAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - init(); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); - - HostSharedPtr added_host = makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}}); - modifyHosts({added_host}, {host_set_.hosts_.back()}); - - EXPECT_EQ(added_host, lb_->chooseHost(nullptr)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); -} - -TEST_F(SubsetLoadBalancerTest, FallbackDefaultSubset) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "new"}}}, - {"tcp://127.0.0.1:81", {{"version", "default"}}}, - }); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); - EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); -} - -TEST_F(SubsetLoadBalancerTest, FallbackPanicMode) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - EXPECT_CALL(subset_info_, panicModeAny()).WillRepeatedly(Return(true)); - - // The default subset will be empty. - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "none"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "new"}}}, - {"tcp://127.0.0.1:81", {{"version", "default"}}}, - }); - - EXPECT_TRUE(lb_->chooseHost(nullptr) != nullptr); - EXPECT_EQ(1U, stats_.lb_subsets_fallback_panic_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); -} - -TEST_P(SubsetLoadBalancerTest, FallbackPanicModeWithUpdates) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - EXPECT_CALL(subset_info_, panicModeAny()).WillRepeatedly(Return(true)); - - // The default subset will be empty. - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "none"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - init({{"tcp://127.0.0.1:80", {{"version", "default"}}}}); - EXPECT_TRUE(lb_->chooseHost(nullptr) != nullptr); - - // Removing current host, adding a new one. - HostSharedPtr added_host = makeHost("tcp://127.0.0.2:8000", {{"version", "new"}}); - modifyHosts({added_host}, {host_set_.hosts_[0]}); - - EXPECT_EQ(1, host_set_.hosts_.size()); - EXPECT_EQ(added_host, lb_->chooseHost(nullptr)); -} - -TEST_P(SubsetLoadBalancerTest, FallbackDefaultSubsetAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "new"}}}, - {"tcp://127.0.0.1:81", {{"version", "default"}}}, - }); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); - - HostSharedPtr added_host1 = makeHost("tcp://127.0.0.1:8000", {{"version", "new"}}); - HostSharedPtr added_host2 = makeHost("tcp://127.0.0.1:8001", {{"version", "default"}}); - - modifyHosts({added_host1, added_host2}, {host_set_.hosts_.back()}); - - EXPECT_EQ(added_host2, lb_->chooseHost(nullptr)); -} - -TEST_F(SubsetLoadBalancerTest, FallbackEmptyDefaultSubsetConvertsToAnyEndpoint) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - EXPECT_CALL(subset_info_, defaultSubset()) - .WillRepeatedly(ReturnRef(ProtobufWkt::Struct::default_instance())); - - init(); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); - EXPECT_EQ(2U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); -} - -TEST_F(SubsetLoadBalancerTest, FallbackOnUnknownMetadata) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - init(); - - TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); - TestLoadBalancerContext context_unknown_value({{"version", "unknown"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_unknown_key)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_unknown_value)); -} - -TEST_F(SubsetLoadBalancerTest, BalancesSubset) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - }); - - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_11({{"version", "1.1"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); - EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_selected_.value()); -} - -TEST_P(SubsetLoadBalancerTest, BalancesSubsetAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - }); - - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_11({{"version", "1.1"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); - EXPECT_EQ(2U, stats_.lb_subsets_created_.value()); - - modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), - makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, - {host_set_.hosts_[1], host_set_.hosts_[2]}); - - TestLoadBalancerContext context_12({{"version", "1.2"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_11)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_12)); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); -} - -TEST_P(SubsetLoadBalancerTest, ListAsAnyEnabled) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - EXPECT_CALL(subset_info_, listAsAny()).WillRepeatedly(Return(true)); - - init({}); - modifyHosts( - {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}}), - makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, - {}, {}, 0); - - { - TestLoadBalancerContext context({{"version", "1.0"}}); - EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); - } - { - TestLoadBalancerContext context({{"version", "1.2"}}); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); - } - TestLoadBalancerContext context({{"version", "1.2.1"}}); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); -} - -TEST_P(SubsetLoadBalancerTest, ListAsAnyEnabledMultipleLists) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - EXPECT_CALL(subset_info_, listAsAny()).WillRepeatedly(Return(true)); - - init({}); - modifyHosts( - {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}}), - makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.2", "1.2"}}}), - makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, - {}, {}, 0); - - { - TestLoadBalancerContext context({{"version", "1.0"}}); - EXPECT_TRUE(host_set_.hosts()[2] == lb_->chooseHost(&context)); - EXPECT_TRUE(host_set_.hosts()[2] == lb_->chooseHost(&context)); - } - { - // This should LB between both hosts marked with version 1.2. - TestLoadBalancerContext context({{"version", "1.2"}}); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); - EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); - } - { - // Choose a host multiple times to ensure that hosts()[0] is the *only* - // thing selected for this subset. - TestLoadBalancerContext context({{"version", "1.2.1"}}); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); - } - - TestLoadBalancerContext context({{"version", "1.2.2"}}); - EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); -} - -TEST_P(SubsetLoadBalancerTest, ListAsAnyEnabledMultipleListsForSingleHost) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version", "hardware"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - EXPECT_CALL(subset_info_, listAsAny()).WillRepeatedly(Return(true)); - - init({}); - modifyHosts( - {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}, - {"hardware", std::vector{"a", "b"}}}), - makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.1", "1.1.1"}}, - {"hardware", std::vector{"b", "c"}}})}, - {}, {}, 0); - - { - TestLoadBalancerContext context({{"version", "1.2"}, {"hardware", "a"}}); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); - } - - { - TestLoadBalancerContext context({{"version", "1.1"}, {"hardware", "b"}}); - EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); - EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); - } - - { - TestLoadBalancerContext context({{"version", "1.1"}, {"hardware", "a"}}); - EXPECT_TRUE(nullptr == lb_->chooseHost(&context)); - } - - TestLoadBalancerContext context({{"version", "1.2.1"}, {"hardware", "b"}}); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); - EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); -} - -TEST_P(SubsetLoadBalancerTest, ListAsAnyDisable) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({}); - modifyHosts( - {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}}), - makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, - {}, {}, 0); - - { - TestLoadBalancerContext context({{"version", "1.0"}}); - EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); - } - TestLoadBalancerContext context({{"version", "1.2"}}); - EXPECT_TRUE(nullptr == lb_->chooseHost(&context)); -} - -// Test that adding backends to a failover group causes no problems. -TEST_P(SubsetLoadBalancerTest, UpdateFailover) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - TestLoadBalancerContext context_10({{"version", "1.0"}}); - - // Start with an empty lb. Choosing a host should result in failure. - init({}); - EXPECT_TRUE(nullptr == lb_->chooseHost(&context_10).get()); - - // Add hosts to the group at priority 1. - // These hosts should be selected as there are no healthy hosts with priority 0 - modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), - makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, - {}, {}, 1); - EXPECT_FALSE(nullptr == lb_->chooseHost(&context_10).get()); - - // Finally update the priority 0 hosts. The LB should now select hosts. - modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), - makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, - {}, {}, 0); - EXPECT_FALSE(nullptr == lb_->chooseHost(&context_10).get()); -} - -TEST_P(SubsetLoadBalancerTest, OnlyMetadataChanged) { - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_12({{"version", "1.2"}}); - TestLoadBalancerContext context_13({{"version", "1.3"}}); - TestLoadBalancerContext context_default({{"default", "true"}}); - - std::vector subset_selectors = { - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"default"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"default", "true"}}); - - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - // Add hosts initial hosts. - init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, - {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"default", "true"}}}}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); - - // Swap the default version. - host_set_.hosts_[0]->metadata(buildMetadata("1.2", true)); - host_set_.hosts_[1]->metadata(buildMetadata("1.0")); - - host_set_.runCallbacks({}, {}); - - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_13)); - - // Bump 1.0 to 1.3, one subset should be removed. - host_set_.hosts_[1]->metadata(buildMetadata("1.3")); - - // No hosts added nor removed, so we bypass modifyHosts(). - host_set_.runCallbacks({}, {}); - - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - - // Rollback from 1.3 to 1.0. - host_set_.hosts_[1]->metadata(buildMetadata("1.0")); - - host_set_.runCallbacks({}, {}); - - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_13)); - - // Make 1.0 default again. - host_set_.hosts_[1]->metadata(buildMetadata("1.0", true)); - host_set_.hosts_[0]->metadata(buildMetadata("1.2")); - - host_set_.runCallbacks({}, {}); - - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); -} - -TEST_P(SubsetLoadBalancerTest, EmptySubsetsPurged) { - std::vector subset_selectors = {makeSelector({"version"}), - makeSelector({"version", "stage"})}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - // Simple add and remove. - init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, - {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"stage", "prod"}}}}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - - host_set_.hosts_[0]->metadata(buildMetadataWithStage("1.3")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); - - // Move host that was in the version + stage subset into a new version only subset. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.4")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_removed_.value()); - - // Create a new version + stage subset. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.5", "devel")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(7U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_removed_.value()); - - // Now move it back to its original version + stage subset. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.0", "prod")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(9U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(6U, stats_.lb_subsets_removed_.value()); - - // Finally, remove the original version + stage subset again. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.6")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(10U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(8U, stats_.lb_subsets_removed_.value()); -} - -TEST_P(SubsetLoadBalancerTest, EmptySubsetsPurgedCollapsed) { - std::vector subset_selectors = {makeSelector({"version"}), - makeSelector({"version", "stage"})}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - // Init subsets. - init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, - {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"stage", "prod"}}}}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - - // Get rid of 1.0. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2", "prod")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); - - // Get rid of stage prod. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(1U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_removed_.value()); - - // Add stage prod back. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2", "prod")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_removed_.value()); -} - -TEST_P(SubsetLoadBalancerTest, EmptySubsetsPurgedVersionChanged) { - std::vector subset_selectors = {makeSelector({"version"}), - makeSelector({"version", "stage"})}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - // Init subsets. - init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, - {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"stage", "prod"}}}}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - - // Get rid of 1.0. - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2", "prod")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); - - // Change versions. - host_set_.hosts_[0]->metadata(buildMetadataWithStage("1.3")); - host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.4", "prod")); - host_set_.runCallbacks({}, {}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(7U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_removed_.value()); -} - -TEST_P(SubsetLoadBalancerTest, MetadataChangedHostsAddedRemoved) { - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_12({{"version", "1.2"}}); - TestLoadBalancerContext context_13({{"version", "1.3"}}); - TestLoadBalancerContext context_14({{"version", "1.4"}}); - TestLoadBalancerContext context_default({{"default", "true"}}); - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"default", "true"}}); - - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - std::vector subset_selectors = { - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"default"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - // Add hosts initial hosts. - init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, - {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"default", "true"}}}}); - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); - - // Swap the default version. - host_set_.hosts_[0]->metadata(buildMetadata("1.2", true)); - host_set_.hosts_[1]->metadata(buildMetadata("1.0")); - - // Add a new host. - modifyHosts({makeHost("tcp://127.0.0.1:8002", {{"version", "1.3"}})}, {}); - - EXPECT_EQ(4U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_13)); - - // Swap default again and remove the previous one. - host_set_.hosts_[0]->metadata(buildMetadata("1.2")); - host_set_.hosts_[1]->metadata(buildMetadata("1.0", true)); - - modifyHosts({}, {host_set_.hosts_[2]}); - - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); - - // Swap the default version once more, this time adding a new host and removing - // the current default version. - host_set_.hosts_[0]->metadata(buildMetadata("1.2", true)); - host_set_.hosts_[1]->metadata(buildMetadata("1.0")); - - modifyHosts({makeHost("tcp://127.0.0.1:8003", {{"version", "1.4"}})}, {host_set_.hosts_[1]}); - - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_13)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_14)); - - // Make 1.4 default, without hosts being added/removed. - host_set_.hosts_[0]->metadata(buildMetadata("1.2")); - host_set_.hosts_[1]->metadata(buildMetadata("1.4", true)); - - host_set_.runCallbacks({}, {}); - - EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_14)); -} - -TEST_P(SubsetLoadBalancerTest, UpdateRemovingLastSubsetHost) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }); - - HostSharedPtr host_v10 = host_set_.hosts_[0]; - HostSharedPtr host_v11 = host_set_.hosts_[1]; - - TestLoadBalancerContext context({{"version", "1.0"}}); - EXPECT_EQ(host_v10, lb_->chooseHost(&context)); - EXPECT_EQ(1U, stats_.lb_subsets_selected_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_created_.value()); - - modifyHosts({}, {host_v10}); - - // fallback to any endpoint - EXPECT_EQ(host_v11, lb_->chooseHost(&context)); - EXPECT_EQ(1U, stats_.lb_subsets_selected_.value()); - EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(1U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(2U, stats_.lb_subsets_created_.value()); - EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); -} - -TEST_P(SubsetLoadBalancerTest, UpdateRemovingUnknownHost) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = { - makeSelector( - {"stage", "version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"stage", "prod"}, {"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"stage", "prod"}, {"version", "1.1"}}}, - }); - - TestLoadBalancerContext context({{"stage", "prod"}, {"version", "1.0"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context)); - - modifyHosts({}, {makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), - makeHost("tcp://127.0.0.1:8001", {{"stage", "prod"}, {"version", "1.2"}})}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context)); -} - -TEST_F(SubsetLoadBalancerTest, UpdateModifyingOnlyHostHealth) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = { - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"hardware"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - }); - - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_11({{"version", "1.1"}}); - - // All hosts are healthy. - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); - - host_set_.hosts_[0]->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); - host_set_.hosts_[2]->healthFlagSet(Host::HealthFlag::FAILED_OUTLIER_CHECK); - host_set_.healthy_hosts_ = {host_set_.hosts_[1], host_set_.hosts_[3]}; - host_set_.runCallbacks({}, {}); - - // Unhealthy hosts are excluded. - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); -} - -TEST_F(SubsetLoadBalancerTest, BalancesDisjointSubsets) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = { - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"hardware"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"hardware", "std"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"hardware", "bigmem"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.1"}, {"hardware", "std"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}, {"hardware", "bigmem"}}}, - }); - - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_bigmem({{"hardware", "bigmem"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_bigmem)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_bigmem)); -} - -TEST_F(SubsetLoadBalancerTest, BalancesOverlappingSubsets) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = { - makeSelector( - {"stage", "version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.0"}, {"stage", "off"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:84", {{"version", "999"}, {"stage", "dev"}}}, - }); - - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_10_prod({{"version", "1.0"}, {"stage", "prod"}}); - TestLoadBalancerContext context_dev({{"version", "999"}, {"stage", "dev"}}); - TestLoadBalancerContext context_unknown({{"version", "2.0"}, {"stage", "prod"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_10)); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10_prod)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10_prod)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10_prod)); - - EXPECT_EQ(host_set_.hosts_[4], lb_->chooseHost(&context_dev)); - EXPECT_EQ(host_set_.hosts_[4], lb_->chooseHost(&context_dev)); - - EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown)); -} - -TEST_F(SubsetLoadBalancerTest, BalancesNestedSubsets) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = { - makeSelector( - {"stage", "version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"stage"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.0"}, {"stage", "off"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:84", {{"version", "999"}, {"stage", "dev"}}}, - }); - - TestLoadBalancerContext context_prod({{"stage", "prod"}}); - TestLoadBalancerContext context_prod_10({{"version", "1.0"}, {"stage", "prod"}}); - TestLoadBalancerContext context_unknown_stage({{"stage", "larval"}}); - TestLoadBalancerContext context_unknown_version({{"version", "2.0"}, {"stage", "prod"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_prod)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_prod)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_prod)); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_prod_10)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_prod_10)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_prod_10)); - - EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_stage)); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_version)); -} - -TEST_F(SubsetLoadBalancerTest, IgnoresUnselectedMetadata) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "ignored"}}}, - {"tcp://127.0.0.1:81", {{"ignore", "value"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - }); - - TestLoadBalancerContext context_ignore({{"ignore", "value"}}); - TestLoadBalancerContext context_version({{"version", "1.0"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_version)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_version)); - - EXPECT_EQ(nullptr, lb_->chooseHost(&context_ignore)); -} - -TEST_F(SubsetLoadBalancerTest, IgnoresHostsWithoutMetadata) { - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - HostVector hosts; - hosts.emplace_back(makeTestHost(info_, "tcp://127.0.0.1:80", simTime())); - hosts.emplace_back(makeHost("tcp://127.0.0.1:81", {{"version", "1.0"}})); - - host_set_.hosts_ = hosts; - host_set_.hosts_per_locality_ = makeHostsPerLocality({hosts}); - - host_set_.healthy_hosts_ = host_set_.hosts_; - host_set_.healthy_hosts_per_locality_ = host_set_.hosts_per_locality_; - - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - lb_ = std::make_shared( - subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, - *stats_store_.rootScope(), runtime_, random_, simTime()); - - TestLoadBalancerContext context_version({{"version", "1.0"}}); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_version)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_version)); -} - -// TODO(mattklein123): The following 4 tests verify basic functionality with all sub-LB tests. -// Optimally these would also be some type of TEST_P, but that is a little bit complicated as -// modifyHosts() also needs params. Clean this up. -TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesRoundRobin) { - doLbTypeTest(LoadBalancerType::RoundRobin); - auto tester = SubsetLoadBalancerInternalStateTester(lb_); - tester.validateLbTypeConfigs(); -} - -TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesLeastRequest) { - doLbTypeTest(LoadBalancerType::LeastRequest); - auto tester = SubsetLoadBalancerInternalStateTester(lb_); - tester.validateLbTypeConfigs(); -} - -TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesRandom) { - doLbTypeTest(LoadBalancerType::Random); - auto tester = SubsetLoadBalancerInternalStateTester(lb_); - tester.validateLbTypeConfigs(); -} - -TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesRingHash) { - doLbTypeTest(LoadBalancerType::RingHash); - auto tester = SubsetLoadBalancerInternalStateTester(lb_); - tester.validateLbTypeConfigs(); -} - -TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesMaglev) { - doLbTypeTest(LoadBalancerType::Maglev); - auto tester = SubsetLoadBalancerInternalStateTester(lb_); - tester.validateLbTypeConfigs(); -} - -TEST_F(SubsetLoadBalancerTest, ZoneAwareFallback) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - std::vector subset_selectors = {makeSelector( - {"x"}, envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - common_config_.mutable_healthy_panic_threshold()->set_value(40); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 40)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - }, - { - {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.1"}}}, - }}, - {{ - {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, - }, - { - {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, - }}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); -} - -TEST_P(SubsetLoadBalancerTest, ZoneAwareFallbackAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - std::vector subset_selectors = {makeSelector( - {"x"}, envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - }, - { - {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.1"}}}, - }}, - {{ - {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, - }, - { - {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, - }}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); - - envoy::config::core::v3::Locality local_locality; - local_locality.set_zone("0"); - - modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}}, local_locality)}, - {host_set_.hosts_[0]}, absl::optional(0)); - - modifyLocalHosts({makeHost("tcp://127.0.0.1:9000", {{"version", "1.0"}}, local_locality)}, - {local_hosts_->at(0)}, 0); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); -} - -TEST_F(SubsetLoadBalancerTest, ZoneAwareFallbackDefaultSubset) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "new"}}}, - {"tcp://127.0.0.1:81", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "new"}}}, - {"tcp://127.0.0.1:83", {{"version", "default"}}}, - {"tcp://127.0.0.1:84", {{"version", "new"}}}, - {"tcp://127.0.0.1:85", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:86", {{"version", "new"}}}, - {"tcp://127.0.0.1:87", {{"version", "default"}}}, - {"tcp://127.0.0.1:88", {{"version", "new"}}}, - {"tcp://127.0.0.1:89", {{"version", "default"}}}, - }}, - {{ - {"tcp://127.0.0.1:90", {{"version", "new"}}}, - {"tcp://127.0.0.1:91", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:92", {{"version", "new"}}}, - {"tcp://127.0.0.1:93", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:94", {{"version", "new"}}}, - {"tcp://127.0.0.1:95", {{"version", "default"}}}, - }}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(nullptr)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); -} - -TEST_P(SubsetLoadBalancerTest, ZoneAwareFallbackDefaultSubsetAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "new"}}}, - {"tcp://127.0.0.1:81", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "new"}}}, - {"tcp://127.0.0.1:83", {{"version", "default"}}}, - {"tcp://127.0.0.1:84", {{"version", "new"}}}, - {"tcp://127.0.0.1:85", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:86", {{"version", "new"}}}, - {"tcp://127.0.0.1:87", {{"version", "default"}}}, - {"tcp://127.0.0.1:88", {{"version", "new"}}}, - {"tcp://127.0.0.1:89", {{"version", "default"}}}, - }}, - {{ - {"tcp://127.0.0.1:90", {{"version", "new"}}}, - {"tcp://127.0.0.1:91", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:92", {{"version", "new"}}}, - {"tcp://127.0.0.1:93", {{"version", "default"}}}, - }, - { - {"tcp://127.0.0.1:94", {{"version", "new"}}}, - {"tcp://127.0.0.1:95", {{"version", "default"}}}, - }}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(nullptr)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); - - envoy::config::core::v3::Locality local_locality; - local_locality.set_zone("0"); - - modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "default"}}, local_locality)}, - {host_set_.hosts_[1]}, absl::optional(0)); - - modifyLocalHosts({local_hosts_->at(1)}, - {makeHost("tcp://127.0.0.1:9001", {{"version", "default"}}, local_locality)}, 0); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(nullptr)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(nullptr)); -} - -TEST_F(SubsetLoadBalancerTest, ZoneAwareBalancesSubsets) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, - }}, - {{ - {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:93", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:94", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:95", {{"version", "1.1"}}}, - }}); - - TestLoadBalancerContext context({{"version", "1.1"}}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); -} - -TEST_P(SubsetLoadBalancerTest, ZoneAwareBalancesSubsetsAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, - }}, - {{ - {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:93", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:94", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:95", {{"version", "1.1"}}}, - }}); - - TestLoadBalancerContext context({{"version", "1.1"}}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); - - envoy::config::core::v3::Locality local_locality; - local_locality.set_zone("0"); - - modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "1.1"}}, local_locality)}, - {host_set_.hosts_[1]}, absl::optional(0)); - - modifyLocalHosts({local_hosts_->at(1)}, - {makeHost("tcp://127.0.0.1:9001", {{"version", "1.1"}}, local_locality)}, 0); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - - // Force request out of small zone. - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); -} - -TEST_F(SubsetLoadBalancerTest, ZoneAwareComplicatedBalancesSubsets) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - // L=local cluster host - // U=upstream host - // - // residuals - // A: 2L 0U 0.00% - // B: 2L 2U 6.67% - // C: 2L 2U 6.67% - // D: 0L 1U 20.00% - // total: 6L 5U 33.33% - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:90", {{"version", "1.1"}}}, - }}, - {{ - {"tcp://127.0.0.1:91", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:92", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:93", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:94", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:95", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:96", {{"version", "1.1"}}}, - }}); - - TestLoadBalancerContext context({{"version", "1.1"}}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(666)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(667)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(&context)); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(1334)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); -} - -TEST_P(SubsetLoadBalancerTest, ZoneAwareComplicatedBalancesSubsetsAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) - .WillRepeatedly(Return(2)); - - // Before update: - // - // L=local cluster host - // U=upstream host - // - // residuals - // A: 2L 0U 0.00% - // B: 2L 2U 6.67% - // C: 2L 2U 6.67% - // D: 0L 1U 20.00% - // total: 6L 5U 33.33% - - zoneAwareInit({{ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:90", {{"version", "1.1"}}}, - }}, - {{ - {"tcp://127.0.0.1:91", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:92", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:93", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:94", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:95", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:96", {{"version", "1.1"}}}, - }}); - - TestLoadBalancerContext context({{"version", "1.1"}}); - - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(666)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(667)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(&context)); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(1334)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); - - envoy::config::core::v3::Locality local_locality; - local_locality.set_zone("0"); - envoy::config::core::v3::Locality locality_2; - locality_2.set_zone("2"); - - modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "1.1"}}, local_locality)}, {}, - absl::optional(0)); - - modifyLocalHosts({makeHost("tcp://127.0.0.1:9001", {{"version", "1.1"}}, locality_2)}, {}, 2); - - // After update: - // - // L=local cluster host - // U=upstream host - // - // residuals - // A: 2L 1U 0.00% - // B: 2L 2U 4.76% - // C: 3L 2U 0.00% - // D: 0L 1U 16.67% - // total: 7L 6U 21.42% - // - // Chance of sampling local host in zone 0: 58.34% - - EXPECT_CALL(random_, random()) - .WillOnce(Return(0)) - .WillOnce(Return(5830)); // 58.31% local routing chance due to rounding error - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(5831)).WillOnce(Return(475)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(476)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2143)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); -} - -TEST_F(SubsetLoadBalancerTest, DescribeMetadata) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - init(); - - ProtobufWkt::Value str_value; - str_value.set_string_value("abc"); - - ProtobufWkt::Value num_value; - num_value.set_number_value(100); - - auto tester = SubsetLoadBalancerInternalStateTester(lb_); - tester.testDescribeMetadata("version=\"abc\"", {{"version", str_value}}); - tester.testDescribeMetadata("number=100", {{"number", num_value}}); - tester.testDescribeMetadata("x=\"abc\", y=100", {{"x", str_value}, {"y", num_value}}); - tester.testDescribeMetadata("y=100, x=\"abc\"", {{"y", num_value}, {"x", str_value}}); - tester.testDescribeMetadata("", {}); -} - -TEST_F(SubsetLoadBalancerTest, DisabledLocalityWeightAwareness) { - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - - // We configure a weighted host set that heavily favors the second locality. - configureWeightedHostSet( - { - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - host_set_, {1, 100}); - - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - lb_ = std::make_shared( - subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, - *stats_store_.rootScope(), runtime_, random_, simTime()); - - TestLoadBalancerContext context({{"version", "1.1"}}); - - // Since we don't respect locality weights, the first locality is selected. - EXPECT_CALL(random_, random()).WillOnce(Return(0)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(&context)); -} - -// Verifies that we do *not* invoke coarseHealth() on hosts when constructing the load balancer. -// Since health is modified concurrently from multiple threads, it is not safe to call on the worker -// threads. -TEST_F(SubsetLoadBalancerTest, DoesNotCheckHostHealth) { - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - - auto mock_host = std::make_shared(); - HostVector hosts{mock_host}; - host_set_.hosts_ = hosts; - - EXPECT_CALL(*mock_host, weight()).WillRepeatedly(Return(1)); - - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - lb_ = std::make_shared( - subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, - *stats_store_.rootScope(), runtime_, random_, simTime()); -} - -TEST_F(SubsetLoadBalancerTest, EnabledLocalityWeightAwareness) { - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); - - // We configure a weighted host set that heavily favors the second locality. - configureWeightedHostSet( - { - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - host_set_, {1, 100}); - - common_config_.mutable_locality_weighted_lb_config(); - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - lb_ = std::make_shared( - subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, - *stats_store_.rootScope(), runtime_, random_, simTime()); - - TestLoadBalancerContext context({{"version", "1.1"}}); - - // Since we respect locality weights, the second locality is selected. - EXPECT_CALL(random_, random()).WillOnce(Return(0)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(&context)); -} - -TEST_F(SubsetLoadBalancerTest, EnabledScaleLocalityWeights) { - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, scaleLocalityWeight()).WillRepeatedly(Return(true)); - - // We configure a weighted host set is weighted equally between each locality. - configureWeightedHostSet( - { - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - host_set_, {50, 50}); - - common_config_.mutable_locality_weighted_lb_config(); - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - lb_ = std::make_shared( - subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, - *stats_store_.rootScope(), runtime_, random_, simTime()); - TestLoadBalancerContext context({{"version", "1.1"}}); - - // Since we scale the locality weights by number of hosts removed, we expect to see the second - // locality to be selected less because we've excluded more hosts in that locality than in the - // first. - // The localities are split 50/50, but because of the scaling we expect to see 66/33 instead. - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); -} - -TEST_F(SubsetLoadBalancerTest, EnabledScaleLocalityWeightsRounding) { - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, scaleLocalityWeight()).WillRepeatedly(Return(true)); - - // We configure a weighted host set where the locality weights are very low to test - // that we are rounding computation instead of flooring it. - configureWeightedHostSet( - { - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }, - { - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, - }, - host_set_, {2, 2}); - - common_config_.mutable_locality_weighted_lb_config(); - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - lb_ = std::make_shared( - subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, - *stats_store_.rootScope(), runtime_, random_, simTime()); - TestLoadBalancerContext context({{"version", "1.0"}}); - - // We expect to see a 33/66 split because 2 * 1 / 2 = 1 and 2 * 3 / 4 = 1.5 -> 2 - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][2], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); -} - -// Regression for bug where missing locality weights crashed scaling and locality aware subset LBs. -TEST_F(SubsetLoadBalancerTest, ScaleLocalityWeightsWithNoLocalityWeights) { - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); - EXPECT_CALL(subset_info_, scaleLocalityWeight()).WillRepeatedly(Return(true)); - - configureHostSet( - { - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, - }, - host_set_); - - auto child_lb_creator = std::make_unique( - lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, - least_request_lb_config_, common_config_); - lb_ = std::make_shared( - subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, - *stats_store_.rootScope(), runtime_, random_, simTime()); -} - -TEST_P(SubsetLoadBalancerTest, GaugesUpdatedOnDestroy) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - }); - - EXPECT_EQ(1U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); - - lb_ = nullptr; - - EXPECT_EQ(0U, stats_.lb_subsets_active_.value()); - EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); -} - -TEST_P(SubsetLoadBalancerTest, SubsetSelectorNoFallbackPerSelector) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, - {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, - }); - - TestLoadBalancerContext context_10({{"version", "1.0"}}); - TestLoadBalancerContext context_11({{"version", "1.1"}}); - TestLoadBalancerContext context_12({{"version", "1.2"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_12)); - EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); - EXPECT_EQ(4U, stats_.lb_subsets_selected_.value()); -} - -TEST_P(SubsetLoadBalancerTest, FallbackNotDefinedForIntermediateSelector) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - std::vector subset_selectors = { - makeSelector( - {"stage"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"stage", "version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::ANY_ENDPOINT)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"stage", "canary"}}}}); - - TestLoadBalancerContext context_match_host0({{"stage", "dev"}}); - TestLoadBalancerContext context_stage_nx({{"stage", "test"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_stage_nx)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_stage_nx)); -} - -TEST_P(SubsetLoadBalancerTest, SubsetSelectorFallbackOverridesTopLevelOne) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init(); - - TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); - TestLoadBalancerContext context_unknown_value({{"version", "unknown"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_unknown_key)); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_value)); -} - -TEST_P(SubsetLoadBalancerTest, SubsetSelectorNoFallbackMatchesTopLevelOne) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init(); - - TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); - TestLoadBalancerContext context_unknown_value({{"version", "unknown"}}); - - EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_key)); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_value)); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_value)); -} - -TEST_F(SubsetLoadBalancerTest, AllowRedundantKeysForSubset) { - // Yaml config for subset load balancer. - const std::string yaml = R"EOF( - subset_selectors: - - keys: - - A - fallback_policy: NO_FALLBACK - - keys: - - A - - B - fallback_policy: NO_FALLBACK - - keys: - - A - - B - - C - fallback_policy: NO_FALLBACK - - keys: - - A - - D - fallback_policy: NO_FALLBACK - - keys: - - version - - stage - fallback_policy: NO_FALLBACK - fallback_policy: NO_FALLBACK - allow_redundant_keys: true - )EOF"; - - envoy::extensions::load_balancing_policies::subset::v3::Subset subset_proto_config; - TestUtility::loadFromYaml(yaml, subset_proto_config); - - actual_subset_info_ = std::make_unique(subset_proto_config); - // Always be true for the LoadBalancerSubsetInfoImpl. - EXPECT_TRUE(actual_subset_info_->isEnabled()); - - // Add hosts initial hosts. - init({{"tcp://127.0.0.1:80", {{"A", "A-V-0"}, {"B", "B-V-0"}, {"C", "C-V-0"}, {"D", "D-V-0"}}}, - {"tcp://127.0.0.1:81", {{"A", "A-V-1"}, {"B", "B-V-1"}, {"C", "C-V-1"}, {"D", "D-V-1"}}}, - {"tcp://127.0.0.1:82", {{"A", "A-V-2"}, {"B", "B-V-2"}, {"C", "C-V-2"}, {"D", "D-V-2"}}}, - {"tcp://127.0.0.1:83", {{"A", "A-V-3"}, {"B", "B-V-3"}, {"C", "C-V-3"}, {"D", "D-V-3"}}}, - {"tcp://127.0.0.1:84", {{"A", "A-V-4"}, {"B", "B-V-4"}, {"C", "C-V-4"}, {"D", "D-V-4"}}}, - {"tcp://127.0.0.1:85", {{"version", "1.0"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:86", {{"version", "1.0"}, {"stage", "canary"}}}}, - {}, true); - - TestLoadBalancerContext context_empty( - std::initializer_list::value_type>{}); - context_empty.matches_.reset(); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_empty)); - - // Request metadata is same with {version, stage}. - // version, stage will be kept and host 6 will be selected. - TestLoadBalancerContext context_v_s_0({{"version", "1.0"}, {"stage", "canary"}}); - EXPECT_EQ(host_set_.hosts_[6], lb_->chooseHost(&context_v_s_0)); - - // Request metadata is superset of {version, stage}. The redundant key will be ignored. - // version, stage will be kept and host 5 will be selected. - TestLoadBalancerContext context_v_s_1({{"version", "1.0"}, {"stage", "dev"}, {"redundant", "X"}}); - EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&context_v_s_1)); - - // Request metadata is superset of {version, stage}. The redundant key will be ignored. - // But one of value not match, so no host will be selected. - TestLoadBalancerContext context_v_s_2( - {{"version", "1.0"}, {"stage", "prod"}, {"redundant", "X"}}); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_v_s_2)); - - // Request metadata is same with {A, B, C} and is superset of selectors {A}, {A, B}. - // All A, B, C will be kept and host 0 will be selected. - TestLoadBalancerContext context_0({{"A", "A-V-0"}, {"B", "B-V-0"}, {"C", "C-V-0"}}); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_0)); - - // Request metadata is same with {A, B, C} and is superset of selectors {A}, {A, B}. - // All A, B, C will be kept But one of value not match, so no host will be selected. - TestLoadBalancerContext context_1({{"A", "A-V-0"}, {"B", "B-V-0"}, {"C", "C-V-X"}}); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_1)); - - // Request metadata is superset of selectors {A}, {A, B} {A, B, C}, {A, D}, the longest win. - // A, B, C will be kept and D will be ignored, so host 1 will be selected. - TestLoadBalancerContext context_2( - {{"A", "A-V-1"}, {"B", "B-V-1"}, {"C", "C-V-1"}, {"D", "D-V-X"}}); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_2)); - - // Request metadata is superset of selectors {A}, {A, B} {A, B, C}, {A, D}, the longest win. - // A, B, C will be kept and D will be ignored, but one of value not match, so no host will be - // selected. - TestLoadBalancerContext context_3( - {{"A", "A-V-1"}, {"B", "B-V-1"}, {"C", "C-V-X"}, {"D", "D-V-X"}}); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_3)); - - // Request metadata is superset of selectors {A}, {A, B}, {A, D}, the longest and first win. - // Only A, B will be kept and D will be ignored, so host 2 will be selected. - TestLoadBalancerContext context_4({{"A", "A-V-2"}, {"B", "B-V-2"}, {"D", "D-V-X"}}); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_4)); - - // Request metadata is superset of selectors {A}, {A, B}, {A, D}, the longest and first win. - // Only A, B will be kept and D will be ignored, but one of value not match, so no host will be - // selected. - TestLoadBalancerContext context_5({{"A", "A-V-3"}, {"B", "B-V-X"}, {"D", "D-V-3"}}); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_5)); - - // Request metadata is superset of selectors {A}, {A, D}, the longest win. - // Only A, D will be kept and C will be ignored, so host 3 will be selected. - TestLoadBalancerContext context_6({{"A", "A-V-3"}, {"C", "C-V-X"}, {"D", "D-V-3"}}); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_6)); -} - -TEST_P(SubsetLoadBalancerTest, SubsetSelectorDefaultAnyFallbackPerSelector) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = { - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::DEFAULT_SUBSET), - makeSelector( - {"app"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::ANY_ENDPOINT), - makeSelector( - {"foo"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"bar", "default"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - // Add hosts initial hosts. - init({{"tcp://127.0.0.1:81", {{"version", "0.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:83", {{"app", "envoy"}}}, - {"tcp://127.0.0.1:84", {{"foo", "abc"}, {"bar", "default"}}}}); - - TestLoadBalancerContext context_ver_10({{"version", "1.0"}}); - TestLoadBalancerContext context_ver_nx({{"version", "x"}}); - TestLoadBalancerContext context_app({{"app", "envoy"}}); - TestLoadBalancerContext context_app_nx({{"app", "ngnix"}}); - TestLoadBalancerContext context_foo({{"foo", "abc"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_app_nx)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_app_nx)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_app)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_ver_nx)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_foo)); -} - -TEST_P(SubsetLoadBalancerTest, SubsetSelectorDefaultAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); - - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::DEFAULT_SUBSET)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:80", {{"version", "new"}}}, - {"tcp://127.0.0.1:81", {{"version", "default"}}}, - }); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); - - HostSharedPtr added_host1 = makeHost("tcp://127.0.0.1:8000", {{"version", "new"}}); - HostSharedPtr added_host2 = makeHost("tcp://127.0.0.1:8001", {{"version", "default"}}); - - TestLoadBalancerContext context_ver_nx({{"version", "x"}}); - - modifyHosts({added_host1, added_host2}, {host_set_.hosts_.back()}); - - EXPECT_EQ(added_host2, lb_->chooseHost(&context_ver_nx)); -} - -TEST_P(SubsetLoadBalancerTest, SubsetSelectorAnyAfterUpdate) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::ANY_ENDPOINT)}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({ - {"tcp://127.0.0.1:81", {{"version", "1"}}}, - {"tcp://127.0.0.1:82", {{"version", "2"}}}, - }); - - TestLoadBalancerContext context_ver_nx({{"version", "x"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_ver_nx)); - - HostSharedPtr added_host1 = makeHost("tcp://127.0.0.1:83", {{"version", "3"}}); - - modifyHosts({added_host1}, {host_set_.hosts_.back()}); - - EXPECT_EQ(added_host1, lb_->chooseHost(&context_ver_nx)); -} - -TEST_P(SubsetLoadBalancerTest, FallbackForCompoundSelector) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"foo", "bar"}}); - EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); - - std::vector subset_selectors = { - makeSelector( - {"version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), - makeSelector( - {"version", "hardware", "stage"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK), - makeSelector( - {"version", "hardware"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::DEFAULT_SUBSET), - makeSelector( - {"version", "stage"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, - {"version"})}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - // Add hosts initial hosts. - init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"hardware", "c32"}}}, - {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"hardware", "c32"}, {"foo", "bar"}}}, - {"tcp://127.0.0.1:82", {{"version", "2.0"}, {"hardware", "c32"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:83", {{"version", "2.0"}}}}); - - TestLoadBalancerContext context_match_host0({{"version", "1.0"}, {"hardware", "c32"}}); - TestLoadBalancerContext context_ver_nx({{"version", "x"}, {"hardware", "c32"}}); - TestLoadBalancerContext context_stage_nx( - {{"version", "2.0"}, {"hardware", "c32"}, {"stage", "x"}}); - TestLoadBalancerContext context_hardware_nx( - {{"version", "2.0"}, {"hardware", "zzz"}, {"stage", "dev"}}); - TestLoadBalancerContext context_match_host2( - {{"version", "2.0"}, {"hardware", "c32"}, {"stage", "dev"}}); - TestLoadBalancerContext context_ver_20({{"version", "2.0"}}); - TestLoadBalancerContext context_ver_stage_match_host2({{"version", "2.0"}, {"stage", "dev"}}); - TestLoadBalancerContext context_ver_stage_nx({{"version", "2.0"}, {"stage", "canary"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_ver_nx)); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_hardware_nx)); - EXPECT_EQ(nullptr, lb_->chooseHost(&context_stage_nx)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_match_host2)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_match_host2)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_20)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_stage_match_host2)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_stage_match_host2)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_ver_stage_nx)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_stage_nx)); -} - -TEST_P(SubsetLoadBalancerTest, KeysSubsetFallbackChained) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - std::vector subset_selectors = { - makeSelector( - {"stage"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK), - makeSelector( - {"stage", "version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, - {"stage"}), - makeSelector( - {"stage", "version", "hardware"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, - {"version", "stage"})}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"hardware", "c32"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:81", {{"version", "2.0"}, {"hardware", "c64"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:82", {{"version", "1.0"}, {"hardware", "c32"}, {"stage", "test"}}}}); - - TestLoadBalancerContext context_match_host0( - {{"version", "1.0"}, {"hardware", "c32"}, {"stage", "dev"}}); - TestLoadBalancerContext context_hw_nx( - {{"version", "2.0"}, {"hardware", "arm"}, {"stage", "dev"}}); - TestLoadBalancerContext context_ver_hw_nx( - {{"version", "1.2"}, {"hardware", "arm"}, {"stage", "dev"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_hw_nx)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_hw_nx)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_ver_hw_nx)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_ver_hw_nx)); -} - -TEST_P(SubsetLoadBalancerTest, KeysSubsetFallbackToNotExistingSelector) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - - std::vector subset_selectors = {makeSelector( - {"stage", "version"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, - {"stage"})}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "dev"}}}}); - - TestLoadBalancerContext context_nx({{"version", "1.0"}, {"stage", "test"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_nx)); - EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); -} - -TEST_P(SubsetLoadBalancerTest, MetadataFallbackList) { - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - EXPECT_CALL(subset_info_, metadataFallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::FALLBACK_LIST)); - - std::vector subset_selectors = {makeSelector({"version"})}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({{"tcp://127.0.0.1:80", {{"version", "1.0"}}}, - {"tcp://127.0.0.1:81", {{"version", "2.0"}}}, - {"tcp://127.0.0.1:82", {{"version", "3.0"}}}}); - - const auto version1_host = host_set_.hosts_[0]; - const auto version2_host = host_set_.hosts_[1]; - const auto version3_host = host_set_.hosts_[2]; - - // No context. - EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); - - TestLoadBalancerContext context_without_metadata({{"key", "value"}}); - context_without_metadata.matches_ = nullptr; - - // No metadata in context. - EXPECT_EQ(nullptr, lb_->chooseHost(&context_without_metadata)); - - TestLoadBalancerContext context_with_fallback({{"fallback_list", valueFromJson(R""""( - [ - {"version": "2.0"}, - {"version": "1.0"} - ] - )"""")}}); - - // version 2.0 is preferred, should be selected - EXPECT_EQ(version2_host, lb_->chooseHost(&context_with_fallback)); - EXPECT_EQ(version2_host, lb_->chooseHost(&context_with_fallback)); - EXPECT_EQ(version2_host, lb_->chooseHost(&context_with_fallback)); - - modifyHosts({}, {version2_host}); - - // version 1.0 is a fallback, should be used when host with version 2.0 is removed - EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_fallback)); - EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_fallback)); - EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_fallback)); - - // if fallback_list is not a list, it should be ignored - // regular metadata is in effect - ProtobufWkt::Value null_value; - null_value.set_null_value(ProtobufWkt::NullValue::NULL_VALUE); - TestLoadBalancerContext context_with_invalid_fallback_list_null( - {{"version", valueFromJson("\"3.0\"")}, {"fallback_list", null_value}}); - - EXPECT_EQ(version3_host, lb_->chooseHost(&context_with_invalid_fallback_list_null)); - EXPECT_EQ(version3_host, lb_->chooseHost(&context_with_invalid_fallback_list_null)); - - // should ignore fallback list entry which is not a struct - TestLoadBalancerContext context_with_invalid_fallback_list_entry( - {{"fallback_list", valueFromJson(R""""( - [ - "invalid string entry", - {"version": "1.0"} - ] - )"""")}}); - - EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_invalid_fallback_list_entry)); - EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_invalid_fallback_list_entry)); - - // simple metadata with no fallback should work as usual - TestLoadBalancerContext context_no_fallback({{"version", "1.0"}}); - EXPECT_EQ(version1_host, lb_->chooseHost(&context_no_fallback)); - EXPECT_EQ(version1_host, lb_->chooseHost(&context_no_fallback)); - - // fallback metadata overrides regular metadata value - TestLoadBalancerContext context_fallback_overrides_metadata_value( - {{"version", valueFromJson("\"1.0\"")}, {"fallback_list", valueFromJson(R""""( - [ - {"hardware": "arm"}, - {"version": "5.0"}, - {"version": "3.0"} - ] - )"""")}}); - EXPECT_EQ(version3_host, lb_->chooseHost(&context_fallback_overrides_metadata_value)); - EXPECT_EQ(version3_host, lb_->chooseHost(&context_fallback_overrides_metadata_value)); -} - -TEST_P(SubsetLoadBalancerTest, MetadataFallbackDisabled) { - - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - EXPECT_CALL(subset_info_, metadataFallbackPolicy()) - .WillRepeatedly( - Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::METADATA_NO_FALLBACK)); - - std::vector subset_selectors = {makeSelector({"fallback_list"})}; - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({{"tcp://127.0.0.1:80", {{"fallback_list", "lorem"}}}, - {"tcp://127.0.0.1:81", {{"fallback_list", "ipsum"}}}}); - - // should treat 'fallback_list' as a regular metadata key - TestLoadBalancerContext context({{"fallback_list", "ipsum"}}); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context)); -} - -TEST_P(SubsetLoadBalancerTest, MetadataFallbackAndSubsetFallback) { - - EXPECT_CALL(subset_info_, fallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - EXPECT_CALL(subset_info_, metadataFallbackPolicy()) - .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::FALLBACK_LIST)); - - std::vector subset_selectors = { - makeSelector( - {"hardware"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK), - makeSelector( - {"hardware", "stage"}, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, - {"hardware"})}; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - - init({{"tcp://127.0.0.1:80", {{"hardware", "c32"}, {"stage", "production"}}}, - {"tcp://127.0.0.1:81", {{"hardware", "c64"}, {"stage", "canary"}}}, - {"tcp://127.0.0.1:82", {{"hardware", "c64"}, {"stage", "production"}}}}); - - const auto c32_production_host = host_set_.hosts_[0]; - const auto c64_canary_host = host_set_.hosts_[1]; - const auto c64_production_host = host_set_.hosts_[2]; - - TestLoadBalancerContext context_canary_c32_preffered( - {{"stage", valueFromJson("\"canary\"")}, {"fallback_list", valueFromJson(R""""( - [ - {"hardware": "c32"}, - {"hardware": "c64"} - ] - )"""")}}); - - // Should select c32_production_host using first fallback entry, even - // when it doesn't match on requested 'stage' - because of the subset fallback policy. - // There is the c64_canary_host which exactly matches second fallback entry, but - // that entry is not used. - EXPECT_EQ(c32_production_host, lb_->chooseHost(&context_canary_c32_preffered)); - EXPECT_EQ(c32_production_host, lb_->chooseHost(&context_canary_c32_preffered)); - - TestLoadBalancerContext context_canary_c16_preffered( - {{"stage", valueFromJson("\"canary\"")}, {"fallback_list", valueFromJson(R""""( - [ - {"hardware": "c16"}, - {"hardware": "c64"} - ] - )"""")}}); - - // Should select c64_canary_host using second fallback entry. First fallback - // entry doesn't match anything even considering subset fallback policy. - EXPECT_EQ(c64_canary_host, lb_->chooseHost(&context_canary_c16_preffered)); - EXPECT_EQ(c64_canary_host, lb_->chooseHost(&context_canary_c16_preffered)); - - TestLoadBalancerContext context_unknown_or_c64({{"fallback_list", valueFromJson(R""""( - [ - {"unknown": "ipsum"}, - {"hardware": "c64"} - ] - )"""")}}); - - // should select any host using first fallback entry, because of ANY_ENDPOINT - // subset fallback policy - EXPECT_EQ(c32_production_host, lb_->chooseHost(&context_unknown_or_c64)); - EXPECT_EQ(c64_canary_host, lb_->chooseHost(&context_unknown_or_c64)); - EXPECT_EQ(c64_production_host, lb_->chooseHost(&context_unknown_or_c64)); -} - -INSTANTIATE_TEST_SUITE_P(UpdateOrderings, SubsetLoadBalancerTest, - testing::ValuesIn({UpdateOrder::RemovesFirst, UpdateOrder::Simultaneous})); - -class SubsetLoadBalancerSingleHostPerSubsetTest : public SubsetLoadBalancerTest { -public: - SubsetLoadBalancerSingleHostPerSubsetTest() - : default_subset_selectors_({ - makeSelector({"key"}, true), - }) { - ON_CALL(subset_info_, subsetSelectors()).WillByDefault(ReturnRef(default_subset_selectors_)); - ON_CALL(subset_info_, fallbackPolicy()) - .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); - } - - using SubsetLoadBalancerTest::init; - void init() { - init({ - {"tcp://127.0.0.1:80", {}}, - {"tcp://127.0.0.1:81", {{"key", "a"}}}, - {"tcp://127.0.0.1:82", {{"key", "b"}}}, - - }); - } - - using SubsetLoadBalancerTest::makeSelector; - SubsetSelectorPtr makeSelector(const std::set& selector_keys, - bool single_host_per_subset) { - return makeSelector( - selector_keys, - envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED, {}, - single_host_per_subset); - } - - std::vector default_subset_selectors_; -}; - -TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, AcceptMultipleSelectors) { - std::vector subset_selectors = { - makeSelector({"version"}, false), - makeSelector({"stage"}, true), - }; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - ON_CALL(subset_info_, fallbackPolicy()) - .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - init({ - {"tcp://127.0.0.1:80", {}}, - {"tcp://127.0.0.1:81", {{"version", "v1"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:82", {{"version", "v1"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:83", {{"version", "v1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:84", {{"version", "v1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:85", {{"version", "v2"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:86", {{"version", "v2"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:87", {{"version", "v2"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:88", {{"version", "v2"}, {"stage", "prod"}}}, - }); - - TestLoadBalancerContext version_v1({{"version", "v1"}}); - TestLoadBalancerContext version_v2({{"version", "v2"}}); - TestLoadBalancerContext stage_dev({{"stage", "dev"}}); - TestLoadBalancerContext stage_prod({{"stage", "prod"}}); - TestLoadBalancerContext stage_test({{"stage", "test"}}); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&version_v1)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&version_v1)); - - EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&version_v2)); - EXPECT_EQ(host_set_.hosts_[6], lb_->chooseHost(&version_v2)); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&stage_dev)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&stage_dev)); - - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&stage_prod)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&stage_prod)); - - EXPECT_EQ(nullptr, lb_->chooseHost(&stage_test)); -} - -TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, AcceptMultipleKeys) { - std::vector subset_selectors = { - makeSelector({"version", "stage"}, true), - }; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - ON_CALL(subset_info_, fallbackPolicy()) - .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - init({ - {"tcp://127.0.0.1:80", {}}, - {"tcp://127.0.0.1:81", {{"version", "v1"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:82", {{"version", "v1"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:83", {{"version", "v1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:84", {{"version", "v1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:85", {{"version", "v2"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:86", {{"version", "v2"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:87", {{"version", "v2"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:88", {{"version", "v2"}, {"stage", "prod"}}}, - }); - - TestLoadBalancerContext v1_dev({{"version", "v1"}, {"stage", "dev"}}); - TestLoadBalancerContext v1_prod({{"version", "v1"}, {"stage", "prod"}}); - TestLoadBalancerContext v2_dev({{"version", "v2"}, {"stage", "dev"}}); - TestLoadBalancerContext v2_prod({{"version", "v2"}, {"stage", "prod"}}); - TestLoadBalancerContext v2_test({{"version", "v2"}, {"stage", "test"}}); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&v1_dev)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&v1_prod)); - EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&v2_dev)); - EXPECT_EQ(host_set_.hosts_[7], lb_->chooseHost(&v2_prod)); - EXPECT_EQ(nullptr, lb_->chooseHost(&v2_test)); -} - -TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, HybridMultipleSelectorsAndKeys) { - std::vector subset_selectors = { - makeSelector({"version", "stage"}, true), - makeSelector({"stage"}, false), - }; - - EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); - ON_CALL(subset_info_, fallbackPolicy()) - .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); - - init({ - {"tcp://127.0.0.1:80", {}}, - {"tcp://127.0.0.1:81", {{"version", "v1"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:82", {{"version", "v1"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:83", {{"version", "v1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:84", {{"version", "v1"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:85", {{"version", "v2"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:86", {{"version", "v2"}, {"stage", "dev"}}}, - {"tcp://127.0.0.1:87", {{"version", "v2"}, {"stage", "prod"}}}, - {"tcp://127.0.0.1:88", {{"version", "v2"}, {"stage", "prod"}}}, - }); - - TestLoadBalancerContext v1_dev({{"version", "v1"}, {"stage", "dev"}}); - TestLoadBalancerContext v1_prod({{"version", "v1"}, {"stage", "prod"}}); - TestLoadBalancerContext v2_dev({{"version", "v2"}, {"stage", "dev"}}); - TestLoadBalancerContext v2_prod({{"version", "v2"}, {"stage", "prod"}}); - TestLoadBalancerContext v2_test({{"version", "v2"}, {"stage", "test"}}); - TestLoadBalancerContext stage_dev({{"stage", "dev"}}); - TestLoadBalancerContext stage_prod({{"stage", "prod"}}); - TestLoadBalancerContext stage_test({{"stage", "test"}}); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&v1_dev)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&v1_prod)); - EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&v2_dev)); - EXPECT_EQ(host_set_.hosts_[7], lb_->chooseHost(&v2_prod)); - EXPECT_EQ(nullptr, lb_->chooseHost(&v2_test)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&stage_dev)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&stage_dev)); - EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&stage_prod)); - EXPECT_EQ(host_set_.hosts_[4], lb_->chooseHost(&stage_prod)); - EXPECT_EQ(nullptr, lb_->chooseHost(&stage_test)); -} - -TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, DuplicateMetadataStat) { - init({ - {"tcp://127.0.0.1:80", {{"key", "a"}}}, - {"tcp://127.0.0.1:81", {{"key", "a"}}}, - {"tcp://127.0.0.1:82", {{"key", "a"}}}, - {"tcp://127.0.0.1:83", {{"key", "b"}}}, - }); - // The first 'a' is the original, the next 2 instances of 'a' are duplicates (counted - // in stat), and 'b' is another non-duplicate. - for (auto& gauge : stats_store_.gauges()) { - ENVOY_LOG_MISC(debug, "name {} value {}", gauge->name(), gauge->value()); - } - EXPECT_EQ(2, TestUtility::findGauge(stats_store_, - "testprefix.lb_subsets_single_host_per_subset_duplicate") - ->value()); -} - -TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, Match) { - init(); - - TestLoadBalancerContext host_1({{"key", "a"}}); - TestLoadBalancerContext host_2({{"key", "b"}}); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_1)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_1)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_2)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_2)); -} - -TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, FallbackOnUnknownMetadata) { - init(); - - TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); - TestLoadBalancerContext context_unknown_value({{"key", "unknown"}}); - - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_unknown_key)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_unknown_value)); -} - -TEST_P(SubsetLoadBalancerSingleHostPerSubsetTest, Update) { - init(); - - TestLoadBalancerContext host_a({{"key", "a"}}); - TestLoadBalancerContext host_b({{"key", "b"}}); - TestLoadBalancerContext host_c({{"key", "c"}}); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_b)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_b)); - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&host_c)); // fallback - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_c)); // fallback - - HostSharedPtr added_host = makeHost("tcp://127.0.0.1:8000", {{"key", "c"}}); - - // Remove b, add c - modifyHosts({added_host}, {host_set_.hosts_.back()}); - - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_c)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_c)); - EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_b)); // fallback - EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&host_b)); // fallback - EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_b)); // fallback -} - -INSTANTIATE_TEST_SUITE_P(UpdateOrderings, SubsetLoadBalancerSingleHostPerSubsetTest, - testing::ValuesIn({UpdateOrder::RemovesFirst, UpdateOrder::Simultaneous})); - -} // namespace SubsetLoadBalancerTest -} // namespace Upstream -} // namespace Envoy diff --git a/test/common/upstream/test_cluster_manager.h b/test/common/upstream/test_cluster_manager.h index ee4623274856..1cb1ad9e6adc 100644 --- a/test/common/upstream/test_cluster_manager.h +++ b/test/common/upstream/test_cluster_manager.h @@ -21,7 +21,6 @@ #include "source/common/singleton/manager_impl.h" #include "source/common/upstream/cluster_factory_impl.h" #include "source/common/upstream/cluster_manager_impl.h" -#include "source/extensions/load_balancing_policies/subset/subset_lb.h" #include "source/extensions/transport_sockets/tls/context_manager_impl.h" #include "test/common/stats/stat_test_utility.h" diff --git a/test/extensions/access_loggers/grpc/BUILD b/test/extensions/access_loggers/grpc/BUILD index 423c8734457f..05d5477338a7 100644 --- a/test/extensions/access_loggers/grpc/BUILD +++ b/test/extensions/access_loggers/grpc/BUILD @@ -35,6 +35,7 @@ envoy_extension_cc_test( extension_names = ["envoy.access_loggers.http_grpc"], deps = [ "//source/extensions/access_loggers/grpc:grpc_access_log_utils", + "//source/extensions/filters/common/expr:cel_state_lib", "//test/mocks/local_info:local_info_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", diff --git a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc index 782cf5178956..372b7512e631 100644 --- a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc +++ b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc @@ -1,6 +1,9 @@ #include "envoy/data/accesslog/v3/accesslog.pb.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/stream_info/filter_state_impl.h" #include "source/extensions/access_loggers/grpc/grpc_access_log_utils.h" +#include "source/extensions/filters/common/expr/cel_state.h" #include "test/mocks/stream_info/mocks.h" @@ -10,6 +13,8 @@ namespace AccessLoggers { namespace GrpcCommon { namespace { +using Filters::Common::Expr::CelStatePrototype; +using Filters::Common::Expr::CelStateType; using testing::_; using testing::Return; @@ -53,6 +58,105 @@ TEST(UtilityResponseFlagsToAccessLogResponseFlagsTest, All) { EXPECT_EQ(common_access_log_expected.DebugString(), common_access_log.DebugString()); } +// key is present only in downstream streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromDownstream) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("downstream_peer"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto state = std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + state->setValue("value_from_downstream_peer"); + stream_info.filter_state_->setData("downstream_peer", std::move(state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("downstream_peer"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("downstream_peer"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["downstream_peer"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_downstream_peer"); +} + +// key is present only in the upstream streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromUpstream) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("upstream_peer"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto state = std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + auto filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); + state->setValue("value_from_upstream_peer"); + filter_state->setData("upstream_peer", std::move(state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("upstream_peer"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("upstream_peer"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["upstream_peer"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_upstream_peer"); +} + +// key is present in both the streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, + FilterStateFromDownstreamIfSameKeyInBothStreamInfo) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("same_key"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto downstream_state = + std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + downstream_state->setValue("value_from_downstream_peer"); + stream_info.filter_state_->setData("same_key", std::move(downstream_state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + auto upstream_state = + std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + auto filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); + upstream_state->setValue("value_from_upstream_peer"); + filter_state->setData("same_key", std::move(upstream_state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("same_key"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("same_key"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["same_key"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_downstream_peer"); +} + } // namespace } // namespace GrpcCommon } // namespace AccessLoggers diff --git a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc index 14de89f4fb8b..0dec36a101b0 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc @@ -41,6 +41,7 @@ using testing::IsSubstring; using testing::NiceMock; using testing::Return; using testing::ReturnRef; +using testing::SaveArg; namespace Envoy { namespace Config { @@ -276,6 +277,62 @@ TEST_F(GrpcMuxImplTest, ResetStream) { expectSendMessage("foo", {}, ""); } +// Validate cached nonces are cleared on reconnection. +TEST_F(GrpcMuxImplTest, ReconnectionResetsNonceAndAcks) { + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + // Create the retry timer that will invoke the callback that will trigger + // reconnection when the gRPC connection is closed. + Event::MockTimer* grpc_stream_retry_timer{new Event::MockTimer()}; + Event::MockTimer* ttl_mgr_timer{new NiceMock()}; + Event::TimerCb grpc_stream_retry_timer_cb; + EXPECT_CALL(dispatcher_, createTimer_(_)) + .WillOnce( + testing::DoAll(SaveArg<0>(&grpc_stream_retry_timer_cb), Return(grpc_stream_retry_timer))) + // Happens when adding a type url watch. + .WillRepeatedly(Return(ttl_mgr_timer)); + setup(); + InSequence s; + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + auto foo_sub = grpc_mux_->addWatch(type_url, {"x", "y"}, callbacks_, resource_decoder, {}); + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + // Send on connection. + expectSendMessage(type_url, {"x", "y"}, {}, true); + grpc_mux_->start(); + + // Create a reply with some nonce. + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("3000"); + response->set_nonce("111"); + auto add_response_resource = [](const std::string& name, + envoy::service::discovery::v3::DiscoveryResponse& response) { + envoy::config::endpoint::v3::ClusterLoadAssignment cla; + cla.set_cluster_name(name); + auto res = response.add_resources(); + res->PackFrom(cla); + }; + add_response_resource("x", *response); + add_response_resource("y", *response); + { + // Pause EDS to allow the ACK to be cached. + auto resume_eds = grpc_mux_->pause(type_url); + // Send the reply. + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + // Now disconnect, gRPC stream retry timer will kick in and reconnection will happen. + EXPECT_CALL(*grpc_stream_retry_timer, enableTimer(_, _)) + .WillOnce(Invoke(grpc_stream_retry_timer_cb)); + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Canceled, ""); + + // Unpausing will initiate a new request, with the same resources, version, + // but empty nonce. + expectSendMessage(type_url, {"x", "y"}, "3000", true, ""); + } + expectSendMessage(type_url, {}, "3000", false); +} + // Validate pause-resume behavior. TEST_F(GrpcMuxImplTest, PauseResume) { setup(); diff --git a/test/extensions/filters/http/compressor/compressor_filter_test.cc b/test/extensions/filters/http/compressor/compressor_filter_test.cc index 3cb207fc7db1..cf4a2885c5a6 100644 --- a/test/extensions/filters/http/compressor/compressor_filter_test.cc +++ b/test/extensions/filters/http/compressor/compressor_filter_test.cc @@ -430,6 +430,7 @@ TEST_F(CompressorFilterTest, EmptyResponse) { // Verify removeAcceptEncoding header. TEST_F(CompressorFilterTest, RemoveAcceptEncodingHeader) { + // Filter true, no response direction overrides. Header is removed. { Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; setUpFilter(R"EOF( @@ -442,9 +443,12 @@ TEST_F(CompressorFilterTest, RemoveAcceptEncodingHeader) { } } )EOF"); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); EXPECT_FALSE(headers.has("accept-encoding")); } + + // Filter false, no response direction overrides. Header is present. { Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; setUpFilter(R"EOF( @@ -456,6 +460,169 @@ TEST_F(CompressorFilterTest, RemoveAcceptEncodingHeader) { } } )EOF"); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_TRUE(headers.has("accept-encoding")); + EXPECT_EQ("deflate, test, gzip, br", headers.get_("accept-encoding")); + } + + // Filter true, response direction overrides present but no override. Header is removed. + { + Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; + setUpFilter(R"EOF( +{ + "remove_accept_encoding_header": true, + "compressor_library": { + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip" + } + } +} +)EOF"); + CompressorPerRoute per_route_proto; + per_route_proto.mutable_overrides()->mutable_response_direction_config(); + + std::unique_ptr per_route_config = + std::make_unique(per_route_proto); + ON_CALL(decoder_callbacks_, mostSpecificPerFilterConfig()) + .WillByDefault(Return(per_route_config.get())); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_FALSE(headers.has("accept-encoding")); + } + + // Filter false, response direction overrides present but no override. Header is present. + { + Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; + setUpFilter(R"EOF( +{ + "compressor_library": { + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip" + } + } +} +)EOF"); + CompressorPerRoute per_route_proto; + per_route_proto.mutable_overrides()->mutable_response_direction_config(); + + std::unique_ptr per_route_config = + std::make_unique(per_route_proto); + ON_CALL(decoder_callbacks_, mostSpecificPerFilterConfig()) + .WillByDefault(Return(per_route_config.get())); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_TRUE(headers.has("accept-encoding")); + EXPECT_EQ("deflate, test, gzip, br", headers.get_("accept-encoding")); + } + + // Filter true, per-route override true. Header is removed. + { + Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; + setUpFilter(R"EOF( +{ + "remove_accept_encoding_header": true, + "compressor_library": { + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip" + } + } +} +)EOF"); + CompressorPerRoute per_route_proto; + per_route_proto.mutable_overrides() + ->mutable_response_direction_config() + ->mutable_remove_accept_encoding_header() + ->set_value(true); + + std::unique_ptr per_route_config = + std::make_unique(per_route_proto); + ON_CALL(decoder_callbacks_, mostSpecificPerFilterConfig()) + .WillByDefault(Return(per_route_config.get())); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_FALSE(headers.has("accept-encoding")); + } + + // Filter true, per-route override false. Header is present. + { + Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; + setUpFilter(R"EOF( +{ + "remove_accept_encoding_header": true, + "compressor_library": { + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip" + } + } +} +)EOF"); + CompressorPerRoute per_route_proto; + per_route_proto.mutable_overrides() + ->mutable_response_direction_config() + ->mutable_remove_accept_encoding_header() + ->set_value(false); + + std::unique_ptr per_route_config = + std::make_unique(per_route_proto); + ON_CALL(decoder_callbacks_, mostSpecificPerFilterConfig()) + .WillByDefault(Return(per_route_config.get())); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_TRUE(headers.has("accept-encoding")); + EXPECT_EQ("deflate, test, gzip, br", headers.get_("accept-encoding")); + } + + // Filter false, per-route override true. Header is removed. + { + Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; + setUpFilter(R"EOF( +{ + "compressor_library": { + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip" + } + } +} +)EOF"); + CompressorPerRoute per_route_proto; + per_route_proto.mutable_overrides() + ->mutable_response_direction_config() + ->mutable_remove_accept_encoding_header() + ->set_value(true); + + std::unique_ptr per_route_config = + std::make_unique(per_route_proto); + ON_CALL(decoder_callbacks_, mostSpecificPerFilterConfig()) + .WillByDefault(Return(per_route_config.get())); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_FALSE(headers.has("accept-encoding")); + } + + // Filter false, per-route override false. Header is present. + { + Http::TestRequestHeaderMapImpl headers = {{"accept-encoding", "deflate, test, gzip, br"}}; + setUpFilter(R"EOF( +{ + "compressor_library": { + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip" + } + } +} +)EOF"); + CompressorPerRoute per_route_proto; + per_route_proto.mutable_overrides() + ->mutable_response_direction_config() + ->mutable_remove_accept_encoding_header() + ->set_value(false); + + std::unique_ptr per_route_config = + std::make_unique(per_route_proto); + ON_CALL(decoder_callbacks_, mostSpecificPerFilterConfig()) + .WillByDefault(Return(per_route_config.get())); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); EXPECT_TRUE(headers.has("accept-encoding")); EXPECT_EQ("deflate, test, gzip, br", headers.get_("accept-encoding")); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index d682c37508f8..311bcb8e3597 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -363,6 +363,7 @@ TEST_F(HttpFilterTest, ErrorOpen) { envoy_grpc: cluster_name: "ext_authz_server" failure_mode_allow: true + failure_mode_allow_header_add: true )EOF"); ON_CALL(decoder_filter_callbacks_, connection()) @@ -400,6 +401,7 @@ TEST_F(HttpFilterTest, ImmediateErrorOpen) { envoy_grpc: cluster_name: "ext_authz_server" failure_mode_allow: true + failure_mode_allow_header_add: true )EOF"); ON_CALL(decoder_filter_callbacks_, connection()) @@ -433,13 +435,9 @@ TEST_F(HttpFilterTest, ImmediateErrorOpen) { EXPECT_EQ(request_headers_.get_("x-envoy-auth-failure-mode-allowed"), "true"); } -// Test when failure_mode_allow is set with runtime flag closed and the response from the +// Test when failure_mode_allow is set with header add closed and the response from the // authorization service is Error that the request is allowed to continue. -TEST_F(HttpFilterTest, ErrorOpenWithRuntimeFlagClose) { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues( - {{"envoy.reloadable_features.http_ext_auth_failure_mode_allow_header_add", "false"}}); - +TEST_F(HttpFilterTest, ErrorOpenWithHeaderAddClose) { InSequence s; initialize(R"EOF( @@ -448,6 +446,7 @@ TEST_F(HttpFilterTest, ErrorOpenWithRuntimeFlagClose) { envoy_grpc: cluster_name: "ext_authz_server" failure_mode_allow: true + failure_mode_allow_header_add: false )EOF"); ON_CALL(decoder_filter_callbacks_, connection()) @@ -474,13 +473,9 @@ TEST_F(HttpFilterTest, ErrorOpenWithRuntimeFlagClose) { EXPECT_EQ(request_headers_.get_("x-envoy-auth-failure-mode-allowed"), EMPTY_STRING); } -// Test when failure_mode_allow is set with runtime flag closed and the response from the +// Test when failure_mode_allow is set with header add closed and the response from the // authorization service is an immediate Error that the request is allowed to continue. -TEST_F(HttpFilterTest, ImmediateErrorOpenWithRuntimeFlagClose) { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues( - {{"envoy.reloadable_features.http_ext_auth_failure_mode_allow_header_add", "false"}}); - +TEST_F(HttpFilterTest, ImmediateErrorOpenWithHeaderAddClose) { InSequence s; initialize(R"EOF( @@ -489,6 +484,7 @@ TEST_F(HttpFilterTest, ImmediateErrorOpenWithRuntimeFlagClose) { envoy_grpc: cluster_name: "ext_authz_server" failure_mode_allow: true + failure_mode_allow_header_add: false )EOF"); ON_CALL(decoder_filter_callbacks_, connection()) diff --git a/test/extensions/filters/http/geoip/BUILD b/test/extensions/filters/http/geoip/BUILD index 03ce9172a8ff..6e626debcd4b 100644 --- a/test/extensions/filters/http/geoip/BUILD +++ b/test/extensions/filters/http/geoip/BUILD @@ -7,7 +7,6 @@ load( "//test/extensions:extensions_build_system.bzl", "envoy_extension_cc_mock", "envoy_extension_cc_test", - "envoy_extension_cc_test_library", ) licenses(["notice"]) # Apache 2 @@ -24,9 +23,9 @@ envoy_extension_cc_test( size = "small", srcs = ["config_test.cc"], extension_names = ["envoy.filters.http.geoip"], + tags = ["skip_on_windows"], deps = [ ":geoip_mocks", - ":utils_lib", "//source/common/http:message_lib", "//source/extensions/filters/http/geoip:config", "//test/mocks/server:factory_context_mocks", @@ -41,7 +40,9 @@ envoy_extension_cc_test( size = "small", srcs = ["geoip_filter_test.cc"], extension_names = ["envoy.filters.http.geoip"], + tags = ["skip_on_windows"], deps = [ + ":dummy_cc_proto", ":geoip_mocks", "//source/extensions/filters/http/geoip:config", "//source/extensions/filters/http/geoip:geoip_filter_lib", @@ -53,19 +54,38 @@ envoy_extension_cc_test( ], ) +envoy_extension_cc_test( + name = "geoip_filter_integration_test", + size = "large", + srcs = select({ + "//bazel:linux": ["geoip_filter_integration_test.cc"], + "//conditions:default": [], + }), + data = [ + "//test/extensions/geoip_providers/maxmind/test_data:geolocation_databases", + ], + extension_names = [ + "envoy.filters.http.geoip", + ], + tags = ["skip_on_windows"], + deps = [ + "//source/extensions/filters/http/geoip:config", + "//source/extensions/filters/http/geoip:geoip_filter_lib", + "//source/extensions/geoip_providers/maxmind:config", + "//source/extensions/geoip_providers/maxmind:provider_impl", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/filters/http/geoip/v3:pkg_cc_proto", + ], +) + envoy_extension_cc_mock( name = "geoip_mocks", hdrs = ["mocks.h"], extension_names = ["envoy.filters.http.geoip"], + tags = ["skip_on_windows"], deps = [ ":dummy_cc_proto", - "//source/extensions/filters/http/geoip:provider_config", + "//envoy/geoip:geoip_provider_driver_interface", ], ) - -envoy_extension_cc_test_library( - name = "utils_lib", - hdrs = ["utils.h"], - extension_names = ["envoy.filters.http.geoip"], - deps = ["//source/extensions/filters/http/geoip:geoip_filter_lib"], -) diff --git a/test/extensions/filters/http/geoip/config_test.cc b/test/extensions/filters/http/geoip/config_test.cc index 7c27734d1b91..dd538804fe21 100644 --- a/test/extensions/filters/http/geoip/config_test.cc +++ b/test/extensions/filters/http/geoip/config_test.cc @@ -1,12 +1,11 @@ #include "envoy/extensions/filters/http/geoip/v3/geoip.pb.h" #include "envoy/extensions/filters/http/geoip/v3/geoip.pb.validate.h" +#include "envoy/geoip/geoip_provider_driver.h" #include "source/extensions/filters/http/geoip/config.h" #include "source/extensions/filters/http/geoip/geoip_filter.h" -#include "source/extensions/filters/http/geoip/geoip_provider_config.h" #include "test/extensions/filters/http/geoip/mocks.h" -#include "test/extensions/filters/http/geoip/utils.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" @@ -20,6 +19,14 @@ namespace Envoy { namespace Extensions { namespace HttpFilters { namespace Geoip { + +class GeoipFilterPeer { +public: + static bool useXff(const GeoipFilter& filter) { return filter.config_->useXff(); } + static uint32_t xffNumTrustedHops(const GeoipFilter& filter) { + return filter.config_->xffNumTrustedHops(); + } +}; namespace { using GeoipFilterConfig = envoy::extensions::filters::http::geoip::v3::Geoip; @@ -44,61 +51,11 @@ MATCHER_P(HasXffNumTrustedHops, expected, "") { return false; } -MATCHER_P(HasGeoHeader, expected_header, "") { - auto filter = std::static_pointer_cast(arg); - auto geo_headers = GeoipFilterPeer::geoHeaders(*filter); - for (const auto& header : geo_headers) { - if (testing::Matches(expected_header)(header)) { - return true; - } - } - *result_listener << "expected header=" << expected_header - << " but header was not found in header map"; - return false; -} - -MATCHER_P(HasGeoHeadersSize, expected_size, "") { - auto filter = std::static_pointer_cast(arg); - auto geo_headers = GeoipFilterPeer::geoHeaders(*filter); - if (expected_size == static_cast(geo_headers.size())) { - return true; - } - *result_listener << "expected geo headers size=" << expected_size << " but was " - << geo_headers.size(); - return false; -} - -MATCHER_P(HasGeoAnonHeader, expected_header, "") { - auto filter = std::static_pointer_cast(arg); - auto geo_anon_headers = GeoipFilterPeer::geoAnonHeaders(*filter); - for (const auto& header : geo_anon_headers) { - if (testing::Matches(expected_header)(header)) { - return true; - } - } - *result_listener << "expected anon header=" << expected_header - << " but header was not found in header map"; - return false; -} - -MATCHER_P(HasAnonGeoHeadersSize, expected_size, "") { - auto filter = std::static_pointer_cast(arg); - auto geo_headers = GeoipFilterPeer::geoAnonHeaders(*filter); - if (expected_size == static_cast(geo_headers.size())) { - return true; - } - *result_listener << "expected geo anon headers size=" << expected_size << " but was " - << geo_headers.size(); - return false; -} - TEST(GeoipFilterConfigTest, GeoipFilterDefaultValues) { TestScopedRuntime scoped_runtime; DummyGeoipProviderFactory dummy_factory; - Registry::InjectFactory registered(dummy_factory); + Registry::InjectFactory registered(dummy_factory); std::string filter_config_yaml = R"EOF( - geo_headers_to_add: - city: "x-geo-city" provider: name: "envoy.geoip_providers.dummy" typed_config: @@ -112,23 +69,17 @@ TEST(GeoipFilterConfigTest, GeoipFilterDefaultValues) { Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(filter_config, "geoip", context); Http::MockFilterChainFactoryCallbacks filter_callback; EXPECT_CALL(filter_callback, - addStreamDecoderFilter(AllOf(HasUseXff(false), HasXffNumTrustedHops(0), - HasGeoHeader("x-geo-city"), HasGeoHeadersSize(1), - HasAnonGeoHeadersSize(0)))); + addStreamDecoderFilter(AllOf(HasUseXff(false), HasXffNumTrustedHops(0)))); cb(filter_callback); } TEST(GeoipFilterConfigTest, GeoipFilterConfigWithCorrectProto) { TestScopedRuntime scoped_runtime; DummyGeoipProviderFactory dummy_factory; - Registry::InjectFactory registered(dummy_factory); + Registry::InjectFactory registered(dummy_factory); std::string filter_config_yaml = R"EOF( xff_config: xff_num_trusted_hops: 1 - geo_headers_to_add: - country: "x-geo-country" - region: "x-geo-region" - anon_vpn: "x-anon-vpn" provider: name: "envoy.geoip_providers.dummy" typed_config: @@ -142,43 +93,17 @@ TEST(GeoipFilterConfigTest, GeoipFilterConfigWithCorrectProto) { Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(filter_config, "geoip", context); Http::MockFilterChainFactoryCallbacks filter_callback; EXPECT_CALL(filter_callback, - addStreamDecoderFilter( - AllOf(HasUseXff(true), HasXffNumTrustedHops(1), HasGeoHeader("x-geo-country"), - HasGeoHeader("x-geo-region"), HasGeoHeadersSize(2), - HasAnonGeoHeadersSize(1), HasGeoAnonHeader("x-anon-vpn")))); + addStreamDecoderFilter(AllOf(HasUseXff(true), HasXffNumTrustedHops(1)))); cb(filter_callback); } -TEST(GeoipFilterConfigTest, GeoipFilterConfigMissingGeoHeaders) { +TEST(GeoipFilterConfigTest, GeoipFilterConfigMissingProvider) { TestScopedRuntime scoped_runtime; DummyGeoipProviderFactory dummy_factory; - Registry::InjectFactory registered(dummy_factory); + Registry::InjectFactory registered(dummy_factory); std::string filter_config_yaml = R"EOF( xff_config: xff_num_trusted_hops: 0 - provider: - typed_config: - "@type": type.googleapis.com/test.extensions.filters.http.geoip.DummyProvider - )EOF"; - GeoipFilterConfig filter_config; - - TestUtility::loadFromYaml(filter_config_yaml, filter_config); - NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); - GeoipFilterFactory factory; - EXPECT_THROW_WITH_REGEX(factory.createFilterFactoryFromProto(filter_config, "geoip", context), - ProtoValidationException, - "Proto constraint validation failed.*value is required.*"); -} - -TEST(GeoipFilterConfigTest, GeoipFilterConfigMissingProvider) { - TestScopedRuntime scoped_runtime; - DummyGeoipProviderFactory dummy_factory; - Registry::InjectFactory registered(dummy_factory); - std::string filter_config_yaml = R"EOF( - geo_headers_to_add: - country: "x-geo-country" - region: "x-geo-region" )EOF"; GeoipFilterConfig filter_config; @@ -194,11 +119,8 @@ TEST(GeoipFilterConfigTest, GeoipFilterConfigMissingProvider) { TEST(GeoipFilterConfigTest, GeoipFilterConfigUnknownProvider) { TestScopedRuntime scoped_runtime; DummyGeoipProviderFactory dummy_factory; - Registry::InjectFactory registered(dummy_factory); + Registry::InjectFactory registered(dummy_factory); std::string filter_config_yaml = R"EOF( - geo_headers_to_add: - country: "x-geo-country" - region: "x-geo-region" provider: name: "envoy.geoip_providers.unknown" )EOF"; @@ -206,7 +128,6 @@ TEST(GeoipFilterConfigTest, GeoipFilterConfigUnknownProvider) { TestUtility::loadFromYaml(filter_config_yaml, filter_config); NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); GeoipFilterFactory factory; EXPECT_THROW_WITH_MESSAGE( factory.createFilterFactoryFromProtoTyped(filter_config, "geoip", context), diff --git a/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc new file mode 100644 index 000000000000..66b3823403e7 --- /dev/null +++ b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc @@ -0,0 +1,200 @@ +#include "envoy/extensions/filters/http/geoip/v3/geoip.pb.h" + +#include "test/integration/http_integration.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::DoAll; +using testing::SaveArg; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Geoip { +namespace { + +const std::string DefaultConfig = R"EOF( +name: envoy.filters.http.geoip +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.geoip.v3.Geoip + provider: + name: envoy.geoip_providers.maxmind + typed_config: + "@type": type.googleapis.com/envoy.extensions.geoip_providers.maxmind.v3.MaxMindConfig + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + asn: "x-geo-asn" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + isp_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb" +)EOF"; + +const std::string ConfigWithXff = R"EOF( +name: envoy.filters.http.geoip +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.geoip.v3.Geoip + xff_config: + xff_num_trusted_hops: 1 + provider: + name: envoy.geoip_providers.maxmind + typed_config: + "@type": type.googleapis.com/envoy.extensions.geoip_providers.maxmind.v3.MaxMindConfig + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + asn: "x-geo-asn" + is_anon: "x-geo-anon" + anon_vpn: "x-geo-anon-vpn" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + isp_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb" + anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" +)EOF"; + +class GeoipFilterIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + GeoipFilterIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {} +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, GeoipFilterIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedNoXff) { + config_helper_.prependFilter(TestEnvironment::substitute(DefaultConfig)); + initialize(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/"}, {":scheme", "http"}, {":authority", "host"}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + EXPECT_EQ("Boxford", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-city"))[0] + ->value() + .getStringView()); + EXPECT_EQ("England", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-region"))[0] + ->value() + .getStringView()); + EXPECT_EQ("United Kingdom", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-country"))[0] + ->value() + .getStringView()); + EXPECT_EQ("15169", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-asn"))[0] + ->value() + .getStringView()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.hit")->value()); +} + +TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedUseXff) { + config_helper_.prependFilter(TestEnvironment::substitute(ConfigWithXff)); + initialize(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "1.2.0.0,9.10.11.12"}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + EXPECT_EQ("Boxford", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-city"))[0] + ->value() + .getStringView()); + EXPECT_EQ("England", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-region"))[0] + ->value() + .getStringView()); + EXPECT_EQ("United Kingdom", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-country"))[0] + ->value() + .getStringView()); + EXPECT_EQ("15169", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-asn"))[0] + ->value() + .getStringView()); + EXPECT_EQ("true", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-anon"))[0] + ->value() + .getStringView()); + EXPECT_EQ("true", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-anon-vpn"))[0] + ->value() + .getStringView()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.hit")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.hit")->value()); +} + +TEST_P(GeoipFilterIntegrationTest, GeoHeadersOverridenInRequest) { + config_helper_.prependFilter(TestEnvironment::substitute(ConfigWithXff)); + initialize(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "81.2.69.142,9.10.11.12"}, + {"x-geo-city", "Berlin"}, + {"x-geo-country", "Germany"}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + EXPECT_EQ("London", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-city"))[0] + ->value() + .getStringView()); + EXPECT_EQ("United Kingdom", upstream_request_->headers() + .get(Http::LowerCaseString("x-geo-country"))[0] + ->value() + .getStringView()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.hit")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.hit")->value()); +} + +TEST_P(GeoipFilterIntegrationTest, GeoDataNotPopulatedOnEmptyLookupResult) { + config_helper_.prependFilter(TestEnvironment::substitute(ConfigWithXff)); + initialize(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.10.10.10,9.10.11.12"}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + // 10.10.10.10 is a private IP and is absent in test_data/GeoIP2-Anonymous-IP-Test.mmdb database. + ASSERT_TRUE(response->headers().get(Http::LowerCaseString("x-geo-anon")).empty()); + ASSERT_TRUE(response->headers().get(Http::LowerCaseString("x-geo-anon-vpn")).empty()); + test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.total")->value()); + EXPECT_EQ(nullptr, test_server_->counter("http.config_test.maxmind.anon_db.hit")); +} + +} // namespace +} // namespace Geoip +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/geoip/geoip_filter_test.cc b/test/extensions/filters/http/geoip/geoip_filter_test.cc index 035827901bc6..2a0aa027ffbd 100644 --- a/test/extensions/filters/http/geoip/geoip_filter_test.cc +++ b/test/extensions/filters/http/geoip/geoip_filter_test.cc @@ -45,8 +45,8 @@ MATCHER_P2(HasExpectedHeader, expected_header, expected_value, "") { class GeoipFilterTest : public testing::Test { public: GeoipFilterTest() - : dummy_factory_(new DummyGeoipProviderFactory()), dummy_driver_(dummy_factory_->getDriver()), - empty_response_(absl::nullopt) {} + : dummy_factory_(new DummyGeoipProviderFactory()), + dummy_driver_(dummy_factory_->getDriver()) {} void initializeFilter(const std::string& yaml) { ON_CALL(filter_callbacks_, dispatcher()).WillByDefault(::testing::ReturnRef(*dispatcher_)); @@ -59,12 +59,13 @@ class GeoipFilterTest : public testing::Test { void initializeProviderFactory() { TestScopedRuntime scoped_runtime; - Registry::InjectFactory registered(*dummy_factory_); + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.no_extension_lookup_by_name", "false"}}); + Registry::InjectFactory registered(*dummy_factory_); } - void expectStats(const std::string& geo_header) { - EXPECT_CALL(stats_, counter(absl::StrCat("prefix.geoip.", geo_header, ".total"))); - EXPECT_CALL(stats_, counter(absl::StrCat("prefix.geoip.", geo_header, ".hit"))); + void expectStats(const uint32_t n_total = 1) { + EXPECT_CALL(stats_, counter("prefix.geoip.total")).Times(n_total); } NiceMock stats_; @@ -75,17 +76,14 @@ class GeoipFilterTest : public testing::Test { NiceMock filter_callbacks_; Api::ApiPtr api_ = Api::createApiForTest(); Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); - absl::optional empty_response_; - LookupRequest captured_rq_; - LookupGeoHeadersCallback captured_cb_; + Geolocation::LookupRequest captured_rq_; + Geolocation::LookupGeoHeadersCallback captured_cb_; Buffer::OwnedImpl data_; }; TEST_F(GeoipFilterTest, NoXffSuccessfulLookup) { initializeProviderFactory(); const std::string external_request_yaml = R"EOF( - geo_headers_to_add: - city: "x-geo-city" provider: name: "envoy.geoip_providers.dummy" typed_config: @@ -93,7 +91,7 @@ TEST_F(GeoipFilterTest, NoXffSuccessfulLookup) { )EOF"; initializeFilter(external_request_yaml); Http::TestRequestHeaderMapImpl request_headers; - expectStats("x-geo-city"); + expectStats(); Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddress("1.2.3.4"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -105,15 +103,12 @@ TEST_F(GeoipFilterTest, NoXffSuccessfulLookup) { EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); - captured_cb_(LookupResult{{"x-geo-city", absl::make_optional("dummy-city")}}); + captured_cb_(Geolocation::LookupResult{{"x-geo-city", "dummy-city"}}); EXPECT_CALL(filter_callbacks_, continueDecoding()); dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_EQ(1, request_headers.size()); EXPECT_THAT(request_headers, HasExpectedHeader("x-geo-city", "dummy-city")); EXPECT_EQ("1.2.3.4:0", captured_rq_.remoteAddress()->asString()); - EXPECT_EQ(0, captured_rq_.geoAnonHeaders().size()); - EXPECT_EQ(1, captured_rq_.geoHeaders().size()); - EXPECT_THAT(captured_rq_.geoHeaders(), testing::UnorderedElementsAre("x-geo-city")); ::testing::Mock::VerifyAndClearExpectations(&filter_callbacks_); filter_->onDestroy(); } @@ -123,15 +118,13 @@ TEST_F(GeoipFilterTest, UseXffSuccessfulLookup) { const std::string external_request_yaml = R"EOF( xff_config: xff_num_trusted_hops: 1 - geo_headers_to_add: - region: "x-geo-region" provider: name: "envoy.geoip_providers.dummy" )EOF"; initializeFilter(external_request_yaml); Http::TestRequestHeaderMapImpl request_headers; request_headers.addCopy("x-forwarded-for", "10.0.0.1,10.0.0.2"); - expectStats("x-geo-region"); + expectStats(); Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddress("1.2.3.4"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -139,7 +132,7 @@ TEST_F(GeoipFilterTest, UseXffSuccessfulLookup) { EXPECT_CALL(*dummy_driver_, lookup(_, _)) .WillRepeatedly( DoAll(SaveArg<0>(&captured_rq_), SaveArg<1>(&captured_cb_), Invoke([this]() { - captured_cb_(LookupResult{{"x-geo-region", absl::make_optional("dummy-region")}}); + captured_cb_(Geolocation::LookupResult{{"x-geo-region", "dummy-region"}}); }))); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); @@ -148,9 +141,6 @@ TEST_F(GeoipFilterTest, UseXffSuccessfulLookup) { EXPECT_EQ(2, request_headers.size()); EXPECT_THAT(request_headers, HasExpectedHeader("x-geo-region", "dummy-region")); EXPECT_EQ("10.0.0.1:0", captured_rq_.remoteAddress()->asString()); - EXPECT_EQ(0, captured_rq_.geoAnonHeaders().size()); - EXPECT_EQ(1, captured_rq_.geoHeaders().size()); - EXPECT_THAT(captured_rq_.geoHeaders(), testing::UnorderedElementsAre("x-geo-region")); ::testing::Mock::VerifyAndClearExpectations(&filter_callbacks_); filter_->onDestroy(); } @@ -158,9 +148,6 @@ TEST_F(GeoipFilterTest, UseXffSuccessfulLookup) { TEST_F(GeoipFilterTest, GeoHeadersOverridenForIncomingRequest) { initializeProviderFactory(); const std::string external_request_yaml = R"EOF( - geo_headers_to_add: - region: "x-geo-region" - city: "x-geo-city" provider: name: "envoy.geoip_providers.dummy" )EOF"; @@ -170,20 +157,16 @@ TEST_F(GeoipFilterTest, GeoHeadersOverridenForIncomingRequest) { request_headers.addCopy("x-geo-city", "ngnix_city"); std::map geo_headers = {{"x-geo-region", "dummy_region"}, {"x-geo-city", "dummy_city"}}; - for (auto& geo_header : geo_headers) { - auto& header = geo_header.first; - expectStats(header); - } + expectStats(); Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddress("1.2.3.4"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( remote_address); EXPECT_CALL(*dummy_driver_, lookup(_, _)) - .WillRepeatedly( - DoAll(SaveArg<0>(&captured_rq_), SaveArg<1>(&captured_cb_), Invoke([this]() { - captured_cb_(LookupResult{{"x-geo-city", absl::make_optional("dummy-city")}, - {"x-geo-region", absl::make_optional("dummy-region")}}); - }))); + .WillRepeatedly(DoAll(SaveArg<0>(&captured_rq_), SaveArg<1>(&captured_cb_), Invoke([this]() { + captured_cb_(Geolocation::LookupResult{ + {"x-geo-city", "dummy-city"}, {"x-geo-region", "dummy-region"}}); + }))); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); EXPECT_CALL(filter_callbacks_, continueDecoding()); @@ -192,10 +175,6 @@ TEST_F(GeoipFilterTest, GeoHeadersOverridenForIncomingRequest) { EXPECT_THAT(request_headers, HasExpectedHeader("x-geo-city", "dummy-city")); EXPECT_THAT(request_headers, HasExpectedHeader("x-geo-region", "dummy-region")); EXPECT_EQ("1.2.3.4:0", captured_rq_.remoteAddress()->asString()); - EXPECT_EQ(0, captured_rq_.geoAnonHeaders().size()); - EXPECT_EQ(2, captured_rq_.geoHeaders().size()); - EXPECT_THAT(captured_rq_.geoHeaders(), - testing::UnorderedElementsAre("x-geo-region", "x-geo-city")); ::testing::Mock::VerifyAndClearExpectations(&filter_callbacks_); filter_->onDestroy(); } @@ -203,16 +182,6 @@ TEST_F(GeoipFilterTest, GeoHeadersOverridenForIncomingRequest) { TEST_F(GeoipFilterTest, AllHeadersPropagatedCorrectly) { initializeProviderFactory(); const std::string external_request_yaml = R"EOF( - geo_headers_to_add: - region: "x-geo-region" - country: "x-geo-country" - city: "x-geo-city" - asn: "x-geo-asn" - is_anon: "x-geo-anon" - anon_vpn: "x-geo-anon-vpn" - anon_hosting: "x-geo-anon-hosting" - anon_tor: "x-geo-anon-tor" - anon_proxy: "x-geo-anon-proxy" provider: name: "envoy.geoip_providers.dummy" )EOF"; @@ -227,29 +196,23 @@ TEST_F(GeoipFilterTest, AllHeadersPropagatedCorrectly) { {"x-geo-anon-hosting", "true"}, {"x-geo-anon-tor", "true"}, {"x-geo-anon-proxy", "true"}}; - for (auto& geo_header : geo_headers) { - auto& header = geo_header.first; - expectStats(header); - } - for (auto& geo_anon_header : geo_anon_headers) { - auto& header = geo_anon_header.first; - expectStats(header); - } + expectStats(); Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddress("1.2.3.4"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( remote_address); EXPECT_CALL(*dummy_driver_, lookup(_, _)) .WillRepeatedly(DoAll(SaveArg<0>(&captured_rq_), SaveArg<1>(&captured_cb_), Invoke([this]() { - captured_cb_(LookupResult{{"x-geo-city", "dummy-city"}, - {"x-geo-region", "dummy-region"}, - {"x-geo-country", "dummy-country"}, - {"x-geo-asn", "dummy-asn"}, - {"x-geo-anon", "true"}, - {"x-geo-anon-vpn", "false"}, - {"x-geo-anon-hosting", "true"}, - {"x-geo-anon-tor", "true"}, - {"x-geo-anon-proxy", "true"}}); + captured_cb_( + Geolocation::LookupResult{{"x-geo-city", "dummy-city"}, + {"x-geo-region", "dummy-region"}, + {"x-geo-country", "dummy-country"}, + {"x-geo-asn", "dummy-asn"}, + {"x-geo-anon", "true"}, + {"x-geo-anon-vpn", "false"}, + {"x-geo-anon-hosting", "true"}, + {"x-geo-anon-tor", "true"}, + {"x-geo-anon-proxy", "true"}}); }))); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); @@ -257,14 +220,6 @@ TEST_F(GeoipFilterTest, AllHeadersPropagatedCorrectly) { dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_EQ("1.2.3.4:0", captured_rq_.remoteAddress()->asString()); EXPECT_EQ(9, request_headers.size()); - EXPECT_EQ(5, captured_rq_.geoAnonHeaders().size()); - EXPECT_EQ(4, captured_rq_.geoHeaders().size()); - EXPECT_THAT( - captured_rq_.geoHeaders(), - testing::UnorderedElementsAre("x-geo-region", "x-geo-city", "x-geo-country", "x-geo-asn")); - EXPECT_THAT(captured_rq_.geoAnonHeaders(), - testing::UnorderedElementsAre("x-geo-anon", "x-geo-anon-vpn", "x-geo-anon-hosting", - "x-geo-anon-tor", "x-geo-anon-proxy")); for (auto& geo_header : geo_headers) { auto& header = geo_header.first; auto& value = geo_header.second; @@ -282,26 +237,20 @@ TEST_F(GeoipFilterTest, AllHeadersPropagatedCorrectly) { TEST_F(GeoipFilterTest, GeoHeaderNotAppendedOnEmptyLookup) { initializeProviderFactory(); const std::string external_request_yaml = R"EOF( - geo_headers_to_add: - region: "x-geo-region" - city: "x-geo-city" provider: name: "envoy.geoip_providers.dummy" )EOF"; initializeFilter(external_request_yaml); Http::TestRequestHeaderMapImpl request_headers; - EXPECT_CALL(stats_, counter("prefix.geoip.x-geo-city.total")); - EXPECT_CALL(stats_, counter("prefix.geoip.x-geo-city.hit")).Times(0); - EXPECT_CALL(stats_, counter("prefix.geoip.x-geo-region.total")); - EXPECT_CALL(stats_, counter("prefix.geoip.x-geo-region.hit")); + expectStats(); Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddress("1.2.3.4"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( remote_address); EXPECT_CALL(*dummy_driver_, lookup(_, _)) .WillRepeatedly(DoAll(SaveArg<0>(&captured_rq_), SaveArg<1>(&captured_cb_), Invoke([this]() { - captured_cb_(LookupResult{{"x-geo-city", empty_response_}, - {"x-geo-region", "dummy-region"}}); + captured_cb_(Geolocation::LookupResult{ + {"x-geo-city", ""}, {"x-geo-region", "dummy-region"}}); }))); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); @@ -309,8 +258,6 @@ TEST_F(GeoipFilterTest, GeoHeaderNotAppendedOnEmptyLookup) { dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_EQ("1.2.3.4:0", captured_rq_.remoteAddress()->asString()); EXPECT_EQ(1, request_headers.size()); - EXPECT_EQ(0, captured_rq_.geoAnonHeaders().size()); - EXPECT_EQ(2, captured_rq_.geoHeaders().size()); EXPECT_THAT(request_headers, HasExpectedHeader("x-geo-region", "dummy-region")); ::testing::Mock::VerifyAndClearExpectations(&filter_callbacks_); filter_->onDestroy(); @@ -319,8 +266,6 @@ TEST_F(GeoipFilterTest, GeoHeaderNotAppendedOnEmptyLookup) { TEST_F(GeoipFilterTest, NoCrashIfFilterDestroyedBeforeCallbackCalled) { initializeProviderFactory(); const std::string external_request_yaml = R"EOF( - geo_headers_to_add: - city: "x-geo-city" provider: name: "envoy.geoip_providers.dummy" )EOF"; @@ -331,10 +276,9 @@ TEST_F(GeoipFilterTest, NoCrashIfFilterDestroyedBeforeCallbackCalled) { filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( remote_address); EXPECT_CALL(*dummy_driver_, lookup(_, _)) - .WillRepeatedly( - DoAll(SaveArg<0>(&captured_rq_), SaveArg<1>(&captured_cb_), Invoke([this]() { - captured_cb_(LookupResult{{"x-geo-city", absl::make_optional("dummy-city")}}); - }))); + .WillRepeatedly(DoAll(SaveArg<0>(&captured_rq_), SaveArg<1>(&captured_cb_), Invoke([this]() { + captured_cb_(Geolocation::LookupResult{{"x-geo-city", "dummy-city"}}); + }))); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); filter_.reset(); diff --git a/test/extensions/filters/http/geoip/mocks.h b/test/extensions/filters/http/geoip/mocks.h index 34a93df3c48c..6f89272740b8 100644 --- a/test/extensions/filters/http/geoip/mocks.h +++ b/test/extensions/filters/http/geoip/mocks.h @@ -1,6 +1,6 @@ #pragma once -#include "source/extensions/filters/http/geoip/geoip_provider_config.h" +#include "envoy/geoip/geoip_provider_driver.h" #include "test/extensions/filters/http/geoip/dummy.pb.h" #include "test/extensions/filters/http/geoip/dummy.pb.validate.h" @@ -12,18 +12,21 @@ namespace Extensions { namespace HttpFilters { namespace Geoip { -class MockDriver : public Driver { +class MockDriver : public Geolocation::Driver { public: - MOCK_METHOD(void, lookup, (LookupRequest && request, LookupGeoHeadersCallback&&), (const)); + MOCK_METHOD(void, lookup, + (Geolocation::LookupRequest && request, Geolocation::LookupGeoHeadersCallback&&), + (const)); }; using MockDriverSharedPtr = std::shared_ptr; -class DummyGeoipProviderFactory : public GeoipProviderFactory { +class DummyGeoipProviderFactory : public Geolocation::GeoipProviderFactory { public: DummyGeoipProviderFactory() : driver_(new MockDriver()) {} - DriverSharedPtr createGeoipProviderDriver(const Protobuf::Message&, - GeoipProviderFactoryContextPtr&) override { + Geolocation::DriverSharedPtr + createGeoipProviderDriver(const Protobuf::Message&, const std::string&, + Server::Configuration::FactoryContext&) override { return driver_; } diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/hcm_header_value_extractor_key_name b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/hcm_header_value_extractor_key_name new file mode 100644 index 000000000000..dfa5b48243c1 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/hcm_header_value_extractor_key_name @@ -0,0 +1,12 @@ +config { + name: "envoy.filters.network.http_connection_manager" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" + value: "\022\002--B\002\020\001\250\001\001\372\001\310\001\n\001Z\022\257\001\n\254\001\n\251\001\n\240\001$config {\n name: \"envoy.filters.network.http_connection_manager\"\n typed_config {\n type_url: \"type.googleapis.com/envoy.extensions.filters.network.http_conn\022\002T+\030&\"\021\n\017\n\001e\032\010\n\006\n\004$v21*\000\350\002\004\270\003\001" + } +} +actions { + on_data { + data: "PUT /2.0nnnnnn\n\n\n\n\022r\n" + } +} 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 02b5d83e65bb..2fb73c1a00e1 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc @@ -645,7 +645,7 @@ TEST_F(DnsFilterTest, RepeatedTypeAQuerySuccess) { 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 uint16_t query_id = (random_.random() + i) % 0xFFFF + 1; const std::string query = Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN, query_id); ASSERT_FALSE(query.empty()); 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 e22b86b94dd7..5ac9faadc974 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 @@ -26,7 +26,7 @@ std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint const uint16_t query_id) { Random::RandomGeneratorImpl random_; struct DnsHeader query {}; - uint16_t id = (query_id ? query_id : random_.random() & 0xFFFF); + uint16_t id = query_id ? query_id : (random_.random() % 0xFFFF) + 1; // Generate a random query ID query.id = id; diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD new file mode 100644 index 000000000000..c5a27baefdf6 --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD @@ -0,0 +1,68 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_package", + "envoy_proto_library", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_proto_library( + name = "dfp_setter_filter_proto", + srcs = ["dfp_setter.proto"], +) + +envoy_cc_test_library( + name = "dfp_setter_filter_config_lib", + srcs = ["dfp_setter.h"], + deps = [ + ":dfp_setter_filter_proto_cc_proto", + "//envoy/registry", + "//source/common/router:string_accessor_lib", + "//source/common/stream_info:uint32_accessor_lib", + "//source/extensions/filters/udp/udp_proxy/session_filters:factory_base_lib", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", + "//test/test_common:utility_lib", + ], + alwayslink = 1, +) + +envoy_extension_cc_test( + name = "proxy_filter_test", + srcs = ["proxy_filter_test.cc"], + extension_names = ["envoy.filters.udp.session.dynamic_forward_proxy"], + deps = [ + "//source/common/router:string_accessor_lib", + "//source/common/stream_info:uint32_accessor_lib", + "//source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy:config", + "//test/extensions/common/dynamic_forward_proxy:mocks", + "//test/extensions/filters/udp/udp_proxy:mocks", + "//test/mocks/server:factory_context_mocks", + "@envoy_api//envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "dynamic_forward_proxy_filter_integration_test", + srcs = ["proxy_filter_integration_test.cc"], + extension_names = ["envoy.filters.udp.session.dynamic_forward_proxy"], + deps = [ + ":dfp_setter_filter_config_lib", + ":dfp_setter_filter_proto_cc_proto", + "//envoy/network:filter_interface", + "//envoy/server:filter_config_interface", + "//source/extensions/clusters/dynamic_forward_proxy:cluster", + "//source/extensions/filters/udp/udp_proxy:config", + "//source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy:config", + "//source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy:proxy_filter_lib", + "//test/integration:integration_lib", + "//test/test_common:registry_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h new file mode 100644 index 000000000000..28d0e78bb155 --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h @@ -0,0 +1,77 @@ +#pragma once + +#include "envoy/registry/registry.h" + +#include "source/common/config/utility.h" +#include "source/common/router/string_accessor_impl.h" +#include "source/common/stream_info/uint32_accessor_impl.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/factory_base.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/filter.h" + +#include "test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.pb.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.pb.validate.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { +namespace DynamicForwardProxy { + +using DynamicForwardProxySetterFilterConfig = test::extensions::filters::udp::udp_proxy:: + session_filters::DynamicForwardProxySetterFilterConfig; + +class DynamicForwardProxySetterFilter : public virtual ReadFilter { +public: + DynamicForwardProxySetterFilter(const std::string host, uint32_t port) + : host_(host), port_(port) {} + + ReadFilterStatus onNewSession() override { + read_callbacks_->streamInfo().filterState()->setData( + "envoy.upstream.dynamic_host", std::make_shared(host_), + StreamInfo::FilterState::StateType::Mutable); + read_callbacks_->streamInfo().filterState()->setData( + "envoy.upstream.dynamic_port", std::make_shared(port_), + StreamInfo::FilterState::StateType::Mutable); + return ReadFilterStatus::Continue; + } + + ReadFilterStatus onData(Network::UdpRecvData&) override { return ReadFilterStatus::Continue; } + + void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + +private: + const std::string host_; + uint32_t port_; + ReadFilterCallbacks* read_callbacks_; +}; + +class DynamicForwardProxySetterFilterConfigFactory + : public FactoryBase { +public: + DynamicForwardProxySetterFilterConfigFactory() : FactoryBase("test.udp_session.dfp_setter") {} + +private: + FilterFactoryCb + createFilterFactoryFromProtoTyped(const DynamicForwardProxySetterFilterConfig& config, + Server::Configuration::FactoryContext&) override { + return [config](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addReadFilter( + std::make_unique(config.host(), config.port())); + }; + } +}; + +static Registry::RegisterFactory + register_dfp_setter_udp_session_read_filter_; + +} // namespace DynamicForwardProxy +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.proto b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.proto new file mode 100644 index 000000000000..c42a189735c8 --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package test.extensions.filters.udp.udp_proxy.session_filters; + +message DynamicForwardProxySetterFilterConfig { + string host = 1; + uint32 port = 2; +} diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc new file mode 100644 index 000000000000..cf76b3ae7b21 --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -0,0 +1,205 @@ +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/network/filter.h" +#include "envoy/server/filter_config.h" + +#include "source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/config.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.h" + +#include "test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.pb.h" +#include "test/integration/integration.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/registry.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { +namespace DynamicForwardProxy { +namespace { + +class DynamicForwardProxyIntegrationTest + : public testing::TestWithParam, + public BaseIntegrationTest { +public: + DynamicForwardProxyIntegrationTest() + : BaseIntegrationTest(GetParam(), ConfigHelper::baseUdpListenerConfig()) { + skip_tag_extraction_rule_check_ = true; + } + + struct BufferConfig { + uint32_t max_buffered_datagrams_; + uint32_t max_buffered_bytes_; + }; + + void setup(absl::optional buffer_config = absl::nullopt, uint32_t max_hosts = 1024, + uint32_t max_pending_requests = 1024) { + setUdpFakeUpstream(FakeUpstreamConfig::UdpConfig()); + + config_helper_.addConfigModifier([this, buffer_config, max_hosts, max_pending_requests]( + envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Switch predefined cluster_0 to CDS filesystem sourcing. + bootstrap.mutable_dynamic_resources()->mutable_cds_config()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); + bootstrap.mutable_dynamic_resources() + ->mutable_cds_config() + ->mutable_path_config_source() + ->set_path(cds_helper_.cdsPath()); + bootstrap.mutable_static_resources()->clear_clusters(); + + std::string filter_config = fmt::format(R"EOF( +name: udp_proxy +typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + stat_prefix: foo + matcher: + on_no_match: + action: + name: route + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: cluster_0 + session_filters: + - name: setter + typed_config: + '@type': type.googleapis.com/test.extensions.filters.udp.udp_proxy.session_filters.DynamicForwardProxySetterFilterConfig + host: localhost + port: {} + - name: dfp + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.session.dynamic_forward_proxy.v3.FilterConfig + stat_prefix: foo + dns_cache_config: + name: foo + dns_lookup_family: {} + max_hosts: {} + dns_cache_circuit_breaker: + max_pending_requests: {} +)EOF", + fake_upstreams_[0]->localAddress()->ip()->port(), + Network::Test::ipVersionToDnsFamily(GetParam()), + max_hosts, max_pending_requests); + + if (buffer_config.has_value()) { + filter_config += fmt::format(R"EOF( + buffer_options: + max_buffered_datagrams: {} + max_buffered_bytes: {} +)EOF", + buffer_config.value().max_buffered_datagrams_, + buffer_config.value().max_buffered_bytes_); + } + + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter = listener->add_listener_filters(); + TestUtility::loadFromYaml(filter_config, *filter); + }); + + // Setup the initial CDS cluster. + cluster_.mutable_connect_timeout()->CopyFrom( + Protobuf::util::TimeUtil::MillisecondsToDuration(100)); + cluster_.set_name("cluster_0"); + cluster_.set_lb_policy(envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); + + const std::string cluster_type_config = fmt::format( + R"EOF( +name: envoy.clusters.dynamic_forward_proxy +typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig + dns_cache_config: + name: foo + dns_lookup_family: {} + max_hosts: {} + dns_cache_circuit_breaker: + max_pending_requests: {} +)EOF", + Network::Test::ipVersionToDnsFamily(GetParam()), max_hosts, max_pending_requests); + + TestUtility::loadFromYaml(cluster_type_config, *cluster_.mutable_cluster_type()); + + // Load the CDS cluster and wait for it to initialize. + cds_helper_.setCds({cluster_}); + BaseIntegrationTest::initialize(); + test_server_->waitForCounterEq("cluster_manager.cluster_added", 1); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + } + + CdsHelper cds_helper_; + envoy::config::cluster::v3::Cluster cluster_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, DynamicForwardProxyIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(DynamicForwardProxyIntegrationTest, BasicFlow) { + setup(); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + Network::Test::UdpSyncPeer client(version_); + + client.write("hello1", *listener_address); + test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); + test_server_->waitForCounterEq("dns_cache.foo.dns_query_success", 1); + test_server_->waitForCounterEq("dns_cache.foo.host_added", 1); + + // There is no buffering in this test, so the first message was dropped. Send another message + // to verify that it's able to go through after the DNS resolution completed. + client.write("hello2", *listener_address); + + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello2", request_datagram.buffer_->toString()); +} + +TEST_P(DynamicForwardProxyIntegrationTest, BasicFlowWithBuffering) { + setup(BufferConfig{1, 1024}); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + Network::Test::UdpSyncPeer client(version_); + + client.write("hello1", *listener_address); + test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); + test_server_->waitForCounterEq("dns_cache.foo.dns_query_success", 1); + test_server_->waitForCounterEq("dns_cache.foo.host_added", 1); + + // Buffering is enabled so check that the first datagram is sent after the resolution completed. + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello1", request_datagram.buffer_->toString()); +} + +TEST_P(DynamicForwardProxyIntegrationTest, BufferOverflowDueToDatagramSize) { + setup(BufferConfig{1, 2}); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + Network::Test::UdpSyncPeer client(version_); + + client.write("hello1", *listener_address); + test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); + test_server_->waitForCounterEq("dns_cache.foo.dns_query_success", 1); + test_server_->waitForCounterEq("dns_cache.foo.host_added", 1); + test_server_->waitForCounterEq("udp.session.dynamic_forward_proxy.foo.buffer_overflow", 1); + + // The first datagram should be dropped because it exceeds the buffer size. Send another message + // to verify that it's able to go through after the DNS resolution completed. + client.write("hello2", *listener_address); + + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello2", request_datagram.buffer_->toString()); +} + +} // namespace +} // namespace DynamicForwardProxy +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc new file mode 100644 index 000000000000..06aafcca58c2 --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc @@ -0,0 +1,312 @@ +#include "envoy/extensions/filters/udp/udp_proxy/session/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.h" + +#include "source/common/router/string_accessor_impl.h" +#include "source/common/stream_info/uint32_accessor_impl.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.h" + +#include "test/extensions/common/dynamic_forward_proxy/mocks.h" +#include "test/extensions/filters/udp/udp_proxy/mocks.h" +#include "test/mocks/server/factory_context.h" + +using testing::Eq; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { +namespace DynamicForwardProxy { +namespace { + +using LoadDnsCacheEntryStatus = + Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryStatus; +using MockLoadDnsCacheEntryResult = + Extensions::Common::DynamicForwardProxy::MockDnsCache::MockLoadDnsCacheEntryResult; + +class DynamicProxyFilterTest + : public testing::Test, + public Extensions::Common::DynamicForwardProxy::DnsCacheManagerFactory { +public: + DynamicProxyFilterTest() { + recv_data_stub1_.buffer_ = std::make_unique("sometestdata1"); + recv_data_stub2_.buffer_ = std::make_unique("sometestdata2"); + } + + void setFilterStateHost(const std::string& host) { + stream_info_.filterState()->setData( + "envoy.upstream.dynamic_host", std::make_shared(host), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + } + + void setFilterStatePort(uint32_t port) { + stream_info_.filterState()->setData( + "envoy.upstream.dynamic_port", std::make_shared(port), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + } + + void setFilterState(const std::string& host, uint32_t port) { + setFilterStateHost(host); + setFilterStatePort(port); + } + + Extensions::Common::DynamicForwardProxy::DnsCacheManagerSharedPtr get() override { + return dns_cache_manager_; + } + + void setup(absl::optional proto_config = absl::nullopt) { + FilterConfig config; + if (proto_config.has_value()) { + config = proto_config.value(); + } + + EXPECT_CALL(*dns_cache_manager_, getCache(_)); + filter_config_ = std::make_shared(config, *this, server_context_); + filter_ = std::make_unique(filter_config_); + filter_->initializeReadFilterCallbacks(callbacks_); + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info_)); + } + + using MockDnsCacheManager = Extensions::Common::DynamicForwardProxy::MockDnsCacheManager; + std::shared_ptr dns_cache_manager_ = std::make_shared(); + NiceMock server_context_; + ProxyFilterConfigSharedPtr filter_config_; + std::unique_ptr filter_; + NiceMock callbacks_; + NiceMock stream_info_; + NiceMock pending_requests_; + Network::UdpRecvData recv_data_stub1_; + Network::UdpRecvData recv_data_stub2_; +}; + +TEST_F(DynamicProxyFilterTest, DefaultConfig) { + setup(); + EXPECT_FALSE(filter_config_->bufferEnabled()); +} + +TEST_F(DynamicProxyFilterTest, DefaultBufferConfig) { + FilterConfig config; + config.mutable_buffer_options(); + setup(config); + + EXPECT_TRUE(filter_config_->bufferEnabled()); + EXPECT_EQ(1024, filter_config_->maxBufferedDatagrams()); + EXPECT_EQ(16384, filter_config_->maxBufferedBytes()); + filter_config_->disableBuffer(); + EXPECT_FALSE(filter_config_->bufferEnabled()); +} + +TEST_F(DynamicProxyFilterTest, CustomBufferConfig) { + FilterConfig config; + auto* buffer_options = config.mutable_buffer_options(); + buffer_options->mutable_max_buffered_datagrams()->set_value(10); + buffer_options->mutable_max_buffered_bytes()->set_value(20); + + setup(config); + EXPECT_TRUE(filter_config_->bufferEnabled()); + EXPECT_EQ(10, filter_config_->maxBufferedDatagrams()); + EXPECT_EQ(20, filter_config_->maxBufferedBytes()); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnMissingHostAndPort) { + setup(); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()).Times(0); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnMissingPort) { + setup(); + setFilterStateHost("host"); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()).Times(0); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnMissingHost) { + setup(); + setFilterStatePort(50); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()).Times(0); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnEmptyHost) { + setup(); + setFilterState("", 50); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()).Times(0); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnPortIsZero) { + setup(); + setFilterState("", 0); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()).Times(0); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnPortOutOfRange) { + setup(); + setFilterState("", 65536); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()).Times(0); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnRequestOverflow) { + setup(); + setFilterState("host", 50); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()).WillOnce(Return(nullptr)); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, StopIterationOnCacheLoadOverflow) { + setup(); + setFilterState("host", 50); + Upstream::ResourceAutoIncDec* circuit_breakers_{ + new Upstream::ResourceAutoIncDec(pending_requests_)}; + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()) + .WillOnce(Return(circuit_breakers_)); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, loadDnsCacheEntry_(Eq("host"), 50, _, _)) + .WillOnce(Return( + MockLoadDnsCacheEntryResult{LoadDnsCacheEntryStatus::Overflow, nullptr, absl::nullopt})); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, PassesThroughImmediatelyWhenDnsAlreadyInCache) { + setup(); + setFilterState("host", 50); + Upstream::ResourceAutoIncDec* circuit_breakers_{ + new Upstream::ResourceAutoIncDec(pending_requests_)}; + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()) + .WillOnce(Return(circuit_breakers_)); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, loadDnsCacheEntry_(Eq("host"), 50, _, _)) + .WillOnce(Return( + MockLoadDnsCacheEntryResult{LoadDnsCacheEntryStatus::InCache, nullptr, absl::nullopt})); + EXPECT_EQ(ReadFilterStatus::Continue, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::Continue, filter_->onData(recv_data_stub1_)); +} + +TEST_F(DynamicProxyFilterTest, + RequestWithCacheMissShouldStopIterationBufferPopulateCacheAndFlushWhenReady) { + setup(); + setFilterState("host", 50); + Upstream::ResourceAutoIncDec* circuit_breakers_{ + new Upstream::ResourceAutoIncDec(pending_requests_)}; + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()) + .WillOnce(Return(circuit_breakers_)); + Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle* handle = + new Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle(); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, loadDnsCacheEntry_(Eq("host"), 50, _, _)) + .WillOnce(Return( + MockLoadDnsCacheEntryResult{LoadDnsCacheEntryStatus::Loading, handle, absl::nullopt})); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); + + EXPECT_CALL(callbacks_, continueFilterChain()); + filter_->onLoadDnsCacheComplete( + std::make_shared()); + EXPECT_CALL(*handle, onDestroy()); +} + +TEST_F(DynamicProxyFilterTest, LoadingCacheEntryWithDefaultBufferConfig) { + FilterConfig config; + config.mutable_buffer_options(); + setup(config); + + setFilterState("host", 50); + EXPECT_TRUE(filter_config_->bufferEnabled()); + Upstream::ResourceAutoIncDec* circuit_breakers_{ + new Upstream::ResourceAutoIncDec(pending_requests_)}; + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()) + .WillOnce(Return(circuit_breakers_)); + Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle* handle = + new Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle(); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, loadDnsCacheEntry_(Eq("host"), 50, _, _)) + .WillOnce(Return( + MockLoadDnsCacheEntryResult{LoadDnsCacheEntryStatus::Loading, handle, absl::nullopt})); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + // Two datagrams will be buffered. + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub2_)); + + EXPECT_CALL(callbacks_, continueFilterChain()); + EXPECT_CALL(callbacks_, injectDatagramToFilterChain(_)).Times(2); + filter_->onLoadDnsCacheComplete( + std::make_shared()); + EXPECT_CALL(*handle, onDestroy()); + EXPECT_FALSE(filter_config_->bufferEnabled()); +} + +TEST_F(DynamicProxyFilterTest, LoadingCacheEntryWithBufferSizeOverflow) { + FilterConfig config; + auto* buffer_options = config.mutable_buffer_options(); + buffer_options->mutable_max_buffered_datagrams()->set_value(1); + setup(config); + + setFilterState("host", 50); + EXPECT_TRUE(filter_config_->bufferEnabled()); + Upstream::ResourceAutoIncDec* circuit_breakers_{ + new Upstream::ResourceAutoIncDec(pending_requests_)}; + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()) + .WillOnce(Return(circuit_breakers_)); + Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle* handle = + new Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle(); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, loadDnsCacheEntry_(Eq("host"), 50, _, _)) + .WillOnce(Return( + MockLoadDnsCacheEntryResult{LoadDnsCacheEntryStatus::Loading, handle, absl::nullopt})); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + // Buffer size is 1, first datagram will be buffered, second will drop. + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub2_)); + + EXPECT_CALL(callbacks_, continueFilterChain()); + EXPECT_CALL(callbacks_, injectDatagramToFilterChain(_)); + filter_->onLoadDnsCacheComplete( + std::make_shared()); + EXPECT_CALL(*handle, onDestroy()); + EXPECT_FALSE(filter_config_->bufferEnabled()); +} + +TEST_F(DynamicProxyFilterTest, LoadingCacheEntryWithBufferBytesOverflow) { + FilterConfig config; + auto* buffer_options = config.mutable_buffer_options(); + buffer_options->mutable_max_buffered_bytes()->set_value(strlen("sometestdata1")); + setup(config); + + setFilterState("host", 50); + EXPECT_TRUE(filter_config_->bufferEnabled()); + Upstream::ResourceAutoIncDec* circuit_breakers_{ + new Upstream::ResourceAutoIncDec(pending_requests_)}; + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()) + .WillOnce(Return(circuit_breakers_)); + Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle* handle = + new Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle(); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, loadDnsCacheEntry_(Eq("host"), 50, _, _)) + .WillOnce(Return( + MockLoadDnsCacheEntryResult{LoadDnsCacheEntryStatus::Loading, handle, absl::nullopt})); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + // Buffer bytes size is 13, first datagram will be buffered, second will drop. + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub2_)); + + EXPECT_CALL(callbacks_, continueFilterChain()); + EXPECT_CALL(callbacks_, injectDatagramToFilterChain(_)); + filter_->onLoadDnsCacheComplete( + std::make_shared()); + EXPECT_CALL(*handle, onDestroy()); + EXPECT_FALSE(filter_config_->bufferEnabled()); +} + +} // namespace +} // namespace DynamicForwardProxy +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/geoip_providers/maxmind/BUILD b/test/extensions/geoip_providers/maxmind/BUILD new file mode 100644 index 000000000000..1757c2c53c8b --- /dev/null +++ b/test/extensions/geoip_providers/maxmind/BUILD @@ -0,0 +1,53 @@ +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", + size = "small", + srcs = select({ + "//bazel:linux": ["config_test.cc"], + "//conditions:default": [], + }), + data = [ + "//test/extensions/geoip_providers/maxmind/test_data:geolocation_databases", + ], + extension_names = ["envoy.geoip_providers.maxmind"], + tags = ["skip_on_windows"], + deps = [ + "//source/extensions/geoip_providers/maxmind:config", + "//source/extensions/geoip_providers/maxmind:provider_impl", + "//test/mocks/server:factory_context_mocks", + "@envoy_api//envoy/extensions/geoip_providers/maxmind/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "geoip_provider_test", + size = "small", + srcs = select({ + "//bazel:linux": ["geoip_provider_test.cc"], + "//conditions:default": [], + }), + data = [ + "//test/extensions/geoip_providers/maxmind/test_data:geolocation_databases", + ], + extension_names = ["envoy.geoip_providers.maxmind"], + tags = ["skip_on_windows"], + deps = [ + "//envoy/registry", + "//source/extensions/geoip_providers/maxmind:config", + "//source/extensions/geoip_providers/maxmind:provider_impl", + "//test/mocks/server:factory_context_mocks", + "@envoy_api//envoy/extensions/geoip_providers/maxmind/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/geoip_providers/maxmind/config_test.cc b/test/extensions/geoip_providers/maxmind/config_test.cc new file mode 100644 index 000000000000..53b40ec948f3 --- /dev/null +++ b/test/extensions/geoip_providers/maxmind/config_test.cc @@ -0,0 +1,359 @@ +#include "envoy/extensions/geoip_providers/maxmind/v3/maxmind.pb.h" +#include "envoy/extensions/geoip_providers/maxmind/v3/maxmind.pb.validate.h" + +#include "source/extensions/geoip_providers/maxmind/config.h" +#include "source/extensions/geoip_providers/maxmind/geoip_provider.h" + +#include "test/mocks/server/factory_context.h" +#include "test/test_common/environment.h" + +#include "absl/strings/str_format.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::AllOf; + +namespace Envoy { +namespace Extensions { +namespace GeoipProviders { +namespace Maxmind { + +using MaxmindProviderConfig = envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig; + +class GeoipProviderPeer { +public: + static const absl::optional& cityDbPath(const GeoipProvider& provider) { + return provider.config_->cityDbPath(); + } + static const absl::optional& ispDbPath(const GeoipProvider& provider) { + return provider.config_->ispDbPath(); + } + static const absl::optional& anonDbPath(const GeoipProvider& provider) { + return provider.config_->anonDbPath(); + } + static const absl::optional& countryHeader(const GeoipProvider& provider) { + return provider.config_->countryHeader(); + } + static const absl::optional& cityHeader(const GeoipProvider& provider) { + return provider.config_->cityHeader(); + } + static const absl::optional& regionHeader(const GeoipProvider& provider) { + return provider.config_->regionHeader(); + } + static const absl::optional& asnHeader(const GeoipProvider& provider) { + return provider.config_->asnHeader(); + } + static const absl::optional& anonVpnHeader(const GeoipProvider& provider) { + return provider.config_->anonVpnHeader(); + } + static const absl::optional& anonTorHeader(const GeoipProvider& provider) { + return provider.config_->anonTorHeader(); + } + static const absl::optional& anonProxyHeader(const GeoipProvider& provider) { + return provider.config_->anonProxyHeader(); + } + static const absl::optional& anonHostingHeader(const GeoipProvider& provider) { + return provider.config_->anonHostingHeader(); + } +}; + +MATCHER_P(HasCityDbPath, expected_db_path, "") { + auto provider = std::static_pointer_cast(arg); + auto city_db_path = GeoipProviderPeer::cityDbPath(*provider); + if (city_db_path && testing::Matches(expected_db_path)(city_db_path.value())) { + return true; + } + *result_listener << "expected city_db_path=" << expected_db_path + << " but city_db_path was not found in provider config"; + return false; +} + +MATCHER_P(HasIspDbPath, expected_db_path, "") { + auto provider = std::static_pointer_cast(arg); + auto isp_db_path = GeoipProviderPeer::ispDbPath(*provider); + if (isp_db_path && testing::Matches(expected_db_path)(isp_db_path.value())) { + return true; + } + *result_listener << "expected isp_db_path=" << expected_db_path + << " but isp_db_path was not found in provider config"; + return false; +} + +MATCHER_P(HasAnonDbPath, expected_db_path, "") { + auto provider = std::static_pointer_cast(arg); + auto anon_db_path = GeoipProviderPeer::anonDbPath(*provider); + if (anon_db_path && testing::Matches(expected_db_path)(anon_db_path.value())) { + return true; + } + *result_listener << "expected anon_db_path=" << expected_db_path + << " but anon_db_path was not found in provider config"; + return false; +} + +MATCHER_P(HasCountryHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto country_header = GeoipProviderPeer::countryHeader(*provider); + if (country_header && testing::Matches(expected_header)(country_header.value())) { + return true; + } + *result_listener << "expected country header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +MATCHER_P(HasCityHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto city_header = GeoipProviderPeer::cityHeader(*provider); + if (city_header && testing::Matches(expected_header)(city_header.value())) { + return true; + } + *result_listener << "expected city header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +MATCHER_P(HasRegionHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto region_header = GeoipProviderPeer::regionHeader(*provider); + if (region_header && testing::Matches(expected_header)(region_header.value())) { + return true; + } + *result_listener << "expected region header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +MATCHER_P(HasAsnHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto asn_header = GeoipProviderPeer::asnHeader(*provider); + if (asn_header && testing::Matches(expected_header)(asn_header.value())) { + return true; + } + *result_listener << "expected asn header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +MATCHER_P(HasAnonVpnHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto anon_vpn_header = GeoipProviderPeer::anonVpnHeader(*provider); + if (anon_vpn_header && testing::Matches(expected_header)(anon_vpn_header.value())) { + return true; + } + *result_listener << "expected anon_vpn header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +MATCHER_P(HasAnonTorHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto anon_tor_header = GeoipProviderPeer::anonTorHeader(*provider); + if (anon_tor_header && testing::Matches(expected_header)(anon_tor_header.value())) { + return true; + } + *result_listener << "expected anon_tor header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +MATCHER_P(HasAnonProxyHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto anon_proxy_header = GeoipProviderPeer::anonProxyHeader(*provider); + if (anon_proxy_header && testing::Matches(expected_header)(anon_proxy_header.value())) { + return true; + } + *result_listener << "expected anon_proxy header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +MATCHER_P(HasAnonHostingHeader, expected_header, "") { + auto provider = std::static_pointer_cast(arg); + auto anon_hosting_header = GeoipProviderPeer::anonHostingHeader(*provider); + if (anon_hosting_header && testing::Matches(expected_header)(anon_hosting_header.value())) { + return true; + } + *result_listener << "expected anon_hosting_header header=" << expected_header + << " but header was not found in provider config with expected value"; + return false; +} + +std::string genGeoDbFilePath(std::string db_name) { + return TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/" + db_name); +} + +TEST(MaxmindProviderConfigTest, EmptyProto) { + MaxmindProviderFactory factory; + EXPECT_TRUE(factory.createEmptyConfigProto() != nullptr); +} + +TEST(MaxmindProviderConfigTest, ProviderConfigWithCorrectProto) { + const auto provider_config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + anon_vpn: "x-anon-vpn" + asn: "x-geo-asn" + is_anon: "x-geo-anon" + anon_vpn: "x-anon-vpn" + anon_tor: "x-anon-tor" + anon_proxy: "x-anon-proxy" + anon_hosting: "x-anon-hosting" + city_db_path: %s + isp_db_path: %s + anon_db_path: %s + )EOF"; + MaxmindProviderConfig provider_config; + auto city_db_path = genGeoDbFilePath("GeoLite2-City-Test.mmdb"); + auto asn_db_path = genGeoDbFilePath("GeoLite2-ASN-Test.mmdb"); + auto anon_db_path = genGeoDbFilePath("GeoIP2-Anonymous-IP-Test.mmdb"); + auto processed_provider_config_yaml = + absl::StrFormat(provider_config_yaml, city_db_path, asn_db_path, anon_db_path); + TestUtility::loadFromYaml(processed_provider_config_yaml, provider_config); + NiceMock context; + EXPECT_CALL(context, messageValidationVisitor()); + MaxmindProviderFactory factory; + Geolocation::DriverSharedPtr driver = + factory.createGeoipProviderDriver(provider_config, "maxmind", context); + EXPECT_THAT(driver, AllOf(HasCityDbPath(city_db_path), HasIspDbPath(asn_db_path), + HasAnonDbPath(anon_db_path), HasCountryHeader("x-geo-country"), + HasCityHeader("x-geo-city"), HasRegionHeader("x-geo-region"), + HasAsnHeader("x-geo-asn"), HasAnonVpnHeader("x-anon-vpn"), + HasAnonTorHeader("x-anon-tor"), HasAnonProxyHeader("x-anon-proxy"), + HasAnonHostingHeader("x-anon-hosting"))); +} + +TEST(MaxmindProviderConfigTest, ProviderConfigWithNoDbPaths) { + std::string provider_config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + )EOF"; + MaxmindProviderConfig provider_config; + TestUtility::loadFromYaml(provider_config_yaml, provider_config); + NiceMock context; + EXPECT_CALL(context, messageValidationVisitor()); + MaxmindProviderFactory factory; + EXPECT_THROW_WITH_MESSAGE(factory.createGeoipProviderDriver(provider_config, "maxmind", context), + Envoy::EnvoyException, + "At least one geolocation database path needs to be configured: " + "city_db_path, isp_db_path or anon_db_path"); +} + +TEST(MaxmindProviderConfigTest, ProviderConfigWithNoGeoHeaders) { + std::string provider_config_yaml = R"EOF( + isp_db_path: "/geoip2/Isp.mmdb" + )EOF"; + MaxmindProviderConfig provider_config; + TestUtility::loadFromYaml(provider_config_yaml, provider_config); + NiceMock context; + EXPECT_CALL(context, messageValidationVisitor()); + MaxmindProviderFactory factory; + EXPECT_THROW_WITH_REGEX(factory.createGeoipProviderDriver(provider_config, "maxmind", context), + ProtoValidationException, + "Proto constraint validation failed.*value is required.*"); +} + +TEST(MaxmindProviderConfigTest, DbPathFormatValidatedWhenNonEmptyValue) { + std::string provider_config_yaml = R"EOF( + isp_db_path: "/geoip2/Isp.exe" + )EOF"; + MaxmindProviderConfig provider_config; + TestUtility::loadFromYaml(provider_config_yaml, provider_config); + NiceMock context; + EXPECT_CALL(context, messageValidationVisitor()); + MaxmindProviderFactory factory; + EXPECT_THROW_WITH_REGEX( + factory.createGeoipProviderDriver(provider_config, "maxmind", context), + ProtoValidationException, + "Proto constraint validation failed.*value does not match regex pattern.*"); +} + +TEST(MaxmindProviderConfigTest, ReusesProviderInstanceForSameProtoConfig) { + const auto provider_config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + city: "x-geo-city" + anon_vpn: "x-anon-vpn" + asn: "x-geo-asn" + anon_tor: "x-anon-tor" + anon_proxy: "x-anon-proxy" + anon_hosting: "x-anon-hosting" + city_db_path: %s + isp_db_path: %s + anon_db_path: %s + )EOF"; + MaxmindProviderConfig provider_config; + auto city_db_path = genGeoDbFilePath("GeoLite2-City-Test.mmdb"); + auto asn_db_path = genGeoDbFilePath("GeoLite2-ASN-Test.mmdb"); + auto anon_db_path = genGeoDbFilePath("GeoIP2-Anonymous-IP-Test.mmdb"); + auto processed_provider_config_yaml = + absl::StrFormat(provider_config_yaml, city_db_path, asn_db_path, anon_db_path); + TestUtility::loadFromYaml(processed_provider_config_yaml, provider_config); + NiceMock context; + EXPECT_CALL(context, messageValidationVisitor()).Times(2); + MaxmindProviderFactory factory; + Geolocation::DriverSharedPtr driver1 = + factory.createGeoipProviderDriver(provider_config, "maxmind", context); + Geolocation::DriverSharedPtr driver2 = + factory.createGeoipProviderDriver(provider_config, "maxmind", context); + EXPECT_EQ(driver1.get(), driver2.get()); +} + +TEST(MaxmindProviderConfigTest, DifferentProviderInstancesForDifferentProtoConfig) { + const auto provider_config_yaml1 = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + city: "x-geo-city" + anon_vpn: "x-anon-vpn" + asn: "x-geo-asn" + anon_tor: "x-anon-tor" + anon_proxy: "x-anon-proxy" + anon_hosting: "x-anon-hosting" + city_db_path: %s + isp_db_path: %s + anon_db_path: %s + )EOF"; + const auto provider_config_yaml2 = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + city: "x-geo-city" + anon_vpn: "x-anon-vpn" + anon_tor: "x-anon-tor" + anon_proxy: "x-anon-proxy" + anon_hosting: "x-anon-hosting" + city_db_path: %s + anon_db_path: %s + )EOF"; + MaxmindProviderConfig provider_config1; + MaxmindProviderConfig provider_config2; + auto city_db_path = genGeoDbFilePath("GeoLite2-City-Test.mmdb"); + auto asn_db_path = genGeoDbFilePath("GeoLite2-ASN-Test.mmdb"); + auto anon_db_path = genGeoDbFilePath("GeoIP2-Anonymous-IP-Test.mmdb"); + auto processed_provider_config_yaml1 = + absl::StrFormat(provider_config_yaml1, city_db_path, asn_db_path, anon_db_path); + auto processed_provider_config_yaml2 = + absl::StrFormat(provider_config_yaml2, city_db_path, anon_db_path); + TestUtility::loadFromYaml(processed_provider_config_yaml1, provider_config1); + TestUtility::loadFromYaml(processed_provider_config_yaml2, provider_config2); + NiceMock context; + EXPECT_CALL(context, messageValidationVisitor()).Times(2); + MaxmindProviderFactory factory; + Geolocation::DriverSharedPtr driver1 = + factory.createGeoipProviderDriver(provider_config1, "maxmind", context); + Geolocation::DriverSharedPtr driver2 = + factory.createGeoipProviderDriver(provider_config2, "maxmind", context); + EXPECT_NE(driver1.get(), driver2.get()); +} + +} // namespace Maxmind +} // namespace GeoipProviders +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc b/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc new file mode 100644 index 000000000000..75806181868f --- /dev/null +++ b/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc @@ -0,0 +1,294 @@ +#include "envoy/extensions/geoip_providers/maxmind/v3/maxmind.pb.h" +#include "envoy/registry/registry.h" + +#include "source/common/network/address_impl.h" +#include "source/common/network/utility.h" +#include "source/extensions/geoip_providers/maxmind/config.h" +#include "source/extensions/geoip_providers/maxmind/geoip_provider.h" + +#include "test/mocks/server/factory_context.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::ReturnRef; +using testing::SaveArg; + +namespace Envoy { +namespace Extensions { +namespace GeoipProviders { +namespace Maxmind { + +class GeoipProviderTest : public testing::Test { +public: + GeoipProviderTest() { + provider_factory_ = dynamic_cast( + Registry::FactoryRegistry::getFactory( + "envoy.geoip_providers.maxmind")); + ASSERT(provider_factory_); + } + + void initializeProvider(const std::string& yaml) { + EXPECT_CALL(context_, scope()).WillRepeatedly(ReturnRef(scope_)); + envoy::extensions::geoip_providers::maxmind::v3::MaxMindConfig config; + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), config); + provider_ = provider_factory_->createGeoipProviderDriver(config, "prefix.", context_); + } + + void expectStats(const std::string& db_type, const uint32_t total_times = 1, + const uint32_t hit_times = 1, const uint32_t error_times = 0) { + EXPECT_CALL(stats_, counter(absl::StrCat("prefix.maxmind.", db_type, ".total"))) + .Times(total_times); + EXPECT_CALL(stats_, counter(absl::StrCat("prefix.maxmind.", db_type, ".hit"))).Times(hit_times); + EXPECT_CALL(stats_, counter(absl::StrCat("prefix.maxmind.", db_type, ".lookup_error"))) + .Times(error_times); + } + + NiceMock stats_; + Stats::MockScope& scope_{stats_.mockScope()}; + NiceMock context_; + DriverSharedPtr provider_; + MaxmindProviderFactory* provider_factory_; + absl::flat_hash_map captured_lookup_response_; +}; + +TEST_F(GeoipProviderTest, ValidConfigCityAndIspDbsSuccessfulLookup) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + asn: "x-geo-asn" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + isp_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("78.26.243.166"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("city_db"); + expectStats("isp_db"); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(4, captured_lookup_response_.size()); + const auto& city_it = captured_lookup_response_.find("x-geo-city"); + EXPECT_EQ("Boxford", city_it->second); + const auto& region_it = captured_lookup_response_.find("x-geo-region"); + EXPECT_EQ("England", region_it->second); + const auto& country_it = captured_lookup_response_.find("x-geo-country"); + EXPECT_EQ("United Kingdom", country_it->second); + const auto& asn_it = captured_lookup_response_.find("x-geo-asn"); + EXPECT_EQ("15169", asn_it->second); +} + +TEST_F(GeoipProviderTest, ValidConfigCityLookupError) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + city: "x-geo-city" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/MaxMind-DB-test-ipv4-24.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("2345:0425:2CA1:0:0:0567:5673:23b5"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("city_db", 1, 0, 1); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(0, captured_lookup_response_.size()); +} + +// Tests for anonymous database replicate expectations from corresponding Maxmind tests: +// https://github.com/maxmind/GeoIP2-perl/blob/main/t/GeoIP2/Database/Reader-Anonymous-IP.t +TEST_F(GeoipProviderTest, ValidConfigAnonVpnSuccessfulLookup) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + is_anon: "x-geo-anon" + anon_vpn: "x-geo-anon-vpn" + anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("1.2.0.0"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("anon_db"); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(2, captured_lookup_response_.size()); + const auto& anon_it = captured_lookup_response_.find("x-geo-anon"); + EXPECT_EQ("true", anon_it->second); + const auto& anon_vpn_it = captured_lookup_response_.find("x-geo-anon-vpn"); + EXPECT_EQ("true", anon_vpn_it->second); +} + +TEST_F(GeoipProviderTest, ValidConfigAnonHostingSuccessfulLookup) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + is_anon: "x-geo-anon" + anon_hosting: "x-geo-anon-hosting" + anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("71.160.223.45"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("anon_db"); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(2, captured_lookup_response_.size()); + const auto& anon_it = captured_lookup_response_.find("x-geo-anon"); + EXPECT_EQ("true", anon_it->second); + const auto& anon_hosting_it = captured_lookup_response_.find("x-geo-anon-hosting"); + EXPECT_EQ("true", anon_hosting_it->second); +} + +TEST_F(GeoipProviderTest, ValidConfigAnonTorNodeSuccessfulLookup) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + is_anon: "x-geo-anon" + anon_tor: "x-geo-anon-tor" + anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("65.4.3.2"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("anon_db"); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(2, captured_lookup_response_.size()); + const auto& anon_it = captured_lookup_response_.find("x-geo-anon"); + EXPECT_EQ("true", anon_it->second); + const auto& anon_tor_it = captured_lookup_response_.find("x-geo-anon-tor"); + EXPECT_EQ("true", anon_tor_it->second); +} + +TEST_F(GeoipProviderTest, ValidConfigAnonProxySuccessfulLookup) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + is_anon: "x-geo-anon" + anon_proxy: "x-geo-anon-proxy" + anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("abcd:1000::1"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("anon_db"); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(2, captured_lookup_response_.size()); + const auto& anon_it = captured_lookup_response_.find("x-geo-anon"); + EXPECT_EQ("true", anon_it->second); + const auto& anon_tor_it = captured_lookup_response_.find("x-geo-anon-proxy"); + EXPECT_EQ("true", anon_tor_it->second); +} + +TEST_F(GeoipProviderTest, ValidConfigEmptyLookupResult) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + is_anon: "x-geo-anon" + anon_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("10.10.10.10"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("anon_db", 1, 0); + provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)); + EXPECT_EQ(0, captured_lookup_response_.size()); +} + +TEST_F(GeoipProviderTest, ValidConfigCityMultipleLookups) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + country: "x-geo-country" + region: "x-geo-region" + city: "x-geo-city" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address1 = + Network::Utility::parseInternetAddress("78.26.243.166"); + Geolocation::LookupRequest lookup_rq1{std::move(remote_address1)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + expectStats("city_db", 2, 2); + provider_->lookup(std::move(lookup_rq1), std::move(lookup_cb_std)); + EXPECT_EQ(3, captured_lookup_response_.size()); + // Another lookup request. + Network::Address::InstanceConstSharedPtr remote_address2 = + Network::Utility::parseInternetAddress("63.25.243.11"); + Geolocation::LookupRequest lookup_rq2{std::move(remote_address2)}; + testing::MockFunction lookup_cb2; + auto lookup_cb_std2 = lookup_cb2.AsStdFunction(); + EXPECT_CALL(lookup_cb2, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + provider_->lookup(std::move(lookup_rq2), std::move(lookup_cb_std2)); + EXPECT_EQ(3, captured_lookup_response_.size()); +} + +using GeoipProviderDeathTest = GeoipProviderTest; + +TEST_F(GeoipProviderDeathTest, GeoDbNotSetForConfiguredHeader) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + city: "x-geo-city" + asn: "x-geo-asn" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb" + )EOF"; + initializeProvider(config_yaml); + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddress("78.26.243.166"); + Geolocation::LookupRequest lookup_rq{std::move(remote_address)}; + testing::MockFunction lookup_cb; + auto lookup_cb_std = lookup_cb.AsStdFunction(); + EXPECT_CALL(lookup_cb, Call(_)).WillRepeatedly(SaveArg<0>(&captured_lookup_response_)); + EXPECT_DEATH(provider_->lookup(std::move(lookup_rq), std::move(lookup_cb_std)), + "assert failure: isp_db_. Details: Maxmind asn database is not initialized for " + "performing lookups"); +} + +TEST_F(GeoipProviderDeathTest, GeoDbPathDoesNotExist) { + const std::string config_yaml = R"EOF( + common_provider_config: + geo_headers_to_add: + city: "x-geo-city" + city_db_path: "{{ test_rundir }}/test/extensions/geoip_providers/maxmind/test_data_atc/GeoLite2-City-Test.mmdb" + )EOF"; + EXPECT_DEATH(initializeProvider(config_yaml), ".*Unable to open Maxmind database file.*"); +} + +} // namespace Maxmind +} // namespace GeoipProviders +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/geoip_providers/maxmind/test_data/BUILD b/test/extensions/geoip_providers/maxmind/test_data/BUILD new file mode 100644 index 000000000000..7323b7eab2ac --- /dev/null +++ b/test/extensions/geoip_providers/maxmind/test_data/BUILD @@ -0,0 +1,15 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +# Copies of certain lighweight testing Maxmind databases are included into this filegroup: +# https://github.com/maxmind/MaxMind-DB/tree/main/test-data +filegroup( + name = "geolocation_databases", + srcs = glob(["*.mmdb"]), +) diff --git a/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb b/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb new file mode 100644 index 000000000000..ad9895c20de9 Binary files /dev/null and b/test/extensions/geoip_providers/maxmind/test_data/GeoIP2-Anonymous-IP-Test.mmdb differ diff --git a/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb new file mode 100644 index 000000000000..bc31924dc151 Binary files /dev/null and b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-ASN-Test.mmdb differ diff --git a/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb new file mode 100644 index 000000000000..f391123b82e1 Binary files /dev/null and b/test/extensions/geoip_providers/maxmind/test_data/GeoLite2-City-Test.mmdb differ diff --git a/test/extensions/geoip_providers/maxmind/test_data/MaxMind-DB-test-ipv4-24.mmdb b/test/extensions/geoip_providers/maxmind/test_data/MaxMind-DB-test-ipv4-24.mmdb new file mode 100644 index 000000000000..71ff41e91dd3 Binary files /dev/null and b/test/extensions/geoip_providers/maxmind/test_data/MaxMind-DB-test-ipv4-24.mmdb differ diff --git a/test/extensions/load_balancing_policies/common/BUILD b/test/extensions/load_balancing_policies/common/BUILD new file mode 100644 index 000000000000..eec0c0100900 --- /dev/null +++ b/test/extensions/load_balancing_policies/common/BUILD @@ -0,0 +1,25 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test_library( + name = "benchmark_base_tester_lib", + srcs = ["benchmark_base_tester.cc"], + hdrs = ["benchmark_base_tester.h"], + deps = [ + "//source/common/common:random_generator_lib", + "//source/common/memory:stats_lib", + "//source/common/upstream:upstream_lib", + "//test/common/upstream:utility_lib", + "//test/mocks/upstream:cluster_info_mocks", + "//test/test_common:printers_lib", + "//test/test_common:simulated_time_system_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/load_balancing_policies/common/benchmark_base_tester.cc b/test/extensions/load_balancing_policies/common/benchmark_base_tester.cc new file mode 100644 index 000000000000..3cc30ae2eb69 --- /dev/null +++ b/test/extensions/load_balancing_policies/common/benchmark_base_tester.cc @@ -0,0 +1,44 @@ +#include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Common { + +BaseTester::BaseTester(uint64_t num_hosts, uint32_t weighted_subset_percent, uint32_t weight, + bool attach_metadata) { + Upstream::HostVector hosts; + ASSERT(num_hosts < 65536); + for (uint64_t i = 0; i < num_hosts; i++) { + const bool should_weight = i < num_hosts * (weighted_subset_percent / 100.0); + const std::string url = fmt::format("tcp://10.0.{}.{}:6379", i / 256, i % 256); + const auto effective_weight = should_weight ? weight : 1; + if (attach_metadata) { + envoy::config::core::v3::Metadata metadata; + ProtobufWkt::Value value; + value.set_number_value(i); + ProtobufWkt::Struct& map = + (*metadata.mutable_filter_metadata())[Config::MetadataFilters::get().ENVOY_LB]; + (*map.mutable_fields())[std::string(metadata_key)] = value; + + hosts.push_back(Upstream::makeTestHost(info_, url, metadata, simTime(), effective_weight)); + } else { + hosts.push_back(Upstream::makeTestHost(info_, url, simTime(), effective_weight)); + } + } + + Upstream::HostVectorConstSharedPtr updated_hosts = std::make_shared(hosts); + Upstream::HostsPerLocalityConstSharedPtr hosts_per_locality = + Upstream::makeHostsPerLocality({hosts}); + priority_set_.updateHosts( + 0, Upstream::HostSetImpl::partitionHosts(updated_hosts, hosts_per_locality), {}, hosts, {}, + absl::nullopt); + local_priority_set_.updateHosts( + 0, Upstream::HostSetImpl::partitionHosts(updated_hosts, hosts_per_locality), {}, hosts, {}, + absl::nullopt); +} + +} // namespace Common +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/common/benchmark_base_tester.h b/test/extensions/load_balancing_policies/common/benchmark_base_tester.h new file mode 100644 index 000000000000..229f4832aec2 --- /dev/null +++ b/test/extensions/load_balancing_policies/common/benchmark_base_tester.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "envoy/config/cluster/v3/cluster.pb.h" + +#include "source/common/common/random_generator.h" +#include "source/common/memory/stats.h" +#include "source/common/upstream/upstream_impl.h" + +#include "test/common/upstream/utility.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/test_common/simulated_time_system.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Common { + +class BaseTester : public Event::TestUsingSimulatedTime { +public: + static constexpr absl::string_view metadata_key = "key"; + // We weight the first weighted_subset_percent of hosts with weight. + BaseTester(uint64_t num_hosts, uint32_t weighted_subset_percent = 0, uint32_t weight = 0, + bool attach_metadata = false); + + Envoy::Thread::MutexBasicLockable lock_; + // Reduce default log level to warn while running this benchmark to avoid problems due to + // excessive debug logging in upstream_impl.cc + Envoy::Logger::Context logging_context_{spdlog::level::warn, + Envoy::Logger::Logger::DEFAULT_LOG_FORMAT, lock_, false}; + + Upstream::PrioritySetImpl priority_set_; + Upstream::PrioritySetImpl local_priority_set_; + + // The following are needed to create a load balancer by the load balancer factory. + Upstream::LoadBalancerParams lb_params_{priority_set_, &local_priority_set_}; + + Stats::IsolatedStoreImpl stats_store_; + Stats::Scope& stats_scope_{*stats_store_.rootScope()}; + Upstream::ClusterLbStatNames stat_names_{stats_store_.symbolTable()}; + Upstream::ClusterLbStats stats_{stat_names_, stats_scope_}; + NiceMock runtime_; + Random::RandomGeneratorImpl random_; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + envoy::config::cluster::v3::Cluster::RoundRobinLbConfig round_robin_lb_config_; + std::shared_ptr info_{new NiceMock()}; +}; + +} // namespace Common +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/subset/BUILD b/test/extensions/load_balancing_policies/subset/BUILD index 417a9affab11..22a1130b3faf 100644 --- a/test/extensions/load_balancing_policies/subset/BUILD +++ b/test/extensions/load_balancing_policies/subset/BUILD @@ -4,6 +4,8 @@ load( ) load( "//test/extensions:extensions_build_system.bzl", + "envoy_extension_benchmark_test", + "envoy_extension_cc_benchmark_binary", "envoy_extension_cc_test", ) @@ -47,13 +49,46 @@ envoy_extension_cc_test( srcs = ["subset_test.cc"], extension_names = ["envoy.load_balancing_policies.subset"], deps = [ - "//source/extensions/load_balancing_policies/random:config", + "//source/common/common:minimal_logger_lib", + "//source/common/network:utility_lib", + "//source/common/upstream:load_balancer_lib", + "//source/common/upstream:upstream_includes", + "//source/common/upstream:upstream_lib", "//source/extensions/load_balancing_policies/subset:config", - "//test/mocks/server:factory_context_mocks", + "//test/common/upstream:utility_lib", + "//test/mocks:common_lib", + "//test/mocks/access_log:access_log_mocks", + "//test/mocks/filesystem:filesystem_mocks", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:host_mocks", + "//test/mocks/upstream:host_set_mocks", "//test/mocks/upstream:load_balancer_context_mock", + "//test/mocks/upstream:load_balancer_mocks", "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:simulated_time_system_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/load_balancing_policies/subset/v3:pkg_cc_proto", ], ) + +envoy_extension_cc_benchmark_binary( + name = "subset_benchmark", + srcs = ["subset_benchmark.cc"], + extension_names = ["envoy.load_balancing_policies.subset"], + external_deps = [ + "benchmark", + ], + deps = [ + "//source/extensions/load_balancing_policies/subset:config", + "//test/extensions/load_balancing_policies/common:benchmark_base_tester_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + ], +) + +envoy_extension_benchmark_test( + name = "subset_benchmark_test", + timeout = "long", + benchmark_binary = "subset_benchmark", + extension_names = ["envoy.load_balancing_policies.subset"], +) diff --git a/test/extensions/load_balancing_policies/subset/subset_benchmark.cc b/test/extensions/load_balancing_policies/subset/subset_benchmark.cc new file mode 100644 index 000000000000..d775c8851e33 --- /dev/null +++ b/test/extensions/load_balancing_policies/subset/subset_benchmark.cc @@ -0,0 +1,116 @@ +// Usage: bazel run //test/common/upstream:load_balancer_benchmark + +#include + +#include "envoy/config/cluster/v3/cluster.pb.h" + +#include "source/common/common/random_generator.h" +#include "source/common/memory/stats.h" +#include "source/common/upstream/upstream_impl.h" +#include "source/extensions/load_balancing_policies/maglev/maglev_lb.h" +#include "source/extensions/load_balancing_policies/ring_hash/ring_hash_lb.h" +#include "source/extensions/load_balancing_policies/subset/subset_lb.h" + +#include "test/benchmark/main.h" +#include "test/common/upstream/utility.h" +#include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/test_common/simulated_time_system.h" + +#include "absl/types/optional.h" +#include "benchmark/benchmark.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Subset { +namespace { + +class SubsetLbTester : public LoadBalancingPolices::Common::BaseTester { +public: + SubsetLbTester(uint64_t num_hosts, bool single_host_per_subset) + : BaseTester(num_hosts, 0, 0, true /* attach metadata */) { + envoy::config::cluster::v3::Cluster::LbSubsetConfig subset_config; + subset_config.set_fallback_policy( + envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT); + auto* selector = subset_config.mutable_subset_selectors()->Add(); + selector->set_single_host_per_subset(single_host_per_subset); + *selector->mutable_keys()->Add() = std::string(metadata_key); + + subset_info_ = std::make_unique(subset_config); + auto child_lb_creator = std::make_unique( + Upstream::LoadBalancerType::Random, absl::nullopt, absl::nullopt, absl::nullopt, + absl::nullopt, common_config_); + lb_ = std::make_unique( + *subset_info_, std::move(child_lb_creator), priority_set_, &local_priority_set_, stats_, + stats_scope_, runtime_, random_, simTime()); + + const Upstream::HostVector& hosts = priority_set_.getOrCreateHostSet(0).hosts(); + ASSERT(hosts.size() == num_hosts); + orig_hosts_ = std::make_shared(hosts); + smaller_hosts_ = std::make_shared(hosts.begin() + 1, hosts.end()); + ASSERT(smaller_hosts_->size() + 1 == orig_hosts_->size()); + orig_locality_hosts_ = Upstream::makeHostsPerLocality({*orig_hosts_}); + smaller_locality_hosts_ = Upstream::makeHostsPerLocality({*smaller_hosts_}); + } + + // Remove a host and add it back. + void update() { + priority_set_.updateHosts( + 0, Upstream::HostSetImpl::partitionHosts(smaller_hosts_, smaller_locality_hosts_), nullptr, + {}, host_moved_, absl::nullopt); + priority_set_.updateHosts( + 0, Upstream::HostSetImpl::partitionHosts(orig_hosts_, orig_locality_hosts_), nullptr, + host_moved_, {}, absl::nullopt); + } + + std::unique_ptr subset_info_; + std::unique_ptr lb_; + Upstream::HostVectorConstSharedPtr orig_hosts_; + Upstream::HostVectorConstSharedPtr smaller_hosts_; + Upstream::HostsPerLocalitySharedPtr orig_locality_hosts_; + Upstream::HostsPerLocalitySharedPtr smaller_locality_hosts_; + Upstream::HostVector host_moved_; +}; + +void benchmarkSubsetLoadBalancerCreate(::benchmark::State& state) { + const bool single_host_per_subset = state.range(0); + const uint64_t num_hosts = state.range(1); + + if (benchmark::skipExpensiveBenchmarks() && num_hosts > 100) { + state.SkipWithError("Skipping expensive benchmark"); + return; + } + + for (auto _ : state) { // NOLINT: Silences warning about dead store + SubsetLbTester tester(num_hosts, single_host_per_subset); + } +} + +BENCHMARK(benchmarkSubsetLoadBalancerCreate) + ->Ranges({{false, true}, {50, 2500}}) + ->Unit(::benchmark::kMillisecond); + +void benchmarkSubsetLoadBalancerUpdate(::benchmark::State& state) { + const bool single_host_per_subset = state.range(0); + const uint64_t num_hosts = state.range(1); + if (benchmark::skipExpensiveBenchmarks() && num_hosts > 100) { + state.SkipWithError("Skipping expensive benchmark"); + return; + } + + SubsetLbTester tester(num_hosts, single_host_per_subset); + for (auto _ : state) { // NOLINT: Silences warning about dead store + tester.update(); + } +} + +BENCHMARK(benchmarkSubsetLoadBalancerUpdate) + ->Ranges({{false, true}, {50, 2500}}) + ->Unit(::benchmark::kMillisecond); + +} // namespace +} // namespace Subset +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/subset/subset_test.cc b/test/extensions/load_balancing_policies/subset/subset_test.cc index c91df65bb5ca..032fc45b5662 100644 --- a/test/extensions/load_balancing_policies/subset/subset_test.cc +++ b/test/extensions/load_balancing_policies/subset/subset_test.cc @@ -1,21 +1,3198 @@ -#include "envoy/config/core/v3/extension.pb.h" -#include "envoy/extensions/load_balancing_policies/subset/v3/subset.pb.h" +#include +#include +#include +#include +#include +#include +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/core/v3/base.pb.h" + +#include "source/common/common/logger.h" +#include "source/common/config/metadata.h" #include "source/common/router/metadatamatchcriteria_impl.h" +#include "source/common/upstream/upstream_impl.h" #include "source/extensions/load_balancing_policies/subset/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/common/upstream/utility.h" +#include "test/mocks/access_log/mocks.h" +#include "test/mocks/common.h" +#include "test/mocks/filesystem/mocks.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/host.h" +#include "test/mocks/upstream/host_set.h" +#include "test/mocks/upstream/load_balancer.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/mocks/upstream/priority_set.h" +#include "test/test_common/simulated_time_system.h" +#include "absl/types/optional.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + namespace Envoy { -namespace Extensions { -namespace LoadBalancingPolices { -namespace Subset { -namespace { +namespace Upstream { + +class SubsetLoadBalancerInternalStateTester { +public: + SubsetLoadBalancerInternalStateTester(std::shared_ptr lb) : lb_(lb) {} + + using MetadataVector = std::vector>; + + void testDescribeMetadata(std::string expected, const MetadataVector& metadata) { + const SubsetLoadBalancer::SubsetMetadata& subset_metadata(metadata); + EXPECT_EQ(expected, lb_.get()->describeMetadata(subset_metadata)); + } + + void validateLbTypeConfigs() const { + const auto* legacy_child_lb_creator = + dynamic_cast(lb_->child_lb_creator_.get()); + + if (legacy_child_lb_creator == nullptr) { + return; + } + + // Each of these expects that an lb_type is set to that type iff the + // returned config for that type exists. + EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::RingHash, + legacy_child_lb_creator->lbRingHashConfig() != absl::nullopt); + EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::Maglev, + legacy_child_lb_creator->lbMaglevConfig() != absl::nullopt); + EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::RoundRobin, + legacy_child_lb_creator->lbRoundRobinConfig() != absl::nullopt); + EXPECT_EQ(legacy_child_lb_creator->lbType() == LoadBalancerType::LeastRequest, + legacy_child_lb_creator->lbLeastRequestConfig() != absl::nullopt); + } + +private: + std::shared_ptr lb_; +}; + +class TestMetadataMatchCriterion : public Router::MetadataMatchCriterion { +public: + TestMetadataMatchCriterion(const std::string& name, const HashedValue& value) + : name_(name), value_(value) {} + + const std::string& name() const override { return name_; } + const HashedValue& value() const override { return value_; } + +private: + std::string name_; + HashedValue value_; +}; + +class TestMetadataMatchCriteria : public Router::MetadataMatchCriteria { +public: + TestMetadataMatchCriteria(const std::map matches) { + for (const auto& it : matches) { + ProtobufWkt::Value v; + v.set_string_value(it.second); + + matches_.emplace_back( + std::make_shared(it.first, HashedValue(v))); + } + } + + TestMetadataMatchCriteria(const std::map matches) { + for (const auto& it : matches) { + matches_.emplace_back( + std::make_shared(it.first, HashedValue(it.second))); + } + } + + const std::vector& + metadataMatchCriteria() const override { + return matches_; + } + + Router::MetadataMatchCriteriaConstPtr + mergeMatchCriteria(const ProtobufWkt::Struct& override) const override { + auto new_criteria = std::make_unique(*this); + + // TODO: this is copied from MetadataMatchCriteriaImpl::extractMetadataMatchCriteria. + // should we start using real impl? + std::vector v; + absl::node_hash_map existing; + + for (const auto& it : matches_) { + existing.emplace(it->name(), v.size()); + v.emplace_back(it); + } + + // Add values from matches, replacing name/values copied from parent. + for (const auto& it : override.fields()) { + const auto index_it = existing.find(it.first); + if (index_it != existing.end()) { + v[index_it->second] = std::make_shared(it.first, it.second); + } else { + v.emplace_back(std::make_shared(it.first, it.second)); + } + } + std::sort(v.begin(), v.end(), + [](const Router::MetadataMatchCriterionConstSharedPtr& a, + const Router::MetadataMatchCriterionConstSharedPtr& b) -> bool { + return a->name() < b->name(); + }); + + new_criteria->matches_ = v; + return new_criteria; + } + + Router::MetadataMatchCriteriaConstPtr + filterMatchCriteria(const std::set& names) const override { + auto new_criteria = std::make_unique(*this); + for (auto it = new_criteria->matches_.begin(); it != new_criteria->matches_.end();) { + if (names.count(it->get()->name()) == 0) { + it = new_criteria->matches_.erase(it); + } else { + it++; + } + } + return new_criteria; + } + +private: + std::vector matches_; +}; + +namespace SubsetLoadBalancerTest { + +class TestLoadBalancerContext : public LoadBalancerContextBase { +public: + TestLoadBalancerContext( + std::initializer_list::value_type> metadata_matches) + : matches_( + new TestMetadataMatchCriteria(std::map(metadata_matches))) {} + + TestLoadBalancerContext( + std::initializer_list::value_type> metadata_matches) + : matches_(new TestMetadataMatchCriteria( + std::map(metadata_matches))) {} + + // Upstream::LoadBalancerContext + absl::optional computeHashKey() override { return {}; } + const Network::Connection* downstreamConnection() const override { return nullptr; } + const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return matches_.get(); } + const Http::RequestHeaderMap* downstreamHeaders() const override { return nullptr; } + + std::shared_ptr matches_; +}; + +enum class UpdateOrder { RemovesFirst, Simultaneous }; + +class SubsetLoadBalancerTest : public Event::TestUsingSimulatedTime, + public testing::TestWithParam { +public: + SubsetLoadBalancerTest() + : scope_(stats_store_.createScope("testprefix")), stat_names_(stats_store_.symbolTable()), + stats_(stat_names_, *stats_store_.rootScope()) { + least_request_lb_config_.mutable_choice_count()->set_value(2); + } + + using HostMetadata = std::map; + using HostListMetadata = std::map>; + using HostURLMetadataMap = std::map; + + void init() { + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, + }); + } + + void configureHostSet(const HostURLMetadataMap& host_metadata, MockHostSet& host_set) { + HostVector hosts; + for (const auto& it : host_metadata) { + hosts.emplace_back(makeHost(it.first, it.second)); + } + + host_set.hosts_ = hosts; + host_set.hosts_per_locality_ = makeHostsPerLocality({hosts}); + host_set.healthy_hosts_ = host_set.hosts_; + host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; + + host_set.runCallbacks({}, {}); + } + + void configureWeightedHostSet(const HostURLMetadataMap& first_locality_host_metadata, + const HostURLMetadataMap& second_locality_host_metadata, + MockHostSet& host_set, LocalityWeights locality_weights) { + HostVector all_hosts; + HostVector first_locality_hosts; + envoy::config::core::v3::Locality first_locality; + first_locality.set_zone("0"); + for (const auto& it : first_locality_host_metadata) { + auto host = makeHost(it.first, it.second, first_locality); + first_locality_hosts.emplace_back(host); + all_hosts.emplace_back(host); + } + + envoy::config::core::v3::Locality second_locality; + second_locality.set_zone("1"); + HostVector second_locality_hosts; + for (const auto& it : second_locality_host_metadata) { + auto host = makeHost(it.first, it.second, second_locality); + second_locality_hosts.emplace_back(host); + all_hosts.emplace_back(host); + } + + host_set.hosts_ = all_hosts; + host_set.hosts_per_locality_ = + makeHostsPerLocality({first_locality_hosts, second_locality_hosts}); + host_set.healthy_hosts_ = host_set.hosts_; + host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; + host_set.locality_weights_ = std::make_shared(locality_weights); + } + + void init(const HostURLMetadataMap& host_metadata) { + HostURLMetadataMap failover; + init(host_metadata, failover); + } + + void init(const HostURLMetadataMap& host_metadata, + const HostURLMetadataMap& failover_host_metadata, bool use_actual_subset_info = false) { + + if (!use_actual_subset_info) { + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + } + + configureHostSet(host_metadata, host_set_); + if (!failover_host_metadata.empty()) { + configureHostSet(failover_host_metadata, *priority_set_.getMockHostSet(1)); + } + + auto child_lb_creator = std::make_unique( + lb_type_, + lb_type_ == LoadBalancerType::RingHash + ? makeOptRef( + ring_hash_lb_config_) + : absl::nullopt, + lb_type_ == LoadBalancerType::Maglev + ? makeOptRef( + maglev_lb_config_) + : absl::nullopt, + lb_type_ == LoadBalancerType::RoundRobin + ? makeOptRef( + round_robin_lb_config_) + : absl::nullopt, + lb_type_ == LoadBalancerType::LeastRequest + ? makeOptRef( + least_request_lb_config_) + : absl::nullopt, + common_config_); + + lb_ = std::make_shared( + use_actual_subset_info ? static_cast(*actual_subset_info_) + : static_cast(subset_info_), + std::move(child_lb_creator), priority_set_, nullptr, stats_, *scope_, runtime_, random_, + simTime()); + } + + void zoneAwareInit(const std::vector& host_metadata_per_locality, + const std::vector& local_host_metadata_per_locality) { + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + + std::vector> localities; + for (uint32_t i = 0; i < 10; ++i) { + envoy::config::core::v3::Locality locality; + locality.set_zone(std::to_string(i)); + localities.emplace_back(std::make_shared(locality)); + } + ASSERT(host_metadata_per_locality.size() <= localities.size()); + ASSERT(local_host_metadata_per_locality.size() <= localities.size()); + + HostVector hosts; + std::vector hosts_per_locality; + for (uint32_t i = 0; i < host_metadata_per_locality.size(); ++i) { + const auto& host_metadata = host_metadata_per_locality[i]; + HostVector locality_hosts; + for (const auto& host_entry : host_metadata) { + HostSharedPtr host = makeHost(host_entry.first, host_entry.second, *localities[i]); + hosts.emplace_back(host); + locality_hosts.emplace_back(host); + } + hosts_per_locality.emplace_back(locality_hosts); + } + + host_set_.hosts_ = hosts; + host_set_.hosts_per_locality_ = makeHostsPerLocality(std::move(hosts_per_locality)); + + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.healthy_hosts_per_locality_ = host_set_.hosts_per_locality_; + + local_hosts_ = std::make_shared(); + std::vector local_hosts_per_locality_vector; + for (uint32_t i = 0; i < local_host_metadata_per_locality.size(); ++i) { + const auto& local_host_metadata = local_host_metadata_per_locality[i]; + HostVector local_locality_hosts; + for (const auto& host_entry : local_host_metadata) { + HostSharedPtr host = makeHost(host_entry.first, host_entry.second, *localities[i]); + local_hosts_->emplace_back(host); + local_locality_hosts.emplace_back(host); + } + local_hosts_per_locality_vector.emplace_back(local_locality_hosts); + } + local_hosts_per_locality_ = makeHostsPerLocality(std::move(local_hosts_per_locality_vector)); + + local_priority_set_.updateHosts( + 0, + HostSetImpl::updateHostsParams( + local_hosts_, local_hosts_per_locality_, + std::make_shared(*local_hosts_), local_hosts_per_locality_, + std::make_shared(), HostsPerLocalityImpl::empty(), + std::make_shared(), HostsPerLocalityImpl::empty()), + {}, {}, {}, absl::nullopt); + + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + + lb_ = std::make_shared(subset_info_, std::move(child_lb_creator), + priority_set_, &local_priority_set_, stats_, *scope_, + runtime_, random_, simTime()); + } + + HostSharedPtr makeHost(const std::string& url, const HostMetadata& metadata) { + envoy::config::core::v3::Metadata m; + for (const auto& m_it : metadata) { + Config::Metadata::mutableMetadataValue(m, Config::MetadataFilters::get().ENVOY_LB, m_it.first) + .set_string_value(m_it.second); + } + + return makeTestHost(info_, url, m, simTime()); + } + + HostSharedPtr makeHost(const std::string& url, const HostMetadata& metadata, + const envoy::config::core::v3::Locality& locality) { + envoy::config::core::v3::Metadata m; + for (const auto& m_it : metadata) { + Config::Metadata::mutableMetadataValue(m, Config::MetadataFilters::get().ENVOY_LB, m_it.first) + .set_string_value(m_it.second); + } + + return makeTestHost(info_, url, m, locality, simTime()); + } + + HostSharedPtr makeHost(const std::string& url, const HostListMetadata& metadata) { + envoy::config::core::v3::Metadata m; + for (const auto& m_it : metadata) { + auto& metadata = Config::Metadata::mutableMetadataValue( + m, Config::MetadataFilters::get().ENVOY_LB, m_it.first); + for (const auto& value : m_it.second) { + metadata.mutable_list_value()->add_values()->set_string_value(value); + } + } + + return makeTestHost(info_, url, m, simTime()); + } + + ProtobufWkt::Struct makeDefaultSubset(HostMetadata metadata) { + ProtobufWkt::Struct default_subset; + + auto* fields = default_subset.mutable_fields(); + for (const auto& it : metadata) { + ProtobufWkt::Value v; + v.set_string_value(it.second); + fields->insert({it.first, v}); + } + + return default_subset; + } + + SubsetSelectorPtr + makeSelector(const std::set& selector_keys, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector:: + LbSubsetSelectorFallbackPolicy fallback_policy, + const std::set& fallback_keys_subset, + bool single_host_per_subset = false) { + Protobuf::RepeatedPtrField selector_keys_mapped; + for (const auto& it : selector_keys) { + selector_keys_mapped.Add(std::string(it)); + } + + Protobuf::RepeatedPtrField fallback_keys_subset_mapped; + for (const auto& it : fallback_keys_subset) { + fallback_keys_subset_mapped.Add(std::string(it)); + } + + return std::make_shared( + selector_keys_mapped, fallback_policy, fallback_keys_subset_mapped, single_host_per_subset); + } + + SubsetSelectorPtr makeSelector( + const std::set& selector_keys, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector:: + LbSubsetSelectorFallbackPolicy fallback_policy = + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED) { + return makeSelector(selector_keys, fallback_policy, {}); + } + + void modifyHosts(HostVector add, HostVector remove, absl::optional add_in_locality = {}, + uint32_t priority = 0) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority); + for (const auto& host : remove) { + auto it = std::find(host_set.hosts_.begin(), host_set.hosts_.end(), host); + if (it != host_set.hosts_.end()) { + host_set.hosts_.erase(it); + } + host_set.healthy_hosts_ = host_set.hosts_; + + std::vector locality_hosts_copy = host_set.hosts_per_locality_->get(); + for (auto& locality_hosts : locality_hosts_copy) { + auto it = std::find(locality_hosts.begin(), locality_hosts.end(), host); + if (it != locality_hosts.end()) { + locality_hosts.erase(it); + } + } + host_set.hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); + host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; + } + + if (GetParam() == UpdateOrder::RemovesFirst && !remove.empty()) { + host_set.runCallbacks({}, remove); + } + + for (const auto& host : add) { + host_set.hosts_.emplace_back(host); + host_set.healthy_hosts_ = host_set.hosts_; + + if (add_in_locality) { + std::vector locality_hosts_copy = host_set.hosts_per_locality_->get(); + locality_hosts_copy[add_in_locality.value()].emplace_back(host); + host_set.hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); + host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; + } + } + + if (GetParam() == UpdateOrder::RemovesFirst) { + if (!add.empty()) { + host_set.runCallbacks(add, {}); + } + } else if (!add.empty() || !remove.empty()) { + host_set.runCallbacks(add, remove); + } + } + + void modifyLocalHosts(HostVector add, HostVector remove, uint32_t add_in_locality) { + for (const auto& host : remove) { + auto it = std::find(local_hosts_->begin(), local_hosts_->end(), host); + if (it != local_hosts_->end()) { + local_hosts_->erase(it); + } + + std::vector locality_hosts_copy = local_hosts_per_locality_->get(); + for (auto& locality_hosts : locality_hosts_copy) { + auto it = std::find(locality_hosts.begin(), locality_hosts.end(), host); + if (it != locality_hosts.end()) { + locality_hosts.erase(it); + } + } + local_hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); + } + + if (GetParam() == UpdateOrder::RemovesFirst && !remove.empty()) { + local_priority_set_.updateHosts( + 0, + updateHostsParams(local_hosts_, local_hosts_per_locality_, + std::make_shared(*local_hosts_), + local_hosts_per_locality_), + {}, {}, remove, absl::nullopt); + } + + for (const auto& host : add) { + local_hosts_->emplace_back(host); + std::vector locality_hosts_copy = local_hosts_per_locality_->get(); + locality_hosts_copy[add_in_locality].emplace_back(host); + local_hosts_per_locality_ = makeHostsPerLocality(std::move(locality_hosts_copy)); + } + + if (GetParam() == UpdateOrder::RemovesFirst) { + if (!add.empty()) { + local_priority_set_.updateHosts( + 0, + updateHostsParams(local_hosts_, local_hosts_per_locality_, + std::make_shared(*local_hosts_), + local_hosts_per_locality_), + {}, add, {}, absl::nullopt); + } + } else if (!add.empty() || !remove.empty()) { + local_priority_set_.updateHosts( + 0, + updateHostsParams(local_hosts_, local_hosts_per_locality_, + std::make_shared(*local_hosts_), + local_hosts_per_locality_), + {}, add, remove, absl::nullopt); + } + } + + void doLbTypeTest(LoadBalancerType type) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + lb_type_ = type; + init({{"tcp://127.0.0.1:80", {{"version", "1.0"}}}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); + + HostSharedPtr added_host = makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}}); + modifyHosts({added_host}, {host_set_.hosts_.back()}); + + EXPECT_EQ(added_host, lb_->chooseHost(nullptr)); + } + + MetadataConstSharedPtr buildMetadata(const std::string& version, bool is_default = false) const { + envoy::config::core::v3::Metadata metadata; + + if (!version.empty()) { + Envoy::Config::Metadata::mutableMetadataValue( + metadata, Config::MetadataFilters::get().ENVOY_LB, "version") + .set_string_value(version); + } + + if (is_default) { + Envoy::Config::Metadata::mutableMetadataValue( + metadata, Config::MetadataFilters::get().ENVOY_LB, "default") + .set_string_value("true"); + } + + return std::make_shared(metadata); + } + + MetadataConstSharedPtr buildMetadataWithStage(const std::string& version, + const std::string& stage = "") const { + envoy::config::core::v3::Metadata metadata; + + if (!version.empty()) { + Envoy::Config::Metadata::mutableMetadataValue( + metadata, Config::MetadataFilters::get().ENVOY_LB, "version") + .set_string_value(version); + } + + if (!stage.empty()) { + Envoy::Config::Metadata::mutableMetadataValue( + metadata, Config::MetadataFilters::get().ENVOY_LB, "stage") + .set_string_value(stage); + } + + return std::make_shared(metadata); + } + + ProtobufWkt::Value valueFromJson(std::string json) { + ProtobufWkt::Value v; + TestUtility::loadFromJson(json, v); + return v; + } + + LoadBalancerType lb_type_{LoadBalancerType::RoundRobin}; + NiceMock priority_set_; + MockHostSet& host_set_ = *priority_set_.getMockHostSet(0); + // Mock subset info is used for testing most logic. + NiceMock subset_info_; + // Actual subset info is used for testing actual subset config parsing and behavior. + std::unique_ptr actual_subset_info_; + std::shared_ptr info_{new NiceMock()}; + envoy::config::cluster::v3::Cluster::RingHashLbConfig ring_hash_lb_config_; + envoy::config::cluster::v3::Cluster::MaglevLbConfig maglev_lb_config_; + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig least_request_lb_config_; + envoy::config::cluster::v3::Cluster::RoundRobinLbConfig round_robin_lb_config_; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + NiceMock runtime_; + NiceMock random_; + Stats::IsolatedStoreImpl stats_store_; + Stats::ScopeSharedPtr scope_; + ClusterLbStatNames stat_names_; + ClusterLbStats stats_; + PrioritySetImpl local_priority_set_; + HostVectorSharedPtr local_hosts_; + HostsPerLocalitySharedPtr local_hosts_per_locality_; + std::shared_ptr lb_; +}; + +TEST_F(SubsetLoadBalancerTest, NoFallback) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + init(); + + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); + + EXPECT_EQ(nullptr, lb_->peekAnotherHost(nullptr)); + EXPECT_FALSE(lb_->lifetimeCallbacks().has_value()); + std::vector hash_key; + auto mock_host = std::make_shared>(); + EXPECT_FALSE(lb_->selectExistingConnection(nullptr, *mock_host, hash_key).has_value()); +} + +// Validate that SubsetLoadBalancer unregisters its priority set member update +// callback. Regression for heap-use-after-free. +TEST_F(SubsetLoadBalancerTest, DeregisterCallbacks) { + init(); + lb_.reset(); + host_set_.runCallbacks({}, {}); +} + +TEST_P(SubsetLoadBalancerTest, NoFallbackAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + init(); + + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); + + modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}})}, {host_set_.hosts_.back()}); + + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); +} + +TEST_F(SubsetLoadBalancerTest, FallbackAnyEndpoint) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + init(); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); +} + +TEST_P(SubsetLoadBalancerTest, FallbackAnyEndpointAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + init(); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); + + HostSharedPtr added_host = makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}}); + modifyHosts({added_host}, {host_set_.hosts_.back()}); + + EXPECT_EQ(added_host, lb_->chooseHost(nullptr)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); +} + +TEST_F(SubsetLoadBalancerTest, FallbackDefaultSubset) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "new"}}}, + {"tcp://127.0.0.1:81", {{"version", "default"}}}, + }); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); +} + +TEST_F(SubsetLoadBalancerTest, FallbackPanicMode) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + EXPECT_CALL(subset_info_, panicModeAny()).WillRepeatedly(Return(true)); + + // The default subset will be empty. + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "none"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "new"}}}, + {"tcp://127.0.0.1:81", {{"version", "default"}}}, + }); + + EXPECT_TRUE(lb_->chooseHost(nullptr) != nullptr); + EXPECT_EQ(1U, stats_.lb_subsets_fallback_panic_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); +} + +TEST_P(SubsetLoadBalancerTest, FallbackPanicModeWithUpdates) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + EXPECT_CALL(subset_info_, panicModeAny()).WillRepeatedly(Return(true)); + + // The default subset will be empty. + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "none"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + init({{"tcp://127.0.0.1:80", {{"version", "default"}}}}); + EXPECT_TRUE(lb_->chooseHost(nullptr) != nullptr); + + // Removing current host, adding a new one. + HostSharedPtr added_host = makeHost("tcp://127.0.0.2:8000", {{"version", "new"}}); + modifyHosts({added_host}, {host_set_.hosts_[0]}); + + EXPECT_EQ(1, host_set_.hosts_.size()); + EXPECT_EQ(added_host, lb_->chooseHost(nullptr)); +} + +TEST_P(SubsetLoadBalancerTest, FallbackDefaultSubsetAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "new"}}}, + {"tcp://127.0.0.1:81", {{"version", "default"}}}, + }); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); + + HostSharedPtr added_host1 = makeHost("tcp://127.0.0.1:8000", {{"version", "new"}}); + HostSharedPtr added_host2 = makeHost("tcp://127.0.0.1:8001", {{"version", "default"}}); + + modifyHosts({added_host1, added_host2}, {host_set_.hosts_.back()}); + + EXPECT_EQ(added_host2, lb_->chooseHost(nullptr)); +} + +TEST_F(SubsetLoadBalancerTest, FallbackEmptyDefaultSubsetConvertsToAnyEndpoint) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + EXPECT_CALL(subset_info_, defaultSubset()) + .WillRepeatedly(ReturnRef(ProtobufWkt::Struct::default_instance())); + + init(); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_selected_.value()); +} + +TEST_F(SubsetLoadBalancerTest, FallbackOnUnknownMetadata) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + init(); + + TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); + TestLoadBalancerContext context_unknown_value({{"version", "unknown"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_unknown_key)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_unknown_value)); +} + +TEST_F(SubsetLoadBalancerTest, BalancesSubset) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + }); + + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_11({{"version", "1.1"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); + EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_selected_.value()); +} + +TEST_P(SubsetLoadBalancerTest, BalancesSubsetAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + }); + + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_11({{"version", "1.1"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); + EXPECT_EQ(2U, stats_.lb_subsets_created_.value()); + + modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), + makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, + {host_set_.hosts_[1], host_set_.hosts_[2]}); + + TestLoadBalancerContext context_12({{"version", "1.2"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_11)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_12)); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); +} + +TEST_P(SubsetLoadBalancerTest, ListAsAnyEnabled) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + EXPECT_CALL(subset_info_, listAsAny()).WillRepeatedly(Return(true)); + + init({}); + modifyHosts( + {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}}), + makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, + {}, {}, 0); + + { + TestLoadBalancerContext context({{"version", "1.0"}}); + EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); + } + { + TestLoadBalancerContext context({{"version", "1.2"}}); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); + } + TestLoadBalancerContext context({{"version", "1.2.1"}}); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); +} + +TEST_P(SubsetLoadBalancerTest, ListAsAnyEnabledMultipleLists) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + EXPECT_CALL(subset_info_, listAsAny()).WillRepeatedly(Return(true)); + + init({}); + modifyHosts( + {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}}), + makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.2", "1.2"}}}), + makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, + {}, {}, 0); + + { + TestLoadBalancerContext context({{"version", "1.0"}}); + EXPECT_TRUE(host_set_.hosts()[2] == lb_->chooseHost(&context)); + EXPECT_TRUE(host_set_.hosts()[2] == lb_->chooseHost(&context)); + } + { + // This should LB between both hosts marked with version 1.2. + TestLoadBalancerContext context({{"version", "1.2"}}); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); + EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); + } + { + // Choose a host multiple times to ensure that hosts()[0] is the *only* + // thing selected for this subset. + TestLoadBalancerContext context({{"version", "1.2.1"}}); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); + } + + TestLoadBalancerContext context({{"version", "1.2.2"}}); + EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); +} + +TEST_P(SubsetLoadBalancerTest, ListAsAnyEnabledMultipleListsForSingleHost) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version", "hardware"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + EXPECT_CALL(subset_info_, listAsAny()).WillRepeatedly(Return(true)); + + init({}); + modifyHosts( + {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}, + {"hardware", std::vector{"a", "b"}}}), + makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.1", "1.1.1"}}, + {"hardware", std::vector{"b", "c"}}})}, + {}, {}, 0); + + { + TestLoadBalancerContext context({{"version", "1.2"}, {"hardware", "a"}}); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); + } + + { + TestLoadBalancerContext context({{"version", "1.1"}, {"hardware", "b"}}); + EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); + EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); + } + + { + TestLoadBalancerContext context({{"version", "1.1"}, {"hardware", "a"}}); + EXPECT_TRUE(nullptr == lb_->chooseHost(&context)); + } + + TestLoadBalancerContext context({{"version", "1.2.1"}, {"hardware", "b"}}); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); + EXPECT_TRUE(host_set_.hosts()[0] == lb_->chooseHost(&context)); +} + +TEST_P(SubsetLoadBalancerTest, ListAsAnyDisable) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({}); + modifyHosts( + {makeHost("tcp://127.0.0.1:8000", {{"version", std::vector{"1.2.1", "1.2"}}}), + makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, + {}, {}, 0); + + { + TestLoadBalancerContext context({{"version", "1.0"}}); + EXPECT_TRUE(host_set_.hosts()[1] == lb_->chooseHost(&context)); + } + TestLoadBalancerContext context({{"version", "1.2"}}); + EXPECT_TRUE(nullptr == lb_->chooseHost(&context)); +} + +// Test that adding backends to a failover group causes no problems. +TEST_P(SubsetLoadBalancerTest, UpdateFailover) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + TestLoadBalancerContext context_10({{"version", "1.0"}}); + + // Start with an empty lb. Choosing a host should result in failure. + init({}); + EXPECT_TRUE(nullptr == lb_->chooseHost(&context_10).get()); + + // Add hosts to the group at priority 1. + // These hosts should be selected as there are no healthy hosts with priority 0 + modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), + makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, + {}, {}, 1); + EXPECT_FALSE(nullptr == lb_->chooseHost(&context_10).get()); + + // Finally update the priority 0 hosts. The LB should now select hosts. + modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), + makeHost("tcp://127.0.0.1:8001", {{"version", "1.0"}})}, + {}, {}, 0); + EXPECT_FALSE(nullptr == lb_->chooseHost(&context_10).get()); +} + +TEST_P(SubsetLoadBalancerTest, OnlyMetadataChanged) { + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_12({{"version", "1.2"}}); + TestLoadBalancerContext context_13({{"version", "1.3"}}); + TestLoadBalancerContext context_default({{"default", "true"}}); + + std::vector subset_selectors = { + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"default"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"default", "true"}}); + + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + // Add hosts initial hosts. + init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, + {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"default", "true"}}}}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); + + // Swap the default version. + host_set_.hosts_[0]->metadata(buildMetadata("1.2", true)); + host_set_.hosts_[1]->metadata(buildMetadata("1.0")); + + host_set_.runCallbacks({}, {}); + + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_13)); + + // Bump 1.0 to 1.3, one subset should be removed. + host_set_.hosts_[1]->metadata(buildMetadata("1.3")); + + // No hosts added nor removed, so we bypass modifyHosts(). + host_set_.runCallbacks({}, {}); + + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + + // Rollback from 1.3 to 1.0. + host_set_.hosts_[1]->metadata(buildMetadata("1.0")); + + host_set_.runCallbacks({}, {}); + + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_13)); + + // Make 1.0 default again. + host_set_.hosts_[1]->metadata(buildMetadata("1.0", true)); + host_set_.hosts_[0]->metadata(buildMetadata("1.2")); + + host_set_.runCallbacks({}, {}); + + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); +} + +TEST_P(SubsetLoadBalancerTest, EmptySubsetsPurged) { + std::vector subset_selectors = {makeSelector({"version"}), + makeSelector({"version", "stage"})}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + // Simple add and remove. + init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, + {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"stage", "prod"}}}}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + + host_set_.hosts_[0]->metadata(buildMetadataWithStage("1.3")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); + + // Move host that was in the version + stage subset into a new version only subset. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.4")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_removed_.value()); + + // Create a new version + stage subset. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.5", "devel")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(7U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_removed_.value()); + + // Now move it back to its original version + stage subset. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.0", "prod")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(9U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(6U, stats_.lb_subsets_removed_.value()); + + // Finally, remove the original version + stage subset again. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.6")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(10U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(8U, stats_.lb_subsets_removed_.value()); +} + +TEST_P(SubsetLoadBalancerTest, EmptySubsetsPurgedCollapsed) { + std::vector subset_selectors = {makeSelector({"version"}), + makeSelector({"version", "stage"})}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + // Init subsets. + init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, + {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"stage", "prod"}}}}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + + // Get rid of 1.0. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2", "prod")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); + + // Get rid of stage prod. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(1U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_removed_.value()); + + // Add stage prod back. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2", "prod")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_removed_.value()); +} + +TEST_P(SubsetLoadBalancerTest, EmptySubsetsPurgedVersionChanged) { + std::vector subset_selectors = {makeSelector({"version"}), + makeSelector({"version", "stage"})}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + // Init subsets. + init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, + {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"stage", "prod"}}}}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + + // Get rid of 1.0. + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.2", "prod")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); + + // Change versions. + host_set_.hosts_[0]->metadata(buildMetadataWithStage("1.3")); + host_set_.hosts_[1]->metadata(buildMetadataWithStage("1.4", "prod")); + host_set_.runCallbacks({}, {}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(7U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_removed_.value()); +} + +TEST_P(SubsetLoadBalancerTest, MetadataChangedHostsAddedRemoved) { + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_12({{"version", "1.2"}}); + TestLoadBalancerContext context_13({{"version", "1.3"}}); + TestLoadBalancerContext context_14({{"version", "1.4"}}); + TestLoadBalancerContext context_default({{"default", "true"}}); + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"default", "true"}}); + + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + std::vector subset_selectors = { + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"default"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + // Add hosts initial hosts. + init({{"tcp://127.0.0.1:8000", {{"version", "1.2"}}}, + {"tcp://127.0.0.1:8001", {{"version", "1.0"}, {"default", "true"}}}}); + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(3U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); + + // Swap the default version. + host_set_.hosts_[0]->metadata(buildMetadata("1.2", true)); + host_set_.hosts_[1]->metadata(buildMetadata("1.0")); + + // Add a new host. + modifyHosts({makeHost("tcp://127.0.0.1:8002", {{"version", "1.3"}})}, {}); + + EXPECT_EQ(4U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_13)); + + // Swap default again and remove the previous one. + host_set_.hosts_[0]->metadata(buildMetadata("1.2")); + host_set_.hosts_[1]->metadata(buildMetadata("1.0", true)); + + modifyHosts({}, {host_set_.hosts_[2]}); + + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); + + // Swap the default version once more, this time adding a new host and removing + // the current default version. + host_set_.hosts_[0]->metadata(buildMetadata("1.2", true)); + host_set_.hosts_[1]->metadata(buildMetadata("1.0")); + + modifyHosts({makeHost("tcp://127.0.0.1:8003", {{"version", "1.4"}})}, {host_set_.hosts_[1]}); + + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_13)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_14)); + + // Make 1.4 default, without hosts being added/removed. + host_set_.hosts_[0]->metadata(buildMetadata("1.2")); + host_set_.hosts_[1]->metadata(buildMetadata("1.4", true)); + + host_set_.runCallbacks({}, {}); + + EXPECT_EQ(3U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(5U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_removed_.value()); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_12)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_default)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_13)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_14)); +} + +TEST_P(SubsetLoadBalancerTest, UpdateRemovingLastSubsetHost) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }); + + HostSharedPtr host_v10 = host_set_.hosts_[0]; + HostSharedPtr host_v11 = host_set_.hosts_[1]; + + TestLoadBalancerContext context({{"version", "1.0"}}); + EXPECT_EQ(host_v10, lb_->chooseHost(&context)); + EXPECT_EQ(1U, stats_.lb_subsets_selected_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_created_.value()); + + modifyHosts({}, {host_v10}); + + // fallback to any endpoint + EXPECT_EQ(host_v11, lb_->chooseHost(&context)); + EXPECT_EQ(1U, stats_.lb_subsets_selected_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(2U, stats_.lb_subsets_created_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); +} + +TEST_P(SubsetLoadBalancerTest, UpdateRemovingUnknownHost) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = { + makeSelector( + {"stage", "version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"stage", "prod"}, {"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"stage", "prod"}, {"version", "1.1"}}}, + }); + + TestLoadBalancerContext context({{"stage", "prod"}, {"version", "1.0"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context)); + + modifyHosts({}, {makeHost("tcp://127.0.0.1:8000", {{"version", "1.2"}}), + makeHost("tcp://127.0.0.1:8001", {{"stage", "prod"}, {"version", "1.2"}})}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context)); +} + +TEST_F(SubsetLoadBalancerTest, UpdateModifyingOnlyHostHealth) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = { + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"hardware"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + }); + + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_11({{"version", "1.1"}}); + + // All hosts are healthy. + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); + + host_set_.hosts_[0]->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); + host_set_.hosts_[2]->healthFlagSet(Host::HealthFlag::FAILED_OUTLIER_CHECK); + host_set_.healthy_hosts_ = {host_set_.hosts_[1], host_set_.hosts_[3]}; + host_set_.runCallbacks({}, {}); + + // Unhealthy hosts are excluded. + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); +} + +TEST_F(SubsetLoadBalancerTest, BalancesDisjointSubsets) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = { + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"hardware"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"hardware", "std"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"hardware", "bigmem"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.1"}, {"hardware", "std"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}, {"hardware", "bigmem"}}}, + }); + + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_bigmem({{"hardware", "bigmem"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_bigmem)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_bigmem)); +} + +TEST_F(SubsetLoadBalancerTest, BalancesOverlappingSubsets) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = { + makeSelector( + {"stage", "version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.0"}, {"stage", "off"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:84", {{"version", "999"}, {"stage", "dev"}}}, + }); + + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_10_prod({{"version", "1.0"}, {"stage", "prod"}}); + TestLoadBalancerContext context_dev({{"version", "999"}, {"stage", "dev"}}); + TestLoadBalancerContext context_unknown({{"version", "2.0"}, {"stage", "prod"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_10)); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10_prod)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10_prod)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10_prod)); + + EXPECT_EQ(host_set_.hosts_[4], lb_->chooseHost(&context_dev)); + EXPECT_EQ(host_set_.hosts_[4], lb_->chooseHost(&context_dev)); + + EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown)); +} + +TEST_F(SubsetLoadBalancerTest, BalancesNestedSubsets) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = { + makeSelector( + {"stage", "version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"stage"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.0"}, {"stage", "off"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:84", {{"version", "999"}, {"stage", "dev"}}}, + }); + + TestLoadBalancerContext context_prod({{"stage", "prod"}}); + TestLoadBalancerContext context_prod_10({{"version", "1.0"}, {"stage", "prod"}}); + TestLoadBalancerContext context_unknown_stage({{"stage", "larval"}}); + TestLoadBalancerContext context_unknown_version({{"version", "2.0"}, {"stage", "prod"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_prod)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_prod)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_prod)); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_prod_10)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_prod_10)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_prod_10)); + + EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_stage)); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_version)); +} + +TEST_F(SubsetLoadBalancerTest, IgnoresUnselectedMetadata) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "ignored"}}}, + {"tcp://127.0.0.1:81", {{"ignore", "value"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + }); + + TestLoadBalancerContext context_ignore({{"ignore", "value"}}); + TestLoadBalancerContext context_version({{"version", "1.0"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_version)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_version)); + + EXPECT_EQ(nullptr, lb_->chooseHost(&context_ignore)); +} + +TEST_F(SubsetLoadBalancerTest, IgnoresHostsWithoutMetadata) { + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + HostVector hosts; + hosts.emplace_back(makeTestHost(info_, "tcp://127.0.0.1:80", simTime())); + hosts.emplace_back(makeHost("tcp://127.0.0.1:81", {{"version", "1.0"}})); + + host_set_.hosts_ = hosts; + host_set_.hosts_per_locality_ = makeHostsPerLocality({hosts}); + + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.healthy_hosts_per_locality_ = host_set_.hosts_per_locality_; + + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + lb_ = std::make_shared( + subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, + *stats_store_.rootScope(), runtime_, random_, simTime()); + + TestLoadBalancerContext context_version({{"version", "1.0"}}); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_version)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_version)); +} + +// TODO(mattklein123): The following 4 tests verify basic functionality with all sub-LB tests. +// Optimally these would also be some type of TEST_P, but that is a little bit complicated as +// modifyHosts() also needs params. Clean this up. +TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesRoundRobin) { + doLbTypeTest(LoadBalancerType::RoundRobin); + auto tester = SubsetLoadBalancerInternalStateTester(lb_); + tester.validateLbTypeConfigs(); +} + +TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesLeastRequest) { + doLbTypeTest(LoadBalancerType::LeastRequest); + auto tester = SubsetLoadBalancerInternalStateTester(lb_); + tester.validateLbTypeConfigs(); +} + +TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesRandom) { + doLbTypeTest(LoadBalancerType::Random); + auto tester = SubsetLoadBalancerInternalStateTester(lb_); + tester.validateLbTypeConfigs(); +} + +TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesRingHash) { + doLbTypeTest(LoadBalancerType::RingHash); + auto tester = SubsetLoadBalancerInternalStateTester(lb_); + tester.validateLbTypeConfigs(); +} + +TEST_P(SubsetLoadBalancerTest, LoadBalancerTypesMaglev) { + doLbTypeTest(LoadBalancerType::Maglev); + auto tester = SubsetLoadBalancerInternalStateTester(lb_); + tester.validateLbTypeConfigs(); +} + +TEST_F(SubsetLoadBalancerTest, ZoneAwareFallback) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector subset_selectors = {makeSelector( + {"x"}, envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + common_config_.mutable_healthy_panic_threshold()->set_value(40); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 40)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, + }}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); +} + +TEST_P(SubsetLoadBalancerTest, ZoneAwareFallbackAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector subset_selectors = {makeSelector( + {"x"}, envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, + }}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); + + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + + modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}}, local_locality)}, + {host_set_.hosts_[0]}, absl::optional(0)); + + modifyLocalHosts({makeHost("tcp://127.0.0.1:9000", {{"version", "1.0"}}, local_locality)}, + {local_hosts_->at(0)}, 0); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); +} + +TEST_F(SubsetLoadBalancerTest, ZoneAwareFallbackDefaultSubset) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "new"}}}, + {"tcp://127.0.0.1:81", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "new"}}}, + {"tcp://127.0.0.1:83", {{"version", "default"}}}, + {"tcp://127.0.0.1:84", {{"version", "new"}}}, + {"tcp://127.0.0.1:85", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "new"}}}, + {"tcp://127.0.0.1:87", {{"version", "default"}}}, + {"tcp://127.0.0.1:88", {{"version", "new"}}}, + {"tcp://127.0.0.1:89", {{"version", "default"}}}, + }}, + {{ + {"tcp://127.0.0.1:90", {{"version", "new"}}}, + {"tcp://127.0.0.1:91", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:92", {{"version", "new"}}}, + {"tcp://127.0.0.1:93", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:94", {{"version", "new"}}}, + {"tcp://127.0.0.1:95", {{"version", "default"}}}, + }}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(nullptr)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); +} + +TEST_P(SubsetLoadBalancerTest, ZoneAwareFallbackDefaultSubsetAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "new"}}}, + {"tcp://127.0.0.1:81", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "new"}}}, + {"tcp://127.0.0.1:83", {{"version", "default"}}}, + {"tcp://127.0.0.1:84", {{"version", "new"}}}, + {"tcp://127.0.0.1:85", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "new"}}}, + {"tcp://127.0.0.1:87", {{"version", "default"}}}, + {"tcp://127.0.0.1:88", {{"version", "new"}}}, + {"tcp://127.0.0.1:89", {{"version", "default"}}}, + }}, + {{ + {"tcp://127.0.0.1:90", {{"version", "new"}}}, + {"tcp://127.0.0.1:91", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:92", {{"version", "new"}}}, + {"tcp://127.0.0.1:93", {{"version", "default"}}}, + }, + { + {"tcp://127.0.0.1:94", {{"version", "new"}}}, + {"tcp://127.0.0.1:95", {{"version", "default"}}}, + }}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(nullptr)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); + + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + + modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "default"}}, local_locality)}, + {host_set_.hosts_[1]}, absl::optional(0)); + + modifyLocalHosts({local_hosts_->at(1)}, + {makeHost("tcp://127.0.0.1:9001", {{"version", "default"}}, local_locality)}, 0); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(nullptr)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(nullptr)); +} + +TEST_F(SubsetLoadBalancerTest, ZoneAwareBalancesSubsets) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:93", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:94", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:95", {{"version", "1.1"}}}, + }}); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); +} + +TEST_P(SubsetLoadBalancerTest, ZoneAwareBalancesSubsetsAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:90", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:91", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:92", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:93", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:94", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:95", {{"version", "1.1"}}}, + }}); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); + + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + + modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "1.1"}}, local_locality)}, + {host_set_.hosts_[1]}, absl::optional(0)); + + modifyLocalHosts({local_hosts_->at(1)}, + {makeHost("tcp://127.0.0.1:9001", {{"version", "1.1"}}, local_locality)}, 0); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + + // Force request out of small zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); +} + +TEST_F(SubsetLoadBalancerTest, ZoneAwareComplicatedBalancesSubsets) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + // L=local cluster host + // U=upstream host + // + // residuals + // A: 2L 0U 0.00% + // B: 2L 2U 6.67% + // C: 2L 2U 6.67% + // D: 0L 1U 20.00% + // total: 6L 5U 33.33% + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:90", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:91", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:92", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:93", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:94", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:95", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:96", {{"version", "1.1"}}}, + }}); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(666)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(667)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(1334)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); +} + +TEST_P(SubsetLoadBalancerTest, ZoneAwareComplicatedBalancesSubsetsAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + // Before update: + // + // L=local cluster host + // U=upstream host + // + // residuals + // A: 2L 0U 0.00% + // B: 2L 2U 6.67% + // C: 2L 2U 6.67% + // D: 0L 1U 20.00% + // total: 6L 5U 33.33% + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:90", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:91", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:92", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:93", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:94", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:95", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:96", {{"version", "1.1"}}}, + }}); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(666)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(667)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(1334)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); + + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + envoy::config::core::v3::Locality locality_2; + locality_2.set_zone("2"); + + modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "1.1"}}, local_locality)}, {}, + absl::optional(0)); + + modifyLocalHosts({makeHost("tcp://127.0.0.1:9001", {{"version", "1.1"}}, locality_2)}, {}, 2); + + // After update: + // + // L=local cluster host + // U=upstream host + // + // residuals + // A: 2L 1U 0.00% + // B: 2L 2U 4.76% + // C: 3L 2U 0.00% + // D: 0L 1U 16.67% + // total: 7L 6U 21.42% + // + // Chance of sampling local host in zone 0: 58.34% + + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(5830)); // 58.31% local routing chance due to rounding error + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(5831)).WillOnce(Return(475)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(476)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2143)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); +} + +TEST_F(SubsetLoadBalancerTest, DescribeMetadata) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + init(); + + ProtobufWkt::Value str_value; + str_value.set_string_value("abc"); + + ProtobufWkt::Value num_value; + num_value.set_number_value(100); + + auto tester = SubsetLoadBalancerInternalStateTester(lb_); + tester.testDescribeMetadata("version=\"abc\"", {{"version", str_value}}); + tester.testDescribeMetadata("number=100", {{"number", num_value}}); + tester.testDescribeMetadata("x=\"abc\", y=100", {{"x", str_value}, {"y", num_value}}); + tester.testDescribeMetadata("y=100, x=\"abc\"", {{"y", num_value}, {"x", str_value}}); + tester.testDescribeMetadata("", {}); +} + +TEST_F(SubsetLoadBalancerTest, DisabledLocalityWeightAwareness) { + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + + // We configure a weighted host set that heavily favors the second locality. + configureWeightedHostSet( + { + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + host_set_, {1, 100}); + + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + lb_ = std::make_shared( + subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, + *stats_store_.rootScope(), runtime_, random_, simTime()); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + // Since we don't respect locality weights, the first locality is selected. + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(&context)); +} + +// Verifies that we do *not* invoke coarseHealth() on hosts when constructing the load balancer. +// Since health is modified concurrently from multiple threads, it is not safe to call on the worker +// threads. +TEST_F(SubsetLoadBalancerTest, DoesNotCheckHostHealth) { + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + + auto mock_host = std::make_shared(); + HostVector hosts{mock_host}; + host_set_.hosts_ = hosts; + + EXPECT_CALL(*mock_host, weight()).WillRepeatedly(Return(1)); + + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + lb_ = std::make_shared( + subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, + *stats_store_.rootScope(), runtime_, random_, simTime()); +} + +TEST_F(SubsetLoadBalancerTest, EnabledLocalityWeightAwareness) { + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); + + // We configure a weighted host set that heavily favors the second locality. + configureWeightedHostSet( + { + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + host_set_, {1, 100}); + + common_config_.mutable_locality_weighted_lb_config(); + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + lb_ = std::make_shared( + subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, + *stats_store_.rootScope(), runtime_, random_, simTime()); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + // Since we respect locality weights, the second locality is selected. + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(&context)); +} + +TEST_F(SubsetLoadBalancerTest, EnabledScaleLocalityWeights) { + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, scaleLocalityWeight()).WillRepeatedly(Return(true)); + + // We configure a weighted host set is weighted equally between each locality. + configureWeightedHostSet( + { + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + host_set_, {50, 50}); + + common_config_.mutable_locality_weighted_lb_config(); + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + lb_ = std::make_shared( + subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, + *stats_store_.rootScope(), runtime_, random_, simTime()); + TestLoadBalancerContext context({{"version", "1.1"}}); + + // Since we scale the locality weights by number of hosts removed, we expect to see the second + // locality to be selected less because we've excluded more hosts in that locality than in the + // first. + // The localities are split 50/50, but because of the scaling we expect to see 66/33 instead. + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); +} + +TEST_F(SubsetLoadBalancerTest, EnabledScaleLocalityWeightsRounding) { + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, scaleLocalityWeight()).WillRepeatedly(Return(true)); + + // We configure a weighted host set where the locality weights are very low to test + // that we are rounding computation instead of flooring it. + configureWeightedHostSet( + { + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + host_set_, {2, 2}); + + common_config_.mutable_locality_weighted_lb_config(); + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + lb_ = std::make_shared( + subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, + *stats_store_.rootScope(), runtime_, random_, simTime()); + TestLoadBalancerContext context({{"version", "1.0"}}); + + // We expect to see a 33/66 split because 2 * 1 / 2 = 1 and 2 * 3 / 4 = 1.5 -> 2 + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][2], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); +} + +// Regression for bug where missing locality weights crashed scaling and locality aware subset LBs. +TEST_F(SubsetLoadBalancerTest, ScaleLocalityWeightsWithNoLocalityWeights) { + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, localityWeightAware()).WillRepeatedly(Return(true)); + EXPECT_CALL(subset_info_, scaleLocalityWeight()).WillRepeatedly(Return(true)); + + configureHostSet( + { + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.1"}}}, + }, + host_set_); + + auto child_lb_creator = std::make_unique( + lb_type_, ring_hash_lb_config_, maglev_lb_config_, round_robin_lb_config_, + least_request_lb_config_, common_config_); + lb_ = std::make_shared( + subset_info_, std::move(child_lb_creator), priority_set_, nullptr, stats_, + *stats_store_.rootScope(), runtime_, random_, simTime()); +} + +TEST_P(SubsetLoadBalancerTest, GaugesUpdatedOnDestroy) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }); + + EXPECT_EQ(1U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + + lb_ = nullptr; + + EXPECT_EQ(0U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); +} + +TEST_P(SubsetLoadBalancerTest, SubsetSelectorNoFallbackPerSelector) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + }); + + TestLoadBalancerContext context_10({{"version", "1.0"}}); + TestLoadBalancerContext context_11({{"version", "1.1"}}); + TestLoadBalancerContext context_12({{"version", "1.2"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_11)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_10)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_11)); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_12)); + EXPECT_EQ(0U, stats_.lb_subsets_fallback_.value()); + EXPECT_EQ(4U, stats_.lb_subsets_selected_.value()); +} + +TEST_P(SubsetLoadBalancerTest, FallbackNotDefinedForIntermediateSelector) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector subset_selectors = { + makeSelector( + {"stage"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"stage", "version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::ANY_ENDPOINT)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"stage", "canary"}}}}); + + TestLoadBalancerContext context_match_host0({{"stage", "dev"}}); + TestLoadBalancerContext context_stage_nx({{"stage", "test"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_stage_nx)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_stage_nx)); +} + +TEST_P(SubsetLoadBalancerTest, SubsetSelectorFallbackOverridesTopLevelOne) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init(); + + TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); + TestLoadBalancerContext context_unknown_value({{"version", "unknown"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_unknown_key)); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_value)); +} + +TEST_P(SubsetLoadBalancerTest, SubsetSelectorNoFallbackMatchesTopLevelOne) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init(); + + TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); + TestLoadBalancerContext context_unknown_value({{"version", "unknown"}}); + + EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_key)); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_value)); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_unknown_value)); +} + +TEST_F(SubsetLoadBalancerTest, AllowRedundantKeysForSubset) { + // Yaml config for subset load balancer. + const std::string yaml = R"EOF( + subset_selectors: + - keys: + - A + fallback_policy: NO_FALLBACK + - keys: + - A + - B + fallback_policy: NO_FALLBACK + - keys: + - A + - B + - C + fallback_policy: NO_FALLBACK + - keys: + - A + - D + fallback_policy: NO_FALLBACK + - keys: + - version + - stage + fallback_policy: NO_FALLBACK + fallback_policy: NO_FALLBACK + allow_redundant_keys: true + )EOF"; + + envoy::extensions::load_balancing_policies::subset::v3::Subset subset_proto_config; + TestUtility::loadFromYaml(yaml, subset_proto_config); + + actual_subset_info_ = std::make_unique(subset_proto_config); + // Always be true for the LoadBalancerSubsetInfoImpl. + EXPECT_TRUE(actual_subset_info_->isEnabled()); + + // Add hosts initial hosts. + init({{"tcp://127.0.0.1:80", {{"A", "A-V-0"}, {"B", "B-V-0"}, {"C", "C-V-0"}, {"D", "D-V-0"}}}, + {"tcp://127.0.0.1:81", {{"A", "A-V-1"}, {"B", "B-V-1"}, {"C", "C-V-1"}, {"D", "D-V-1"}}}, + {"tcp://127.0.0.1:82", {{"A", "A-V-2"}, {"B", "B-V-2"}, {"C", "C-V-2"}, {"D", "D-V-2"}}}, + {"tcp://127.0.0.1:83", {{"A", "A-V-3"}, {"B", "B-V-3"}, {"C", "C-V-3"}, {"D", "D-V-3"}}}, + {"tcp://127.0.0.1:84", {{"A", "A-V-4"}, {"B", "B-V-4"}, {"C", "C-V-4"}, {"D", "D-V-4"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.0"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:86", {{"version", "1.0"}, {"stage", "canary"}}}}, + {}, true); + + TestLoadBalancerContext context_empty( + std::initializer_list::value_type>{}); + context_empty.matches_.reset(); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_empty)); + + // Request metadata is same with {version, stage}. + // version, stage will be kept and host 6 will be selected. + TestLoadBalancerContext context_v_s_0({{"version", "1.0"}, {"stage", "canary"}}); + EXPECT_EQ(host_set_.hosts_[6], lb_->chooseHost(&context_v_s_0)); + + // Request metadata is superset of {version, stage}. The redundant key will be ignored. + // version, stage will be kept and host 5 will be selected. + TestLoadBalancerContext context_v_s_1({{"version", "1.0"}, {"stage", "dev"}, {"redundant", "X"}}); + EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&context_v_s_1)); + + // Request metadata is superset of {version, stage}. The redundant key will be ignored. + // But one of value not match, so no host will be selected. + TestLoadBalancerContext context_v_s_2( + {{"version", "1.0"}, {"stage", "prod"}, {"redundant", "X"}}); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_v_s_2)); + + // Request metadata is same with {A, B, C} and is superset of selectors {A}, {A, B}. + // All A, B, C will be kept and host 0 will be selected. + TestLoadBalancerContext context_0({{"A", "A-V-0"}, {"B", "B-V-0"}, {"C", "C-V-0"}}); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_0)); + + // Request metadata is same with {A, B, C} and is superset of selectors {A}, {A, B}. + // All A, B, C will be kept But one of value not match, so no host will be selected. + TestLoadBalancerContext context_1({{"A", "A-V-0"}, {"B", "B-V-0"}, {"C", "C-V-X"}}); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_1)); + + // Request metadata is superset of selectors {A}, {A, B} {A, B, C}, {A, D}, the longest win. + // A, B, C will be kept and D will be ignored, so host 1 will be selected. + TestLoadBalancerContext context_2( + {{"A", "A-V-1"}, {"B", "B-V-1"}, {"C", "C-V-1"}, {"D", "D-V-X"}}); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_2)); + + // Request metadata is superset of selectors {A}, {A, B} {A, B, C}, {A, D}, the longest win. + // A, B, C will be kept and D will be ignored, but one of value not match, so no host will be + // selected. + TestLoadBalancerContext context_3( + {{"A", "A-V-1"}, {"B", "B-V-1"}, {"C", "C-V-X"}, {"D", "D-V-X"}}); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_3)); + + // Request metadata is superset of selectors {A}, {A, B}, {A, D}, the longest and first win. + // Only A, B will be kept and D will be ignored, so host 2 will be selected. + TestLoadBalancerContext context_4({{"A", "A-V-2"}, {"B", "B-V-2"}, {"D", "D-V-X"}}); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_4)); + + // Request metadata is superset of selectors {A}, {A, B}, {A, D}, the longest and first win. + // Only A, B will be kept and D will be ignored, but one of value not match, so no host will be + // selected. + TestLoadBalancerContext context_5({{"A", "A-V-3"}, {"B", "B-V-X"}, {"D", "D-V-3"}}); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_5)); + + // Request metadata is superset of selectors {A}, {A, D}, the longest win. + // Only A, D will be kept and C will be ignored, so host 3 will be selected. + TestLoadBalancerContext context_6({{"A", "A-V-3"}, {"C", "C-V-X"}, {"D", "D-V-3"}}); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_6)); +} + +TEST_P(SubsetLoadBalancerTest, SubsetSelectorDefaultAnyFallbackPerSelector) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = { + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::DEFAULT_SUBSET), + makeSelector( + {"app"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::ANY_ENDPOINT), + makeSelector( + {"foo"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"bar", "default"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + // Add hosts initial hosts. + init({{"tcp://127.0.0.1:81", {{"version", "0.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"app", "envoy"}}}, + {"tcp://127.0.0.1:84", {{"foo", "abc"}, {"bar", "default"}}}}); + + TestLoadBalancerContext context_ver_10({{"version", "1.0"}}); + TestLoadBalancerContext context_ver_nx({{"version", "x"}}); + TestLoadBalancerContext context_app({{"app", "envoy"}}); + TestLoadBalancerContext context_app_nx({{"app", "ngnix"}}); + TestLoadBalancerContext context_foo({{"foo", "abc"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_app_nx)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_app_nx)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_app)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_ver_nx)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_foo)); +} + +TEST_P(SubsetLoadBalancerTest, SubsetSelectorDefaultAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::DEFAULT_SUBSET)); + + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"version", "default"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::DEFAULT_SUBSET)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "new"}}}, + {"tcp://127.0.0.1:81", {{"version", "default"}}}, + }); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(nullptr)); + + HostSharedPtr added_host1 = makeHost("tcp://127.0.0.1:8000", {{"version", "new"}}); + HostSharedPtr added_host2 = makeHost("tcp://127.0.0.1:8001", {{"version", "default"}}); + + TestLoadBalancerContext context_ver_nx({{"version", "x"}}); + + modifyHosts({added_host1, added_host2}, {host_set_.hosts_.back()}); + + EXPECT_EQ(added_host2, lb_->chooseHost(&context_ver_nx)); +} + +TEST_P(SubsetLoadBalancerTest, SubsetSelectorAnyAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::ANY_ENDPOINT)}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({ + {"tcp://127.0.0.1:81", {{"version", "1"}}}, + {"tcp://127.0.0.1:82", {{"version", "2"}}}, + }); + + TestLoadBalancerContext context_ver_nx({{"version", "x"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_ver_nx)); + + HostSharedPtr added_host1 = makeHost("tcp://127.0.0.1:83", {{"version", "3"}}); + + modifyHosts({added_host1}, {host_set_.hosts_.back()}); + + EXPECT_EQ(added_host1, lb_->chooseHost(&context_ver_nx)); +} + +TEST_P(SubsetLoadBalancerTest, FallbackForCompoundSelector) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + const ProtobufWkt::Struct default_subset = makeDefaultSubset({{"foo", "bar"}}); + EXPECT_CALL(subset_info_, defaultSubset()).WillRepeatedly(ReturnRef(default_subset)); + + std::vector subset_selectors = { + makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED), + makeSelector( + {"version", "hardware", "stage"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK), + makeSelector( + {"version", "hardware"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::DEFAULT_SUBSET), + makeSelector( + {"version", "stage"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, + {"version"})}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + // Add hosts initial hosts. + init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"hardware", "c32"}}}, + {"tcp://127.0.0.1:81", {{"version", "1.0"}, {"hardware", "c32"}, {"foo", "bar"}}}, + {"tcp://127.0.0.1:82", {{"version", "2.0"}, {"hardware", "c32"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:83", {{"version", "2.0"}}}}); + + TestLoadBalancerContext context_match_host0({{"version", "1.0"}, {"hardware", "c32"}}); + TestLoadBalancerContext context_ver_nx({{"version", "x"}, {"hardware", "c32"}}); + TestLoadBalancerContext context_stage_nx( + {{"version", "2.0"}, {"hardware", "c32"}, {"stage", "x"}}); + TestLoadBalancerContext context_hardware_nx( + {{"version", "2.0"}, {"hardware", "zzz"}, {"stage", "dev"}}); + TestLoadBalancerContext context_match_host2( + {{"version", "2.0"}, {"hardware", "c32"}, {"stage", "dev"}}); + TestLoadBalancerContext context_ver_20({{"version", "2.0"}}); + TestLoadBalancerContext context_ver_stage_match_host2({{"version", "2.0"}, {"stage", "dev"}}); + TestLoadBalancerContext context_ver_stage_nx({{"version", "2.0"}, {"stage", "canary"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_ver_nx)); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_hardware_nx)); + EXPECT_EQ(nullptr, lb_->chooseHost(&context_stage_nx)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_match_host2)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_match_host2)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_20)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_stage_match_host2)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_stage_match_host2)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&context_ver_stage_nx)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&context_ver_stage_nx)); +} + +TEST_P(SubsetLoadBalancerTest, KeysSubsetFallbackChained) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = { + makeSelector( + {"stage"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK), + makeSelector( + {"stage", "version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, + {"stage"}), + makeSelector( + {"stage", "version", "hardware"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, + {"version", "stage"})}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"hardware", "c32"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:81", {{"version", "2.0"}, {"hardware", "c64"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:82", {{"version", "1.0"}, {"hardware", "c32"}, {"stage", "test"}}}}); + + TestLoadBalancerContext context_match_host0( + {{"version", "1.0"}, {"hardware", "c32"}, {"stage", "dev"}}); + TestLoadBalancerContext context_hw_nx( + {{"version", "2.0"}, {"hardware", "arm"}, {"stage", "dev"}}); + TestLoadBalancerContext context_ver_hw_nx( + {{"version", "1.2"}, {"hardware", "arm"}, {"stage", "dev"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_match_host0)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_hw_nx)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_hw_nx)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_ver_hw_nx)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_ver_hw_nx)); +} + +TEST_P(SubsetLoadBalancerTest, KeysSubsetFallbackToNotExistingSelector) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector subset_selectors = {makeSelector( + {"stage", "version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, + {"stage"})}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({{"tcp://127.0.0.1:80", {{"version", "1.0"}, {"stage", "dev"}}}}); + + TestLoadBalancerContext context_nx({{"version", "1.0"}, {"stage", "test"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_nx)); + EXPECT_EQ(1U, stats_.lb_subsets_fallback_.value()); +} + +TEST_P(SubsetLoadBalancerTest, MetadataFallbackList) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + EXPECT_CALL(subset_info_, metadataFallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::FALLBACK_LIST)); + + std::vector subset_selectors = {makeSelector({"version"})}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({{"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:81", {{"version", "2.0"}}}, + {"tcp://127.0.0.1:82", {{"version", "3.0"}}}}); + + const auto version1_host = host_set_.hosts_[0]; + const auto version2_host = host_set_.hosts_[1]; + const auto version3_host = host_set_.hosts_[2]; + + // No context. + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); + + TestLoadBalancerContext context_without_metadata({{"key", "value"}}); + context_without_metadata.matches_ = nullptr; + + // No metadata in context. + EXPECT_EQ(nullptr, lb_->chooseHost(&context_without_metadata)); + + TestLoadBalancerContext context_with_fallback({{"fallback_list", valueFromJson(R""""( + [ + {"version": "2.0"}, + {"version": "1.0"} + ] + )"""")}}); + + // version 2.0 is preferred, should be selected + EXPECT_EQ(version2_host, lb_->chooseHost(&context_with_fallback)); + EXPECT_EQ(version2_host, lb_->chooseHost(&context_with_fallback)); + EXPECT_EQ(version2_host, lb_->chooseHost(&context_with_fallback)); + + modifyHosts({}, {version2_host}); + + // version 1.0 is a fallback, should be used when host with version 2.0 is removed + EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_fallback)); + EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_fallback)); + EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_fallback)); + + // if fallback_list is not a list, it should be ignored + // regular metadata is in effect + ProtobufWkt::Value null_value; + null_value.set_null_value(ProtobufWkt::NullValue::NULL_VALUE); + TestLoadBalancerContext context_with_invalid_fallback_list_null( + {{"version", valueFromJson("\"3.0\"")}, {"fallback_list", null_value}}); + + EXPECT_EQ(version3_host, lb_->chooseHost(&context_with_invalid_fallback_list_null)); + EXPECT_EQ(version3_host, lb_->chooseHost(&context_with_invalid_fallback_list_null)); + + // should ignore fallback list entry which is not a struct + TestLoadBalancerContext context_with_invalid_fallback_list_entry( + {{"fallback_list", valueFromJson(R""""( + [ + "invalid string entry", + {"version": "1.0"} + ] + )"""")}}); + + EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_invalid_fallback_list_entry)); + EXPECT_EQ(version1_host, lb_->chooseHost(&context_with_invalid_fallback_list_entry)); + + // simple metadata with no fallback should work as usual + TestLoadBalancerContext context_no_fallback({{"version", "1.0"}}); + EXPECT_EQ(version1_host, lb_->chooseHost(&context_no_fallback)); + EXPECT_EQ(version1_host, lb_->chooseHost(&context_no_fallback)); + + // fallback metadata overrides regular metadata value + TestLoadBalancerContext context_fallback_overrides_metadata_value( + {{"version", valueFromJson("\"1.0\"")}, {"fallback_list", valueFromJson(R""""( + [ + {"hardware": "arm"}, + {"version": "5.0"}, + {"version": "3.0"} + ] + )"""")}}); + EXPECT_EQ(version3_host, lb_->chooseHost(&context_fallback_overrides_metadata_value)); + EXPECT_EQ(version3_host, lb_->chooseHost(&context_fallback_overrides_metadata_value)); +} + +TEST_P(SubsetLoadBalancerTest, MetadataFallbackDisabled) { + + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + EXPECT_CALL(subset_info_, metadataFallbackPolicy()) + .WillRepeatedly( + Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::METADATA_NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector({"fallback_list"})}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({{"tcp://127.0.0.1:80", {{"fallback_list", "lorem"}}}, + {"tcp://127.0.0.1:81", {{"fallback_list", "ipsum"}}}}); + + // should treat 'fallback_list' as a regular metadata key + TestLoadBalancerContext context({{"fallback_list", "ipsum"}}); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context)); +} + +TEST_P(SubsetLoadBalancerTest, MetadataFallbackAndSubsetFallback) { + + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + EXPECT_CALL(subset_info_, metadataFallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::FALLBACK_LIST)); + + std::vector subset_selectors = { + makeSelector( + {"hardware"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NO_FALLBACK), + makeSelector( + {"hardware", "stage"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::KEYS_SUBSET, + {"hardware"})}; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + init({{"tcp://127.0.0.1:80", {{"hardware", "c32"}, {"stage", "production"}}}, + {"tcp://127.0.0.1:81", {{"hardware", "c64"}, {"stage", "canary"}}}, + {"tcp://127.0.0.1:82", {{"hardware", "c64"}, {"stage", "production"}}}}); + + const auto c32_production_host = host_set_.hosts_[0]; + const auto c64_canary_host = host_set_.hosts_[1]; + const auto c64_production_host = host_set_.hosts_[2]; + + TestLoadBalancerContext context_canary_c32_preffered( + {{"stage", valueFromJson("\"canary\"")}, {"fallback_list", valueFromJson(R""""( + [ + {"hardware": "c32"}, + {"hardware": "c64"} + ] + )"""")}}); + + // Should select c32_production_host using first fallback entry, even + // when it doesn't match on requested 'stage' - because of the subset fallback policy. + // There is the c64_canary_host which exactly matches second fallback entry, but + // that entry is not used. + EXPECT_EQ(c32_production_host, lb_->chooseHost(&context_canary_c32_preffered)); + EXPECT_EQ(c32_production_host, lb_->chooseHost(&context_canary_c32_preffered)); + + TestLoadBalancerContext context_canary_c16_preffered( + {{"stage", valueFromJson("\"canary\"")}, {"fallback_list", valueFromJson(R""""( + [ + {"hardware": "c16"}, + {"hardware": "c64"} + ] + )"""")}}); + + // Should select c64_canary_host using second fallback entry. First fallback + // entry doesn't match anything even considering subset fallback policy. + EXPECT_EQ(c64_canary_host, lb_->chooseHost(&context_canary_c16_preffered)); + EXPECT_EQ(c64_canary_host, lb_->chooseHost(&context_canary_c16_preffered)); + + TestLoadBalancerContext context_unknown_or_c64({{"fallback_list", valueFromJson(R""""( + [ + {"unknown": "ipsum"}, + {"hardware": "c64"} + ] + )"""")}}); + + // should select any host using first fallback entry, because of ANY_ENDPOINT + // subset fallback policy + EXPECT_EQ(c32_production_host, lb_->chooseHost(&context_unknown_or_c64)); + EXPECT_EQ(c64_canary_host, lb_->chooseHost(&context_unknown_or_c64)); + EXPECT_EQ(c64_production_host, lb_->chooseHost(&context_unknown_or_c64)); +} + +INSTANTIATE_TEST_SUITE_P(UpdateOrderings, SubsetLoadBalancerTest, + testing::ValuesIn({UpdateOrder::RemovesFirst, UpdateOrder::Simultaneous})); + +class SubsetLoadBalancerSingleHostPerSubsetTest : public SubsetLoadBalancerTest { +public: + SubsetLoadBalancerSingleHostPerSubsetTest() + : default_subset_selectors_({ + makeSelector({"key"}, true), + }) { + ON_CALL(subset_info_, subsetSelectors()).WillByDefault(ReturnRef(default_subset_selectors_)); + ON_CALL(subset_info_, fallbackPolicy()) + .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + } + + using SubsetLoadBalancerTest::init; + void init() { + init({ + {"tcp://127.0.0.1:80", {}}, + {"tcp://127.0.0.1:81", {{"key", "a"}}}, + {"tcp://127.0.0.1:82", {{"key", "b"}}}, + + }); + } + + using SubsetLoadBalancerTest::makeSelector; + SubsetSelectorPtr makeSelector(const std::set& selector_keys, + bool single_host_per_subset) { + return makeSelector( + selector_keys, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED, {}, + single_host_per_subset); + } + + std::vector default_subset_selectors_; +}; + +TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, AcceptMultipleSelectors) { + std::vector subset_selectors = { + makeSelector({"version"}, false), + makeSelector({"stage"}, true), + }; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + ON_CALL(subset_info_, fallbackPolicy()) + .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + init({ + {"tcp://127.0.0.1:80", {}}, + {"tcp://127.0.0.1:81", {{"version", "v1"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:82", {{"version", "v1"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:83", {{"version", "v1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:84", {{"version", "v1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:85", {{"version", "v2"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:86", {{"version", "v2"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:87", {{"version", "v2"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:88", {{"version", "v2"}, {"stage", "prod"}}}, + }); + + TestLoadBalancerContext version_v1({{"version", "v1"}}); + TestLoadBalancerContext version_v2({{"version", "v2"}}); + TestLoadBalancerContext stage_dev({{"stage", "dev"}}); + TestLoadBalancerContext stage_prod({{"stage", "prod"}}); + TestLoadBalancerContext stage_test({{"stage", "test"}}); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&version_v1)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&version_v1)); + + EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&version_v2)); + EXPECT_EQ(host_set_.hosts_[6], lb_->chooseHost(&version_v2)); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&stage_dev)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&stage_dev)); + + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&stage_prod)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&stage_prod)); + + EXPECT_EQ(nullptr, lb_->chooseHost(&stage_test)); +} + +TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, AcceptMultipleKeys) { + std::vector subset_selectors = { + makeSelector({"version", "stage"}, true), + }; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + ON_CALL(subset_info_, fallbackPolicy()) + .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + init({ + {"tcp://127.0.0.1:80", {}}, + {"tcp://127.0.0.1:81", {{"version", "v1"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:82", {{"version", "v1"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:83", {{"version", "v1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:84", {{"version", "v1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:85", {{"version", "v2"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:86", {{"version", "v2"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:87", {{"version", "v2"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:88", {{"version", "v2"}, {"stage", "prod"}}}, + }); + + TestLoadBalancerContext v1_dev({{"version", "v1"}, {"stage", "dev"}}); + TestLoadBalancerContext v1_prod({{"version", "v1"}, {"stage", "prod"}}); + TestLoadBalancerContext v2_dev({{"version", "v2"}, {"stage", "dev"}}); + TestLoadBalancerContext v2_prod({{"version", "v2"}, {"stage", "prod"}}); + TestLoadBalancerContext v2_test({{"version", "v2"}, {"stage", "test"}}); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&v1_dev)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&v1_prod)); + EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&v2_dev)); + EXPECT_EQ(host_set_.hosts_[7], lb_->chooseHost(&v2_prod)); + EXPECT_EQ(nullptr, lb_->chooseHost(&v2_test)); +} + +TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, HybridMultipleSelectorsAndKeys) { + std::vector subset_selectors = { + makeSelector({"version", "stage"}, true), + makeSelector({"stage"}, false), + }; + + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + ON_CALL(subset_info_, fallbackPolicy()) + .WillByDefault(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + init({ + {"tcp://127.0.0.1:80", {}}, + {"tcp://127.0.0.1:81", {{"version", "v1"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:82", {{"version", "v1"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:83", {{"version", "v1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:84", {{"version", "v1"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:85", {{"version", "v2"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:86", {{"version", "v2"}, {"stage", "dev"}}}, + {"tcp://127.0.0.1:87", {{"version", "v2"}, {"stage", "prod"}}}, + {"tcp://127.0.0.1:88", {{"version", "v2"}, {"stage", "prod"}}}, + }); + + TestLoadBalancerContext v1_dev({{"version", "v1"}, {"stage", "dev"}}); + TestLoadBalancerContext v1_prod({{"version", "v1"}, {"stage", "prod"}}); + TestLoadBalancerContext v2_dev({{"version", "v2"}, {"stage", "dev"}}); + TestLoadBalancerContext v2_prod({{"version", "v2"}, {"stage", "prod"}}); + TestLoadBalancerContext v2_test({{"version", "v2"}, {"stage", "test"}}); + TestLoadBalancerContext stage_dev({{"stage", "dev"}}); + TestLoadBalancerContext stage_prod({{"stage", "prod"}}); + TestLoadBalancerContext stage_test({{"stage", "test"}}); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&v1_dev)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&v1_prod)); + EXPECT_EQ(host_set_.hosts_[5], lb_->chooseHost(&v2_dev)); + EXPECT_EQ(host_set_.hosts_[7], lb_->chooseHost(&v2_prod)); + EXPECT_EQ(nullptr, lb_->chooseHost(&v2_test)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&stage_dev)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&stage_dev)); + EXPECT_EQ(host_set_.hosts_[3], lb_->chooseHost(&stage_prod)); + EXPECT_EQ(host_set_.hosts_[4], lb_->chooseHost(&stage_prod)); + EXPECT_EQ(nullptr, lb_->chooseHost(&stage_test)); +} + +TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, DuplicateMetadataStat) { + init({ + {"tcp://127.0.0.1:80", {{"key", "a"}}}, + {"tcp://127.0.0.1:81", {{"key", "a"}}}, + {"tcp://127.0.0.1:82", {{"key", "a"}}}, + {"tcp://127.0.0.1:83", {{"key", "b"}}}, + }); + // The first 'a' is the original, the next 2 instances of 'a' are duplicates (counted + // in stat), and 'b' is another non-duplicate. + for (auto& gauge : stats_store_.gauges()) { + ENVOY_LOG_MISC(debug, "name {} value {}", gauge->name(), gauge->value()); + } + EXPECT_EQ(2, TestUtility::findGauge(stats_store_, + "testprefix.lb_subsets_single_host_per_subset_duplicate") + ->value()); +} + +TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, Match) { + init(); + + TestLoadBalancerContext host_1({{"key", "a"}}); + TestLoadBalancerContext host_2({{"key", "b"}}); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_1)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_1)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_2)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_2)); +} + +TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, FallbackOnUnknownMetadata) { + init(); + + TestLoadBalancerContext context_unknown_key({{"unknown", "unknown"}}); + TestLoadBalancerContext context_unknown_value({{"key", "unknown"}}); + + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&context_unknown_key)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&context_unknown_value)); +} + +TEST_P(SubsetLoadBalancerSingleHostPerSubsetTest, Update) { + init(); + + TestLoadBalancerContext host_a({{"key", "a"}}); + TestLoadBalancerContext host_b({{"key", "b"}}); + TestLoadBalancerContext host_c({{"key", "c"}}); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_b)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_b)); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&host_c)); // fallback + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_c)); // fallback + + HostSharedPtr added_host = makeHost("tcp://127.0.0.1:8000", {{"key", "c"}}); + + // Remove b, add c + modifyHosts({added_host}, {host_set_.hosts_.back()}); + + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_a)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_c)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_c)); + EXPECT_EQ(host_set_.hosts_[2], lb_->chooseHost(&host_b)); // fallback + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(&host_b)); // fallback + EXPECT_EQ(host_set_.hosts_[1], lb_->chooseHost(&host_b)); // fallback +} + +INSTANTIATE_TEST_SUITE_P(UpdateOrderings, SubsetLoadBalancerSingleHostPerSubsetTest, + testing::ValuesIn({UpdateOrder::RemovesFirst, UpdateOrder::Simultaneous})); // Test to improve coverage of the SubsetLoadBalancerFactory. TEST(LoadBalancerContextWrapperTest, LoadBalancingContextWrapperTest) { @@ -51,8 +3228,6 @@ TEST(LoadBalancerContextWrapperTest, LoadBalancingContextWrapperTest) { wrapper.overrideHostToSelect(); } -} // namespace -} // namespace Subset -} // namespace LoadBalancingPolices -} // namespace Extensions +} // namespace SubsetLoadBalancerTest +} // namespace Upstream } // namespace Envoy diff --git a/test/extensions/path/match/uri_template/library_test.cc b/test/extensions/path/match/uri_template/library_test.cc index ca10ecf37e81..e154986aa0e2 100644 --- a/test/extensions/path/match/uri_template/library_test.cc +++ b/test/extensions/path/match/uri_template/library_test.cc @@ -43,6 +43,36 @@ TEST(MatchTest, BasicUsage) { EXPECT_TRUE(matcher->match("/bar/en/us")); } +TEST(MatchTest, MatchDoubleEqualsInWildcard) { + const std::string yaml_string = R"EOF( + name: envoy.path.match.uri_template.uri_template_matcher + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig + path_template: "/bar/{lang}/{country=**}" +)EOF"; + + Router::PathMatcherSharedPtr matcher = createMatcherFromYaml(yaml_string); + EXPECT_EQ(matcher->uriTemplate(), "/bar/{lang}/{country=**}"); + EXPECT_EQ(matcher->name(), "envoy.path.match.uri_template.uri_template_matcher"); + + EXPECT_TRUE(matcher->match("/bar/en/us==")); +} + +TEST(MatchTest, MatchDoubleEquals) { + const std::string yaml_string = R"EOF( + name: envoy.path.match.uri_template.uri_template_matcher + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig + path_template: "/bar/{lang}/{country=**}" +)EOF"; + + Router::PathMatcherSharedPtr matcher = createMatcherFromYaml(yaml_string); + EXPECT_EQ(matcher->uriTemplate(), "/bar/{lang}/{country=**}"); + EXPECT_EQ(matcher->name(), "envoy.path.match.uri_template.uri_template_matcher"); + + EXPECT_TRUE(matcher->match("/bar/en==/us")); +} + } // namespace Match } // namespace UriTemplate } // namespace Extensions diff --git a/test/extensions/path/rewrite/uri_template/library_test.cc b/test/extensions/path/rewrite/uri_template/library_test.cc index bddbea1f3989..6d34771872e8 100644 --- a/test/extensions/path/rewrite/uri_template/library_test.cc +++ b/test/extensions/path/rewrite/uri_template/library_test.cc @@ -71,6 +71,34 @@ TEST(RewriteTest, BasicUsage) { EXPECT_EQ(rewriter->name(), "envoy.path.rewrite.uri_template.uri_template_rewriter"); } +TEST(RewriteTest, DoubleEqualAtEndOfPath) { + const std::string yaml_string = R"EOF( + name: envoy.path.rewrite.uri_template.uri_template_rewriter + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.rewrite.uri_template.v3.UriTemplateRewriteConfig + path_template_rewrite: "/bar/{country}/{final}" +)EOF"; + + Router::PathRewriterSharedPtr rewriter = createRewriterFromYaml(yaml_string); + EXPECT_EQ(rewriter->rewritePath("/bar/usa/final==1", "/bar/{final}/{country}").value(), + "/bar/final==1/usa"); + EXPECT_EQ(rewriter->name(), "envoy.path.rewrite.uri_template.uri_template_rewriter"); +} + +TEST(RewriteTest, DoubleEqual) { + const std::string yaml_string = R"EOF( + name: envoy.path.rewrite.uri_template.uri_template_rewriter + typed_config: + "@type": type.googleapis.com/envoy.extensions.path.rewrite.uri_template.v3.UriTemplateRewriteConfig + path_template_rewrite: "/bar/{country}/final" +)EOF"; + + Router::PathRewriterSharedPtr rewriter = createRewriterFromYaml(yaml_string); + EXPECT_EQ(rewriter->rewritePath("/bar/usa==/final", "/bar/{country}/final").value(), + "/bar/usa==/final"); + EXPECT_EQ(rewriter->name(), "envoy.path.rewrite.uri_template.uri_template_rewriter"); +} + TEST(RewriteTest, PatternNotMatched) { const std::string yaml_string = R"EOF( name: envoy.path.rewrite.uri_template.uri_template_rewriter diff --git a/test/extensions/path/uri_template_lib/uri_template_internal_test.cc b/test/extensions/path/uri_template_lib/uri_template_internal_test.cc index ab03e28c6f1c..254eef55eea4 100644 --- a/test/extensions/path/uri_template_lib/uri_template_internal_test.cc +++ b/test/extensions/path/uri_template_lib/uri_template_internal_test.cc @@ -49,7 +49,7 @@ TEST(InternalParsing, ParsedPathDebugString) { EXPECT_EQ(patt2.debugString(), "/{var}"); } -TEST(InternalParsing, isValidLiteralWorks) { +TEST(InternalParsing, IsValidLiteralWorks) { EXPECT_TRUE(isValidLiteral("123abcABC")); EXPECT_TRUE(isValidLiteral("._~-")); EXPECT_TRUE(isValidLiteral("-._~%20!$&'()+,;:@")); @@ -57,7 +57,7 @@ TEST(InternalParsing, isValidLiteralWorks) { EXPECT_FALSE(isValidLiteral("abc/")); EXPECT_FALSE(isValidLiteral("ab*c")); EXPECT_FALSE(isValidLiteral("a**c")); - EXPECT_FALSE(isValidLiteral("a=c")); + EXPECT_TRUE(isValidLiteral("a=c")); EXPECT_FALSE(isValidLiteral("?abc")); EXPECT_FALSE(isValidLiteral("?a=c")); EXPECT_FALSE(isValidLiteral("{abc")); @@ -75,7 +75,7 @@ TEST(InternalParsing, IsValidRewriteLiteralWorks) { EXPECT_FALSE(isValidRewriteLiteral("`~!@#$%^&()-_+;:,<.>'\"| ")); EXPECT_FALSE(isValidRewriteLiteral("ab}c")); EXPECT_FALSE(isValidRewriteLiteral("ab{c")); - EXPECT_FALSE(isValidRewriteLiteral("a=c")); + EXPECT_TRUE(isValidRewriteLiteral("a=c")); EXPECT_FALSE(isValidRewriteLiteral("?a=c")); } @@ -133,7 +133,8 @@ class ParseVariableSuccess : public testing::TestWithParam {}; INSTANTIATE_TEST_SUITE_P(ParseVariableSuccessTestSuite, ParseVariableSuccess, testing::Values("{var=*}", "{Var}", "{v1=**}", "{v_1=*/abc/**}", - "{v3=abc}", "{v=123/*/*}", "{var=abc/*/def}")); + "{v3=abc}", "{v=123/*/*}", "{var=abc/*/def}", + "{var=abc=def}")); TEST_P(ParseVariableSuccess, ParseVariableSuccessTest) { std::string pattern = GetParam(); @@ -151,8 +152,7 @@ class ParseVariableFailure : public testing::TestWithParam {}; INSTANTIATE_TEST_SUITE_P(ParseVariableFailureTestSuite, ParseVariableFailure, testing::Values("{var", "{=abc}", "{_var=*}", "{1v}", "{1v=abc}", "{var=***}", "{v-a-r}", "{var=*/abc?q=1}", "{var=abc/a*}", - "{var=*def/abc}", "{var=}", "{var=abc=def}", - "{rc=||||(A+yl/}", "/")); + "{var=*def/abc}", "{var=}", "{rc=||||(A+yl/}", "/")); TEST_P(ParseVariableFailure, ParseVariableFailureTest) { std::string pattern = GetParam(); @@ -268,8 +268,8 @@ TEST(InternalRegexGen, DollarSignMatchesIfself) { } TEST(InternalRegexGen, OperatorRegexPattern) { - EXPECT_EQ(toRegexPattern(Operator::PathGlob), "[a-zA-Z0-9-._~%!$&'()+,;:@]+"); - EXPECT_EQ(toRegexPattern(Operator::TextGlob), "[a-zA-Z0-9-._~%!$&'()+,;:@/]*"); + EXPECT_EQ(toRegexPattern(Operator::PathGlob), "[a-zA-Z0-9-._~%!$&'()+,;:@=]+"); + EXPECT_EQ(toRegexPattern(Operator::TextGlob), "[a-zA-Z0-9-._~%!$&'()+,;:@=/]*"); } TEST(InternalRegexGen, PathGlobRegex) { @@ -291,10 +291,10 @@ TEST(InternalRegexGen, TextGlobRegex) { } TEST(InternalRegexGen, VariableRegexPattern) { - EXPECT_EQ(toRegexPattern(Variable("var1", {})), "(?P[a-zA-Z0-9-._~%!$&'()+,;:@]+)"); + EXPECT_EQ(toRegexPattern(Variable("var1", {})), "(?P[a-zA-Z0-9-._~%!$&'()+,;:@=]+)"); EXPECT_EQ(toRegexPattern(Variable("var2", {Operator::PathGlob, "abc", Operator::TextGlob})), - "(?P[a-zA-Z0-9-._~%!$&'()+,;:@]+/abc/" - "[a-zA-Z0-9-._~%!$&'()+,;:@/]*)"); + "(?P[a-zA-Z0-9-._~%!$&'()+,;:@=]+/abc/" + "[a-zA-Z0-9-._~%!$&'()+,;:@=/]*)"); } TEST(InternalRegexGen, VariableRegexDefaultMatch) { diff --git a/test/extensions/path/uri_template_lib/uri_template_test.cc b/test/extensions/path/uri_template_lib/uri_template_test.cc index abebf4833a01..baa72a43d431 100644 --- a/test/extensions/path/uri_template_lib/uri_template_test.cc +++ b/test/extensions/path/uri_template_lib/uri_template_test.cc @@ -32,15 +32,15 @@ static constexpr absl::string_view kCaptureRegex = "/(?P[a-zA-Z0-9-._~%!$& TEST(ConvertPathPattern, ValidPattern) { EXPECT_THAT(convertPathPatternSyntaxToRegex("/abc"), IsOkAndHolds("/abc")); EXPECT_THAT(convertPathPatternSyntaxToRegex("/**.mpd"), - IsOkAndHolds("/[a-zA-Z0-9-._~%!$&'()+,;:@/]*\\.mpd")); + IsOkAndHolds("/[a-zA-Z0-9-._~%!$&'()+,;:@=/]*\\.mpd")); EXPECT_THAT(convertPathPatternSyntaxToRegex("/api/*/{resource=*}/{method=**}"), - IsOkAndHolds("/api/[a-zA-Z0-9-._~%!$&'()+,;:@]+/" - "(?P[a-zA-Z0-9-._~%!$&'()+,;:@]+)/" - "(?P[a-zA-Z0-9-._~%!$&'()+,;:@/]*)")); + IsOkAndHolds("/api/[a-zA-Z0-9-._~%!$&'()+,;:@=]+/" + "(?P[a-zA-Z0-9-._~%!$&'()+,;:@=]+)/" + "(?P[a-zA-Z0-9-._~%!$&'()+,;:@=/]*)")); EXPECT_THAT(convertPathPatternSyntaxToRegex("/api/{VERSION}/{version}/{verSION}"), - IsOkAndHolds("/api/(?P[a-zA-Z0-9-._~%!$&'()+,;:@]+)/" - "(?P[a-zA-Z0-9-._~%!$&'()+,;:@]+)/" - "(?P[a-zA-Z0-9-._~%!$&'()+,;:@]+)")); + IsOkAndHolds("/api/(?P[a-zA-Z0-9-._~%!$&'()+,;:@=]+)/" + "(?P[a-zA-Z0-9-._~%!$&'()+,;:@=]+)/" + "(?P[a-zA-Z0-9-._~%!$&'()+,;:@=]+)")); } TEST(ConvertPathPattern, InvalidPattern) { diff --git a/test/extensions/tracers/datadog/tracer_test.cc b/test/extensions/tracers/datadog/tracer_test.cc index 1247b8a44f28..8a4e276fd3da 100644 --- a/test/extensions/tracers/datadog/tracer_test.cc +++ b/test/extensions/tracers/datadog/tracer_test.cc @@ -116,9 +116,13 @@ TEST_F(DatadogTracerTest, SpanProperties) { ASSERT_TRUE(maybe_dd_span); const datadog::tracing::Span& dd_span = *maybe_dd_span; - // Verify that the span has the expected service name, operation name, start - // time, and sampling decision. - EXPECT_EQ("do.thing", dd_span.name()); + // Verify that the span has the expected service name, operation name, + // resource name, start time, and sampling decision. + // Note that the `operation_name` we specified above becomes the + // `resource_name()` of the resulting Datadog span, while the Datadog span's + // `name()` (operation name) is hard-coded to "envoy.proxy." + EXPECT_EQ("envoy.proxy", dd_span.name()); + EXPECT_EQ("do.thing", dd_span.resource_name()); EXPECT_EQ("envoy", dd_span.service_name()); ASSERT_TRUE(dd_span.trace_segment().sampling_decision()); EXPECT_EQ(int(datadog::tracing::SamplingPriority::USER_DROP), diff --git a/test/integration/BUILD b/test/integration/BUILD index 26e405245a80..6b33bb785b74 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -535,6 +535,7 @@ envoy_cc_test( "//source/extensions/filters/http/buffer:config", "//source/extensions/load_balancing_policies/ring_hash:config", "//test/integration/filters:encode1xx_local_reply_config_lib", + "//test/integration/filters:local_reply_during_decoding_filter_lib", "//test/integration/filters:metadata_stop_all_filter_config_lib", "//test/integration/filters:on_local_reply_filter_config_lib", "//test/integration/filters:request_metadata_filter_config_lib", diff --git a/test/integration/cds_integration_test.cc b/test/integration/cds_integration_test.cc index 1956b21e39c5..aa3fb252c749 100644 --- a/test/integration/cds_integration_test.cc +++ b/test/integration/cds_integration_test.cc @@ -157,8 +157,10 @@ class CdsIntegrationTest : public Grpc::DeltaSotwDeferredClustersIntegrationPara cluster_creator_; }; -INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDeltaDeferredCluster, CdsIntegrationTest, - DELTA_SOTW_GRPC_CLIENT_DEFERRED_CLUSTERS_INTEGRATION_PARAMS); +INSTANTIATE_TEST_SUITE_P( + IpVersionsClientTypeDeltaDeferredCluster, CdsIntegrationTest, + DELTA_SOTW_GRPC_CLIENT_DEFERRED_CLUSTERS_INTEGRATION_PARAMS, + Grpc::DeltaSotwDeferredClustersIntegrationParamTest::protocolTestParamsToString); // 1) Envoy starts up with no static clusters (other than the CDS-over-gRPC server). // 2) Envoy is told of a cluster via CDS. @@ -303,8 +305,10 @@ class DeferredCreationClusterStatsTest : public CdsIntegrationTest { } }; -INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, DeferredCreationClusterStatsTest, - DELTA_SOTW_GRPC_CLIENT_DEFERRED_CLUSTERS_INTEGRATION_PARAMS); +INSTANTIATE_TEST_SUITE_P( + IpVersionsClientTypeDelta, DeferredCreationClusterStatsTest, + DELTA_SOTW_GRPC_CLIENT_DEFERRED_CLUSTERS_INTEGRATION_PARAMS, + Grpc::DeltaSotwDeferredClustersIntegrationParamTest::protocolTestParamsToString); // Test that DeferredCreationTrafficStats gets created and updated correctly. TEST_P(DeferredCreationClusterStatsTest, @@ -331,8 +335,9 @@ TEST_P(DeferredCreationClusterStatsTest, test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); EXPECT_EQ(test_server_->counter("cluster_manager.cluster_added")->value(), 3); // Now the cluster_1 stats are gone, as well as the lazy init wrapper. + test_server_->waitForCounterNonexistent("cluster.cluster_1.upstream_cx_total", + TestUtility::DefaultTimeout); EXPECT_EQ(test_server_->gauge("cluster.cluster_1.ClusterTrafficStats.initialized"), nullptr); - EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total"), nullptr); // No cluster_2 traffic stats. EXPECT_EQ(test_server_->gauge("cluster.cluster_2.ClusterTrafficStats.initialized")->value(), 0); @@ -362,14 +367,15 @@ TEST_P(DeferredCreationClusterStatsTest, test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); EXPECT_EQ(test_server_->counter("cluster_manager.cluster_added")->value(), 3); // Now the cluster_1 stats are gone. - EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total"), nullptr); + test_server_->waitForCounterNonexistent("cluster.cluster_1.upstream_cx_total", + TestUtility::DefaultTimeout); // cluster_2 traffic stats stays. EXPECT_EQ(test_server_->counter("cluster.cluster_2.upstream_cx_total")->value(), 0); } // Test that DeferredCreationTrafficStats with cluster_1 create-remove-create sequence. TEST_P(DeferredCreationClusterStatsTest, - DeferredCreationTrafficStatsWithClusterCreateDeleteRecrete) { + DeferredCreationTrafficStatsWithClusterCreateDeleteRecreate) { initializeDeferredCreationTest(/*enable_deferred_creation_stats=*/true); EXPECT_EQ(test_server_->gauge("cluster.cluster_1.ClusterTrafficStats.initialized")->value(), 0); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total"), nullptr); @@ -387,8 +393,9 @@ TEST_P(DeferredCreationClusterStatsTest, EXPECT_EQ(test_server_->gauge("cluster.cluster_2.ClusterTrafficStats.initialized")->value(), 0); EXPECT_EQ(test_server_->counter("cluster.cluster_2.upstream_cx_total"), nullptr); // Now the cluster_1 stats are gone, as well as the lazy init wrapper. + test_server_->waitForCounterNonexistent("cluster.cluster_1.upstream_cx_total", + TestUtility::DefaultTimeout); EXPECT_EQ(test_server_->gauge("cluster.cluster_1.ClusterTrafficStats.initialized"), nullptr); - EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total"), nullptr); // Now add cluster1 back. updateCluster(); test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); @@ -405,7 +412,7 @@ TEST_P(DeferredCreationClusterStatsTest, // Test that Non-DeferredCreationTrafficStats with cluster_1 create-remove-create sequence. TEST_P(DeferredCreationClusterStatsTest, - NonDeferredCreationTrafficStatsWithClusterCreateDeleteRecrete) { + NonDeferredCreationTrafficStatsWithClusterCreateDeleteRecreate) { initializeDeferredCreationTest(/*enable_deferred_creation_stats=*/false); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 0); @@ -420,7 +427,8 @@ TEST_P(DeferredCreationClusterStatsTest, // cluster_2 traffic stats created. EXPECT_EQ(test_server_->counter("cluster.cluster_2.upstream_cx_total")->value(), 0); // Now the cluster_1 stats are gone. - EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total"), nullptr); + test_server_->waitForCounterNonexistent("cluster.cluster_1.upstream_cx_total", + TestUtility::DefaultTimeout); // Now add cluster1 back. updateCluster(); test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); diff --git a/test/integration/drain_close_integration_test.cc b/test/integration/drain_close_integration_test.cc index ecfa06efe0f7..5ffaaabb2869 100644 --- a/test/integration/drain_close_integration_test.cc +++ b/test/integration/drain_close_integration_test.cc @@ -167,6 +167,60 @@ TEST_P(DrainCloseIntegrationTest, RepeatedAdminGracefulDrain) { ASSERT_TRUE(waitForPortAvailable(http_port)); } +TEST_P(DrainCloseIntegrationTest, AdminGracefulDrainSkipExit) { + drain_strategy_ = Server::DrainStrategy::Immediate; + drain_time_ = std::chrono::seconds(1); + initialize(); + uint32_t http_port = lookupPort("http"); + codec_client_ = makeHttpConnection(http_port); + + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_THAT(response->headers(), Http::HttpStatusIs("200")); + // The request is completed but the connection remains open. + EXPECT_TRUE(codec_client_->connected()); + + // Invoke /drain_listeners with graceful drain + BufferingStreamDecoderPtr admin_response = IntegrationUtil::makeSingleRequest( + lookupPort("admin"), "POST", "/drain_listeners?graceful&skip_exit", "", downstreamProtocol(), + version_); + EXPECT_EQ(admin_response->headers().Status()->value().getStringView(), "200"); + + // Listeners should remain open + EXPECT_EQ(test_server_->counter("listener_manager.listener_stopped")->value(), 0); + + response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_THAT(response->headers(), Http::HttpStatusIs("200")); + + // Connections will terminate on request complete + ASSERT_TRUE(codec_client_->waitForDisconnect()); + if (downstream_protocol_ == Http::CodecType::HTTP2) { + EXPECT_TRUE(codec_client_->sawGoAway()); + } else { + EXPECT_EQ("close", response->headers().getConnectionValue()); + } + + // New connections can still be made. + auto second_codec_client_ = makeRawHttpConnection(makeClientConnection(http_port), absl::nullopt); + EXPECT_TRUE(second_codec_client_->connected()); + + // Invoke /drain_listeners and shut down listeners. + second_codec_client_->rawConnection().close(Network::ConnectionCloseType::NoFlush); + admin_response = IntegrationUtil::makeSingleRequest( + lookupPort("admin"), "POST", "/drain_listeners", "", downstreamProtocol(), version_); + EXPECT_EQ(admin_response->headers().Status()->value().getStringView(), "200"); + + test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + ASSERT_TRUE(waitForPortAvailable(http_port)); +} + INSTANTIATE_TEST_SUITE_P(Protocols, DrainCloseIntegrationTest, testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( {Http::CodecType::HTTP1, Http::CodecType::HTTP2}, diff --git a/test/integration/filters/local_reply_during_decoding_filter.cc b/test/integration/filters/local_reply_during_decoding_filter.cc index 9af2df9e723d..c01cd9f09401 100644 --- a/test/integration/filters/local_reply_during_decoding_filter.cc +++ b/test/integration/filters/local_reply_during_decoding_filter.cc @@ -15,7 +15,12 @@ class LocalReplyDuringDecode : public Http::PassThroughFilter { public: constexpr static char name[] = "local-reply-during-decode"; - Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override { + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& request_headers, bool) override { + auto result = request_headers.get(Http::LowerCaseString("skip-local-reply")); + if (!result.empty() && result[0]->value() == "true") { + local_reply_skipped_ = true; + return Http::FilterHeadersStatus::Continue; + } decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError, "", nullptr, absl::nullopt, ""); return Http::FilterHeadersStatus::StopIteration; @@ -23,15 +28,18 @@ class LocalReplyDuringDecode : public Http::PassThroughFilter { // Due to the above local reply, this method should never be invoked in tests. Http::FilterDataStatus decodeData(Buffer::Instance&, bool) override { - ASSERT(false); + ASSERT(local_reply_skipped_); return Http::FilterDataStatus::Continue; } // Due to the above local reply, this method should never be invoked in tests. Http::FilterMetadataStatus decodeMetadata(Http::MetadataMap&) override { - ASSERT(false); + ASSERT(local_reply_skipped_); return Http::FilterMetadataStatus::Continue; } + +private: + bool local_reply_skipped_ = false; }; constexpr char LocalReplyDuringDecode::name[]; diff --git a/test/integration/hotrestart_handoff_test.py b/test/integration/hotrestart_handoff_test.py index 9a6e40af7e60..f3b01df58eed 100644 --- a/test/integration/hotrestart_handoff_test.py +++ b/test/integration/hotrestart_handoff_test.py @@ -1,10 +1,10 @@ """Tests the behavior of connection handoff between instances during hot restart. Specifically, tests that: -1. A tcp connection opened before hot restart begins continues to function during drain. -2. A tcp connection opened after hot restart begins while the old instance is still running - goes to the new instance. -TODO(ravenblack): perform the same tests for quic connections once they will work as expected. +1. TCP connections opened before hot restart begins continue to function during drain. +2. TCP connections opened after hot restart begins while the old instance is still running + go to the new instance. +TODO(ravenblack): perform the same tests for QUIC connections once they will work as expected. """ import asyncio @@ -38,8 +38,15 @@ def random_loopback_host(): # reason we want to keep it as low as possible without causing flaky failure. # # Ideally this would be adjusted (3x) for tsan and coverage runs, but making that -# possible for python is outside the scope of this test. +# possible for python is outside the scope of this test, so we're stuck using the +# 3x value for all tests. STARTUP_TOLERANCE_SECONDS = 10 + +# We send multiple requests in parallel and require them all to function correctly +# - this makes it so if something is flaky we're more likely to encounter it, and +# also tests that there's not an "only one" success situation. +PARALLEL_REQUESTS = 5 + UPSTREAM_SLOW_PORT = 54321 UPSTREAM_FAST_PORT = 54322 UPSTREAM_HOST = random_loopback_host() @@ -234,10 +241,14 @@ async def test_connection_handoffs(self) -> None: ) log.info("waiting for envoy ready") await _wait_for_envoy_epoch(0) - log.info("making request") - slow_response = _http_request(f"http://{ENVOY_HOST}:{ENVOY_PORT}/") - log.info("waiting for response to begin") - self.assertEqual(await anext(slow_response, None), b"start\n") + log.info("making requests") + slow_responses = [ + _http_request(f"http://{ENVOY_HOST}:{ENVOY_PORT}/") for i in range(PARALLEL_REQUESTS) + # TODO(ravenblack): add http3 slow requests + ] + log.info("waiting for responses to begin") + for response in slow_responses: + self.assertEqual(await anext(response, None), b"start\n") base_id = int(self.base_id_path.read_text()) log.info(f"starting envoy hot restart for base id {base_id}") envoy_process_2 = await asyncio.create_subprocess_exec( @@ -254,25 +265,43 @@ async def test_connection_handoffs(self) -> None: log.info("waiting for new envoy instance to begin") await _wait_for_envoy_epoch(1) log.info("sending request to fast upstream") - fast_response = await _full_http_request(f"http://{ENVOY_HOST}:{ENVOY_PORT}/") - self.assertEqual( - fast_response, "fast instance", - "new requests after hot restart begins should go to new cluster") + fast_responses = [ + _full_http_request(f"http://{ENVOY_HOST}:{ENVOY_PORT}/") + for i in range(PARALLEL_REQUESTS) + # TODO(ravenblack): add http3 requests + ] + for response in fast_responses: + self.assertEqual( + await response, "fast instance", + "new requests after hot restart begins should go to new cluster") + # Now wait for the slow request to complete, and make sure it still gets the # response from the old instance. log.info("waiting for completion of original slow request") t1 = datetime.now() - self.assertEqual(await anext(slow_response, None), b"end\n") + for response in slow_responses: + self.assertEqual(await anext(response, None), b"end\n") t2 = datetime.now() self.assertGreater( (t2 - t1).total_seconds(), 0.5, "slow request should be incomplete when the test waits for it, otherwise the test is not necessarily validating during-drain behavior" ) - self.assertIsNone(await anext(slow_response, None)) - log.info("shutting everything down") - envoy_process_1.terminate() - envoy_process_2.terminate() + for response in slow_responses: + self.assertIsNone(await anext(response, None)) + log.info("waiting for parent instance to terminate") await envoy_process_1.wait() + log.info("sending second request to fast upstream") + fast_responses = [ + _full_http_request(f"http://{ENVOY_HOST}:{ENVOY_PORT}/") + for i in range(PARALLEL_REQUESTS) + # TODO(ravenblack): add http3 requests + ] + for response in fast_responses: + self.assertEqual( + await response, "fast instance", + "new requests after old instance terminates should go to new cluster") + log.info("shutting child instance down") + envoy_process_2.terminate() await envoy_process_2.wait() diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 676ea9f5ab1d..f75f642ddb27 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -1096,12 +1096,26 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTime, GoAwayAfterTooManyResets) { const int total_streams = 100; config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", absl::StrCat(total_streams)); + autonomous_upstream_ = true; + autonomous_allow_incomplete_streams_ = true; initialize(); Http::TestRequestHeaderMapImpl headers{ {":method", "GET"}, {":path", "/healthcheck"}, {":scheme", "http"}, {":authority", "host"}}; codec_client_ = makeHttpConnection(lookupPort("http")); + + for (int i = 0; i < total_streams; ++i) { + // Send and wait + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + codec_client_->sendData(*request_encoder_, 0, true); + auto response = std::move(encoder_decoder.second); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + } for (int i = 0; i < total_streams; ++i) { + // Send and reset auto encoder_decoder = codec_client_->startRequest(headers); request_encoder_ = &encoder_decoder.first; auto response = std::move(encoder_decoder.second); @@ -1115,6 +1129,34 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTime, GoAwayAfterTooManyResets) { test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); } +TEST_P(MultiplexedIntegrationTestWithSimulatedTime, GoAwayQuicklyAfterTooManyResets) { + EXCLUDE_DOWNSTREAM_HTTP3; // Need to wait for the server to reset the stream + // before opening new one. + config_helper_.addRuntimeOverride("envoy.restart_features.send_goaway_for_premature_rst_streams", + "true"); + const int total_streams = 100; + config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", + absl::StrCat(total_streams)); + const int num_reset_streams = total_streams / 2; + initialize(); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "GET"}, {":path", "/healthcheck"}, {":scheme", "http"}, {":authority", "host"}}; + codec_client_ = makeHttpConnection(lookupPort("http")); + for (int i = 0; i < num_reset_streams; i++) { + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_client_->sendReset(*request_encoder_); + ASSERT_TRUE(response->waitForReset()); + } + + // Envoy should disconnect client due to premature reset check + ASSERT_TRUE(codec_client_->waitForDisconnect()); + test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", num_reset_streams); + test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); +} + TEST_P(MultiplexedIntegrationTestWithSimulatedTime, DontGoAwayAfterTooManyResetsForLongStreams) { EXCLUDE_DOWNSTREAM_HTTP3; // Need to wait for the server to reset the stream // before opening new one. @@ -2242,6 +2284,43 @@ TEST_P(Http2FrameIntegrationTest, MultipleRequests) { tcp_client_->close(); } +// Validate the request completion during processing of deferred list works. +TEST_P(Http2FrameIntegrationTest, MultipleRequestsDecodeHeadersEndsRequest) { + const int kRequestsSentPerIOCycle = 20; + // The local-reply-during-decode will call sendLocalReply, completing them + // when processing headers. This will cause the ConnectionManagerImpl::ActiveRequest + // object to be removed from the streams_ list during the onDeferredRequestProcessing call. + config_helper_.addFilter("{ name: local-reply-during-decode }"); + // Process more than 1 deferred request at a time to validate the removal of elements from + // the list does not break reverse iteration. + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "3"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = + Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a", + Http2Frame::DataFlags::EndStream); + absl::StrAppend(&buffer, std::string(data)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + // The local-reply-during-decode filter sends 500 status to the client + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::InternalServerError, frame.responseStatus()); + } + tcp_client_->close(); +} + TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailers) { const int kRequestsSentPerIOCycle = 20; autonomous_upstream_ = true; @@ -2281,6 +2360,59 @@ TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailers) { tcp_client_->close(); } +// Validate the request completion during processing of headers in the deferred requests, +// is ok, when deferred data and trailers are also present. +TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailersDecodeHeadersEndsRequest) { + const int kRequestsSentPerIOCycle = 20; + autonomous_upstream_ = true; + config_helper_.addFilter("{ name: local-reply-during-decode }"); + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "6"); + beginSession(); + + std::string buffer; + // Make every 4th request to be reset by the local-reply-during-decode filter, this will give a + // good distribution of removed requests from the deferred sequence. + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, + {"no_trailers", "1"}, + {"skip-local-reply", i % 4 ? "true" : "false"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a"); + absl::StrAppend(&buffer, std::string(data)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto trailers = Http2Frame::makeEmptyHeadersFrame( + Http2Frame::makeClientStreamId(i), + static_cast(Http::Http2::orFlags( + Http2Frame::HeadersFlags::EndStream, Http2Frame::HeadersFlags::EndHeaders))); + trailers.appendHeaderWithoutIndexing({"k", "v"}); + trailers.adjustPayloadSize(); + absl::StrAppend(&buffer, std::string(trailers)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + uint32_t stream_id = frame.streamId(); + // Client stream indexes are multiples of 2 starting at 1 + if ((stream_id / 2) % 4) { + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()) + << " for stream=" << stream_id; + } else { + EXPECT_EQ(Http2Frame::ResponseStatus::InternalServerError, frame.responseStatus()) + << " for stream=" << stream_id; + } + } + tcp_client_->close(); +} + TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequestsFollowedByReset) { // This number of requests stays below premature reset detection. const int kRequestsSentPerIOCycle = 20; @@ -2311,8 +2443,8 @@ TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequestsFollowedByReset) { // This test depends on an another patch with premature resets TEST_P(Http2FrameIntegrationTest, ResettingDeferredRequestsTriggersPrematureResetCheck) { const int kRequestsSentPerIOCycle = 20; - // Set premature stream count to the number of streams we are about to send - config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", "20"); + // Set premature stream count to twice the number of streams we are about to send. + config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", "40"); config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); beginSession(); diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 528a93d00d12..304164ef09fd 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -36,7 +36,6 @@ #include "test/test_common/utility.h" #include "quiche/quic/core/crypto/quic_client_session_cache.h" -#include "quiche/quic/core/http/quic_client_push_promise_index.h" #include "quiche/quic/core/quic_utils.h" #include "quiche/quic/test_tools/quic_sent_packet_manager_peer.h" #include "quiche/quic/test_tools/quic_session_peer.h" @@ -92,6 +91,8 @@ class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection { return AssertionFailure() << "Timed out waiting for path response\n"; } } + waiting_for_path_response_ = false; + saw_path_response_ = false; return AssertionSuccess(); } @@ -133,12 +134,42 @@ class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection { return EnvoyQuicClientConnection::OnHandshakeDoneFrame(frame); } + AssertionResult waitForNewCid(std::chrono::milliseconds timeout = TestUtility::DefaultTimeout) { + bool timer_fired = false; + if (!saw_new_cid_) { + Event::TimerPtr timer(dispatcher_.createTimer([this, &timer_fired]() -> void { + timer_fired = true; + dispatcher_.exit(); + })); + timer->enableTimer(timeout); + waiting_for_new_cid_ = true; + dispatcher_.run(Event::Dispatcher::RunType::Block); + if (timer_fired) { + return AssertionFailure() << "Timed out waiting for new cid\n"; + } + } + waiting_for_new_cid_ = false; + return AssertionSuccess(); + } + + bool OnNewConnectionIdFrame(const quic::QuicNewConnectionIdFrame& frame) override { + bool ret = EnvoyQuicClientConnection::OnNewConnectionIdFrame(frame); + saw_new_cid_ = true; + if (waiting_for_new_cid_) { + dispatcher_.exit(); + } + saw_new_cid_ = false; + return ret; + } + private: Event::Dispatcher& dispatcher_; bool saw_path_response_{false}; bool saw_handshake_done_{false}; + bool saw_new_cid_{false}; bool waiting_for_path_response_{false}; bool waiting_for_handshake_done_{false}; + bool waiting_for_new_cid_{false}; bool validation_failure_on_path_response_{false}; }; @@ -189,7 +220,7 @@ class QuicHttpIntegrationTestBase : public HttpIntegrationTest { (host.empty() ? transport_socket_factory_->clientContextConfig()->serverNameIndication() : host), static_cast(port), false}, - transport_socket_factory_->getCryptoConfig(), &push_promise_index_, *dispatcher_, + transport_socket_factory_->getCryptoConfig(), *dispatcher_, // Use smaller window than the default one to have test coverage of client codec buffer // exceeding high watermark. /*send_buffer_limit=*/2 * Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE, @@ -360,7 +391,6 @@ class QuicHttpIntegrationTestBase : public HttpIntegrationTest { } protected: - quic::QuicClientPushPromiseIndex push_promise_index_; quic::ParsedQuicVersionVector supported_versions_; EnvoyQuicConnectionHelper conn_helper_; EnvoyQuicAlarmFactory alarm_factory_; @@ -798,12 +828,20 @@ TEST_P(QuicHttpIntegrationTest, PortMigrationOnPathDegrading) { codec_client_->sendData(*request_encoder_, 1024u, false); ASSERT_TRUE(quic_connection_->waitForHandshakeDone()); - auto old_self_addr = quic_connection_->self_address(); - EXPECT_CALL(*option, setOption(_, _)).Times(3u); + + for (uint8_t i = 0; i < 5; i++) { + auto old_self_addr = quic_connection_->self_address(); + EXPECT_CALL(*option, setOption(_, _)).Times(3u); + quic_connection_->OnPathDegradingDetected(); + ASSERT_TRUE(quic_connection_->waitForPathResponse()); + auto self_addr = quic_connection_->self_address(); + EXPECT_NE(old_self_addr, self_addr); + ASSERT_TRUE(quic_connection_->waitForNewCid()); + } + + // port migration is disabled once socket switch limit is reached. + EXPECT_CALL(*option, setOption(_, _)).Times(0); quic_connection_->OnPathDegradingDetected(); - ASSERT_TRUE(quic_connection_->waitForPathResponse()); - auto self_addr = quic_connection_->self_address(); - EXPECT_NE(old_self_addr, self_addr); // Send the rest data. codec_client_->sendData(*request_encoder_, 1024u, true); diff --git a/test/integration/server.h b/test/integration/server.h index 5733782b577a..1cdc27b04aa4 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -463,6 +463,16 @@ class IntegrationTestServer : public Logger::Loggable, notifyingStatsAllocator().waitForCounterExists(name); } + void waitForCounterNonexistent(const std::string& name, + std::chrono::milliseconds timeout) override { + Event::TestTimeSystem::RealTimeBound bound(timeout); + while (TestUtility::findCounter(statStore(), name) != nullptr) { + time_system_.advanceTimeWait(std::chrono::milliseconds(10)); + ASSERT_FALSE(!bound.withinBound()) + << "timed out waiting for counter " << name << " to not exist."; + } + } + void waitForProactiveOverloadResourceUsageEq( const Server::OverloadProactiveResourceName resource_name, int64_t value, Event::Dispatcher& dispatcher, diff --git a/test/integration/server_stats.h b/test/integration/server_stats.h index 04384db7d8be..45d08d3bdbb2 100644 --- a/test/integration/server_stats.h +++ b/test/integration/server_stats.h @@ -38,6 +38,14 @@ class IntegrationTestServerStats { */ virtual void waitForCounterExists(const std::string& name) PURE; + /** + * Wait for a counter to not exist. + * @param name counter name. + * @param timeout amount of time to wait for the counter to not exist. + */ + virtual void waitForCounterNonexistent(const std::string& name, + std::chrono::milliseconds timeout) PURE; + /** * Wait until a histogram has samples. * @param name histogram name. diff --git a/test/mocks/router/mocks.cc b/test/mocks/router/mocks.cc index f0b17e19e3b8..862b99b7848e 100644 --- a/test/mocks/router/mocks.cc +++ b/test/mocks/router/mocks.cc @@ -74,6 +74,8 @@ MockShadowWriter::~MockShadowWriter() = default; MockVirtualHost::MockVirtualHost() { ON_CALL(*this, name()).WillByDefault(ReturnRef(name_)); ON_CALL(*this, rateLimitPolicy()).WillByDefault(ReturnRef(rate_limit_policy_)); + ON_CALL(*this, metadata()).WillByDefault(ReturnRef(metadata_)); + ON_CALL(*this, typedMetadata()).WillByDefault(ReturnRef(typed_metadata_)); } MockVirtualHost::~MockVirtualHost() = default; @@ -129,6 +131,8 @@ MockConfig::MockConfig() : route_(new NiceMock()) { ON_CALL(*this, internalOnlyHeaders()).WillByDefault(ReturnRef(internal_only_headers_)); ON_CALL(*this, name()).WillByDefault(ReturnRef(name_)); ON_CALL(*this, usesVhds()).WillByDefault(Return(false)); + ON_CALL(*this, metadata()).WillByDefault(ReturnRef(metadata_)); + ON_CALL(*this, typedMetadata()).WillByDefault(ReturnRef(typed_metadata_)); } MockConfig::~MockConfig() = default; diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index e7e3e7af36bb..e16cf8691f49 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -42,6 +42,17 @@ namespace Envoy { namespace Router { using ::testing::NiceMock; +struct MockRouteMetadataObj : public Envoy::Config::TypedMetadata::Object {}; +class MockRouteMetadata : public Envoy::Config::TypedMetadata { +public: + const Envoy::Config::TypedMetadata::Object* getData(const std::string&) const override { + return &object_; + } + +private: + MockRouteMetadataObj object_; +}; + class MockDirectResponseEntry : public DirectResponseEntry { public: MockDirectResponseEntry(); @@ -313,6 +324,8 @@ class MockVirtualHost : public VirtualHost { MOCK_METHOD(void, traversePerFilterConfig, (const std::string&, std::function), (const)); + MOCK_METHOD(const envoy::config::core::v3::Metadata&, metadata, (), (const)); + MOCK_METHOD(const Envoy::Config::TypedMetadata&, typedMetadata, (), (const)); Stats::StatName statName() const override { stat_name_ = std::make_unique(name(), *symbol_table_); @@ -324,6 +337,8 @@ class MockVirtualHost : public VirtualHost { mutable std::unique_ptr stat_name_; testing::NiceMock rate_limit_policy_; TestCorsPolicy cors_policy_; + envoy::config::core::v3::Metadata metadata_; + MockRouteMetadata typed_metadata_; }; class MockHashPolicy : public Http::HashPolicy { @@ -486,16 +501,6 @@ class MockRouteTracing : public RouteTracing { class MockRoute : public Route { public: - struct MockRouteMetadataObj : public Envoy::Config::TypedMetadata::Object {}; - class MockRouteMetadata : public Envoy::Config::TypedMetadata { - public: - const Envoy::Config::TypedMetadata::Object* getData(const std::string&) const override { - return &object_; - } - - private: - MockRouteMetadataObj object_; - }; MockRoute(); ~MockRoute() override; @@ -543,10 +548,14 @@ class MockConfig : public Config { MOCK_METHOD(bool, usesVhds, (), (const)); MOCK_METHOD(bool, mostSpecificHeaderMutationsWins, (), (const)); MOCK_METHOD(uint32_t, maxDirectResponseBodySizeBytes, (), (const)); + MOCK_METHOD(const envoy::config::core::v3::Metadata&, metadata, (), (const)); + MOCK_METHOD(const Envoy::Config::TypedMetadata&, typedMetadata, (), (const)); std::shared_ptr route_; std::list internal_only_headers_; std::string name_{"fake_config"}; + envoy::config::core::v3::Metadata metadata_; + MockRouteMetadata typed_metadata_; }; class MockRouteConfigProvider : public RouteConfigProvider { diff --git a/test/mocks/tracing/mocks.h b/test/mocks/tracing/mocks.h index 02518f454da2..665dd4563366 100644 --- a/test/mocks/tracing/mocks.h +++ b/test/mocks/tracing/mocks.h @@ -62,14 +62,13 @@ class MockTracer : public Tracer { SpanPtr startSpan(const Config& config, TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, - const Tracing::Decision tracing_decision) override { + Tracing::Decision tracing_decision) override { return SpanPtr{startSpan_(config, trace_context, stream_info, tracing_decision)}; } MOCK_METHOD(Span*, startSpan_, (const Config& config, TraceContext& trace_context, - const StreamInfo::StreamInfo& stream_info, - const Tracing::Decision tracing_decision)); + const StreamInfo::StreamInfo& stream_info, Tracing::Decision tracing_decision)); }; class MockDriver : public Driver { @@ -79,7 +78,7 @@ class MockDriver : public Driver { SpanPtr startSpan(const Config& config, TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision) override { + Tracing::Decision tracing_decision) override { return SpanPtr{ startSpan_(config, trace_context, stream_info, operation_name, tracing_decision)}; } @@ -87,7 +86,7 @@ class MockDriver : public Driver { MOCK_METHOD(Span*, startSpan_, (const Config& config, TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, const std::string& operation_name, - const Tracing::Decision tracing_decision)); + Tracing::Decision tracing_decision)); }; class MockTracerManager : public TracerManager { diff --git a/test/server/admin/admin_test.cc b/test/server/admin/admin_test.cc index c2cb8593c545..6835226edeb6 100644 --- a/test/server/admin/admin_test.cc +++ b/test/server/admin/admin_test.cc @@ -151,6 +151,7 @@ TEST_P(AdminInstanceTest, Help) { enable: enables the CPU profiler; One of (y, n) /drain_listeners (POST): drain listeners graceful: When draining listeners, enter a graceful drain period prior to closing listeners. This behaviour and duration is configurable via server options or CLI + skip_exit: When draining listeners, do not exit after the drain period. This must be used with graceful inboundonly: Drains all inbound listeners. traffic_direction field in envoy_v3_api_msg_config.listener.v3.Listener is used to determine whether a listener is inbound or outbound. /healthcheck/fail (POST): cause the server to fail health checks /healthcheck/ok (POST): cause the server to pass health checks diff --git a/test/server/admin/logs_handler_test.cc b/test/server/admin/logs_handler_test.cc index 1bc8be70925d..e3226a3c9bd7 100644 --- a/test/server/admin/logs_handler_test.cc +++ b/test/server/admin/logs_handler_test.cc @@ -45,10 +45,11 @@ TEST_P(AdminInstanceTest, LogLevelSetting) { // Test multiple log levels with invalid logger name const std::string file_not_exists = "xxxxxxxxxx_not_exists_xxxxxxxxxxx"; query = fmt::format("/logging?paths={}:warning,{}:warning", __FILE__, file_not_exists); - EXPECT_EQ(Http::Code::BadRequest, postCallback(query, header_map, response)); - FINE_GRAIN_LOG(trace, "After post 3: level should not change due to invalid logger name!"); - EXPECT_THAT(response.toString(), HasSubstr("error: unknown logger name\n")); - EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::info); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(trace, + "After post 3: level should be changed if there is a match with an OK response."); + EXPECT_THAT(response.toString(), HasSubstr("active loggers:\n")); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::warn); EXPECT_THAT(getFineGrainLogContext().getFineGrainLogEntry(file_not_exists), IsNull()); // Test multiple log levels at once @@ -70,5 +71,89 @@ TEST_P(AdminInstanceTest, LogLevelSetting) { EXPECT_EQ(Http::Code::OK, postCallback("/logging?level=warning&paths=", header_map, response)); } +TEST_P(AdminInstanceTest, LogLevelFineGrainGlobSupport) { + Http::TestResponseHeaderMapImpl header_map; + Buffer::OwnedImpl response; + + // Enable fine grain logger right now. + Logger::Context::enableFineGrainLogger(); + postCallback("/logging", header_map, response); + FINE_GRAIN_LOG(error, response.toString()); + + EXPECT_EQ(Http::Code::OK, postCallback("/logging?level=trace", header_map, response)); + FINE_GRAIN_LOG(warn, "After post /logging?level=trace, all level is trace now!"); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::trace); + + std::string query = fmt::format("/logging?{}=info", "logs_handler_test"); + postCallback(query, header_map, response); + FINE_GRAIN_LOG(info, "After post {}, level for this file is info now!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::info); + + // Test multiple log levels at once + const std::string file_one = "admin/logs_handler_test_one.cc"; + const std::string file_two = "admin/logs_handler_test_two.cc"; + std::atomic logger_one; + std::atomic logger_two; + getFineGrainLogContext().initFineGrainLogger(file_one, logger_one); + getFineGrainLogContext().initFineGrainLogger(file_two, logger_two); + query = fmt::format("/logging?{}=critical", "logs_handle*"); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(critical, "After post {}, level for this file is critical now!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), + spdlog::level::critical); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_one)->level(), + spdlog::level::critical); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_two)->level(), + spdlog::level::critical); + + query = fmt::format("/logging?paths={}:warning", "admin/*"); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(trace, "After post {}, level for this file is trace (the default) now!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::trace); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_one)->level(), spdlog::level::warn); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_two)->level(), spdlog::level::warn); + + query = fmt::format("/logging?paths={}:info", "admin/logs_handler_test????.cc"); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(trace, "After post {}, level for this file is still trace!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::trace); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_one)->level(), spdlog::level::info); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_two)->level(), spdlog::level::info); + + query = fmt::format("/logging?paths={}:warning", "*admin/logs_handler_test*"); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(warn, "After post {}, level for this file is warn now!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::warn); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_one)->level(), spdlog::level::warn); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_two)->level(), spdlog::level::warn); + + // Only first glob match takes effect. + query = + fmt::format("/logging?paths={}:warning,{}:info", "logs_handler_test*", "logs_handler_test*"); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(warn, "After post {}, level for this file is warn now!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::warn); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_one)->level(), spdlog::level::warn); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_two)->level(), spdlog::level::warn); + + // The first glob or base-name match takes effect. + query = fmt::format("/logging?paths={}:warning,{}:info", "logs_handler_test_one", + "logs_handler_test*"); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(info, "After post {}, level for this file is info now!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), spdlog::level::info); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_one)->level(), spdlog::level::warn); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_two)->level(), spdlog::level::info); + + // The level of unmatched loggers will be the default. + query = fmt::format("/logging?paths={}:critical", "logs_handler_test"); + EXPECT_EQ(Http::Code::OK, postCallback(query, header_map, response)); + FINE_GRAIN_LOG(info, "After post {}, level for this file is info now!", query); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(__FILE__)->level(), + spdlog::level::critical); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_one)->level(), spdlog::level::trace); + EXPECT_EQ(getFineGrainLogContext().getFineGrainLogEntry(file_two)->level(), spdlog::level::trace); +} + } // namespace Server } // namespace Envoy diff --git a/tools/base/BUILD b/tools/base/BUILD index 7f30400f1b00..494b97fc6ec9 100644 --- a/tools/base/BUILD +++ b/tools/base/BUILD @@ -1,3 +1,4 @@ +load("@rules_python//python:pip.bzl", "compile_pip_requirements") load("//bazel:envoy_build_system.bzl", "envoy_package") licenses(["notice"]) # Apache 2 @@ -7,3 +8,13 @@ envoy_package() exports_files([ "entry_point.py", ]) + +compile_pip_requirements( + name = "requirements", + extra_args = [ + "--allow-unsafe", + "--generate-hashes", + "--reuse-hashes", + "--resolver=backtracking", + ], +) diff --git a/tools/base/requirements.in b/tools/base/requirements.in index ff2ed1d70663..7a5c23a9517a 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -2,6 +2,7 @@ abstracts>=0.0.12 aio.api.bazel aio.api.github>=0.2.5 aiohttp>=3.8.1 +aioquic>=0.9.21 cffi>=1.15.0 clang-format==14.0.6 clang-tidy==14.0.6 @@ -9,7 +10,7 @@ colorama coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 -envoy.base.utils>=0.4.12 +envoy.base.utils>=0.4.16 envoy.code.check>=0.5.8 envoy.dependency.check>=0.1.10 envoy.distribution.release>=0.0.9 @@ -21,7 +22,6 @@ envoy.gpg.sign>=0.2.0 flake8>=6 frozendict>=2.3.7 gitpython -google-cloud-storage gsutil jinja2 multidict>=6.0.2 @@ -35,7 +35,6 @@ pygithub pyreadline pyyaml setuptools -slackclient slack_sdk sphinx>=7 sphinxcontrib.googleanalytics @@ -43,3 +42,10 @@ thrift verboselogs yapf yarl>=1.7.2 + +# Remove when https://github.com/sphinx-doc/sphinx/issues/11567 is finally resolved +sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 0d601cd47076..ae3d73aefbee 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -2,12 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes requirements.in +# bazel run //tools/base:requirements.update # abstracts==0.0.12 \ --hash=sha256:acc01ff56c8a05fb88150dff62e295f9071fc33388c42f1dfc2787a8d1c755ff # via - # -r requirements.in + # -r tools/base/requirements.in # aio-api-bazel # aio-api-github # aio-api-nist @@ -24,12 +24,12 @@ abstracts==0.0.12 \ aio-api-bazel==0.0.2 \ --hash=sha256:56e36463d236e477b7e282f2d870185a0b978b50e2c3803c1ebf8b8ac4b18f5b \ --hash=sha256:d3f563b7698e874437d80538a89dd4d79bc37de2e850c846330ae456e3f21dcc - # via -r requirements.in + # via -r tools/base/requirements.in aio-api-github==0.2.5 \ --hash=sha256:301a357209831ac2bc0fb5c79f8b8795a5363da5cabc2229f10155bdb6d42f5d \ --hash=sha256:3532d0892e875e8bb6b188c0beba4e8bac9d5147e249ce987bb2beef1e7b711e # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-base-utils # envoy-dependency-check aio-api-nist==0.0.3 \ @@ -82,9 +82,9 @@ aiodocker==0.21.0 \ # envoy-distribution-distrotest # envoy-distribution-verify # envoy-docker-utils -aiofiles==23.1.0 \ - --hash=sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2 \ - --hash=sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635 +aiofiles==23.2.1 \ + --hash=sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107 \ + --hash=sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a # via envoy-github-release aiohttp==3.8.6 \ --hash=sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c \ @@ -175,7 +175,7 @@ aiohttp==3.8.6 \ --hash=sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df \ --hash=sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479 # via - # -r requirements.in + # -r tools/base/requirements.in # aio-api-github # aio-api-nist # aiodocker @@ -184,7 +184,27 @@ aiohttp==3.8.6 \ # envoy-github-abstract # envoy-github-release # google-auth - # slackclient +aioquic==0.9.21 \ + --hash=sha256:11c383563c35e6b1a2928d84679edf820bcf906415a01286256f35a5d38c1e10 \ + --hash=sha256:179370300ff722303caf4de81cdad9ed878ad64f47f16d44ac943ad7f113fa3a \ + --hash=sha256:26170f9df96c4ac7cb5ebb2018694b8c631e3f36f6052be3ef40b0011d324beb \ + --hash=sha256:309fbbd377d98f77aa4122d00e45e1e688e765a2713dcd670ac0c698f440014f \ + --hash=sha256:415d061d1715f1f7d75ddf8979b078a38e413b2e4c1aef345ef75841a27c20c5 \ + --hash=sha256:464bb8509558571cf37467c5327339b136d3d066b50f8e4595f255898f080baa \ + --hash=sha256:465c15a4d9fbd3e2eeef992f44894e61d87bda5b9e545bc3848e590234b0c167 \ + --hash=sha256:4c146ed6ed9c2b17c827f463465ff3283560036d8f276dc09fa6eeeae8f7742e \ + --hash=sha256:79c7ec06318e785627665ca4e8723adf347b7225b7d006e332d2575a1f516ef5 \ + --hash=sha256:a4e17734d317fa4f4efe7ed0e4019ce6e3c6dae656200886f0b39cd9d9d5272a \ + --hash=sha256:ab74622fcb1f24fd46f836255066fd5da746a72d5a921a24982b002609f68448 \ + --hash=sha256:afc9aed9accd9a968c76659c198eb9105b64fbcd60827449d590780b8d650c3b \ + --hash=sha256:b8dc4bd7e72f044fcbca69cd31a08df9cfc30646601b9cca11bd106efe09627a \ + --hash=sha256:bd6df9cb0a3091b981978b4b5479da0fbe1d1ae363733f93f8da2ba107f64146 \ + --hash=sha256:c062a5786b4a69017ddc8301da255aed4db6d7ef6339fc5b5f8cea8a57cae65a \ + --hash=sha256:c5ac4c9b930a474cea9e6f73212e78b2310c4d8857a791e27fe16e74f0702ec9 \ + --hash=sha256:c627ed67f008cc020e67e20872c844c60c2dcc45d73e6f1d035c399c2953a974 \ + --hash=sha256:efbf4de92df68d11a42909b4750430a61380d32e2b5d5da1525f6e482a09f60c \ + --hash=sha256:f6d9f6f1f778875fc8bd06b387b5916f48f610a698eee31385688ab8ba696fcf + # via -r requirements.in aiosignal==1.3.1 \ --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ --hash=sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17 @@ -193,21 +213,21 @@ alabaster==0.7.13 \ --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \ --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2 # via sphinx -argcomplete==3.1.1 \ - --hash=sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948 \ - --hash=sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff +argcomplete==3.1.2 \ + --hash=sha256:d5d1e5efd41435260b8f85673b74ea2e883affcbec9f4230c582689e8e78251b \ + --hash=sha256:d97c036d12a752d1079f190bc1521c545b941fda89ad85d15afa909b4d1b9a99 # via gsutil -async-timeout==4.0.2 \ - --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ - --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c +async-timeout==4.0.3 \ + --hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \ + --hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028 # via aiohttp attrs==23.1.0 \ --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via aiohttp -babel==2.12.1 \ - --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \ - --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455 +babel==2.13.0 \ + --hash=sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210 \ + --hash=sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec # via sphinx boto==2.49.0 \ --hash=sha256:147758d41ae7240dc989f0039f27da8ca0d53734be0eb869ef16e3adcfa462e8 \ @@ -220,7 +240,9 @@ cachetools==5.3.1 \ certifi==2023.7.22 \ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 - # via requests + # via + # aioquic + # requests cffi==1.16.0 \ --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ @@ -275,85 +297,100 @@ cffi==1.16.0 \ --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 # via - # -r requirements.in + # -r tools/base/requirements.in # cryptography # pynacl -charset-normalizer==3.2.0 \ - --hash=sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96 \ - --hash=sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c \ - --hash=sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710 \ - --hash=sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706 \ - --hash=sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020 \ - --hash=sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252 \ - --hash=sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad \ - --hash=sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329 \ - --hash=sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a \ - --hash=sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f \ - --hash=sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6 \ - --hash=sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4 \ - --hash=sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a \ - --hash=sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46 \ - --hash=sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2 \ - --hash=sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23 \ - --hash=sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace \ - --hash=sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd \ - --hash=sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982 \ - --hash=sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10 \ - --hash=sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2 \ - --hash=sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea \ - --hash=sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09 \ - --hash=sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5 \ - --hash=sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149 \ - --hash=sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489 \ - --hash=sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9 \ - --hash=sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80 \ - --hash=sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592 \ - --hash=sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3 \ - --hash=sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6 \ - --hash=sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed \ - --hash=sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c \ - --hash=sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200 \ - --hash=sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a \ - --hash=sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e \ - --hash=sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d \ - --hash=sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6 \ - --hash=sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623 \ - --hash=sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669 \ - --hash=sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3 \ - --hash=sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa \ - --hash=sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9 \ - --hash=sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2 \ - --hash=sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f \ - --hash=sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1 \ - --hash=sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4 \ - --hash=sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a \ - --hash=sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8 \ - --hash=sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3 \ - --hash=sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029 \ - --hash=sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f \ - --hash=sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959 \ - --hash=sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22 \ - --hash=sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7 \ - --hash=sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952 \ - --hash=sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346 \ - --hash=sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e \ - --hash=sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d \ - --hash=sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299 \ - --hash=sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd \ - --hash=sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a \ - --hash=sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3 \ - --hash=sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037 \ - --hash=sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94 \ - --hash=sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c \ - --hash=sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858 \ - --hash=sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a \ - --hash=sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449 \ - --hash=sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c \ - --hash=sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918 \ - --hash=sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1 \ - --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \ - --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \ - --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa +charset-normalizer==3.3.0 \ + --hash=sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843 \ + --hash=sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786 \ + --hash=sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e \ + --hash=sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8 \ + --hash=sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4 \ + --hash=sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa \ + --hash=sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d \ + --hash=sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82 \ + --hash=sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7 \ + --hash=sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895 \ + --hash=sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d \ + --hash=sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a \ + --hash=sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382 \ + --hash=sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678 \ + --hash=sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b \ + --hash=sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e \ + --hash=sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741 \ + --hash=sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4 \ + --hash=sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596 \ + --hash=sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9 \ + --hash=sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69 \ + --hash=sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c \ + --hash=sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77 \ + --hash=sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13 \ + --hash=sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459 \ + --hash=sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e \ + --hash=sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7 \ + --hash=sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908 \ + --hash=sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a \ + --hash=sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f \ + --hash=sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8 \ + --hash=sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482 \ + --hash=sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d \ + --hash=sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d \ + --hash=sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545 \ + --hash=sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34 \ + --hash=sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86 \ + --hash=sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6 \ + --hash=sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe \ + --hash=sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e \ + --hash=sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc \ + --hash=sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7 \ + --hash=sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd \ + --hash=sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c \ + --hash=sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557 \ + --hash=sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a \ + --hash=sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89 \ + --hash=sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078 \ + --hash=sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e \ + --hash=sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4 \ + --hash=sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403 \ + --hash=sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0 \ + --hash=sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89 \ + --hash=sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115 \ + --hash=sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9 \ + --hash=sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05 \ + --hash=sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a \ + --hash=sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec \ + --hash=sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56 \ + --hash=sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38 \ + --hash=sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479 \ + --hash=sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c \ + --hash=sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e \ + --hash=sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd \ + --hash=sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186 \ + --hash=sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455 \ + --hash=sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c \ + --hash=sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65 \ + --hash=sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78 \ + --hash=sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287 \ + --hash=sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df \ + --hash=sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43 \ + --hash=sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1 \ + --hash=sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7 \ + --hash=sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989 \ + --hash=sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a \ + --hash=sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63 \ + --hash=sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884 \ + --hash=sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649 \ + --hash=sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810 \ + --hash=sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828 \ + --hash=sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4 \ + --hash=sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2 \ + --hash=sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd \ + --hash=sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5 \ + --hash=sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe \ + --hash=sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293 \ + --hash=sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e \ + --hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e \ + --hash=sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8 # via # aiohttp # requests @@ -367,7 +404,7 @@ clang-format==14.0.6 \ --hash=sha256:d780c04334bca80f2b60d25bf53c37bd0618520ee295a7888a11f25bde114ac4 \ --hash=sha256:d7c1c5e404c58e55f0170f01b3c5611dce6c119e62b5d1020347e0ad97d5a047 \ --hash=sha256:dbfd60528eb3bb7d7cfe8576faa70845fbf93601f815ef75163d36606e87f388 - # via -r requirements.in + # via -r tools/base/requirements.in clang-tidy==14.0.6 \ --hash=sha256:02bce40a56cc344e20d2f63bef6b85acf9837954559e0091804d6e748dfc0359 \ --hash=sha256:173a757415108095b541eb9a2d0c222d41f5624e7bb5b98772476957228ce2c7 \ @@ -377,18 +414,18 @@ clang-tidy==14.0.6 \ --hash=sha256:c9ffcb91f17ee920fdd7a83f30484f3cb4c183f7b490d092373e4a6f2c82729d \ --hash=sha256:d595b8e9a155d63b6b9dec0afa62725590626c9f0e945c3d9e448a28e0082b39 \ --hash=sha256:fef62fb706adccef94128761ca0796973a196e2d60fb938a312cfa2bc59730bd - # via -r requirements.in + # via -r tools/base/requirements.in colorama==0.4.6 \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-docs-sphinx-runner coloredlogs==15.0.1 \ --hash=sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934 \ --hash=sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0 # via - # -r requirements.in + # -r tools/base/requirements.in # aio-run-runner crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e @@ -418,13 +455,13 @@ cryptography==41.0.4 \ --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f # via - # -r requirements.in + # -r tools/base/requirements.in # pyjwt # pyopenssl dependatool==0.2.2 \ --hash=sha256:8e66850c79e37325735efa67ce06e2d5a939c0dab758f37b9bd3d09d0fb1f9a4 \ --hash=sha256:dff28853a7252d6a5d670c2519165506902c0d4746cbbdac99d2ad63ed96d82d - # via -r requirements.in + # via -r tools/base/requirements.in deprecated==1.2.14 \ --hash=sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c \ --hash=sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3 @@ -436,11 +473,11 @@ docutils==0.19 \ # envoy-docs-sphinx-runner # sphinx # sphinx-rtd-theme -envoy-base-utils==0.4.13 \ - --hash=sha256:58a35870e15ca00e921f9ab8266c6a1f83dc40f830bf0f1002e940aae2067a06 \ - --hash=sha256:a3a1f1289ad7fabb33766699912d06a81385f4e3619f6bec80d5bdaab5e606a8 +envoy-base-utils==0.4.16 \ + --hash=sha256:b1ad6684dcf525651b01ded26ebb9f8ee5900089c786dd58b7a50ed663dafe3e \ + --hash=sha256:edaf42b3ae24aa34bb8bbb41b5e2eb1c5b230207cb00ff5a47cf259d31c6c628 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-code-check # envoy-dependency-check # envoy-distribution-distrotest @@ -453,11 +490,11 @@ envoy-base-utils==0.4.13 \ envoy-code-check==0.5.8 \ --hash=sha256:03f32588cc9ed98ab6703cbca6f81df1527db71c3a0f962be6a6084ded40d528 \ --hash=sha256:2b12c51098c78d393823cf055a54e9308c37321d769041f01a2f35b04074d6f3 - # via -r requirements.in + # via -r tools/base/requirements.in envoy-dependency-check==0.1.10 \ --hash=sha256:4a637e0ed7184791b495041f9baf44567a95cbb979e1e5f26f6a8c33f724cf9e \ --hash=sha256:e6ae41249f298c865a357edcd8e4850354f222ea4f0dd629c737706b23670c75 - # via -r requirements.in + # via -r tools/base/requirements.in envoy-distribution-distrotest==0.0.10 \ --hash=sha256:83e912c48da22eb3e514fc1142247d33eb7ed0d59e94eca2ffbd178a26fbf808 \ --hash=sha256:c2ef639f10ff26c8f14124847956a35c6a602f944fdefba29699fa73b5226165 @@ -465,22 +502,22 @@ envoy-distribution-distrotest==0.0.10 \ envoy-distribution-release==0.0.9 \ --hash=sha256:592bdc8bc6847daa7e677011d72163b507e3fee821f5ea13a944e27c2fda334f \ --hash=sha256:974308468be49d034e5b174745bd6a5671364d090a9a810f0f6f36e81afbcb5d - # via -r requirements.in + # via -r tools/base/requirements.in envoy-distribution-repo==0.0.8 \ --hash=sha256:84151ae1c77e63a6967404b5e4fd1130138010b540d3081a0c016c28a657a170 \ --hash=sha256:c264232b666964696dbbc0ced1a82a4aefcf8f0af89ffd88c05ca8428f2557b5 - # via -r requirements.in + # via -r tools/base/requirements.in envoy-distribution-verify==0.0.11 \ --hash=sha256:7a560cd283321ec00e206c3d6938751836e16ba686fe9585af2383fb11499b38 \ --hash=sha256:f62a64d158aa656b25629714bef2a1d20d0cbfcab040c6351fd6e960567885e9 - # via -r requirements.in + # via -r tools/base/requirements.in envoy-docker-utils==0.0.2 \ --hash=sha256:a12cb57f0b6e204d646cbf94f927b3a8f5a27ed15f60d0576176584ec16a4b76 # via envoy-distribution-distrotest envoy-docs-sphinx-runner==0.2.9 \ --hash=sha256:1fa789b1d29ea929df67b07e5ca910d62e2057cd229719725030889da53b1a09 \ --hash=sha256:4bfa1946104e263471d522b47d683e127124a5ad47334d69de4aea0eac282576 - # via -r requirements.in + # via -r tools/base/requirements.in envoy-github-abstract==0.0.22 \ --hash=sha256:2dd65e2f247a4947d0198b295c82716c13162e30c433b7625c27d59eee7bcf78 \ --hash=sha256:86de8bbe2ecf9db896ecc4ff30ab48fc44a516d868ab1748cd4ae538facacb10 @@ -495,15 +532,15 @@ envoy-gpg-identity==0.1.1 \ --hash=sha256:03f615278b2ca0de652be9d9e3a45faffae74f85f483347c1e0d690edd4019f3 \ --hash=sha256:c41505491f906bd5ab22504b0ae2f9e76430ae492c9f59278a306225ed19c785 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-gpg-sign envoy-gpg-sign==0.2.0 \ --hash=sha256:53ef217a05555d725d467ceb70fbf7bc623eeb973a41996e8bbe1f295d8c9aab \ --hash=sha256:8bca326766a2b82864ec6274c51d99c9924f2ec773316c2f13034925ddf50772 - # via -r requirements.in -fasteners==0.18 \ - --hash=sha256:1d4caf5f8db57b0e4107d94fd5a1d02510a450dced6ca77d1839064c1bacf20c \ - --hash=sha256:cb7c13ef91e0c7e4fe4af38ecaf6b904ec3f5ce0dda06d34924b6b74b869d953 + # via -r tools/base/requirements.in +fasteners==0.19 \ + --hash=sha256:758819cb5d94cdedf4e836988b74de396ceacb8e2794d21f82d131fd9ee77237 \ + --hash=sha256:b4f37c3ac52d8a445af3a66bce57b33b5e90b97c696b7b984f530cf8f0ded09c # via # google-apitools # gsutil @@ -511,7 +548,7 @@ flake8==6.1.0 \ --hash=sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23 \ --hash=sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-code-check # pep8-naming frozendict==2.3.8 \ @@ -553,7 +590,7 @@ frozendict==2.3.8 \ --hash=sha256:f83fed36497af9562ead5e9fb8443224ba2781786bd3b92b1087cb7d0ff20135 \ --hash=sha256:ffc684773de7c88724788fa9787d0016fd75830412d58acbd9ed1a04762c675b # via - # -r requirements.in + # -r tools/base/requirements.in # aio-run-runner # envoy-base-utils frozenlist==1.4.0 \ @@ -640,120 +677,24 @@ gitdb==4.0.10 \ gitpython==3.1.37 \ --hash=sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33 \ --hash=sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54 - # via -r requirements.in -google-api-core==2.11.1 \ - --hash=sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a \ - --hash=sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a - # via - # google-cloud-core - # google-cloud-storage + # via -r tools/base/requirements.in google-apitools==0.5.32 \ --hash=sha256:b78f74116558e0476e19501b5b4b2ac7c93261a69c5449c861ea95cbc853c688 \ --hash=sha256:c3763e52289f61e21c41d5531e20fbda9cc8484a088b8686fd460770db8bad13 # via gsutil -google-auth[aiohttp]==2.22.0 \ - --hash=sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce \ - --hash=sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873 - # via - # google-api-core - # google-cloud-core - # google-cloud-storage - # gsutil -google-cloud-core==2.3.3 \ - --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ - --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 - # via google-cloud-storage -google-cloud-storage==2.11.0 \ - --hash=sha256:6fbf62659b83c8f3a0a743af0d661d2046c97c3a5bfb587c4662c4bc68de3e31 \ - --hash=sha256:88cbd7fb3d701c780c4272bc26952db99f25eb283fb4c2208423249f00b5fe53 - # via -r requirements.in -google-crc32c==1.5.0 \ - --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ - --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ - --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ - --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ - --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ - --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ - --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ - --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ - --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ - --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ - --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ - --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ - --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ - --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ - --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ - --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ - --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ - --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ - --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ - --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ - --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ - --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ - --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ - --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ - --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ - --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ - --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ - --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ - --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ - --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ - --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ - --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ - --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ - --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ - --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ - --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ - --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ - --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ - --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ - --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ - --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ - --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ - --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ - --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ - --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ - --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ - --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ - --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ - --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ - --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ - --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ - --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ - --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ - --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ - --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ - --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ - --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ - --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ - --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ - --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ - --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ - --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ - --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ - --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ - --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ - --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ - --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ - --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 - # via google-resumable-media +google-auth[aiohttp]==2.23.3 \ + --hash=sha256:6864247895eea5d13b9c57c9e03abb49cb94ce2dc7c58e91cba3248c7477c9e3 \ + --hash=sha256:a8f4608e65c244ead9e0538f181a96c6e11199ec114d41f1d7b1bffa96937bda + # via gsutil google-reauth==0.1.1 \ --hash=sha256:cb39074488d74c8853074dde47368bbf8f739d4a4338b89aab696c895b6d8368 \ --hash=sha256:f9f6852a55c2c5453d581cd01f3d1278e86147c03d008409800390a834235892 # via # gcs-oauth2-boto-plugin # gsutil -google-resumable-media==2.6.0 \ - --hash=sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7 \ - --hash=sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b - # via google-cloud-storage -googleapis-common-protos==1.59.1 \ - --hash=sha256:0cbedb6fb68f1c07e18eb4c48256320777707e7d0c55063ae56c15db3224a61e \ - --hash=sha256:b35d530fe825fb4227857bc47ad84c33c809ac96f312e13182bdeaa2abe1178a - # via google-api-core gsutil==5.26 \ --hash=sha256:cb18b8d2067d9a9e45c99b7614e241c173632eb66b7d3cb007979d6aee7146cf - # via -r requirements.in + # via -r tools/base/requirements.in httplib2==0.20.4 \ --hash=sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585 \ --hash=sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543 @@ -784,7 +725,7 @@ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-base-utils # envoy-dependency-check # sphinx @@ -793,8 +734,11 @@ markupsafe==2.1.3 \ --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ + --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ @@ -802,6 +746,7 @@ markupsafe==2.1.3 \ --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ @@ -810,6 +755,7 @@ markupsafe==2.1.3 \ --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ @@ -817,9 +763,12 @@ markupsafe==2.1.3 \ --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ @@ -838,7 +787,9 @@ markupsafe==2.1.3 \ --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ - --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 + --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ + --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 # via jinja2 mccabe==0.7.0 \ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ @@ -924,7 +875,7 @@ multidict==6.0.4 \ --hash=sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d \ --hash=sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba # via - # -r requirements.in + # -r tools/base/requirements.in # aiohttp # yarl oauth2client==4.1.3 \ @@ -933,63 +884,63 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.8 \ - --hash=sha256:002f7ca314cc8fbed5f00990bf48eda098ba1bba1e0c23be4bb024381e7889d1 \ - --hash=sha256:0619df2454b87d883f7f9ea95d79fc21fec0b8a4d600b549a1e91f59a3493d6b \ - --hash=sha256:0a27e5161b1f23fd1b5e549b38018bbc7a0f0bd3699d3dec04e2e62d271480d3 \ - --hash=sha256:119a6edcecef4e37d30d6998e9cedd9e0ecdc894fa07216221dc8dd2eb24dd9d \ - --hash=sha256:154f048e4da06275c1f173445dfbd88f038d29f7529a0dae6157293241b7f5bd \ - --hash=sha256:235b4aa46c58ded90c8b368722c1eb941613fe5a6b18bc14cfaae929f0be902e \ - --hash=sha256:24915b65ac19731a57a5ab7dbf463f91555e10d4ad833513e7d8cc6848487c24 \ - --hash=sha256:2bcc9dc53f9e1d679515349bf299ed5e75310146c755d2ba227a7e37851ab3fb \ - --hash=sha256:2c56dd62754e2ee5b7f64d37f3e85685d3bd5bcaa448076e9113be9069078dfc \ - --hash=sha256:34eec476141a043d478651d1efbf218162cdd57add24dfa659ac89e1a001477a \ - --hash=sha256:3be3da93c4d044d2f60de816320087a8494c3e75cdf3369655e014240b1a229d \ - --hash=sha256:3d30621cf18a0e16a16fbcf2fa536d800f78514a46f5321130f1b54e88994267 \ - --hash=sha256:3e8f5ac250184dcb6b00543f0f82853d7e840e476d0135733e459aee058695e5 \ - --hash=sha256:423774c85e73054acfef10fc3328f35c8d3e0193a7247d47308ebfccde70695d \ - --hash=sha256:428fec9497d17ebb5936495bbeaf12b5952bff5f6fde8a0e64030887b8d8cf94 \ - --hash=sha256:4433dd903d5b022a64e9dd1dca94f08ab04d5d928a0ecd33dd46110468960879 \ - --hash=sha256:4c836845177d6ee92682d0d9b61346a06b140b5666319905a5b423ebb0ecc5d3 \ - --hash=sha256:52c0480d5be12697b10b4d748b86acd4999f47e1d8e44e49486d0a550f30fcba \ - --hash=sha256:5311ce1457a29084146d2599588dc8ad96256feb921af8e365444fa8ad67afac \ - --hash=sha256:55ae6509f078eb90d157da7717f2826e55ef08756bc4f5b89448c6b56be4ff2c \ - --hash=sha256:5c818f19315251d68954c529f5d8322053f1c35b500b47d008e968bf2d32ed97 \ - --hash=sha256:68ed63273ec4ecdd7865e9d984d65a749c0d780882cf9dde6ab2bc6323f6471a \ - --hash=sha256:6ad73fde11117b6b103c1d4071168b0e2875d890556fa8597663a5eca81bb812 \ - --hash=sha256:6d1aab08b373232f568ea9ae048f9f77e09f389068afee6dd44bb6140e2c3ea3 \ - --hash=sha256:742d4d16d66579ffff4b2048a8de4a0b03d731847233e92c4edd418a9c582d0f \ - --hash=sha256:764306f6370e6c76cbbf3139dd9b05be9c4481ee0b15966bd1907827a5777216 \ - --hash=sha256:823525bfb27b804b492acc59a45dc0973ea629d97557eac81dde7b34b5267611 \ - --hash=sha256:8a1c92f467f5fd0f8fb79273006b563364b1e45667b3760423498348dc2e22fa \ - --hash=sha256:9ce982f3c1df83f7dc74f3b2690605470ff4790d12558e44359f01e822c5cb08 \ - --hash=sha256:9df23493a72f073b2ab1005e628a963248dc577a2816e9c82caf09ff74908414 \ - --hash=sha256:a119c73520192c2882d0549151b9cdd65e0bb5396bedf8951ba5f70d6a873879 \ - --hash=sha256:a3c7c4d60e21b0f10c8214d7ca9f2243019dd1bf9d2750b3b4a9250935977a24 \ - --hash=sha256:a9bcd3a48b260d3dfe68b8ce93d11f99a70bd4c908efe22d195a1b1dcfb15ac2 \ - --hash=sha256:ab9c234bfe89aeba825feb897718c65a80851f367a4a8308d6b5074a80fce6e5 \ - --hash=sha256:af8e6185516ce0c93d6ce1f4105918504da629c631fd969686f32a1be3ed3c9b \ - --hash=sha256:be6f2634fe6c88a0e1e785fc0b6845ad75bef6e20f1ee3d62fd81b17e7505cbf \ - --hash=sha256:c863c7805a7961428a40431a8f47c3f71c74e6c5ddf1ab023e6e79bc5806e6d5 \ - --hash=sha256:c9ae634b8a55539c3d5a53813552325733ab3da3601feef8e99f91cef634f3c4 \ - --hash=sha256:ca4f3e15517bdcdb573dfe6c97d4171247ce50ec82e3a7b708941b53d5f4bc29 \ - --hash=sha256:cc449bff1d4152438615f4a6a003577942908c4e166d64dc46d1f3f0cde72ecd \ - --hash=sha256:d23edcb32383f3d86b2f4914f9825ce2d67625abd34be6e5ed1f59ec30127b7a \ - --hash=sha256:e26836a11b88f839b6902f92e8dd997c32f49486119a1aa67d714bc288aae172 \ - --hash=sha256:e32ac29f9c30cc152e7432a26c665232a382678f2402bf782f73fbc985cfb37e \ - --hash=sha256:e538974e2ed20504f3dad0bcdab41cd5e4fa086dabea852a150e4cc98293183d \ - --hash=sha256:e6a267c0fc64fc4d0b8fb146e1a060a40f570441a9390ec4bc6de0b5fda148cd \ - --hash=sha256:ed1adc6db9841974170a5195b827ee4e392b1e8ca385b19fcdc3248489844059 \ - --hash=sha256:edafb45fc5b2063abd8a0baf6be21c38497df2d9e0b75cdb053eb0ff100fa26c \ - --hash=sha256:ee887aeb8ab0c1d25e9f2b540f9a34b4cbfe8894f95b63a5984441a9f337d2ff \ - --hash=sha256:f9b070c895fc81c362b1b41dc6d0c81a84ee4abb1193804de15683549aeeb0ee \ - --hash=sha256:ff2e6e429416b6287006ba0556083f62396199299ab85afd3ba1e83be14677e2 +orjson==3.9.9 \ + --hash=sha256:02e693843c2959befdd82d1ebae8b05ed12d1cb821605d5f9fe9f98ca5c9fd2b \ + --hash=sha256:06f0c024a75e8ba5d9101facb4fb5a028cdabe3cdfe081534f2a9de0d5062af2 \ + --hash=sha256:0a1a4d9e64597e550428ba091e51a4bcddc7a335c8f9297effbfa67078972b5c \ + --hash=sha256:0d2cd6ef4726ef1b8c63e30d8287225a383dbd1de3424d287b37c1906d8d2855 \ + --hash=sha256:0f89dc338a12f4357f5bf1b098d3dea6072fb0b643fd35fec556f4941b31ae27 \ + --hash=sha256:12b83e0d8ba4ca88b894c3e00efc59fe6d53d9ffb5dbbb79d437a466fc1a513d \ + --hash=sha256:1ef06431f021453a47a9abb7f7853f04f031d31fbdfe1cc83e3c6aadde502cce \ + --hash=sha256:1f352117eccac268a59fedac884b0518347f5e2b55b9f650c2463dd1e732eb61 \ + --hash=sha256:24301f2d99d670ded4fb5e2f87643bc7428a54ba49176e38deb2887e42fe82fb \ + --hash=sha256:31d676bc236f6e919d100fb85d0a99812cff1ebffaa58106eaaec9399693e227 \ + --hash=sha256:335406231f9247f985df045f0c0c8f6b6d5d6b3ff17b41a57c1e8ef1a31b4d04 \ + --hash=sha256:397a185e5dd7f8ebe88a063fe13e34d61d394ebb8c70a443cee7661b9c89bda7 \ + --hash=sha256:4a308aeac326c2bafbca9abbae1e1fcf682b06e78a54dad0347b760525838d85 \ + --hash=sha256:50232572dd300c49f134838c8e7e0917f29a91f97dbd608d23f2895248464b7f \ + --hash=sha256:512e5a41af008e76451f5a344941d61f48dddcf7d7ddd3073deb555de64596a6 \ + --hash=sha256:5424ecbafe57b2de30d3b5736c5d5835064d522185516a372eea069b92786ba6 \ + --hash=sha256:543b36df56db195739c70d645ecd43e49b44d5ead5f8f645d2782af118249b37 \ + --hash=sha256:678ffb5c0a6b1518b149cc328c610615d70d9297e351e12c01d0beed5d65360f \ + --hash=sha256:6fcf06c69ccc78e32d9f28aa382ab2ab08bf54b696dbe00ee566808fdf05da7d \ + --hash=sha256:75b805549cbbcb963e9c9068f1a05abd0ea4c34edc81f8d8ef2edb7e139e5b0f \ + --hash=sha256:8038ba245d0c0a6337cfb6747ea0c51fe18b0cf1a4bc943d530fd66799fae33d \ + --hash=sha256:879d2d1f6085c9c0831cec6716c63aaa89e41d8e036cabb19a315498c173fcc6 \ + --hash=sha256:8cba20c9815c2a003b8ca4429b0ad4aa87cb6649af41365821249f0fd397148e \ + --hash=sha256:8e7877256b5092f1e4e48fc0f1004728dc6901e7a4ffaa4acb0a9578610aa4ce \ + --hash=sha256:906cac73b7818c20cf0f6a7dde5a6f009c52aecc318416c7af5ea37f15ca7e66 \ + --hash=sha256:920814e02e3dd7af12f0262bbc18b9fe353f75a0d0c237f6a67d270da1a1bb44 \ + --hash=sha256:957a45fb201c61b78bcf655a16afbe8a36c2c27f18a998bd6b5d8a35e358d4ad \ + --hash=sha256:9a4402e7df1b5c9a4c71c7892e1c8f43f642371d13c73242bda5964be6231f95 \ + --hash=sha256:9d9b5440a5d215d9e1cfd4aee35fd4101a8b8ceb8329f549c16e3894ed9f18b5 \ + --hash=sha256:a3bf6ca6bce22eb89dd0650ef49c77341440def966abcb7a2d01de8453df083a \ + --hash=sha256:a71b0cc21f2c324747bc77c35161e0438e3b5e72db6d3b515310457aba743f7f \ + --hash=sha256:ab7bae2b8bf17620ed381e4101aeeb64b3ba2a45fc74c7617c633a923cb0f169 \ + --hash=sha256:ae72621f216d1d990468291b1ec153e1b46e0ed188a86d54e0941f3dabd09ee8 \ + --hash=sha256:b20becf50d4aec7114dc902b58d85c6431b3a59b04caa977e6ce67b6fee0e159 \ + --hash=sha256:b28c1a65cd13fff5958ab8b350f0921121691464a7a1752936b06ed25c0c7b6e \ + --hash=sha256:b97a67c47840467ccf116136450c50b6ed4e16a8919c81a4b4faef71e0a2b3f4 \ + --hash=sha256:bd55ea5cce3addc03f8fb0705be0cfed63b048acc4f20914ce5e1375b15a293b \ + --hash=sha256:c4eb31a8e8a5e1d9af5aa9e247c2a52ad5cf7e968aaa9aaefdff98cfcc7f2e37 \ + --hash=sha256:c63eca397127ebf46b59c9c1fb77b30dd7a8fc808ac385e7a58a7e64bae6e106 \ + --hash=sha256:c959550e0705dc9f59de8fca1a316da0d9b115991806b217c82931ac81d75f74 \ + --hash=sha256:cffb77cf0cd3cbf20eb603f932e0dde51b45134bdd2d439c9f57924581bb395b \ + --hash=sha256:d1c01cf4b8e00c7e98a0a7cf606a30a26c32adf2560be2d7d5d6766d6f474b31 \ + --hash=sha256:d3f56e41bc79d30fdf077073072f2377d2ebf0b946b01f2009ab58b08907bc28 \ + --hash=sha256:e159b97f5676dcdac0d0f75ec856ef5851707f61d262851eb41a30e8fadad7c9 \ + --hash=sha256:e98ca450cb4fb176dd572ce28c6623de6923752c70556be4ef79764505320acb \ + --hash=sha256:eb50d869b3c97c7c5187eda3759e8eb15deb1271d694bc5d6ba7040db9e29036 \ + --hash=sha256:ece2d8ed4c34903e7f1b64fb1e448a00e919a4cdb104fc713ad34b055b665fca \ + --hash=sha256:f28090060a31f4d11221f9ba48b2273b0d04b702f4dcaa197c38c64ce639cc51 \ + --hash=sha256:f692e7aabad92fa0fff5b13a846fb586b02109475652207ec96733a085019d80 \ + --hash=sha256:f708ca623287186e5876256cb30599308bce9b2757f90d917b7186de54ce6547 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-base-utils -packaging==23.1 \ - --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ - --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f +packaging==23.2 \ + --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ + --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via # aio-api-github # aio-api-nist @@ -1000,22 +951,22 @@ packaging==23.1 \ # envoy-github-abstract # envoy-github-release # sphinx -pathspec==0.11.1 \ - --hash=sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687 \ - --hash=sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293 +pathspec==0.11.2 \ + --hash=sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20 \ + --hash=sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3 # via yamllint pep8-naming==0.13.3 \ --hash=sha256:1705f046dfcd851378aac3be1cd1551c7c1e5ff363bacad707d43007877fa971 \ --hash=sha256:1a86b8c71a03337c97181917e2b472f0f5e4ccb06844a0d6f0a33522549e7a80 - # via -r requirements.in -platformdirs==3.9.1 \ - --hash=sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421 \ - --hash=sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f + # via -r tools/base/requirements.in +platformdirs==3.11.0 \ + --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ + --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e # via yapf ply==3.11 \ --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \ --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce - # via -r requirements.in + # via -r tools/base/requirements.in protobuf==4.21.12 \ --hash=sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30 \ --hash=sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b \ @@ -1032,11 +983,9 @@ protobuf==4.21.12 \ --hash=sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97 \ --hash=sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-base-utils # envoy-docs-sphinx-runner - # google-api-core - # googleapis-common-protos pyasn1==0.5.0 \ --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde @@ -1050,9 +999,9 @@ pyasn1-modules==0.3.0 \ # via # google-auth # oauth2client -pycodestyle==2.11.0 \ - --hash=sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0 \ - --hash=sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8 +pycodestyle==2.11.1 \ + --hash=sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f \ + --hash=sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67 # via flake8 pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ @@ -1065,10 +1014,10 @@ pyflakes==3.1.0 \ pygithub==2.1.1 \ --hash=sha256:4b528d5d6f35e991ea5fd3f942f58748f24938805cb7fcf24486546637917337 \ --hash=sha256:ecf12c2809c44147bce63b047b3d2e9dac8a41b63e90fcb263c703f64936b97c - # via -r requirements.in -pygments==2.15.1 \ - --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ - --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1 + # via -r tools/base/requirements.in +pygments==2.16.1 \ + --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ + --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 # via # envoy-docs-sphinx-runner # sphinx @@ -1078,6 +1027,58 @@ pyjwt[crypto]==2.8.0 \ # via # gidgethub # pygithub +pylsqpack==0.3.17 \ + --hash=sha256:016cfc9dc2dea9c0b06b7aba64da89315b4c96c309c69f5ea548f4e994429781 \ + --hash=sha256:0b1ed55872f8f984f655a4c853ad749b834a1fcd4c7e5a962c6f17d4d27687a2 \ + --hash=sha256:105e636a33cac3ad7bd62aab97d655b53486fab6eb33429e45e7963be814947c \ + --hash=sha256:14dd6f623afe7f4b2a45078ef6bf3cc4a686489abf487f6fe15deeede893a551 \ + --hash=sha256:1b5c86a9feb137d197fe9f3c4775f5308f91f00a720d212b205044c83d32ab2e \ + --hash=sha256:1f657966917239518dcbf508ca5311ded178d88b81fe4e8f5fc6f7d1d84153a4 \ + --hash=sha256:27922bb8bd2e6a32693ac0b4cf3e164695b33c3fe3d243648f2f6403f03636d9 \ + --hash=sha256:2b010cbd59416f1911e392f6ccfbedd51af1b9bb7c1da3ac40a844b103628d3a \ + --hash=sha256:2bd2848a8ee065e4632f38c36fa79e40159271872f1ac9c1fd68ab913523c8de \ + --hash=sha256:2f20778db956dc7e4b1a8a79722d57a4650c45997fb65c1352cbf85eb7aa3ce2 \ + --hash=sha256:346d804ca196c29d8eae69d943b272e22101bd7611268b2db726097bd74c9c11 \ + --hash=sha256:35159d4e2f8005162cb4d5d5f2b477e45e9a54850203ec67c00806addedfdcf0 \ + --hash=sha256:3563f7e563b0b9d48958e92b2262872d056325b19e12c7a9e0ea1eb297302ae4 \ + --hash=sha256:3a7256825ea88f3c64b80676f5aa0fc1b19cd0bc28dd4d6c37397bcbd6ed3b25 \ + --hash=sha256:41bf0f33cd2fe4c745a790adc0e3cf1bfcb5c156e2217b3ff9d0eaa7fab763be \ + --hash=sha256:4580bce6eb2c2dd3c60ac7eb13beae83bc899696e2446ee9499537f037083426 \ + --hash=sha256:46762cc6993ee64c24dcaf9ec8665dbd68633efc7198ceb65a27e5232b9760c6 \ + --hash=sha256:4e501e9b6422db8b6d8248ef7fe325713ffce59fc2ab99d7dcca026b7451ce7d \ + --hash=sha256:5d992ffa01bc03a8e705a64de68b91b6c1de1960afa777917e6a37cef394faed \ + --hash=sha256:64791cc8b45d63a0f7f447614b7d5d5fa9a3f6ce20c7c10d9ea94003aedeff4b \ + --hash=sha256:6b3e381cd3b72ae466c0f34e109071a006d9939bbdcdd4856f72f08579ed0e76 \ + --hash=sha256:6dbc15ed7438e0f7c3ce323cfb878a3ba7aa8fc961a1f57df7c0be87520dd63a \ + --hash=sha256:6fb77ceae0a88f481ebfe902dcb8a18c6026aaacc9fd3cb709e7fc8d70bad26b \ + --hash=sha256:73ce2604f6dbc0bf11cc8d0858c60a74f9aff227c98054ebd946b69c5adb34e0 \ + --hash=sha256:7c30e9b21d8575e3ec42cd0ffcabb87cd2f5cf6dbdd07a9b209fbffa085a5f33 \ + --hash=sha256:80f49846ab14c12189b4517fb56c493ca7dec840d878102af6918ac9a2018602 \ + --hash=sha256:8e0d8ff7f6267674b3989c58d9c9e7bf2387454f51488317062e7b5b81a88d00 \ + --hash=sha256:95561d3e77ba8a78565bbd7ee3d2c01ad19a240806e1f1fe3e9b2d5a455f9992 \ + --hash=sha256:9c3c3ad219c8525d70866cc8c3fc584b772d3bfdced6825bfe1e6f1f2c3a33fa \ + --hash=sha256:abcfe2630ccb3a671cae45700fcdfbae3419340d4ad15c85db0534d8ea7dc745 \ + --hash=sha256:ac16e1c01e1b1610598b1b530c698476581beca5dc13186efe248077c734e1de \ + --hash=sha256:add2ace26276b78b9e063b5439556cda6b36a36aed747d93ea06c43bc8176208 \ + --hash=sha256:bb74b8a730876626e63d83564af0f31d27a0e1dd9219e9e5bc0d4921e85da71e \ + --hash=sha256:bbbf789dc1273680430e3231144fc38214c7460e836cb7c6076365b16147a151 \ + --hash=sha256:bf5210736b79effe3fc0e6b0d8d6aa0afd4b50c0cdb21e49f3835c25642e1f6d \ + --hash=sha256:c245a2241ed295633d3c60f949a00e43bcf7d108eb6b062b51e2ebf735fb83ed \ + --hash=sha256:c760638dce05ac657c9452d02424e07e99b65f431f7e366e79f6e23ed7a1313f \ + --hash=sha256:c7b3da5f2d3a4ae87ec46e6b1acf7ee54beeea049a99bbd6973012f0309b85bb \ + --hash=sha256:c7efb558ab48e364dad500bd4a78536a802dae54a46176021d3889b4e77cb150 \ + --hash=sha256:c89bbbcf69afa77f422edd7cb4054b2a1a60d51b835024087bb407c6b27181f4 \ + --hash=sha256:d4232501a5554c1f05334dfe0cb35ec66cc16154e6f31ce3107b8a8cffc9420c \ + --hash=sha256:db6e72e2c1312a1d918f8fd3a6bb38de98473b8cbdf6a3ce0237bd7ba0f25ad2 \ + --hash=sha256:e3556432d8dbb8649bf38d87fa5c97280b16d5a5425d8f943c676ace3041f4b3 \ + --hash=sha256:e6ca0ecda0040afdce031c7b966b78d8ca2375416170410ac862689ed9a60e68 \ + --hash=sha256:ea41faaf98857fa94ff12b6f72bd39c23fcaaa26ecece65cb3db9fa2b28633e7 \ + --hash=sha256:f1e40bec882d6aac75e814a2b8ee92add31a82eddcb705530497ab25f0c09f9a \ + --hash=sha256:f5c46627262389a9151cb4a120aa5b3210ba2066ab8c3026f3263adf8336b0c1 \ + --hash=sha256:f864f3e6c9e0a42b89f3388723575e306f45e736e32f6c0317eefb53c6ff1a40 \ + --hash=sha256:fc93abbe5bee19ceb1cb76b90298d2f47923f2a6dd1398f99aa498de1da0e553 \ + --hash=sha256:fdbfb5df079a50a7a0eed87d51a12495c820d30277ddb58b52c4862b5d557fc4 + # via aioquic pynacl==1.5.0 \ --hash=sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858 \ --hash=sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d \ @@ -1094,15 +1095,16 @@ pyopenssl==23.2.0 \ --hash=sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2 \ --hash=sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac # via + # aioquic # gcs-oauth2-boto-plugin # gsutil -pyparsing==3.1.0 \ - --hash=sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1 \ - --hash=sha256:edb662d6fe322d6e990b1594b5feaeadf806803359e3d4d42f11e295e588f0ea +pyparsing==3.1.1 \ + --hash=sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb \ + --hash=sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db # via httplib2 pyreadline==2.1 \ --hash=sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1 - # via -r requirements.in + # via -r tools/base/requirements.in python-dateutil==2.8.2 \ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 @@ -1111,9 +1113,9 @@ python-gnupg==0.5.1 \ --hash=sha256:5674bad4e93876c0b0d3197e314d7f942d39018bf31e2b833f6788a6813c3fb8 \ --hash=sha256:bf9b2d9032ef38139b7d64184176cd0b293eaeae6e4f93f50e304c7051174482 # via envoy-gpg-identity -pytz==2023.3 \ - --hash=sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588 \ - --hash=sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb +pytz==2023.3.post1 \ + --hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \ + --hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7 # via # aio-core # envoy-base-utils @@ -1121,7 +1123,9 @@ pyu2f==0.1.5 \ --hash=sha256:a3caa3a11842fc7d5746376f37195e6af5f17c0a15737538bb1cebf656fb306b # via google-reauth pyyaml==6.0.1 \ + --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ @@ -1129,7 +1133,10 @@ pyyaml==6.0.1 \ --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ + --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ @@ -1137,9 +1144,12 @@ pyyaml==6.0.1 \ --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ + --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ @@ -1154,7 +1164,9 @@ pyyaml==6.0.1 \ --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ @@ -1162,7 +1174,7 @@ pyyaml==6.0.1 \ --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f # via - # -r requirements.in + # -r tools/base/requirements.in # aio-core # envoy-base-utils # yamllint @@ -1170,9 +1182,7 @@ requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via - # google-api-core # google-auth - # google-cloud-storage # pygithub # sphinx retry-decorator==1.1.1 \ @@ -1193,7 +1203,6 @@ six==1.16.0 \ # via # gcs-oauth2-boto-plugin # google-apitools - # google-auth # gsutil # oauth2client # python-dateutil @@ -1203,14 +1212,10 @@ six==1.16.0 \ slack-sdk==3.23.0 \ --hash=sha256:2a8513505cced20ceee22b5b49c11d9545caa6234b56bf0ad47133ea5b357d10 \ --hash=sha256:9d6ebc4ff74e7983e1b27dbdb0f2bb6fc3c2a2451694686eaa2be23bbb085a73 - # via -r requirements.in -slackclient==2.9.4 \ - --hash=sha256:a8cab9146795e23d66a03473b80dd23df8c500829dfa9d06b3e3d5aec0a2b293 \ - --hash=sha256:ab79fefb5412d0595bc01d2f195a787597f2a617b6766562932ab9ffbe5cb173 - # via -r requirements.in -smmap==5.0.0 \ - --hash=sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94 \ - --hash=sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936 + # via -r tools/base/requirements.in +smmap==5.0.1 \ + --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ + --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da # via gitdb snowballstemmer==2.2.0 \ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ @@ -1220,7 +1225,7 @@ sphinx==7.2.2 \ --hash=sha256:1c0abe6d4de7a6b2c2b109a2e18387bf27b240742e1b34ea42ac3ed2ac99978c \ --hash=sha256:ed33bc597dd8f05cd37118f64cbac0b8bf773389a628ddfe95ab9e915c9308dc # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-docs-sphinx-runner # sphinx-copybutton # sphinx-rtd-theme @@ -1239,19 +1244,25 @@ sphinx-rtd-theme==2.0.0rc2 \ sphinxcontrib-applehelp==1.0.4 \ --hash=sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228 \ --hash=sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e - # via sphinx + # via + # -r tools/base/requirements.in + # sphinx sphinxcontrib-devhelp==1.0.2 \ --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \ --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4 - # via sphinx + # via + # -r tools/base/requirements.in + # sphinx sphinxcontrib-googleanalytics==0.4 \ --hash=sha256:4b19c1f0fce5df6c7da5633201b64a9e5b0cb3210a14fdb4134942ceee8c5d12 \ --hash=sha256:a6574983f9a58e5864ec10d34dc99914c4d647108b22c9249c8f0038b0cb18b3 - # via -r requirements.in + # via -r tools/base/requirements.in sphinxcontrib-htmlhelp==2.0.1 \ --hash=sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff \ --hash=sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903 - # via sphinx + # via + # -r tools/base/requirements.in + # sphinx sphinxcontrib-httpdomain==1.8.1 \ --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \ --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b @@ -1269,11 +1280,14 @@ sphinxcontrib-jsmath==1.0.1 \ sphinxcontrib-qthelp==1.0.3 \ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 - # via sphinx + # via + # -r tools/base/requirements.in + # sphinx sphinxcontrib-serializinghtml==1.1.5 \ --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \ --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952 # via + # -r tools/base/requirements.in # envoy-docs-sphinx-runner # sphinx sphinxext-rediraffe==0.2.7 \ @@ -1282,7 +1296,7 @@ sphinxext-rediraffe==0.2.7 \ # via envoy-docs-sphinx-runner thrift==0.16.0 \ --hash=sha256:2b5b6488fcded21f9d312aa23c9ff6a0195d0f6ae26ddbd5ad9e3e25dfc14408 - # via -r requirements.in + # via -r tools/base/requirements.in tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f @@ -1293,9 +1307,9 @@ trycast==1.0.0 \ # via # aio-core # envoy-base-utils -typing-extensions==4.7.1 \ - --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \ - --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2 +typing-extensions==4.8.0 \ + --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ + --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef # via # aiodocker # pygithub @@ -1303,50 +1317,55 @@ uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e # via gidgethub -urllib3==1.26.17 \ - --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ - --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b +urllib3==2.0.6 \ + --hash=sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 \ + --hash=sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564 # via - # google-auth # pygithub # requests -uvloop==0.17.0 \ - --hash=sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d \ - --hash=sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1 \ - --hash=sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595 \ - --hash=sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b \ - --hash=sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05 \ - --hash=sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8 \ - --hash=sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20 \ - --hash=sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded \ - --hash=sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c \ - --hash=sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8 \ - --hash=sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474 \ - --hash=sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f \ - --hash=sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62 \ - --hash=sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376 \ - --hash=sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c \ - --hash=sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e \ - --hash=sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b \ - --hash=sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4 \ - --hash=sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578 \ - --hash=sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811 \ - --hash=sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d \ - --hash=sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738 \ - --hash=sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa \ - --hash=sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9 \ - --hash=sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539 \ - --hash=sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c \ - --hash=sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718 \ - --hash=sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667 \ - --hash=sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c \ - --hash=sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024 +uvloop==0.18.0 \ + --hash=sha256:1121087dfeb46e9e65920b20d1f46322ba299b8d93f7cb61d76c94b5a1adc20c \ + --hash=sha256:12af0d2e1b16780051d27c12de7e419b9daeb3516c503ab3e98d364cc55303bb \ + --hash=sha256:1f354d669586fca96a9a688c585b6257706d216177ac457c92e15709acaece10 \ + --hash=sha256:1f4a549cd747e6f4f8446f4b4c8cb79504a8372d5d3a9b4fc20e25daf8e76c05 \ + --hash=sha256:211ce38d84118ae282a91408f61b85cf28e2e65a0a8966b9a97e0e9d67c48722 \ + --hash=sha256:25b714f07c68dcdaad6994414f6ec0f2a3b9565524fba181dcbfd7d9598a3e73 \ + --hash=sha256:280904236a5b333a273292b3bcdcbfe173690f69901365b973fa35be302d7781 \ + --hash=sha256:2b8b7cf7806bdc745917f84d833f2144fabcc38e9cd854e6bc49755e3af2b53e \ + --hash=sha256:4d90858f32a852988d33987d608bcfba92a1874eb9f183995def59a34229f30d \ + --hash=sha256:53aca21735eee3859e8c11265445925911ffe410974f13304edb0447f9f58420 \ + --hash=sha256:54b211c46facb466726b227f350792770fc96593c4ecdfaafe20dc00f3209aef \ + --hash=sha256:56c1026a6b0d12b378425e16250acb7d453abaefe7a2f5977143898db6cfe5bd \ + --hash=sha256:585b7281f9ea25c4a5fa993b1acca4ad3d8bc3f3fe2e393f0ef51b6c1bcd2fe6 \ + --hash=sha256:58e44650cbc8607a218caeece5a689f0a2d10be084a69fc32f7db2e8f364927c \ + --hash=sha256:61151cc207cf5fc88863e50de3d04f64ee0fdbb979d0b97caf21cae29130ed78 \ + --hash=sha256:6132318e1ab84a626639b252137aa8d031a6c0550250460644c32ed997604088 \ + --hash=sha256:680da98f12a7587f76f6f639a8aa7708936a5d17c5e7db0bf9c9d9cbcb616593 \ + --hash=sha256:6e20bb765fcac07879cd6767b6dca58127ba5a456149717e0e3b1f00d8eab51c \ + --hash=sha256:74020ef8061678e01a40c49f1716b4f4d1cc71190d40633f08a5ef8a7448a5c6 \ + --hash=sha256:75baba0bfdd385c886804970ae03f0172e0d51e51ebd191e4df09b929771b71e \ + --hash=sha256:847f2ed0887047c63da9ad788d54755579fa23f0784db7e752c7cf14cf2e7506 \ + --hash=sha256:8849b8ef861431543c07112ad8436903e243cdfa783290cbee3df4ce86d8dd48 \ + --hash=sha256:895a1e3aca2504638a802d0bec2759acc2f43a0291a1dff886d69f8b7baff399 \ + --hash=sha256:99deae0504547d04990cc5acf631d9f490108c3709479d90c1dcd14d6e7af24d \ + --hash=sha256:ad79cd30c7e7484bdf6e315f3296f564b3ee2f453134a23ffc80d00e63b3b59e \ + --hash=sha256:b028776faf9b7a6d0a325664f899e4c670b2ae430265189eb8d76bd4a57d8a6e \ + --hash=sha256:b0a8f706b943c198dcedf1f2fb84899002c195c24745e47eeb8f2fb340f7dfc3 \ + --hash=sha256:c65585ae03571b73907b8089473419d8c0aff1e3826b3bce153776de56cbc687 \ + --hash=sha256:c6d341bc109fb8ea69025b3ec281fcb155d6824a8ebf5486c989ff7748351a37 \ + --hash=sha256:d5d1135beffe9cd95d0350f19e2716bc38be47d5df296d7cc46e3b7557c0d1ff \ + --hash=sha256:db1fcbad5deb9551e011ca589c5e7258b5afa78598174ac37a5f15ddcfb4ac7b \ + --hash=sha256:e14de8800765b9916d051707f62e18a304cde661fa2b98a58816ca38d2b94029 \ + --hash=sha256:e3d301e23984dcbc92d0e42253e0e0571915f0763f1eeaf68631348745f2dccc \ + --hash=sha256:ed3c28337d2fefc0bac5705b9c66b2702dc392f2e9a69badb1d606e7e7f773bb \ + --hash=sha256:edbb4de38535f42f020da1e3ae7c60f2f65402d027a08a8c60dc8569464873a6 \ + --hash=sha256:f3b18663efe0012bc4c315f1b64020e44596f5fabc281f5b0d9bc9465288559c # via aio-run-runner verboselogs==1.7 \ --hash=sha256:d63f23bf568295b95d3530c6864a0b580cec70e7ff974177dead1e4ffbc6ff49 \ --hash=sha256:e33ddedcdfdafcb3a174701150430b11b46ceb64c2a9a26198c76a156568e427 # via - # -r requirements.in + # -r tools/base/requirements.in # aio-run-runner # envoy-distribution-distrotest # envoy-distribution-repo @@ -1437,7 +1456,7 @@ yapf==0.40.2 \ --hash=sha256:4dab8a5ed7134e26d57c1647c7483afb3f136878b579062b786c9ba16b94637b \ --hash=sha256:adc8b5dd02c0143108878c499284205adb258aad6db6634e5b869e7ee2bd548b # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-code-check yarl==1.9.2 \ --hash=sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571 \ @@ -1515,11 +1534,11 @@ yarl==1.9.2 \ --hash=sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78 \ --hash=sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7 # via - # -r requirements.in + # -r tools/base/requirements.in # aiohttp -zipp==3.16.2 \ - --hash=sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0 \ - --hash=sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147 +zipp==3.17.0 \ + --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ + --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 # via importlib-metadata zstandard==0.21.0 \ --hash=sha256:0aad6090ac164a9d237d096c8af241b8dcd015524ac6dbec1330092dba151657 \ @@ -1571,4 +1590,4 @@ zstandard==0.21.0 \ setuptools==68.2.2 \ --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a - # via -r requirements.in + # via -r tools/base/requirements.in diff --git a/tools/h3_request/BUILD b/tools/h3_request/BUILD new file mode 100644 index 000000000000..f92d0a37a254 --- /dev/null +++ b/tools/h3_request/BUILD @@ -0,0 +1,22 @@ +load("@base_pip3//:requirements.bzl", "requirement") +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load("//tools/base:envoy_python.bzl", "envoy_py_namespace", "envoy_pytool_binary") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_py_namespace() + +# To use in tests, you probably will want to include +# args="--ca-certs=$(location //test/config/integration/certs:cacert.pem)" +envoy_pytool_binary( + name = "h3_request", + srcs = ["h3_request.py"], + deps = [ + requirement("aioquic"), + ], +) diff --git a/tools/h3_request/h3_request.py b/tools/h3_request/h3_request.py new file mode 100644 index 000000000000..f4023a00f21b --- /dev/null +++ b/tools/h3_request/h3_request.py @@ -0,0 +1,118 @@ +import argparse +import asyncio +import os +import sys +from functools import cached_property +from typing import AsyncIterator, cast +from urllib.parse import urlparse + +import aioquic +from aioquic.asyncio.protocol import QuicConnectionProtocol +from aioquic.h3.connection import H3_ALPN, H3Connection +from aioquic.h3.exceptions import H3Error +from aioquic.h3.events import ( + DataReceived, + H3Event, + HeadersReceived, +) +from aioquic.quic.configuration import QuicConfiguration +from aioquic.quic.events import QuicEvent + + +class Http3Client(QuicConnectionProtocol): + """Note, this class is extremely minimal. + + It supports only GET, doesn't properly validate URLs, etc. Since this + is just for tests, that's all that's required right now. + It is based on https://github.com/aiortc/aioquic/blob/main/examples/http3_client.py + which is a far more complete implementation. + """ + + @cached_property + def _http(self) -> H3Connection: + return H3Connection(self._quic) + + @cached_property + def _stream_ids(self) -> dict[int, asyncio.Future[bool]]: + return {} + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + def headers_received(self, event: H3Event) -> None: + if not self._include_headers: + return + for header, value in event.headers: + print(f"{header.decode('utf-8')}: {value.decode('utf-8')}\n", end="") + print("") # One blank newline after headers. + + def http_event_received(self, event: H3Event) -> None: + stream_id = event.stream_id + if stream_id not in self._stream_ids: + return + if isinstance(event, HeadersReceived): + self.headers_received(event) + elif isinstance(event, DataReceived): + print(event.data.decode("utf-8"), end="") + else: + raise H3Error(f"unexpected quic event type {event}") + if event.stream_ended: + self._stream_ids.pop(stream_id).set_result(True) + + def quic_event_received(self, event: QuicEvent) -> None: + for http_event in self._http.handle_event(event): + self.http_event_received(http_event) + + async def request(self, url: str, include_headers: bool = False) -> None: + """Issue an http/3 get request, print response pieces as the packets arrive.""" + stream_id: int = self._quic.get_next_available_stream_id() + future: asyncio.Future[bool] = self._loop.create_future() + parsed_url = urlparse(url) + self._stream_ids[stream_id] = future + self._include_headers = include_headers + self._http.send_headers( + stream_id=stream_id, + headers=[ + (b":method", "GET".encode()), + (b":scheme", parsed_url.scheme.encode()), + (b":authority", parsed_url.netloc.encode()), + (b":path", parsed_url.path.encode()), + ], + end_stream=True, + ) + await future + + +async def request(url: str, config: QuicConfiguration, include_headers: bool) -> None: + parsed_url = urlparse(url) + client_resolver = aioquic.asyncio.client.connect( + host=parsed_url.hostname, + port=parsed_url.port or 443, + configuration=config, + create_protocol=Http3Client, + wait_connected=True, + ) + async with client_resolver as client: + client = cast(Http3Client, client) + await client.request(url, include_headers) + + +async def main(argv) -> None: + parser = argparse.ArgumentParser(description="HTTP/3 client") + parser.add_argument("url", type=str, help="the URL to query (must be HTTPS)") + parser.add_argument( + "--ca-certs", type=str, nargs="+", help="load CA certificates from the specified file") + parser.add_argument( + "--include-headers", action="store_true", help="output the headers before the body") + args = parser.parse_args(argv) + config = QuicConfiguration( + is_client=True, + alpn_protocols=H3_ALPN, + ) + for cert in args.ca_certs or []: + config.load_verify_locations(cert) + await request(args.url, config, args.include_headers) + + +if __name__ == '__main__': + sys.exit(asyncio.run(main(sys.argv[1:]))) diff --git a/tools/repo/notify.py b/tools/repo/notify.py index ee66c82ffbb4..9a8df01e64a1 100644 --- a/tools/repo/notify.py +++ b/tools/repo/notify.py @@ -8,6 +8,7 @@ # NOTE: Slack IDs can be found in the user's full profile from within Slack. import datetime +import html import json import os import sys @@ -16,7 +17,7 @@ import aiohttp -from slack_sdk import WebClient +from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.errors import SlackApiError from aio.api import github as github @@ -114,7 +115,7 @@ def should_report(self): @cached_property def slack_client(self): - return WebClient(token=self.slack_bot_token) + return AsyncWebClient(token=self.slack_bot_token) @cached_property def slack_bot_token(self): @@ -249,7 +250,7 @@ def pr_message(self, age, pull): hours = age.seconds // 3600 markup = ("*" if age > self.slo_max else "") return ( - f"<{pull['html_url']}|{pull['title']}> has been waiting " + f"<{pull['html_url']}|{html.escape(pull['title'])}> has been waiting " f"{markup}{days} days {hours} hours{markup}") async def run(self): @@ -278,14 +279,12 @@ async def report(self): print(json.dumps(report)) async def send_message(self, channel, text): - # TODO(phlax): this is blocking, switch to async slack client if self.dry_run: self.log.notice(f"Slack message ({channel}):\n{text}") return - self.slack_client.chat_postMessage(channel=channel, text=text) + await self.slack_client.chat_postMessage(channel=channel, text=text) async def _post_to_assignees(self, assignees, messages): - # TODO(phlax): this is blocking, switch to async slack client for name, text in messages.items(): # Only send texts if we have the slack UID if not (uid := assignees.get(name)): @@ -296,9 +295,9 @@ async def _post_to_assignees(self, assignees, messages): continue # Ship texts off to slack. try: - response = self.slack_client.conversations_open(users=uid, text="hello") + response = await self.slack_client.conversations_open(users=uid, text="hello") channel_id = response["channel"]["id"] - self.slack_client.chat_postMessage(channel=channel_id, text=message) + await self.slack_client.chat_postMessage(channel=channel_id, text=message) except SlackApiError as e: print(f"Unexpected error {e.response['error']}") diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index e8866cc61a90..3a73591424f9 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -35,6 +35,7 @@ CIO deadcode DFP DOM +Gasd GiB IPTOS Repick @@ -170,6 +171,7 @@ FIPS FIRSTHDR FQDN FREEBIND +curr eslint freeifaddrs FUZZER @@ -181,6 +183,7 @@ dnsresolvers endpos eps fo +gb ghi golang guarddog @@ -252,6 +255,7 @@ LF LHS hls iframe +ilp ingressed integrations iouring @@ -303,6 +307,7 @@ ODCDS middlewildcard monostate mpd +na oghttp OID OK @@ -772,6 +777,7 @@ gateway gcov genrule geolocation +geo geoip Geoip getaddrinfo @@ -856,6 +862,7 @@ iovecs ips iptables ish +isp istio istream istringstream @@ -918,6 +925,8 @@ matcher matchers maxage maxbuffer +Maxmind +maxmind megamiss mem memcmp @@ -939,6 +948,7 @@ misconfigured mixin mkdir mmap +mmdb mmsg mmsghdr mongo @@ -1347,6 +1357,7 @@ transcoder transcoding transferral trds +tri triaged trie tuple