From 164e8cb30a300e67258d855c05b150536eac86f1 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 11 Oct 2023 11:39:37 +0000 Subject: [PATCH 01/34] repo: Dev v1.27.2 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.1.yaml | 30 ++++++++++++++++++++++++++++++ changelogs/current.yaml | 39 +++++++++++++-------------------------- 3 files changed, 44 insertions(+), 27 deletions(-) create mode 100644 changelogs/1.27.1.yaml diff --git a/VERSION.txt b/VERSION.txt index 08002f86cc8d9..5b9b4efa580b7 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.1 +1.27.2-dev diff --git a/changelogs/1.27.1.yaml b/changelogs/1.27.1.yaml new file mode 100644 index 0000000000000..a6ce592912136 --- /dev/null +++ b/changelogs/1.27.1.yaml @@ -0,0 +1,30 @@ +date: October 11, 2023 + +behavior_changes: +- area: http + change: | + Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key + ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream + reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, + with the default value of 500, determines the number of requests received from a connection before the check for premature + resets is applied. The connection is disconnected if more than 50% of resets are premature. + Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables + this check. +- area: http + change: | + Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed + from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This + mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other + connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. + By default this limit is disabled. + +bug_fixes: +- area: connection limit + change: | + fixed a use-after-free bug in the connection limit filter. +- area: tls + change: | + fixed a bug where handshake may fail when both private key provider and cert validation are set. +- area: docker/publishing + change: | + Update base images to resolve various glibc vulnerabilities. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a6ce592912136..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,30 +1,17 @@ -date: October 11, 2023 +date: Pending behavior_changes: -- area: http - change: | - Close HTTP/2 and HTTP/3 connections that prematurely reset streams. The runtime key - ``overload.premature_reset_min_stream_lifetime_seconds`` determines the interval where received stream - reset is considered premature (with 1 second default). The runtime key ``overload.premature_reset_total_stream_count``, - with the default value of 500, determines the number of requests received from a connection before the check for premature - resets is applied. The connection is disconnected if more than 50% of resets are premature. - Setting the runtime key ``envoy.restart_features.send_goaway_for_premature_rst_streams`` to ``false`` completely disables - this check. -- area: http - change: | - Add runtime flag ``http.max_requests_per_io_cycle`` for setting the limit on the number of HTTP requests processed - from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. This - mitigates CPU starvation by connections that simultaneously send high number of requests by allowing requests from other - connections to make progress. This runtime value can be set to 1 in the presence of abusive HTTP/2 or HTTP/3 connections. - By default this limit is disabled. +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +minor_behavior_changes: +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: connection limit - change: | - fixed a use-after-free bug in the connection limit filter. -- area: tls - change: | - fixed a bug where handshake may fail when both private key provider and cert validation are set. -- area: docker/publishing - change: | - Update base images to resolve various glibc vulnerabilities. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: + +deprecated: From 200a6dd733add75af4ad8787a6a0565e1c2523e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:06:34 +0100 Subject: [PATCH 02/34] build(deps): bump distroless/base-nossl-debian12 from `54f30b8` to `bad3646` in /ci (#30048) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `54f30b8` to `bad3646`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 026bd59f3a5f1..37857b0469fcd 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:54f30b80bb6a6b0185deff049fa35cc65d883b641ee655747db97ffd17432e00 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:bad36468fcd4e6a96d961eab19ec794be3f86d97da4b75730673d63d8cad336d AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From e477510333338384b7e0926ab374016b4760e25e Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 07:23:20 +0100 Subject: [PATCH 03/34] ci/release: Dont run release tests/prechecks during actual release (#30120) Signed-off-by: Ryan Northey Signed-off-by: phlax --- .azure-pipelines/env.yml | 7 +++++++ .azure-pipelines/stage/linux.yml | 12 +++++++++++- .azure-pipelines/stage/prechecks.yml | 16 +++++++++++++++- .azure-pipelines/stages.yml | 5 +++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index c70c868965ba4..20d86c42b05ca 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -148,6 +148,8 @@ jobs: RUN_CHECKS=true RUN_DOCKER=true RUN_PACKAGING=true + RUN_RELEASE_TESTS=true + if [[ "$(changed.mobileOnly)" == true || "$(changed.docsOnly)" == true ]]; then RUN_BUILD=false RUN_DOCKER=false @@ -156,10 +158,14 @@ jobs: RUN_CHECKS=false RUN_PACKAGING=false fi + if [[ "$ISSTABLEBRANCH" == True && -n "$POSTSUBMIT" && "$(state.isDev)" == false ]]; then + RUN_RELEASE_TESTS=false + fi echo "##vso[task.setvariable variable=build;isoutput=true]${RUN_BUILD}" echo "##vso[task.setvariable variable=checks;isoutput=true]${RUN_CHECKS}" echo "##vso[task.setvariable variable=docker;isoutput=true]${RUN_DOCKER}" echo "##vso[task.setvariable variable=packaging;isoutput=true]${RUN_PACKAGING}" + echo "##vso[task.setvariable variable=releaseTests;isoutput=true]${RUN_RELEASE_TESTS}" displayName: "Decide what to run" workingDirectory: $(Build.SourcesDirectory) @@ -211,6 +217,7 @@ jobs: echo "env.outputs['run.build']: $(run.build)" echo "env.outputs['run.checks']: $(run.checks)" echo "env.outputs['run.packaging']: $(run.packaging)" + echo "env.outputs['run.releaseTests]: $(run.releaseTests)" echo echo "env.outputs['publish.githubRelease']: $(publish.githubRelease)" echo "env.outputs['publish.dockerhub]: $(publish.dockerhub)" diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index acf1cca81ba06..3946910246bb8 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -11,6 +11,10 @@ parameters: displayName: "Artifact suffix" type: string default: +- name: runTests + displayName: "Run release tests" + type: string + default: true - name: rbe displayName: "Use RBE" type: boolean @@ -44,11 +48,17 @@ jobs: eq(${{ parameters.runBuild }}, 'true')) timeoutInMinutes: ${{ parameters.timeoutBuild }} pool: ${{ parameters.pool }} + variables: + - name: ciTarget + ${{ if eq(parameters.runTests, false) }}: + value: release.server_only + ${{ if ne(parameters.runTests, false) }}: + value: release steps: - template: ../ci.yml parameters: managedAgent: ${{ parameters.managedAgent }} - ciTarget: release + ciTarget: $(ciTarget) cacheName: "release" bazelBuildExtraOptions: ${{ parameters.bazelBuildExtraOptions }} cacheTestResults: ${{ parameters.cacheTestResults }} diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 5e83ae21feadd..846c97c723f18 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -25,11 +25,25 @@ parameters: type: string default: "" +# Timeout/s +- name: timeoutPrechecks + type: number + # Building the rst from protos can take a while even with RBE if there is + # a lot of change - eg protobuf changed, or a primitve proto changed. + default: 40 + +- name: runPrechecks + displayName: "Run prechecks" + type: string + default: true jobs: - job: prechecks displayName: Precheck - timeoutInMinutes: 30 + timeoutInMinutes: ${{ parameters.timeoutPrechecks }} + condition: | + and(not(canceled()), + eq(${{ parameters.runPrechecks }}, 'true')) pool: vmImage: $(agentUbuntu) variables: diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index dad053a7af180..1b535277e2da1 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -60,6 +60,8 @@ stages: - stage: prechecks displayName: Prechecks dependsOn: ["env"] + variables: + RUN_PRECHECKS: $[stageDependencies.env.repo.outputs['run.releaseTests']] jobs: - template: stage/prechecks.yml parameters: @@ -70,17 +72,20 @@ stages: authGPGKey: $(MaintainerGPGKeySecureFileDownloadPath) authGPGPath: $(MaintainerGPGKey.secureFilePath) bucketGCP: $(GcsArtifactBucket) + runPrechecks: variables['RUN_PRECHECKS'] - stage: linux_x64 displayName: Linux x64 dependsOn: ${{ parameters.buildStageDeps }} variables: RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] + RUN_TESTS: $[stageDependencies.env.repo.outputs['run.releaseTests']] jobs: - template: stage/linux.yml parameters: cacheTestResults: ${{ parameters.cacheTestResults }} runBuild: variables['RUN_BUILD'] + runTests: variables['RUN_TESTS'] tmpfsDockerDisabled: true - stage: linux_arm64 From 9513891077530a169d1c4a2e7d360c3dc4747308 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 09:07:53 +0100 Subject: [PATCH 04/34] github/ci: Switch prechecks to pull_request_target and fix (#30126) Signed-off-by: Ryan Northey --- .github/actions/env/action.yml | 37 +++++++++++++++++++++------ .github/workflows/_env.yml | 14 ++-------- .github/workflows/envoy-prechecks.yml | 4 +-- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/.github/actions/env/action.yml b/.github/actions/env/action.yml index b5d44c56d24f6..d30cab498dc57 100644 --- a/.github/actions/env/action.yml +++ b/.github/actions/env/action.yml @@ -82,13 +82,16 @@ outputs: runs: using: composite steps: - - - if: ${{ inputs.check_mobile_run != 'false' }} - id: should_run - name: 'Check what to run' - run: ./mobile/tools/what_to_run.sh - shell: bash - + # Pull request/targets are _never_ trusted. + # + # For dispatch events, only specified bots are trusted. + # + # Commits to a branch are always trusted. + # + # If code is trusted its not allowed to check out any + # non-ancestor commit of a stable branch. + # + # Untrusted code can check out any commit. - id: trusted name: 'Check if its a trusted run' run: | @@ -109,7 +112,7 @@ runs: TRUSTED= fi fi - if [[ "${{ github.event_name }}" == "pull_request" ]]; then + if [[ "${{ github.event_name }}" == "pull_request" || "${{ github.event_name }}" == "pull_request_target" ]]; then echo "Not trusted pull_request event" TRUSTED= fi @@ -120,6 +123,24 @@ runs: fi shell: bash + # If we are in a trusted CI run then the provided commit _must_ be either the latest for + # this branch, or an antecdent. + - run: | + if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD &> /dev/null; then + echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 + exit 1 + fi + git checkout "${{ inputs.repo_ref }}" + if: ${{ steps.trusted.outputs.trusted == 'true' && inputs.repo_ref }} + name: Check provided ref + shell: bash + + - if: ${{ inputs.check_mobile_run != 'false' }} + id: should_run + name: 'Check what to run' + run: ./mobile/tools/what_to_run.sh + shell: bash + - id: context name: 'CI context' run: | diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 6abbdf220920c..72423bfc084b8 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -136,21 +136,11 @@ jobs: - uses: actions/checkout@v3 name: Checkout Envoy repository with: - fetch-depth: ${{ ! (inputs.check_mobile_run || inputs.trusted) && 1 || 0 }} + fetch-depth: ${{ ! (inputs.check_mobile_run || ! startsWith(github.event_name, 'pull_request')) && 1 || 0 }} # WARNING: This allows untrusted code to run!!! # If this is set, then anything before or after in the job should be regarded as # compromised. - ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} - # If we are in a trusted CI run then the provided commit _must_ be either the latest for - # this branch, or an antecdent. - - run: | - if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD; then - echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 - exit 1 - fi - git checkout "${{ inputs.repo_ref }}" - if: ${{ inputs.trusted }} - name: Check provided ref + ref: ${{ startsWith(github.event_name, 'pull_request') && inputs.repo_ref || '' }} - uses: ./.github/actions/env name: Generate environment variables diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 67fff9920a8e7..85d5a52e525aa 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -8,7 +8,7 @@ on: branches: - main - release/v* - pull_request: + pull_request_target: paths: - '**/requirements*.txt' - '**/go.mod' @@ -29,7 +29,7 @@ jobs: check_mobile_run: false permissions: contents: read - statuses: write + containers: read prechecks: needs: From f6d5bd98d4d6770451af05be7c908b69da6f8db7 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 09:55:19 +0100 Subject: [PATCH 05/34] github/prechecks: Minor fix for workflow (#30138) Signed-off-by: Ryan Northey --- .github/workflows/envoy-prechecks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 85d5a52e525aa..5e5c8d0555532 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -29,7 +29,7 @@ jobs: check_mobile_run: false permissions: contents: read - containers: read + packages: read prechecks: needs: From c8f625155402e36bedb8c73478999e25bacfa3f7 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Oct 2023 10:30:39 +0100 Subject: [PATCH 06/34] github/prechecks: Add back statuses cred for now (#30140) Signed-off-by: Ryan Northey --- .github/workflows/envoy-prechecks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 5e5c8d0555532..4a20abdc435fe 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -30,6 +30,8 @@ jobs: permissions: contents: read packages: read + # TODO(phlax): figure out how to remove this + statuses: write prechecks: needs: From 86f79e79e3b00e0db3794c555b42339d5ed06556 Mon Sep 17 00:00:00 2001 From: Florian Mutter Date: Fri, 13 Oct 2023 15:02:42 +0200 Subject: [PATCH 07/34] tracing: fix Datadog span name (#29932) (#30186) Backport c3646f994e0296ed44ecac2869fa65e7f58bf986 Additional testing: Also, I ran the sources/extensions/tracers/datadog/demo both with and without these changes. Verified that the produced span's "operation name" before these changes is not as desired. Verified that the produced span's "operation name" after these changes is as desired. Desired: "Operation name" is "envoy.proxy", and "resource name" is the operation_name passed to startSpan. Risk Level: low Testing: See the modified unit test. Docs Changes: n/a Release Notes: updated Signed-off-by: David Goffredo --- changelogs/current.yaml | 3 +++ source/extensions/tracers/datadog/tracer.cc | 7 ++++++- test/extensions/tracers/datadog/tracer_test.cc | 10 +++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..38c1f0f5611a9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,9 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: tracing + change: | + Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/tracers/datadog/tracer.cc b/source/extensions/tracers/datadog/tracer.cc index 180a2c5a3c36e..ac898f317b481 100644 --- a/source/extensions/tracers/datadog/tracer.cc +++ b/source/extensions/tracers/datadog/tracer.cc @@ -86,7 +86,12 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext // The OpenTracing implementation ignored the `Tracing::Config` argument, // so we will as well. datadog::tracing::SpanConfig span_config; - span_config.name = operation_name; + // The `operation_name` parameter to this function more closely matches + // Datadog's concept of "resource name." Datadog's "span name," or "operation + // name," instead describes the category of operation being performed, which + // here we hard-code. + span_config.name = "envoy.proxy"; + span_config.resource = operation_name; span_config.start = estimateTime(stream_info.startTime()); datadog::tracing::Tracer& tracer = *thread_local_tracer.tracer; diff --git a/test/extensions/tracers/datadog/tracer_test.cc b/test/extensions/tracers/datadog/tracer_test.cc index 1247b8a44f28a..8a4e276fd3daa 100644 --- a/test/extensions/tracers/datadog/tracer_test.cc +++ b/test/extensions/tracers/datadog/tracer_test.cc @@ -116,9 +116,13 @@ TEST_F(DatadogTracerTest, SpanProperties) { ASSERT_TRUE(maybe_dd_span); const datadog::tracing::Span& dd_span = *maybe_dd_span; - // Verify that the span has the expected service name, operation name, start - // time, and sampling decision. - EXPECT_EQ("do.thing", dd_span.name()); + // Verify that the span has the expected service name, operation name, + // resource name, start time, and sampling decision. + // Note that the `operation_name` we specified above becomes the + // `resource_name()` of the resulting Datadog span, while the Datadog span's + // `name()` (operation name) is hard-coded to "envoy.proxy." + EXPECT_EQ("envoy.proxy", dd_span.name()); + EXPECT_EQ("do.thing", dd_span.resource_name()); EXPECT_EQ("envoy", dd_span.service_name()); ASSERT_TRUE(dd_span.trace_segment().sampling_decision()); EXPECT_EQ(int(datadog::tracing::SamplingPriority::USER_DROP), From 7d27881305e78be191d107ab6cbd26953624fef1 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 13 Oct 2023 14:54:42 +0100 Subject: [PATCH 08/34] [bp/1.27] Backport stack (2) (#30173) publishing workflow fixes/cleanups enable engflow RBE Signed-off-by: Ryan Northey --- .bazelrc | 12 ++++++++++++ .github/workflows/_ci.yml | 18 ++++++++++++++++++ .github/workflows/_env.yml | 14 -------------- .github/workflows/_stage_publish.yml | 15 +++++++++++---- .github/workflows/envoy-prechecks.yml | 6 ++++-- .github/workflows/envoy-publish.yml | 17 ++++++++++++++--- bazel/engflow-bazel-credential-helper.sh | 8 ++++++++ tools/base/requirements.in | 2 +- tools/base/requirements.txt | 6 +++--- 9 files changed, 71 insertions(+), 27 deletions(-) create mode 100755 bazel/engflow-bazel-credential-helper.sh diff --git a/.bazelrc b/.bazelrc index a473dc4020da1..1a512e1879450 100644 --- a/.bazelrc +++ b/.bazelrc @@ -489,6 +489,18 @@ build:rbe-engflow --remote_timeout=3600s build:rbe-engflow --bes_timeout=3600s build:rbe-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --google_default_credentials=false +build:rbe-envoy-engflow --remote_cache=grpcs://morganite.cluster.engflow.com +build:rbe-envoy-engflow --remote_executor=grpcs://morganite.cluster.engflow.com +build:rbe-envoy-engflow --bes_backend=grpcs://morganite.cluster.engflow.com/ +build:rbe-envoy-engflow --bes_results_url=https://morganite.cluster.engflow.com/invocation/ +build:rbe-envoy-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:rbe-envoy-engflow --grpc_keepalive_time=30s +build:rbe-envoy-engflow --remote_timeout=3600s +build:rbe-envoy-engflow --bes_timeout=3600s +build:rbe-envoy-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 + ############################################################################# # debug: Various Bazel debugging flags ############################################################################# diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 60ba5b29cfbd2..8359114dac36e 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -2,6 +2,9 @@ name: Envoy CI on: workflow_call: + secrets: + app_id: + app_key: inputs: target: required: true @@ -96,6 +99,20 @@ jobs: with: image_tag: ${{ inputs.cache_build_image }} + - name: Check workflow context + id: context + run: | + if [[ "${{ inputs.trusted }}" != "false" && -n "${{ secrets.app_id }}" && -n "${{ secrets.app_key }}" ]]; then + echo "use_appauth=true" >> $GITHUB_OUTPUT + fi + - if: ${{ steps.context.outputs.use_appauth == 'true' }} + name: Fetch token for app auth + id: appauth + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.18 + with: + app_id: ${{ secrets.app_id }} + key: ${{ secrets.app_key }} + - uses: actions/checkout@v4 name: Checkout Envoy repository with: @@ -104,6 +121,7 @@ jobs: # If this is set, then anything before or after in the job should be regarded as # compromised. ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + token: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} # If we are in a trusted CI run then the provided commit _must_ be either the latest for # this branch, or an antecdent. diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 72423bfc084b8..f400c6ffb2538 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -27,10 +27,6 @@ on: type: boolean default: false - start_check_status: - type: string - default: - repo_ref: type: string default: @@ -173,16 +169,6 @@ jobs: echo "PR: https://github.com/envoyproxy/envoy/pull/${{ steps.env.outputs.repo_ref_pr_number }}" fi - check: - if: ${{ inputs.start_check_status && github.event_name != 'pull_request' }} - uses: ./.github/workflows/_workflow-start.yml - permissions: - contents: read - statuses: write - with: - workflow_name: ${{ inputs.start_check_status }} - sha: ${{ inputs.repo_ref_sha }} - cache: if: ${{ inputs.prime_build_image }} uses: ./.github/workflows/_cache_docker.yml diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 7765531c85eb1..ef3ba7f01e5d6 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -26,11 +26,13 @@ on: default: '' repo_ref: type: string - given_ref: + sha: type: string secrets: ENVOY_CI_SYNC_APP_ID: ENVOY_CI_SYNC_APP_KEY: + ENVOY_CI_PUBLISH_APP_ID: + ENVOY_CI_PUBLISH_APP_KEY: concurrency: group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-publish @@ -48,7 +50,7 @@ jobs: name: github run_pre: ./.github/actions/publish/release/setup run_pre_with: | - ref: ${{ inputs.given_ref }} + ref: ${{ inputs.repo_ref }} bucket: envoy-pr env: | export ENVOY_PUBLISH_DRY_RUN=1 @@ -68,7 +70,8 @@ jobs: if: ${{ inputs.trusted }} name: ${{ matrix.name || matrix.target }} permissions: - contents: write + contents: read + packages: read strategy: fail-fast: false matrix: @@ -77,9 +80,10 @@ jobs: name: github run_pre: ./.github/actions/publish/release/setup run_pre_with: | - ref: ${{ inputs.given_ref }} + ref: ${{ inputs.repo_ref }} bucket: envoy-postsubmit env: | + export ENVOY_COMMIT=${{ inputs.sha }} if [[ '${{ inputs.version_dev }}' == 'dev' ]]; then export ENVOY_PUBLISH_DRY_RUN=1 fi @@ -94,6 +98,9 @@ jobs: env: ${{ matrix.env }} trusted: true repo_ref: ${{ inputs.repo_ref }} + secrets: + app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} publish_docs: # For normal commits to Envoy main this will trigger an update in the website repo, diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 4a20abdc435fe..d12715c918cbe 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -30,8 +30,6 @@ jobs: permissions: contents: read packages: read - # TODO(phlax): figure out how to remove this - statuses: write prechecks: needs: @@ -45,8 +43,12 @@ jobs: managed: true uses: ./.github/workflows/_ci.yml name: CI ${{ matrix.target }} + permissions: + contents: read + packages: read with: target: ${{ matrix.target }} rbe: ${{ matrix.rbe }} + bazel_extra: '--config=rbe-envoy-engflow' managed: ${{ matrix.managed }} cache_build_image: ${{ needs.env.outputs.build_image_ubuntu }} diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 8c31140a54b83..3c99a8a7ac3cb 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -35,31 +35,42 @@ jobs: with: check_mobile_run: false prime_build_image: true - start_check_status: Verify/examples repo_ref: ${{ inputs.ref }} repo_ref_sha: ${{ inputs.sha }} repo_ref_name: ${{ inputs.head_ref }} + permissions: + contents: read + packages: read + check: + if: ${{ github.event_name != 'pull_request' }} + uses: ./.github/workflows/_workflow-start.yml permissions: contents: read statuses: write + with: + workflow_name: Verify/examples + sha: ${{ inputs.sha }} publish: needs: - env + - check uses: ./.github/workflows/_stage_publish.yml name: Publish ${{ needs.env.outputs.repo_ref_title }} with: build_image_ubuntu: ${{ needs.env.outputs.build_image_ubuntu }} trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} version_dev: ${{ needs.env.outputs.version_dev }} - given_ref: ${{ inputs.ref }} repo_ref: ${{ inputs.ref }} permissions: - contents: write + contents: read + packages: read secrets: ENVOY_CI_SYNC_APP_ID: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} ENVOY_CI_SYNC_APP_KEY: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} + ENVOY_CI_PUBLISH_APP_ID: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + ENVOY_CI_PUBLISH_APP_KEY: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} verify: uses: ./.github/workflows/_stage_verify.yml diff --git a/bazel/engflow-bazel-credential-helper.sh b/bazel/engflow-bazel-credential-helper.sh new file mode 100755 index 0000000000000..c6c1bd339b624 --- /dev/null +++ b/bazel/engflow-bazel-credential-helper.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Bazel expects the helper to read stdin. +# See https://github.com/bazelbuild/bazel/pull/17666 +cat /dev/stdin > /dev/null + +# `GITHUB_TOKEN` is provided as a secret. +echo "{\"headers\":{\"Authorization\":[\"Bearer ${GITHUB_TOKEN}\"]}}" diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 365fc84ff42f0..32c3fcbd48362 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -9,7 +9,7 @@ colorama coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 -envoy.base.utils>=0.4.12 +envoy.base.utils>=0.4.16 envoy.code.check>=0.5.8 envoy.dependency.check>=0.1.10 envoy.distribution.release>=0.0.9 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 0c25be0a5c190..23f3914623e85 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -448,9 +448,9 @@ docutils==0.19 \ # envoy-docs-sphinx-runner # sphinx # sphinx-rtd-theme -envoy-base-utils==0.4.12 \ - --hash=sha256:2bafcb6be3c1223968c9ee90b7a33d6d93aee16fe99bef37410ea9e4bc1a957f \ - --hash=sha256:b9b409abe83be6911aa03cfe635ed1e2e2b9e44e7c8595b2b7e5c7aae7bf70fc +envoy-base-utils==0.4.16 \ + --hash=sha256:b1ad6684dcf525651b01ded26ebb9f8ee5900089c786dd58b7a50ed663dafe3e \ + --hash=sha256:edaf42b3ae24aa34bb8bbb41b5e2eb1c5b230207cb00ff5a47cf259d31c6c628 # via # -r requirements.in # envoy-code-check From be35697efdc3c0a57cd9e47dcbe16caf98475327 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 16 Oct 2023 09:01:57 +0100 Subject: [PATCH 09/34] build/image: Bump to `fdd65c62` Signed-off-by: Ryan Northey --- .bazelrc | 4 ++-- .devcontainer/Dockerfile | 2 +- .github/workflows/_env.yml | 6 +++--- bazel/repository_locations.bzl | 6 +++--- ci/run_envoy_docker.sh | 4 ---- examples/shared/build/Dockerfile | 2 +- mobile/third_party/rbe_configs/config/BUILD | 4 ++-- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.bazelrc b/.bazelrc index 1a512e1879450..b9d84f1124677 100644 --- a/.bazelrc +++ b/.bazelrc @@ -339,7 +339,7 @@ build:compile-time-options --@envoy//source/extensions/filters/http/kill_request # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker @@ -499,7 +499,7 @@ build:rbe-envoy-engflow --grpc_keepalive_time=30s build:rbe-envoy-engflow --remote_timeout=3600s build:rbe-envoy-engflow --bes_timeout=3600s build:rbe-envoy-engflow --bes_upload_mode=fully_async -build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 ############################################################################# # debug: Various Bazel debugging flags diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3859774ea0b0d..066695f4922a2 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:94e5d873c145ae86f205117e76276161c9af4806@sha256:3c3d299423a878a219a333153726cddf7cc5e5ff30f596dc97bafba521f2f928 +FROM gcr.io/envoy-ci/envoy-build:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:2a473cd9808182735d54e03b158975389948b9559b8e8fc624cfafbaf7059e62 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index f400c6ffb2538..a469aa3156b14 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -12,13 +12,13 @@ on: default: envoyproxy/envoy-build-ubuntu build_image_sha: type: string - default: 8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 + default: 06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 build_image_mobile_sha: type: string - default: 0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95 + default: f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e build_image_tag: type: string - default: 94e5d873c145ae86f205117e76276161c9af4806 + default: fdd65c6270a8507a18d5acd6cf19a18cb695e4fa check_mobile_run: type: boolean diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 5c9fd142833f2..7f83d0cd5f5aa 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -102,11 +102,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "fc1ab3e96cf275ecaac913be2a22bce4a74b9272", - sha256 = "75fff0c28766ccb4e625244e35c950eb071d4bfb4a443b387140e1c037eeb6cc", + version = "f727ec142156c8076384a35c0e2d51da3c1d7813", + sha256 = "72510592f34f3fd6269c5fdd2286465a05ce6ca438ac1faebfdb88ed309fe9da", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2023-09-20", + release_date = "2023-10-16", use_category = ["build"], license = "Apache-2.0", license_url = "https://github.com/envoyproxy/envoy-build-tools/blob/{version}/LICENSE", diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index c0a36e08df527..8fdf249bcf8e1 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -19,10 +19,6 @@ export GOPROXY="${go_proxy:-}" if is_windows; then [[ -z "${IMAGE_NAME}" ]] && IMAGE_NAME="envoyproxy/envoy-build-windows2019" - # Container networking is unreliable in the most recently built images, pin Windows to a known - # good container. This can create a mismatch between the host environment, and the toolchain - # environment. - ENVOY_BUILD_SHA=41c5a05d708972d703661b702a63ef5060125c33 # TODO(sunjayBhatia): Currently ENVOY_DOCKER_OPTIONS is ignored on Windows because # CI sets it to a Linux-specific value. Undo this once https://github.com/envoyproxy/envoy/issues/13272 # is resolved. diff --git a/examples/shared/build/Dockerfile b/examples/shared/build/Dockerfile index 8fed6f57e6ce7..37357200b93bf 100644 --- a/examples/shared/build/Dockerfile +++ b/examples/shared/build/Dockerfile @@ -1,4 +1,4 @@ -FROM envoyproxy/envoy-build-ubuntu:94e5d873c145ae86f205117e76276161c9af4806@sha256:8d3763e19d5b71fdc95666d75073ce4581e566ce28ca09106607b6a3ef7ba902 +FROM envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ diff --git a/mobile/third_party/rbe_configs/config/BUILD b/mobile/third_party/rbe_configs/config/BUILD index 558a7ce5f01b3..77ce2843c8acd 100644 --- a/mobile/third_party/rbe_configs/config/BUILD +++ b/mobile/third_party/rbe_configs/config/BUILD @@ -42,7 +42,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e", "OSFamily": "Linux", "Pool": "linux", }, @@ -57,7 +57,7 @@ platform( "@bazel_tools//tools/cpp:clang", ], exec_properties = { - "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-94e5d873c145ae86f205117e76276161c9af4806@sha256:0f51a1015964355092d9204acdacdd727474d1af189bc256dd5a28e6126f9c95", + "container-image": "docker://envoyproxy/envoy-build-ubuntu:mobile-fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e", "OSFamily": "Linux", "Pool": "linux", # Necessary to workaround https://github.com/google/sanitizers/issues/916, otherwise, dangling threads in the From f1c8165c54ea4b10bb3786698766b0abe9548248 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 16 Oct 2023 11:42:37 +0100 Subject: [PATCH 10/34] ci: Run linux/win/mac ci immediately on release branches Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 99f458d07abde..8b3a86af2bd09 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -65,6 +65,13 @@ stages: # Presubmit/default - ${{ if eq(variables.pipelineDefault, true) }}: - template: stages.yml + parameters: + buildStageDeps: + - env + macBuildStageDeps: + - env + windowsBuildStageDeps: + - env # Scheduled run anywhere - ${{ if eq(variables.pipelineScheduled, true) }}: From d3c640f454f52a043c76562ac2b9892e735737bd Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 16 Oct 2023 16:03:33 +0100 Subject: [PATCH 11/34] bazel/ci: Cleanup flags and env vars (#30211) Signed-off-by: Ryan Northey --- .azure-pipelines/ci.yml | 106 ++++++++++++++++----------- .azure-pipelines/stage/checks.yml | 8 -- .azure-pipelines/stage/macos.yml | 8 +- .azure-pipelines/stage/prechecks.yml | 15 +--- .azure-pipelines/stage/publish.yml | 24 +++--- .bazelrc | 2 + ci/build_setup.sh | 8 -- ci/run_envoy_docker.sh | 8 +- ci/setup_cache.sh | 33 +++------ ci/upload_gcs_artifact.sh | 20 ++--- 10 files changed, 100 insertions(+), 132 deletions(-) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index db6de8c31567a..5e8b380cafad0 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -176,31 +176,68 @@ steps: tmpfsDockerDisabled: "${{ parameters.tmpfsDockerDisabled }}" - script: | - if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then - unset GOOGLE_BES_PROJECT_ID + ENVOY_SHARED_TMP_DIR=/tmp/bazel-shared + mkdir -p "$ENVOY_SHARED_TMP_DIR" + BAZEL_BUILD_EXTRA_OPTIONS="${{ parameters.bazelBuildExtraOptions }}" + if [[ "${{ parameters.rbe }}" == "True" ]]; then + # mktemp will create a tempfile with u+rw permission minus umask, it will not be readable by all + # users by default. + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -p "${ENVOY_SHARED_TMP_DIR}" -t gcp_service_account.XXXXXX.json) + bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" + BAZEL_BUILD_EXTRA_OPTIONS+=" ${{ parameters.bazelConfigRBE }} --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" + ENVOY_RBE=1 + if [[ "${{ parameters.bazelUseBES }}" == "True" ]]; then + BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" + fi + else + echo "using local build cache." + # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` + TARGET_BRANCH=$(echo "${CI_TARGET_BRANCH}" | cut -d/ -f2-) + BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" + if [[ "$BRANCH_NAME" == "merge" ]]; then + # Manually run PR commit - there is no easy way of telling which branch + # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` + BRANCH_NAME=main + fi + BAZEL_REMOTE_INSTANCE="branch/${BRANCH_NAME}" + echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." + BAZEL_BUILD_EXTRA_OPTIONS+=" --config=ci --config=cache-local --remote_instance_name=${BAZEL_REMOTE_INSTANCE} --remote_timeout=600" fi - ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' - condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + if [[ "${{ parameters.cacheTestResults }}" != "True" ]]; then + VERSION_DEV="$(cut -d- -f2 "VERSION.txt")" + # Use uncached test results for non-release scheduledruns. + if [[ $VERSION_DEV == "dev" ]]; then + BAZEL_EXTRA_TEST_OPTIONS+=" --nocache_test_results" + fi + fi + # Any PR or CI run in envoy-presubmit uses the fake SCM hash + if [[ "${{ variables['Build.Reason'] }}" == "PullRequest" || "${{ variables['Build.DefinitionName'] }}" == 'envoy-presubmit' ]]; then + # sha1sum of `ENVOY_PULL_REQUEST` + BAZEL_FAKE_SCM_REVISION=e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 + fi + echo "##vso[task.setvariable variable=BAZEL_BUILD_EXTRA_OPTIONS]${BAZEL_BUILD_EXTRA_OPTIONS}" + echo "##vso[task.setvariable variable=BAZEL_EXTRA_TEST_OPTIONS]${BAZEL_EXTRA_TEST_OPTIONS}" + echo "##vso[task.setvariable variable=BAZEL_FAKE_SCM_REVISION]${BAZEL_FAKE_SCM_REVISION}" + echo "##vso[task.setvariable variable=BAZEL_STARTUP_EXTRA_OPTIONS]${{ parameters.bazelStartupExtraOptions }}" + echo "##vso[task.setvariable variable=CI_TARGET_BRANCH]${CI_TARGET_BRANCH}" + echo "##vso[task.setvariable variable=ENVOY_BUILD_FILTER_EXAMPLE]${{ parameters.envoyBuildFilterExample }}" + echo "##vso[task.setvariable variable=ENVOY_DOCKER_BUILD_DIR]$(Build.StagingDirectory)" + echo "##vso[task.setvariable variable=ENVOY_RBE]${ENVOY_RBE}" + echo "##vso[task.setvariable variable=ENVOY_SHARED_TMP_DIR]${ENVOY_SHARED_TMP_DIR}" + echo "##vso[task.setvariable variable=GCP_SERVICE_ACCOUNT_KEY_PATH]${GCP_SERVICE_ACCOUNT_KEY_PATH}" + echo "##vso[task.setvariable variable=GITHUB_TOKEN]${{ parameters.authGithub }}" workingDirectory: $(Build.SourcesDirectory) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - GITHUB_TOKEN: "${{ parameters.authGithub }}" - BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" - # Any PR or CI run in envoy-presubmit uses the fake SCM hash - ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: - # sha1sum of `ENVOY_PULL_REQUEST` - BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 - ${{ if parameters.rbe }}: - GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" - ${{ if eq(parameters.rbe, false) }}: - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: $(LocalBuildCache) + displayName: "CI env ${{ parameters.ciTarget }}" + +- script: ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' + condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + workingDirectory: $(Build.SourcesDirectory) + env: ${{ each var in parameters.env }}: ${{ var.key }}: ${{ var.value }} displayName: "Fetch assets (${{ parameters.ciTarget }})" @@ -231,34 +268,10 @@ steps: displayName: "Enable IPv6" condition: ${{ parameters.managedAgent }} -- script: | - if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then - unset GOOGLE_BES_PROJECT_ID - fi - ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' +- script: ci/run_envoy_docker.sh 'ci/do_ci.sh ${{ parameters.ciTarget }}' workingDirectory: $(Build.SourcesDirectory) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_BUILD_FILTER_EXAMPLE: ${{ parameters.envoyBuildFilterExample }} - GITHUB_TOKEN: "${{ parameters.authGithub }}" - BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" - ${{ if ne(parameters['cacheTestResults'], true) }}: - BAZEL_NO_CACHE_TEST_RESULTS: 1 - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" - # Any PR or CI run in envoy-presubmit uses the fake SCM hash - ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: - # sha1sum of `ENVOY_PULL_REQUEST` - BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 - ${{ if parameters.rbe }}: - GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" - ${{ if eq(parameters.rbe, false) }}: - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: $(LocalBuildCache) ${{ each var in parameters.env }}: ${{ var.key }}: ${{ var.value }} displayName: "Run CI script ${{ parameters.ciTarget }}" @@ -296,6 +309,13 @@ steps: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} +- bash: | + if [[ -n "$GCP_SERVICE_ACCOUNT_KEY_PATH" && -e "$GCP_SERVICE_ACCOUNT_KEY_PATH" ]]; then + echo "Removed key: ${GCP_SERVICE_ACCOUNT_KEY_PATH}" + rm -rf "$GCP_SERVICE_ACCOUNT_KEY_PATH" + fi + condition: not(canceled()) + - script: | set -e sudo .azure-pipelines/docker/save_cache.sh "$(Build.StagingDirectory)" /mnt/cache/all true true diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index f39eec4787d9c..8c03249e227b3 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -101,15 +101,7 @@ jobs: displayName: "Upload $(CI_TARGET) Report to GCS" condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'coverage'), eq(variables['CI_TARGET'], 'fuzz_coverage'))) env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(System.PullRequest.TargetBranch)" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - BAZEL_REMOTE_INSTANCE_BRANCH: "$(Build.SourceBranchName)" - job: complete displayName: "Checks complete" diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml index 6089bd89ee896..4b7f99e718d31 100644 --- a/.azure-pipelines/stage/macos.yml +++ b/.azure-pipelines/stage/macos.yml @@ -27,9 +27,11 @@ jobs: - script: ./ci/mac_ci_steps.sh displayName: "Run Mac CI" env: - BAZEL_BUILD_EXTRA_OPTIONS: "--remote_download_toplevel --flaky_test_attempts=2" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: >- + --remote_download_toplevel + --flaky_test_attempts=2 + --remote_cache=grpcs://remotebuildexecution.googleapis.com + --remote_instance_name=projects/envoy-ci/instances/default_instance GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} ENVOY_RBE: 1 diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 846c97c723f18..b699a960eacec 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -99,7 +99,7 @@ jobs: authGPGKey: ${{ parameters.authGPGKey }} # GNUPGHOME inside the container pathGPGConfiguredHome: /build/.gnupg - pathGPGHome: /tmp/envoy-docker-build/.gnupg + pathGPGHome: $(Build.StagingDirectory)/.gnupg - bash: | set -e ci/run_envoy_docker.sh " @@ -107,7 +107,7 @@ jobs: && gpg --clearsign /tmp/authority \ && cat /tmp/authority.asc \ && gpg --verify /tmp/authority.asc" - rm -rf /tmp/envoy-docker-build/.gnupg + rm -rf $(Build.StagingDirectory)/.gnupg displayName: "Ensure container CI can sign with GPG" condition: and(not(canceled()), eq(variables['CI_TARGET'], 'docs')) @@ -129,10 +129,6 @@ jobs: ci/run_envoy_docker.sh 'ci/do_ci.sh dockerhub-readme' displayName: "Dockerhub publishing test" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') @@ -155,14 +151,9 @@ jobs: condition: and(failed(), eq(variables['CI_TARGET'], 'check_and_fix_proto_format')) # Publish docs - - script: | - ci/run_envoy_docker.sh 'ci/do_ci.sh docs-upload' + - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs-upload' displayName: "Upload Docs to GCS" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 1eb1d57584cbe..b361552e4e205 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -123,10 +123,6 @@ jobs: eq(${{ parameters.publishDockerhub }}, 'true')) displayName: "Publish Dockerhub description and README" env: - ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) - ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} @@ -277,6 +273,16 @@ jobs: pool: vmImage: $(agentUbuntu) steps: + - task: DownloadSecureFile@1 + name: WorkflowTriggerKey + displayName: 'Download workflow trigger key' + inputs: + secureFile: '${{ parameters.authGithubWorkflow }}' + - bash: | + set -e + KEY="$(cat $(WorkflowTriggerKey.secureFilePath) | base64 -w0)" + echo "##vso[task.setvariable variable=value;isoutput=true]$KEY" + name: key - template: ../ci.yml parameters: ciTarget: verify.trigger @@ -310,13 +316,3 @@ jobs: mkdir -p $(Build.StagingDirectory)/release.signed mv release.signed.tar.zst $(Build.StagingDirectory)/release.signed displayName: Fetch signed release - - task: DownloadSecureFile@1 - name: WorkflowTriggerKey - displayName: 'Download workflow trigger key' - inputs: - secureFile: '${{ parameters.authGithubWorkflow }}' - - bash: | - set -e - KEY="$(cat $(WorkflowTriggerKey.secureFilePath) | base64 -w0)" - echo "##vso[task.setvariable variable=value;isoutput=true]$KEY" - name: key diff --git a/.bazelrc b/.bazelrc index b9d84f1124677..46caa84309f85 100644 --- a/.bazelrc +++ b/.bazelrc @@ -221,6 +221,8 @@ build:fuzz-coverage --config=plain-fuzzer build:fuzz-coverage --run_under=@envoy//bazel/coverage:fuzz_coverage_wrapper.sh build:fuzz-coverage --test_tag_filters=-nocoverage +build:cache-local --remote_cache=grpc://localhost:9092 + # Remote execution: https://docs.bazel.build/versions/master/remote-execution.html build:rbe-toolchain --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 2d54fa423bc35..00f4c2c752278 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -119,14 +119,6 @@ bazel () { export _bazel export -f bazel -if [[ -n "$BAZEL_NO_CACHE_TEST_RESULTS" ]]; then - VERSION_DEV="$(cut -d- -f2 "${ENVOY_SRCDIR}/VERSION.txt")" - # Use uncached test results for non-release commits to a branch. - if [[ $VERSION_DEV == "dev" ]]; then - BAZEL_EXTRA_TEST_OPTIONS+=("--nocache_test_results") - fi -fi - # Use https://docs.bazel.build/versions/master/command-line-reference.html#flag--experimental_repository_cache_hardlinks # to save disk space. BAZEL_GLOBAL_OPTIONS=( diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 8fdf249bcf8e1..36c438bf01132 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -86,13 +86,13 @@ VOLUMES=( -v "${ENVOY_DOCKER_BUILD_DIR}":"${BUILD_DIR_MOUNT_DEST}" -v "${SOURCE_DIR}":"${SOURCE_DIR_MOUNT_DEST}") -if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" ]]; then +if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" || -n "$ENVOY_SHARED_TMP_DIR" ]]; then # Create a "shared" directory that has the same path in/outside the container # This allows the host docker engine to see artefacts using a temporary path created inside the container, # at the same path. # For example, a directory created with `mktemp -d --tmpdir /tmp/bazel-shared` can be mounted as a volume # from within the build container. - SHARED_TMP_DIR=/tmp/bazel-shared + SHARED_TMP_DIR="${ENVOY_SHARED_TMP_DIR:-/tmp/bazel-shared}" mkdir -p "${SHARED_TMP_DIR}" chmod +rwx "${SHARED_TMP_DIR}" VOLUMES+=(-v "${SHARED_TMP_DIR}":"${SHARED_TMP_DIR}") @@ -102,7 +102,6 @@ if [[ -n "${ENVOY_DOCKER_PULL}" ]]; then time docker pull "${ENVOY_BUILD_IMAGE}" fi - # Since we specify an explicit hash, docker-run will pull from the remote repo if missing. docker run --rm \ "${ENVOY_DOCKER_OPTIONS[@]}" \ @@ -124,10 +123,9 @@ docker run --rm \ -e DOCKERHUB_PASSWORD \ -e ENVOY_STDLIB \ -e BUILD_REASON \ - -e BAZEL_NO_CACHE_TEST_RESULTS \ -e BAZEL_REMOTE_INSTANCE \ - -e GOOGLE_BES_PROJECT_ID \ -e GCP_SERVICE_ACCOUNT_KEY \ + -e GCP_SERVICE_ACCOUNT_KEY_PATH \ -e NUM_CPUS \ -e ENVOY_BRANCH \ -e ENVOY_RBE \ diff --git a/ci/setup_cache.sh b/ci/setup_cache.sh index da0b189dd4a88..ca910ec1a090c 100755 --- a/ci/setup_cache.sh +++ b/ci/setup_cache.sh @@ -14,37 +14,22 @@ if [[ -n "${GCP_SERVICE_ACCOUNT_KEY:0:1}" ]]; then trap gcp_service_account_cleanup EXIT + echo "Setting GCP_SERVICE_ACCOUNT_KEY is deprecated, please place your decoded GCP key in " \ + "an exported/shared tmp directory and add it to BAZEL_BUILD_EXTRA_OPTIONS, eg: " >&2 + # shellcheck disable=SC2086 + echo "$ export ENVOY_SHARED_TMP_DIR=/tmp/envoy-shared" \ + "$ ENVOY_RBE_KEY_PATH=$(mktemp -p \"${ENVOY_SHARED_TMP_DIR}\" -t gcp_service_account.XXXXXX.json)" \ + "$ bash -c 'echo \"$(GcpServiceAccountKey)\"' | base64 --decode > \"${ENVOY_RBE_KEY_PATH}\"" \ + "$ export BAZEL_BUILD_EXTRA_OPTIONS+=\" --google_credentials=${ENVOY_RBE_KEY_PATH}\"" >&2 bash -c 'echo "${GCP_SERVICE_ACCOUNT_KEY}"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_FILE}" - export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_FILE}" - - if [[ -n "${GOOGLE_BES_PROJECT_ID}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" - fi - fi if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then + echo "Setting BAZEL_REMOTE_CACHE is deprecated, please use BAZEL_BUILD_EXTRA_OPTIONS " \ + "or use a user.bazelrc config " >&2 export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_cache=${BAZEL_REMOTE_CACHE}" echo "Set up bazel remote read/write cache at ${BAZEL_REMOTE_CACHE}." - - if [[ -z "${ENVOY_RBE}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_timeout=600" - echo "using local build cache." - # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` - TARGET_BRANCH="${CI_TARGET_BRANCH}" - if [[ "$TARGET_BRANCH" =~ ^origin/ ]]; then - TARGET_BRANCH=$(echo "$TARGET_BRANCH" | cut -d/ -f2-) - fi - BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" - if [[ "$BRANCH_NAME" == "merge" ]]; then - # Manually run PR commit - there is no easy way of telling which branch - # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` - BRANCH_NAME=main - fi - BAZEL_REMOTE_INSTANCE="branch/${BRANCH_NAME}" - fi - if [[ -n "${BAZEL_REMOTE_INSTANCE}" ]]; then export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_instance_name=${BAZEL_REMOTE_INSTANCE}" echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." diff --git a/ci/upload_gcs_artifact.sh b/ci/upload_gcs_artifact.sh index 6367184a408b5..339a4e98dc4dc 100755 --- a/ci/upload_gcs_artifact.sh +++ b/ci/upload_gcs_artifact.sh @@ -7,27 +7,17 @@ if [[ -z "${GCS_ARTIFACT_BUCKET}" ]]; then exit 1 fi -if [[ -z "${GCP_SERVICE_ACCOUNT_KEY}" ]]; then - echo "GCP key is not set, not uploading artifacts." - exit 1 -fi - read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTION_LIST:-}" read -ra BAZEL_BUILD_OPTIONS <<< "${BAZEL_BUILD_OPTION_LIST:-}" -remove_key () { - rm -rf "$KEYFILE" -} - -trap remove_key EXIT - -# Fail when service account key is not specified -KEYFILE="$(mktemp)" -bash -c 'echo ${GCP_SERVICE_ACCOUNT_KEY}' | base64 --decode > "$KEYFILE" +if [[ ! -s "${GCP_SERVICE_ACCOUNT_KEY_PATH}" ]]; then + echo "GCP key is not set, not uploading artifacts." + exit 1 +fi cat < ~/.boto [Credentials] -gs_service_key_file=${KEYFILE} +gs_service_key_file=${GCP_SERVICE_ACCOUNT_KEY_PATH} EOF SOURCE_DIRECTORY="$1" From 9f3e8a37d0d919938ac1ce930800dc62cab0129f Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 16 Oct 2023 10:10:02 -0400 Subject: [PATCH 12/34] HCM: Make reverse iteration resilient to element deletion (#30158) --------- Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 4 + source/common/http/conn_manager_impl.cc | 26 ++++-- test/common/http/http2/http2_frame.cc | 10 +++ test/common/http/http2/http2_frame.h | 3 +- test/integration/BUILD | 1 + .../local_reply_during_decoding_filter.cc | 6 +- .../multiplexed_integration_test.cc | 90 +++++++++++++++++++ 7 files changed, 130 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 38c1f0f5611a9..0ae4cf227f7e7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -11,6 +11,10 @@ bug_fixes: - area: tracing change: | Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index bf04397084134..f567d659f7022 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -2195,6 +2196,8 @@ bool ConnectionManagerImpl::ActiveStream::onDeferredRequestProcessing() { if (end_stream) { return true; } + // Filter manager will return early from decodeData and decodeTrailers if + // request has completed. if (deferred_data_ != nullptr) { end_stream = state_.deferred_end_stream_ && request_trailers_ == nullptr; filter_manager_.decodeData(*deferred_data_, end_stream); @@ -2224,19 +2227,26 @@ bool ConnectionManagerImpl::shouldDeferRequestProxyingToNextIoCycle() { } void ConnectionManagerImpl::onDeferredRequestProcessing() { + if (streams_.empty()) { + return; + } requests_during_dispatch_count_ = 1; // 1 stream is always let through // Streams are inserted at the head of the list. As such process deferred - // streams at the back of the list first. - for (auto reverse_iter = streams_.rbegin(); reverse_iter != streams_.rend();) { - auto& stream_ptr = *reverse_iter; - // Move the iterator to the next item in case the `onDeferredRequestProcessing` call removes the - // stream from the list. - ++reverse_iter; - bool was_deferred = stream_ptr->onDeferredRequestProcessing(); + // streams in the reverse order. + auto reverse_iter = std::prev(streams_.end()); + bool at_first_element = false; + do { + at_first_element = reverse_iter == streams_.begin(); + // Move the iterator to the previous item in case the `onDeferredRequestProcessing` call removes + // the stream from the list. + auto previous_element = std::prev(reverse_iter); + bool was_deferred = (*reverse_iter)->onDeferredRequestProcessing(); if (was_deferred && shouldDeferRequestProxyingToNextIoCycle()) { break; } - } + reverse_iter = previous_element; + // TODO(yanavlasov): see if `rend` can be used. + } while (!at_first_element); } } // namespace Http diff --git a/test/common/http/http2/http2_frame.cc b/test/common/http/http2/http2_frame.cc index 46ba3751ba242..319b3fc87380f 100644 --- a/test/common/http/http2/http2_frame.cc +++ b/test/common/http/http2/http2_frame.cc @@ -51,12 +51,22 @@ Http2Frame::ResponseStatus Http2Frame::responseStatus() const { return ResponseStatus::Ok; case StaticHeaderIndex::Status404: return ResponseStatus::NotFound; + case StaticHeaderIndex::Status500: + return ResponseStatus::InternalServerError; default: break; } return ResponseStatus::Unknown; } +uint32_t Http2Frame::streamId() const { + if (empty() || size() <= HeaderSize) { + return 0; + } + return (uint32_t(data_[5]) << 24) + (uint32_t(data_[6]) << 16) + (uint32_t(data_[7]) << 8) + + uint32_t(data_[8]); +} + void Http2Frame::buildHeader(Type type, uint32_t payload_size, uint8_t flags, uint32_t stream_id) { data_.assign(payload_size + HeaderSize, 0); setPayloadSize(payload_size); diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index 5df3375c16616..6349f5d94d845 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -121,7 +121,7 @@ class Http2Frame { Http11Required }; - enum class ResponseStatus { Unknown, Ok, NotFound }; + enum class ResponseStatus { Unknown, Ok, NotFound, InternalServerError }; struct Header { Header(absl::string_view key, absl::string_view value) : key_(key), value_(value) {} @@ -226,6 +226,7 @@ class Http2Frame { return false; } ResponseStatus responseStatus() const; + uint32_t streamId() const; // Copy HTTP2 header. The `header` parameter must at least be HeaderSize long. // Allocates payload size based on the value in the header. diff --git a/test/integration/BUILD b/test/integration/BUILD index 260c51d042c7e..f2b7b874f6bbb 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -504,6 +504,7 @@ envoy_cc_test( "//source/extensions/filters/http/buffer:config", "//source/extensions/load_balancing_policies/ring_hash:config", "//test/integration/filters:encode1xx_local_reply_config_lib", + "//test/integration/filters:local_reply_during_decoding_filter_lib", "//test/integration/filters:metadata_stop_all_filter_config_lib", "//test/integration/filters:on_local_reply_filter_config_lib", "//test/integration/filters:request_metadata_filter_config_lib", diff --git a/test/integration/filters/local_reply_during_decoding_filter.cc b/test/integration/filters/local_reply_during_decoding_filter.cc index 69d822e8dcca2..f29beb5723655 100644 --- a/test/integration/filters/local_reply_during_decoding_filter.cc +++ b/test/integration/filters/local_reply_during_decoding_filter.cc @@ -15,7 +15,11 @@ class LocalReplyDuringDecode : public Http::PassThroughFilter { public: constexpr static char name[] = "local-reply-during-decode"; - Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override { + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& request_headers, bool) override { + auto result = request_headers.get(Http::LowerCaseString("skip-local-reply")); + if (!result.empty() && result[0]->value() == "true") { + return Http::FilterHeadersStatus::Continue; + } decoder_callbacks_->sendLocalReply(Http::Code::InternalServerError, "", nullptr, absl::nullopt, ""); return Http::FilterHeadersStatus::StopIteration; diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index edce9a1838d98..3aca9af441b40 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2183,6 +2183,43 @@ TEST_P(Http2FrameIntegrationTest, MultipleRequests) { tcp_client_->close(); } +// Validate the request completion during processing of deferred list works. +TEST_P(Http2FrameIntegrationTest, MultipleRequestsDecodeHeadersEndsRequest) { + const int kRequestsSentPerIOCycle = 20; + // The local-reply-during-decode will call sendLocalReply, completing them + // when processing headers. This will cause the ConnectionManagerImpl::ActiveRequest + // object to be removed from the streams_ list during the onDeferredRequestProcessing call. + config_helper_.addFilter("{ name: local-reply-during-decode }"); + // Process more than 1 deferred request at a time to validate the removal of elements from + // the list does not break reverse iteration. + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "3"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = + Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a", + Http2Frame::DataFlags::EndStream); + absl::StrAppend(&buffer, std::string(data)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + // The local-reply-during-decode filter sends 500 status to the client + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::InternalServerError, frame.responseStatus()); + } + tcp_client_->close(); +} + TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailers) { const int kRequestsSentPerIOCycle = 20; autonomous_upstream_ = true; @@ -2222,6 +2259,59 @@ TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailers) { tcp_client_->close(); } +// Validate the request completion during processing of headers in the deferred requests, +// is ok, when deferred data and trailers are also present. +TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithTrailersDecodeHeadersEndsRequest) { + const int kRequestsSentPerIOCycle = 20; + autonomous_upstream_ = true; + config_helper_.addFilter("{ name: local-reply-during-decode }"); + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "6"); + beginSession(); + + std::string buffer; + // Make every 4th request to be reset by the local-reply-during-decode filter, this will give a + // good distribution of removed requests from the deferred sequence. + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, + {"no_trailers", "1"}, + {"skip-local-reply", i % 4 ? "true" : "false"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "a"); + absl::StrAppend(&buffer, std::string(data)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto trailers = Http2Frame::makeEmptyHeadersFrame( + Http2Frame::makeClientStreamId(i), + static_cast(Http::Http2::orFlags( + Http2Frame::HeadersFlags::EndStream, Http2Frame::HeadersFlags::EndHeaders))); + trailers.appendHeaderWithoutIndexing({"k", "v"}); + trailers.adjustPayloadSize(); + absl::StrAppend(&buffer, std::string(trailers)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + uint32_t stream_id = frame.streamId(); + // Client stream indexes are multiples of 2 starting at 1 + if ((stream_id / 2) % 4) { + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()) + << " for stream=" << stream_id; + } else { + EXPECT_EQ(Http2Frame::ResponseStatus::InternalServerError, frame.responseStatus()) + << " for stream=" << stream_id; + } + } + tcp_client_->close(); +} + TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequestsFollowedByReset) { // This number of requests stays below premature reset detection. const int kRequestsSentPerIOCycle = 20; From 5ad31d0e2e9fc27485088f032a7406f1590f4a0f Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 17 Oct 2023 07:56:12 +0100 Subject: [PATCH 13/34] build/image: Fix sha (#30257) Signed-off-by: Ryan Northey --- .bazelrc | 4 ++-- .github/workflows/_env.yml | 2 +- examples/shared/build/Dockerfile | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index 46caa84309f85..6b080508f38cf 100644 --- a/.bazelrc +++ b/.bazelrc @@ -341,7 +341,7 @@ build:compile-time-options --@envoy//source/extensions/filters/http/kill_request # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker @@ -501,7 +501,7 @@ build:rbe-envoy-engflow --grpc_keepalive_time=30s build:rbe-envoy-engflow --remote_timeout=3600s build:rbe-envoy-engflow --bes_timeout=3600s build:rbe-envoy-engflow --bes_upload_mode=fully_async -build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 +build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e ############################################################################# # debug: Various Bazel debugging flags diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index a469aa3156b14..dd0ca05d204da 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -12,7 +12,7 @@ on: default: envoyproxy/envoy-build-ubuntu build_image_sha: type: string - default: 06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 + default: 3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e build_image_mobile_sha: type: string default: f47fb698cfda583769b9d28e8d1c58cfc7774d5da4f31cd8190d8975c3850c7e diff --git a/examples/shared/build/Dockerfile b/examples/shared/build/Dockerfile index 37357200b93bf..1b0994c43501a 100644 --- a/examples/shared/build/Dockerfile +++ b/examples/shared/build/Dockerfile @@ -1,4 +1,4 @@ -FROM envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:06d3d10a99cce5bf4036be65190f192a30503fa93b9df3c119fd1260d3ed7024 +FROM envoyproxy/envoy-build-ubuntu:fdd65c6270a8507a18d5acd6cf19a18cb695e4fa@sha256:3c8a3ce6f90dcfb5d09dc8f79bb01404d3526d420061f9a176e0a8e91e1e573e ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ From fc387671393cd13f568b2c895d3091ef5240173e Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Tue, 17 Oct 2023 12:30:18 +0100 Subject: [PATCH 14/34] ci/github: Fix app auth publishing token (#30262) Signed-off-by: Ryan Northey --- .github/workflows/_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 8359114dac36e..8e857fab7c377 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -166,7 +166,7 @@ jobs: command_prefix: ${{ inputs.command_prefix }} command_ci: ${{ inputs.command_ci }} env: ${{ inputs.env }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} From ae07f9a11715245f7d25d2a13699c260c2ae8ebb Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Mon, 16 Oct 2023 20:19:55 +0000 Subject: [PATCH 15/34] repo: Release v1.27.2 Summary of changes: * Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, can cause a crash. **Docker images**: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.27.2 **Docs**: https://www.envoyproxy.io/docs/envoy/v1.27.2/ **Release notes**: https://www.envoyproxy.io/docs/envoy/v1.27.2/version_history/v1.27/v1.27.2 **Full changelog**: https://github.com/envoyproxy/envoy/compare/v1.27.1...v1.27.2 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.24.12.yaml | 7 +++++++ changelogs/1.25.11.yaml | 7 +++++++ changelogs/1.26.6.yaml | 10 ++++++++++ changelogs/current.yaml | 16 +--------------- docs/inventories/v1.24/objects.inv | Bin 141778 -> 141802 bytes docs/inventories/v1.25/objects.inv | Bin 149818 -> 149859 bytes docs/inventories/v1.26/objects.inv | Bin 153855 -> 153917 bytes docs/inventories/v1.27/objects.inv | Bin 159699 -> 159791 bytes docs/versions.yaml | 8 ++++---- 10 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 changelogs/1.24.12.yaml create mode 100644 changelogs/1.25.11.yaml create mode 100644 changelogs/1.26.6.yaml diff --git a/VERSION.txt b/VERSION.txt index 5b9b4efa580b7..457f0385465cb 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.2-dev +1.27.2 diff --git a/changelogs/1.24.12.yaml b/changelogs/1.24.12.yaml new file mode 100644 index 0000000000000..4beae10fad69d --- /dev/null +++ b/changelogs/1.24.12.yaml @@ -0,0 +1,7 @@ +date: October 16, 2023 + +bug_fixes: +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/1.25.11.yaml b/changelogs/1.25.11.yaml new file mode 100644 index 0000000000000..4beae10fad69d --- /dev/null +++ b/changelogs/1.25.11.yaml @@ -0,0 +1,7 @@ +date: October 16, 2023 + +bug_fixes: +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/1.26.6.yaml b/changelogs/1.26.6.yaml new file mode 100644 index 0000000000000..a5caeaa72fa50 --- /dev/null +++ b/changelogs/1.26.6.yaml @@ -0,0 +1,10 @@ +date: October 17, 2023 + +bug_fixes: +- area: tracing + change: | + Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0ae4cf227f7e7..91d3633c01549 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,13 +1,6 @@ -date: Pending - -behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* +date: October 16, 2023 bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: tracing change: | Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. @@ -15,10 +8,3 @@ bug_fixes: change: | Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, can cause a crash. - -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - -new_features: - -deprecated: diff --git a/docs/inventories/v1.24/objects.inv b/docs/inventories/v1.24/objects.inv index 4a3daae93885ef97995b0e00002d10f73ee3d9fd..dc548419533ff5ce944145fb3f3dbf158c33a663 100644 GIT binary patch delta 3224 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 Gc_@NC@dW+= diff --git a/docs/inventories/v1.25/objects.inv b/docs/inventories/v1.25/objects.inv index 5fd23cdd958d31ff25ddd01180d26b366cfead68..5e31b50384a4d5398afa903aba066019813d6346 100644 GIT binary patch delta 4077 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 delta 4053 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 Ha+ne#o}$gr diff --git a/docs/inventories/v1.26/objects.inv b/docs/inventories/v1.26/objects.inv index 999a8f0cdf60e88a481c7f317baf8ed7eada6523..477894d1f6bccd82887a88b242a0da1ebc5bf066 100644 GIT binary patch delta 8294 zcmXY#byU>N_s3zW1y%$E1VvzRS5mrFVgVIFz(=~fOHyL_z|tYHf^Kz8E&sv8{4;YG5~(?;?vmGPiS=NTb)3=!}^1BmTG@KeR=b0=y&q^+Yt-jGTAoN!M2n)_0W4pG!G!KowRp)zD^iKPD|?j zpql=!YS5$b?xF{P?*X)c`5Ckza&l8`v6O9|W$w`3s4`v?S7{JsNMF+bhReS2WEI(< zIsRf;yY_v3(yyH16%1MMiHGKbB#e>o!FNu_3X_oL(&*^MtAwG|CUqoG@BL4N@y5Zn zLD(}_k>i6L^?Bf`d@gybG@!K%)eVlMalA?HPK(I8Hg$ePNPeBjT~}---|;;7`F0W8 zev~VmhSeALcaT>Z$-vs^UL^dJ_`9g8^~ha;8|+uGsS2HO#tkX<=01|6z!#UUs;2ue zT)(ovdxk!2K?{UQL#l*F5UmhSFTfZC7_h`C)n!~8%>W7`u zwVjBLZghP_L!XnGhwj74Fh1cX8qX9KqBrc2{}Tx#3-@wtV~9pwpFQ#4Vg+AEjJn;hr;uP$Aj7 z=5Tv9KNdGx$iV!GRnL;hj9aXVaxzV8nGKyPKWYNX+RSjj%QI9Ut2;(n^_ zkM&SD@RO+l^V4}eqxpxNxTi|%NuE2qp8K15u70M71oIpt?q@U^7*txgm_Ys<^av1^9bQkObhjs! z?;BQet`_t06n8!U_N{{};=P{gD&t_#dY*TqQ`zG_2byN&BYl-@FhtAaCB&l-#>!NtX>w(-^M#vJ< zQ|5TDpZ)Y29t?Z>`MD&(vLI5Qd4ba`-1q@cI+h5qQ_7n)vw2&kTu@P(AZB4sB4!co zodumSt;BD5M0nw2SMg)*HQx>cpKXx*=-kP#oK#xE+^NQr@(hFEL>*J^hT5{Nv)7Im zgn9YoQ**luKcBzQ%zS##b3M`Fnpij5e#|syI^}(Lk7lfj?_8P}NYLNd>v@dq_j&KT zPHJdl?sYVx%!D%yocbZzX*KC($}0S%Aqnqi25IQ%xeGqc&);tY?pUES6V$n0VkHOO zKPhDfy7Ix>&aoN&hYEwA4YBv%Fg{G*Y61~8cwH3+$1YMQnP$2--t(wbAYt&%jC}J- zo{58FU{572ZEyTZ62QcmBwACNCyd+*UONaI^dH9t8;QCyYLJ}eYKY54$8x_-Qxi5L z{k6E=Doi}qCB1X!GFOA+mHjoL`65LbZn|@`IRyeDoOw5y^wIH#iP>Z639~xtBj#pR zwfL{cqaYE?Af|crLW(_0e>Yx%-2!d#Xn>1$d^WVXeS~AP2B0<2P6)f2_^A?xg ztU>lQTjLdL$@~`Q$r%wt`StRWkM*5yFSk(*%RE7u*hpFf;LJpA!gFRU26UX*P+e(K zgey(Ko=hIf0OSRWQ}%*V)Ph373jGx7kALUG`OSTwm8Mw2jxc}6j55K4Xp@~lhEm^n zcVYr`28)^Da+05a5BHz?W_A5P9w}erGw@FYU>vyn?1 zx&OmF@TX#oplXNK)!o{kG+{HdMM9KsJaDZKAsdmC8Nk~b!y}->$^~E1+c6-?lx%_xGkwV?^do9N@R&NPz60khtWzinE7K4uQbtgRZ z>+X&Iu+7#LyFi#YmRqb6B3kk&0Y5)}=gF6@;M&!(`I^I@PU1FVwirDT)v9F6^>pJj zy!X?7)|%FN7D-8aIvxWARl|@c@he-{0l|A!{gGcIRuU@*1Y|u;6V3Rzvm2MgEP}#* zd9Mt_M)IX~@G6PipE6?GR^%#G zNdf9b#!V-l(!{^dxLKUOM_ZBIr5mMcWN?jtvatNy?cPU-<<{25g6dyns=a>eLQGMW z1hnaAY22^rL3O#|X$s3@qrv(k-;RI0<MsJ*KIP+L z;Sn9OUcGlTe?_Ih$M46o4ganUGhtxWde0LOs6Z=|SIke(8X}}mdgzr~ zKl9b3RW8x6({!o+thzI9iYL*|%z5X?ec+%gFu76==S1z=4BDkRN&!)|OeKc_M zn}x|B%6cUB`Q<2d->~UF+IW^v_=0Ki&j0KKq6JSp=R-J{Z>u zOAj9DjoTS4dkmiYi(;oO_J%P$VW9`|M@VNv8xOV@!|v6NK6U~YipqZcaiZCCG&*TA zdcLPrtmIysa_KRnf=Cwl#rQY*E04jW>!lZL43Q6Y#73<{e+T$rF7%O@-P0XSGUK82 zpTQX$hk|qaG7cWa=N|j-O(#g!29NZUCa_eR16*I0+=K*r;#lV-<>qlRLBI5XfSOpK z;B~Z948nB}OZ=Y+v57SitbH`ay|!;80%70FnK#a7E} z=nTfN6SIuRi#z&BbN$!Q3N}xcobBpV^*TxRJ4x}*$9oML{m8iDCmvf+ZAPrwbe@Lu zNyOwEj_ZdlQrDMjp&7hL2X)jt+cD07!q4BpD&QDX5r?9BvRz1bXfFUHmJ7S>0yh@` zMd^~Lr=3p0@Ycqr?Cf5!8+bn`szff-)0SI6k%pB563B-MWP_H3eLJf$fN%={RH7*R zy{<6{=fyPe`moF1bGwq8pZBFKf2BpS``COuq3GDFu#)2M{)NQ8T?q6_(+n5Vugi#H z9*V=Vtv9+4vQn5OHv@`sgEMs+Qe&%o&p*C_An!NVb;PYP2?g|iv$IDaAuPdcY;4jJ zpW{nO`@~esZ{{EE+{e!!N_1&GHx<1rCh*Zk%-_+jsqsBZX=PInllY>fHu)tZvRX2o zRD*7Q^m@*#faqr0(bD7r!3}QC@4y~u$D?lvDxo^E#QVA!Cjmfbe|=7+y_l|IEzC`F zva+336UZ9vPvGXeGT}kt_5o;7W_G^l$6#sq9QoHn=HKSeNp&>G^MG|jMI}`|s^`kOo3`op?6=NW z*IY3;Lp`C&xon?N#n^$hjPI5w@Ap+7pMIA(t>Ganjm%xtdJwsdd+SRQ>u%3oTAs0P zjI5sQO%=1_^)A%`A#g{A47S!479u{bH!atjta*9so zNVm?^@}jQYjFcyN`h&dUn!JO*=-cSX_Y9n;7F$nH!{>y6M~mSSJlFk{bFV^tX0+Ni zf+&Q@60n~)HXM0YGJM#Qv6oH_#S2+#X^&fKTaFzwIxS->*%V(-LF6S-f3GRB| za*j5=*hYrqGQiy2?gB_nS@clL9nycj~!fZl2a=SrwPi63$_DE zvBq=~wk~l3Y;m4jaKuu#8LKkKz%TkAbmT7@mrFkVwKE*K&^x`LKPhPl2f@Jz%DpLv zC>8;lSSrGaV*O4=_6+)|M^Y(!Qzu3|E0PKFbi^3w|Q28(W;36PK#$TtfS;h^UVpoRc^b-ZfXq`x=~^0l4;%Q3wcT zn1Xe=g(h#Ww5jmc*aibqY(*uqm|HcRB`&Y@S`}DKvCO1yGh=`9I1yqiUuF{};N0F5 ztFg{W$DYL!2(puRIT8=9=tOWOyG(WI#`13Ir-v}SzV2u&0-1x`DaYv<`g*{RQ@VQI zy;P=GC2*!Ck5i>=?;r+*d^2{)sb9-C)}&7iSBY)*`9#UsRgNf^i`lMWC+;6B8{U2` zQlIgbMt+`trMbEUYxDn>Aer52OKTCe^b2-6L{0^bnmk!L}r*M%FXJ1 zk7$P7%!t+8htAX#ui5#5vTa=!{)k4;TmB_;CT)|7L$eQO4E9<7q8CxlvPtFtaU1p4 z6$$QDReH(CFguIHGIp*LuKh_kcnzY%CrJP+fZ)LQ=pnp}rB(xS1eKGDQ>`G)XR64S zJAD@5$FCT~jn`kRIQ@4r)UIu|G$lNooiy8ZuttuOHoO3wogIDVLSb5_lOxsel+H`G z>sHKnnln8lc3<4xXNvwyWEjIoIOgXgf67Prf@0-vM@^PbAd30*?(6oPPttt*DydYV zK+2{lZ(sM2W)!P4sz={35gH1Try+c&gotC$hK2Hl{kXec<`9J71QnipC_heQ2v4FK zh}7e?cs-t+6DOWEu&qwI!>-A`8E#ZI|DpkNc!$sVNc?3*XFFfw?Zt)27>;oI|dLdAOUML8+G=ZfjWcik&Y|pr@UW zU)Gnd;oD{)!f@XVoGZ;iltr^ezsi}wo(qdZM?op`5d5@lYQ7Nv0aV9U9dIG#v+t*EHBm2PKD(3p3(&{CqkMEPmJUa}sj3tlrBUV_^^Y^cdaz}P z3NkN4hT^Cg?NfjH?xg~~x$VIzeKvxMRz_{cYNKdKwJVa(;`7VmgLl*SVUgm_ch@-$ zvI4mf4JZe{H%H>cw~%50UFTa$dbb%={LM4TgLmE9fX}|7VzOiF@0)~Bat#jeGGfnKhKtsl1snh=tLcd^k&3k+I;WBYElp94F>^ZEF zMS{f=?TJ~D48{X7&_4CloIr8tKtAN%T23bKYf8Vbh7>bCZc1Ae854;s3Q8+ueU4=>moGnLnyWy63 zyLzy$wLH7JBQ;cc^sb|6OEAZ}w~VBnRSkKA$mu&rf)S`VmdRBYQin1k1lWx<)SZZ{V@p-dmqxk@MG`^VTB^~Hwu3j;?w{r7Tr-LJA(43 z(~4@>@b&MBqXOXmXWUoN2+N8<3l?>3QA49bT2Atn4 zH^HcmD8HHXOvb5tDVS_Q4Oz+!Qf}I1SjyYQp-Z@);WEx5T-WJ%QozI%DoAh%8?hiqDT$g zjTH<|eM>r!0Apol{RI*4mc_xtMnf8iemosnMk-{VKy7+e6}eLl(LLT@enw%U`MJi? zSus8eCI`?GqY4uQ5Yr4BJ);yeZDnAp<5g&q>=s0Ia0jrFOb>}7|1Mi`&Ue9!8LfKDHzY>3QpQ@>5px_ z1n9?_#@bhLaMFp83RGJ-Db*!9@P9|SXL1fF-3)0JCm0%K2Nc?GU29V%=iW0!``x-G zFhH#aijf21p)?>hpjvzw6_H=2Ah86&BnU?O+cFC~a!o(cbeH(?HJlXpKS~eq(1bVt zQ7Yi{gE`~FqyT=kHjrSiWPLkKG40tk%#5yP)5IPhrJreG(ac_bMMg=lzv*+-m zpD{3{3c^$PiqBGHQ~(->#HexO1vqze^1NffSHKbQ@p(6ZH0WaF{=oxTLPQB_iD6U> zewpF~gEBaL1v3(sf3>X|Elmvtfpr8%s5RJQ0J&tm|KMh7vxI5k6Q=VoGnARIg!$li zqzTTS5$Y2t5Z+)-?se%2GFU8!-@oy!)gt}R3MjJwQE3Aq z{0?kVpta#QKDpY4^go^ikkk*$5L(F?xdXga=NU)>B1Vn?e{~Zg+TBS7dnfM){3}(b zZ-ff2)GQ9YPW_}HGBi7z^l~`9X2Dk!OoxoptxMH1vj+v+A)|C1lKxfj8wK+sr^JP4 z8Dt?KXsag`6LmI-NE^BxKNB)^0lTzbn-dXFIZI&~hy-kE$?k=wz~9-`lYO!~#hUEX#Xb{O9W#mG^BObqx3xK!B?)CweYvvh8u zGJ__3s=G4Ou_XVcvq@%HJhHWQo+_gM_GgOblrge9Dd;2wK7kcFpe(Lkw^b`)1obyC zI9YInaWXbWP8yU=2dTx2hr?py{4(JLPv4c2D#$%yvFTLhQbQuBf0Bkum=*{sMI)%8 zWTE1gi7u*H2x@CGuve#ax%42J!a7{bO4o>$SUBPVbb(ZXknKOwsAtE>p+MOd5aAy+ z955;%*YDrlCsRwNV8;1BeY7B`SIGX;hh|NTTpwsv15)2E%wtBGyIk>9I~+VQ;1U&~ z-jm7GwxV-RO_k~y6RgH&78AA{`ZOyPJUU-sd`Km*^}?y=%c?A)^-ESc5(M!IF~tnp zArx;=KudZ=hM0rIk1f~}6!$R(kooLSP^ZSgDqYQaZ_Fk}M$i9o-8wC+Ts4Y4te&fTdzQ^fR2M zl+UUq+_E7Kdc{)XjvTq>PXD4B+~GR%eO2b-=wrJC19iBJtr7{=u=C!hUJ@&;+G{ad z!F2g*)!rB=na-7n)s;ZH_?zHJv5^N^G8e4r9It{IACJuK$y{hZ{~HQfy&AdrEOX(h zH(^LzYhxwgFyc$4S@}gK8KCv5VXQIig6o=7z6jiFBf)BZ;Fmk|<6eGjmPm+Z5#`^#5&hw z43?VQgfH(0INjtHx$+$sFC^Hhfy9B|ir3jE@TC-FuZsIp=Nk629K<~FE zj`ONw>5<2$mJm~n7QAtHT~`-bufjCdFQJaHT)`|o@4eb{E!lAQh?{O4cPC2S_syMx z+NJ-4d1mjP-<5|$9>r||FBGcTM!m0H58T|WB-WaJ=Un?bw?8({l}D>M?%|9nW1r_G zQuVC;Zkhs`tt-FmPNsJUQX@fykA#~W8;cW)hDEV!4b0-{AGg6m4#IoV<0dUk8&@VR zGmKsPS5^EUJ4_%8G)46`ew&49CP<@c<;Y3b?gi_X5>K^8wTua%vfY1u!Qq=#nFM`> zz8C#`WuL?dA9J8`*E@2vkD(`;In%VtkRMT8(RPV!Y&MuKUT5n+-}@2G?YMN=C;alU z{GHwVwfGW6NaTBfh$E{Yx8M%thSR*LrW8cBe|9Rr9un~K*|ea9Iz-?kqQf^VL&fV_ z!!i1SI-pr4zBPW>Gq;xj37|IpB)wsBmdjA~#qeD8_zv)AUxRiuH)Z$hH>h>p*SBYC zn&*29hKtwpKCp<*;Ep>$7-DA*?q<4|{#?jj`%m=S6i4FC@81^0cb#NHO z7e{eR)6FTMkU?(0h_zuzh?rc__ES^8+}q6Cb{(WDMgN%&*(ekLrsBoz;2=AfsE(XQ zIw558cB1Dt;~bMyvV$b+Lft&YkdoYgcZYEdsY3K;RxO2P3qs?DcHR;-qd3{M@j_2} zYhR4)QN!$WdC8wmg$C;V({ScsFlBUN(ik!7*@dE@#;mJ{ZZ4AQg`a7iJ8-* zy-MpK30wY`(h0s_i~X@X!Lm%zi${b4WiE>AN}V848!OYRtN0|8m1COQb*L>T1j_ z|3YxKqCSx`!g_A?RsA?br8LdHJF5HF9aLe_>Grtc+?4X1APJex@}KKQiJ09h2SFj# XR2}#*A=f?oRG1y@?w6_8p&x{(lASQ->Wxdm~~ZMB`2&5O02h7aQg{@(=GTUxIT24d(80*%|bYVJo>seb*4cD;Di_JBvJ%&OHB1o)#sOVtY z_?FYaoXy+A_|F^(>6iA&qKqP^?sP;{aj)NF zMwW3^(QWBwbppj$@jg$Sbx!62pX2crY^ueW{r60Y{J)|5@bi1>TMdodohBL0{TYqD z1mhs@gM}*t#SnwqT8lv3CMKu$&1&N7s*&SBduhb?3On`n{i{s3Zp^+X5%~Q*A3=(A zTG-Uo+CI~>cP~ynB}57z{hpvq8BLfbTQf60{KS)vJbYtWHqE?-s&0=nozdMXi>a-4 zX`5q0)$2}n^_fogFBIl&XCTKfM=K`MIF@RG*MlK`Uut=oUa>D&pH@F!b^lDEQmd@C z;ar87n2e-T|2f4+SqI#~U3kA6$W>$`L7YjyBPvRx+y8#p>{3k#dL;4yq>&>+@{5k%1 z^~!+TQs}m%GE~^8ub*_Ht>YO~C-=a8EHN@n&I7D!8j)Qfg#t?A&><|5-+Befy&MVU z`-We;RlW%D74x{NtLx;9)iTm(6Z=ZjtL|OzQo`KlWWV>ywYyU+NZ+P$a;rAWG@#{% zcY|46bd&AQVYfJ_nt69}1rS)FqxJ5Sr+m@ZAv_uyuCB&CLCZSjCU>e2sjAgt;O6U- z(Cr13=FzPxNLgI%uKe8I`Zln#xi`WU8-|CAJYZ5S99USu4o}A&BjdQuvc7)JQm564>i##23KG6z5Y@a zbOZU$l+!OC#M93v{5q%_p|Lu11fPDw-a5Ii4DLH2kGJa| zl7H`A6jY7}&O#CWVa&V8n#roqBnvK*&1GUPbql0>$b|MChL#DA$Zh`Q+=7%sq{GU5 z!wZ-AkShKpKU_qCcAsXNH0;Z-u=lr~<#mUwVOZE+Z#`KT=MWE*#5pCA1J-zVx2jQe z;hh0=yh`;C%x<~Q#Mw6}Rg>jU5!y8~DM+1CSmgv6TOfF9esk^$lz$h$eqQb@C))g| z^Q+vC8GwFA5Tmo-@}nmLs*ch%XpjiT6^(1f52YXbTi)D|;uq^u+MH{@CiS98^iGrh zf^i2?(armNDBh_nE2V00#5Vl43i@NfInVPn&g$-NvH^Erb`gi&kchB#12a<^EumuFG9uCZ zNYxko--4Kv`9lQ{$W@LCnDI|wE=aq+D3dpcs5T>#pPf3gz^`X$UALw|LBgR3zlh?? z&Ax+YB)v8ZTXmOY%6$C641Zbb^`RCJCOs{luyExku+K$I)uBJx>ya$IeHu2{aX%k7 zjVU_3;WtAo)q4=OPZ?fv#`&VIT6HS=m8jgtG{SadL}E7%jy#zxWvv3}IX+jyn`W#Q;X+jR0n%8jd#>qaRg|3cB3a8yiP1t#Hk^g#0-T zJ+7_%Dez~~>14@Vn$WKAtNZ->Ep>m%HQcP zD<4buZ`jnVgv}r5U=Ek_y7Ec7(zY4|3g(zgQgyjnc5SVap3Bz+M%C`hM{La_!m2(X z*mD(Aop>xIe6Ip9NJXS5ERH(E8%8GDW0-pon?*G)k9cw2*u+RL`Fq|QQ-8Ew{EPqf zMC{f-vc&+;MeAyx;$Qlp5{wk5?5USUX?+&_WrJ{=40<7{<{lk3DUp|RP9-cuZ^-WBm*^39t{uau<{FN+d|^B0u!7h+;A>!#rH<(2T!bgmvSVEuG4Ng3a~E-c zjUX;w67_X3$RFO`n39`4Kn4Pj;uH4ZQNHiF1Pm#eh(SuJCcje{-dnJKpNb7p-{f6Y zC>vDgDrVjqgfk$%37(c2+*djW4Gl>Yvj^Dwc;Q>Ll-aiMb#Ddkb1#JZW$4BT8P}L5 z&<}mwr(SRH8e}E5`qHe5_&7LIqa!`GdLZee0t%$Qa{y>1_>X{Cw<@X$G5o<`lk{|~ zzg#*sRP1XqP518J`KEa3;+8|$yNNF)mwT4iU2v9We7y5;1R{Ft{P_L#c5R=!o(*34 zuKqQVNyjGemCJ7 zn>N0hHoWgFh+ZJuh+T}adUijN{)e|sE`E7;XL-Dyy-MJ-pQRn&j|fUQLJ|+|OIzxqAW0*8W=rtL@#t*O+4<15QR#81)hX(o&n(yV`S^P8g?!|%Y@sj`c@ z{0_Dgy1E0Gh4zhib#@lBlQ+rkn6ZEaGKccz0~VY8#}2&ze)1l9UtD91-`Gc%t`euE z&^6W9=(*^*Af~$L&H+vBR~+}(e-ECM;kf*JyjQ^h95qTAS3m7RMbU;>u|t`YxO{Ja zZMfz=?N1N0+H?n*$401rcb)3uZQ|WF*7?-fUxyX&tmUei0Sz3BPvShqZx(@|g;4j` zesQ`5~()-kUQ9DdNe@#UqSNP9y06oCm0nDG41qQV zfCw7}r2cl{pk`mX#wHpCe3{q_Pk)m-%#Y-z^7Crbr#bx7oOw}qbP7H%tc}580m_m( z*^4TYLSlM=I6IRi7765TvaNpAMTGYi02rlKQ1pMF;X~^->^$i!0Ora1rxi)cJr|Vy zQs?#VdKN2N=({DJA=g=Ta;3=PbN~!-=o*a?*450!g8^~wBaAVi4Xf}J^{sVwHnwzH zN97W?kT*oK&&9cwk3hBDpTac@>*D>5Xqnz_x(_sSQ_b3U>eDzEJ%soxi$s4fe`kk? zk_i|tWqEw6qAMZC4{5Wq<#hYx{xO+CC83&0w^{}QFsGz281<3B00_}%u?S-QHkkGQ zRrH7W+Ru1VZPSqk$!rp~n+VrW02Q+Ev8qY>onik|&|9WdP^T4Mig|I5_MoQ1E3nvD zj8P{J3uU-QaIx(}iAZboY~|gDW_>tM08Ra;WjMX2KDyeOAQ;%@>4ENw)xVk z9jP#vz_~d;(Z6Z!ss1{ogX>^3_mZ~KDHfp1EB4;m%)f2vg{nAU11Vstn@I{|$hxR; z0HvT)&)R7Q|OYo8Vz* zvr+&WSj$#B3yJ9kcD)X(fc)0%89HIH!mUrpx zsSQ)xBu=pri%AdN6hU#mE%i z4cOrc%JB<^e)(|S39>VA|0uxrQLKp*Mz=xKX+$>gDJ^VHVhM{`^nZMU!X8>J0DJ~$ z>h|@Sw^D@ZYGndpr0cGNkqk%CKtI;iF=G0jTm(T^n?|xM{Q5W`F1hVv+?$GXb^mZ2 zIrcCI5QX5nO-S1NSL|ni*GLVbk8em-LJ}BD+@jy8$UgrI#xduh)FM;c=qAmN9@OZH zn8gQngJ#@jOnk%ZO*7-Y3U?$Iix_3ZjD>ZP4bfhmRRhWiW$l z7pU;G%)+9I46@>q`TC~59q|8OJ<4Dm+8O9Li1`5PW2Ut*G_Mla-O7HI!vr19DO(%5 z$@0Js?u>;wluPXW8Z(-An~n!zSfuIxFn~UcP(vw0yzEUOQV`g%f>D;`$D(iO^aqZn zivgiMn($YoL7Zausq2MQ|0C(Cn2~yI-Df;a3+Bio19V&rU*XWxb)Y3uz&e~?PJvk0 zHGzCHobS7UTO7%&W(JrMY&^U#AOYK8 z`SxF1&26-*n!J<-H%$CUx~Kq)2<_x@ZZ=LNiWSr0<0kBsTkH`CMl@z`l%X{hjrq6o zzeW6GL$C~KE!cJ;S+#on4Ia}x#Y{YL|E9BoY5t@1d{a{zV_jTM6aReuHvPY|@h9#< zUl;tYkSq!dv~;4zh8g#4Zu@o#OVOKnldW&8c=o#CFO zJV`KlxbFGzbb?8>IM!T+gLViRoFV*rJ`-`n&m>OI1cz=}lDk8MR@&d3CvY@!TE3R| z`(pBHB&MI?U^A23*Bg&Rh>|i;HEY-!?HJ(&djE=w%tlioV%!hs` zHL_@0R<74PlTa8^&+D|BP2R3#EHSIap?rvH2AK)L9prod3;NV4>Wkn2Ev2_=)($cg zfXmHS_X{fS9HlQX;1+{rM7|-4fR}*XgyqS3qN!+^hQTKw>S)z0;>cQeq(t|-wPf~2 zeJ8^;x-}~6DljTYHR}?Y8fs3Y0pf`!q7AhM3AL}UByay_v>xJf*Wlxcenb1e=%MS$ zw%z|l-~T+zO?hISw9caE36CHOZc6NG1M)@^4YQ0!RZi5XNGc$>)t$M~iv~JZE5SGoDn6{>uoF*!yI&JD4T?j2*%OsX8H zK}vT0R8vLfY$V(EG5$0wvk09rBL5_YHw1P;_CI*akeg3m+017!CT9_cbA#v#gvRZGfJ&4NX?LU4yPTk+#&;BtbSM~SmN|WjGxAfF ziPt8?F~k>SXJThw@2-ruMXJN6#}A9SDu>rkE%T!0fKN5$yhH$|{p3KN}hrP;X$ z;AavF{*)v{5SbS8)mORWgjC2TQ!GCV1(za$J;FB#uJ=>M!~D<3&ngt$g2bE?-%x#I zQI)AavQ-dYvVHPja?cUtLHf!N+x967*{0kwCw@Q#$2kkx^aFg2QVPN}VhGa7Ca9Y~ zkOQ0_?D;R5=!l=kyJnUA=%qYk%KPEEl`7? zs5HTOTIx&Htb@o}Ks&d~pBAVeBFa!;-c6PPOPL8vW;q61*VYzG*#bvq0wR|Y(uM<# zCdYzpfjknBBuP*wEO?UzB45{523xu6o~yd}E>Uw(fsT(o7&?l@@Lw~NP%zj9V1WiQ z%4^}w1WHc@uy8B$xLENhlAtkI;X`0IWF&PGv;?bT5aJ}oxEu`qfY0F47W_yEF9cdn zz+m62p~}Y|0v!c1*mlZr3aN!aj|mxe;?P4Qt$<`WS8%>N5+U9#3sE9($FeZtAK>Jx z4S~7>^=u``VIX@Es}O7=XtWxZE1>;}PY;f=KVUMRLU6tyQoNms zRf(Jf+hPcU*rbg^GbkV70sT5-nyCgP}Hi=OIi@-siFbZps2gdFd46;BZ%p_kcMp-LTgYjhW zFQr>4+torM07)b`y8DXeB=)#4F5Sj};EpxMSpR{ls2^S^#tm_38+}y}l}qWWz~e)TF4|avizTmJCS= z2tGdvhfDFV=?gU^g1&fXF6f0Jrg+yNzE(X#IQ#mLD&AFyl2y+FR#PBd)%Q+!@g7o2 zI)h>Qs_)%lx&5R7utDn6D$|Z^+18BKt$<5YR-~Hg7Y3cg*tHX{>V^-o*VXgW)0pN(p52<`&7w0#M8mve z>9nY`@#FhQSnYjop$Fet(Mwd(TO2V&0P03Q5EbCutQu6YWGuNT)r)~m%|$MxNKVX6 z`uQ_ECw5=|ta}!DGs$==X!o4;V%Q{?IzQAhg~;oE+jeLh9twcnwq?meGz-v3pxS`~v(rsJelTHOB} zyIB&3J(nBVMpyI`cQBab8_)B;%;=YWFQpcyoke$obPqAZrc0d#VSvY{3r>}G$ovb_ zLz=9F{D0G(=bKfv1u4^qi}#qc4xN`}XOm8qdd~a=w)$)B-F{1Pe$nDuk(PElCR6i@ z2d}$vk$c*<84dNvEYdp&XZ^jaXW4ab_$O-w+v$7X!D#Inv%6+v)6!k-lazOX7b}XN ze>n$jJk9=)g~Y^)V`WS>Hy5;oe$<$FGl3oD_TnI|fgXu|=eRqrh=gTl>_Rd)@FCao z7A+maY)wH$iHh-s2;B;?zvCx8a|b9;7`bJ<%!b8fE~MnM=@kXy3hC`r6tg`x7&xY; z^p)98OKS2{v;PI^m$WE-Z`PNj+7(_+rI*HUDBLgB(RTfQ2#47KLB8ohkF9*0qxtna z4c^!GL+HDi>F#B0{*Qighb=rJ7v7KWy-cTfX}?*mIkvqEV$CZ;woSdilhD;G6tC?h=_pl14VjqjrG56dA1 zdxsm}iy5I~as9<&8k^+yz^1nsALLGa8eUwP_-LTY{tD*ZdbKiRax@XzY~|wPycmsD zu7XHg4DOwup>vA-Df69IA%+@Nt%m~zi%DWReY+ItyFcw2ijlVS&wT8tu;GU$Mjh+n zP>5aRLcST8*3d~j`z{LnQMj7-8sTUt;W$joo*&Y3-7*<%bm*Oi4q2Vx@MYEeVw;Vy M&Aun;(^b diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index bedf4e67eb50b2c6d9b59f636aa6f925418458bf..4ebd9397af40885f3f5a5dc2e390d930f9cbc5da 100644 GIT binary patch delta 3851 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 N{bL{c{{v0pbnY6r3pM}% delta 3759 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 Date: Tue, 17 Oct 2023 13:45:49 +0100 Subject: [PATCH 16/34] repo: Dev v1.27.3 Signed-off-by: Ryan Northey --- VERSION.txt | 2 +- changelogs/1.27.2.yaml | 10 ++++++++++ changelogs/current.yaml | 23 +++++++++++++++-------- 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 changelogs/1.27.2.yaml diff --git a/VERSION.txt b/VERSION.txt index 457f0385465cb..a759f59ddc72e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.27.2 +1.27.3-dev diff --git a/changelogs/1.27.2.yaml b/changelogs/1.27.2.yaml new file mode 100644 index 0000000000000..91d3633c01549 --- /dev/null +++ b/changelogs/1.27.2.yaml @@ -0,0 +1,10 @@ +date: October 16, 2023 + +bug_fixes: +- area: tracing + change: | + Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. +- area: http + change: | + Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, + can cause a crash. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 91d3633c01549..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,10 +1,17 @@ -date: October 16, 2023 +date: Pending + +behavior_changes: +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +minor_behavior_changes: +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: tracing - change: | - Fixed a bug in the Datadog tracer where Datadog's "operation name" field would contain what should be in the "resource name" field. -- area: http - change: | - Fixed a bug where processing of deferred streams with the value of ``http.max_requests_per_io_cycle`` more than 1, - can cause a crash. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: + +deprecated: From 0ea97ffecf6e43884296ad2e0b4e947dd282e975 Mon Sep 17 00:00:00 2001 From: Keith Mattix II Date: Wed, 18 Oct 2023 08:06:55 -0500 Subject: [PATCH 17/34] Add release target to copy binary after build server_only (#30204) Signed-off-by: Keith Mattix II --- ci/README.md | 7 +++++++ ci/do_ci.sh | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/ci/README.md b/ci/README.md index 2282bc57ca562..7bfec04f0fda0 100644 --- a/ci/README.md +++ b/ci/README.md @@ -106,6 +106,13 @@ For a release version of the Envoy binary you can run: The build artifact can be found in `/tmp/envoy-docker-build/envoy/source/exe/envoy` (or wherever `$ENVOY_DOCKER_BUILD_DIR` points). +To enable the previous behavior of the `release.server_only` target where the final binary was copied to a tar.gz file +(e.g. envoy-binary.tar.gz), you can run: + + ```bash + ./ci/run_envoy_docker.sh './ci/do_ci.sh release.server_only.binary + ``` + For a debug version of the Envoy binary you can run: ```bash diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 9cd420d38f6d3..bec54b718462a 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -858,6 +858,12 @@ case $CI_TARGET in echo "Release files created in ${ENVOY_BINARY_DIR}" ;; + release.server_only.binary) + setup_clang_toolchain + echo "bazel release build..." + bazel_envoy_binary_build release + ;; + release.signed) echo "Signing binary packages..." setup_clang_toolchain From 65c8901709834a675581bade33ff80afc2bdebf4 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 19 Oct 2023 13:01:53 +0100 Subject: [PATCH 18/34] ci/rbe: Only enable BES where project is set (#30318) Signed-off-by: Ryan Northey --- .azure-pipelines/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index 5e8b380cafad0..0103648c1ad22 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -186,7 +186,7 @@ steps: bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" BAZEL_BUILD_EXTRA_OPTIONS+=" ${{ parameters.bazelConfigRBE }} --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" ENVOY_RBE=1 - if [[ "${{ parameters.bazelUseBES }}" == "True" ]]; then + if [[ "${{ parameters.bazelUseBES }}" == "True" && -n "${GOOGLE_BES_PROJECT_ID}" ]]; then BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" fi else From 452895108fc6cbaa15b2e079543690874955fcc1 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 23 Oct 2023 06:28:56 +0100 Subject: [PATCH 19/34] [bp/1.27] Backport stack (0) (#30371) * ci fixes/cleanups * test deflaking * dep/vuln update (nghttp2) Signed-off-by: Ryan Northey --- .azure-pipelines/env.yml | 3 ++- .azure-pipelines/stage/linux.yml | 17 ++++++++------ .azure-pipelines/stage/macos.yml | 8 +++++-- .azure-pipelines/stage/windows.yml | 19 ++++++++++++--- .azure-pipelines/stages.yml | 5 +++- .devcontainer/setup.sh | 4 +--- BUILD | 2 +- bazel/repository_locations.bzl | 6 ++--- ci/do_ci.sh | 2 -- ci/mac_ci_steps.sh | 3 --- ci/run_envoy_docker.sh | 2 +- ci/setup_cache.sh | 37 ------------------------------ ci/windows_ci_steps.sh | 3 --- distribution/dockerhub/BUILD | 3 ++- tools/BUILD | 2 +- tools/base/envoy_python.bzl | 16 ------------- tools/code/BUILD | 3 ++- tools/dependency/BUILD | 3 ++- tools/distribution/BUILD | 3 ++- tools/docs/BUILD | 3 ++- tools/proto_format/BUILD | 3 ++- tools/protodoc/BUILD | 3 ++- tools/protoprint/BUILD | 3 ++- tools/python/BUILD | 1 + tools/python/namespace.bzl | 17 ++++++++++++++ 25 files changed, 79 insertions(+), 92 deletions(-) delete mode 100755 ci/setup_cache.sh create mode 100644 tools/python/BUILD create mode 100644 tools/python/namespace.bzl diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index 20d86c42b05ca..c511ebc67a7b1 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -161,6 +161,7 @@ jobs: if [[ "$ISSTABLEBRANCH" == True && -n "$POSTSUBMIT" && "$(state.isDev)" == false ]]; then RUN_RELEASE_TESTS=false fi + echo "##vso[task.setvariable variable=build;isoutput=true]${RUN_BUILD}" echo "##vso[task.setvariable variable=checks;isoutput=true]${RUN_CHECKS}" echo "##vso[task.setvariable variable=docker;isoutput=true]${RUN_DOCKER}" @@ -217,7 +218,7 @@ jobs: echo "env.outputs['run.build']: $(run.build)" echo "env.outputs['run.checks']: $(run.checks)" echo "env.outputs['run.packaging']: $(run.packaging)" - echo "env.outputs['run.releaseTests]: $(run.releaseTests)" + echo "env.outputs['run.releaseTests']: $(run.releaseTests)" echo echo "env.outputs['publish.githubRelease']: $(publish.githubRelease)" echo "env.outputs['publish.dockerhub]: $(publish.dockerhub)" diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index 3946910246bb8..01adafd38369f 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -48,17 +48,20 @@ jobs: eq(${{ parameters.runBuild }}, 'true')) timeoutInMinutes: ${{ parameters.timeoutBuild }} pool: ${{ parameters.pool }} - variables: - - name: ciTarget - ${{ if eq(parameters.runTests, false) }}: - value: release.server_only - ${{ if ne(parameters.runTests, false) }}: - value: release steps: + - bash: | + if [[ "${{ parameters.runTests }}" == "false" ]]; then + CI_TARGET="release.server_only" + else + CI_TARGET="release" + fi + echo "${CI_TARGET}" + echo "##vso[task.setvariable variable=value;isoutput=true]${CI_TARGET}" + name: target - template: ../ci.yml parameters: managedAgent: ${{ parameters.managedAgent }} - ciTarget: $(ciTarget) + ciTarget: $(target.value) cacheName: "release" bazelBuildExtraOptions: ${{ parameters.bazelBuildExtraOptions }} cacheTestResults: ${{ parameters.cacheTestResults }} diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml index 4b7f99e718d31..fc990eafd737f 100644 --- a/.azure-pipelines/stage/macos.yml +++ b/.azure-pipelines/stage/macos.yml @@ -24,7 +24,12 @@ jobs: - script: ./ci/mac_ci_setup.sh displayName: "Install dependencies" - - script: ./ci/mac_ci_steps.sh + - bash: | + set -e + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -t gcp_service_account.XXXXXX.json) + bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" + BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" + ./ci/mac_ci_steps.sh displayName: "Run Mac CI" env: BAZEL_BUILD_EXTRA_OPTIONS: >- @@ -32,7 +37,6 @@ jobs: --flaky_test_attempts=2 --remote_cache=grpcs://remotebuildexecution.googleapis.com --remote_instance_name=projects/envoy-ci/instances/default_instance - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} ENVOY_RBE: 1 - task: PublishTestResults@2 diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index 9be16bebcf5c6..fa2729b822545 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -26,14 +26,27 @@ jobs: key: '"windows.release" | $(cacheKeyBazel)' path: $(Build.StagingDirectory)/repository_cache continueOnError: true - - bash: ci/run_envoy_docker.sh ci/windows_ci_steps.sh + + - bash: | + set -e + ENVOY_SHARED_TMP_DIR="C:\\Users\\VSSADM~1\\AppData\\Local\\Temp\\bazel-shared" + mkdir -p "$ENVOY_SHARED_TMP_DIR" + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -p "${ENVOY_SHARED_TMP_DIR}" -t gcp_service_account.XXXXXX.json) + bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" + export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" + export ENVOY_SHARED_TMP_DIR + ci/run_envoy_docker.sh ci/windows_ci_steps.sh displayName: "Run Windows msvc-cl CI" env: CI_TARGET: "windows" ENVOY_DOCKER_BUILD_DIR: "$(Build.StagingDirectory)" ENVOY_RBE: "true" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --config=remote-msvc-cl --jobs=$(RbeJobs) --flaky_test_attempts=2" - GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} + BAZEL_BUILD_EXTRA_OPTIONS: >- + --config=remote-ci + --config=rbe-google + --config=remote-msvc-cl + --jobs=$(RbeJobs) + --flaky_test_attempts=2 - task: PublishTestResults@2 inputs: diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 1b535277e2da1..c87f3996e6c28 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -84,8 +84,9 @@ stages: - template: stage/linux.yml parameters: cacheTestResults: ${{ parameters.cacheTestResults }} + # these are parsed differently and _must_ be expressed in this way runBuild: variables['RUN_BUILD'] - runTests: variables['RUN_TESTS'] + runTests: $(RUN_TESTS) tmpfsDockerDisabled: true - stage: linux_arm64 @@ -93,6 +94,7 @@ stages: dependsOn: ${{ parameters.buildStageDeps }} variables: RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] + RUN_TESTS: $[stageDependencies.env.repo.outputs['run.releaseTests']] jobs: - template: stage/linux.yml parameters: @@ -102,6 +104,7 @@ stages: timeoutBuild: 180 pool: envoy-arm-large runBuild: variables['RUN_BUILD'] + runTests: $(RUN_TESTS) bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" - stage: check diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index d2a54b474bb17..b50bb1190d661 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,10 +1,8 @@ #!/usr/bin/env bash -. ci/setup_cache.sh -trap - EXIT # Don't remove the key file written into a temporary file - BAZELRC_FILE=~/.bazelrc bazel/setup_clang.sh /opt/llvm +# TODO(phlax): use user.bazelrc # Use generated toolchain config because we know the base container is the one we're using in RBE. # Not using libc++ here because clangd will raise some tidy issue in libc++ header as of version 9. echo "build --config=rbe-toolchain-clang" >> ~/.bazelrc diff --git a/BUILD b/BUILD index 28d92d8c704a8..3b48868fd6f31 100644 --- a/BUILD +++ b/BUILD @@ -1,5 +1,5 @@ load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_py_namespace") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7f83d0cd5f5aa..94457b963dc60 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -456,12 +456,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Nghttp2", project_desc = "Implementation of HTTP/2 and its header compression algorithm HPACK in C", project_url = "https://nghttp2.org", - version = "1.55.1", - sha256 = "e12fddb65ae3218b4edc083501519379928eba153e71a1673b185570f08beb96", + version = "1.57.0", + sha256 = "1e3258453784d3b7e6cc48d0be087b168f8360b5d588c66bfeda05d07ad39ffd", strip_prefix = "nghttp2-{version}", urls = ["https://github.com/nghttp2/nghttp2/releases/download/v{version}/nghttp2-{version}.tar.gz"], use_category = ["controlplane", "dataplane_core"], - release_date = "2023-07-14", + release_date = "2023-10-10", cpe = "cpe:2.3:a:nghttp2:nghttp2:*", license = "MIT", license_url = "https://github.com/nghttp2/nghttp2/blob/v{version}/LICENSE", diff --git a/ci/do_ci.sh b/ci/do_ci.sh index bec54b718462a..484a1038474c7 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -8,8 +8,6 @@ set -e export SRCDIR="${SRCDIR:-$PWD}" export ENVOY_SRCDIR="${ENVOY_SRCDIR:-$PWD}" -# shellcheck source=ci/setup_cache.sh -. "$(dirname "$0")"/setup_cache.sh # shellcheck source=ci/build_setup.sh . "$(dirname "$0")"/build_setup.sh diff --git a/ci/mac_ci_steps.sh b/ci/mac_ci_steps.sh index 2ab857c72970a..dc779a665c713 100755 --- a/ci/mac_ci_steps.sh +++ b/ci/mac_ci_steps.sh @@ -11,9 +11,6 @@ trap finish EXIT echo "disk space at beginning of build:" df -h -# shellcheck source=ci/setup_cache.sh -. "$(dirname "$0")"/setup_cache.sh - read -ra BAZEL_BUILD_EXTRA_OPTIONS <<< "${BAZEL_BUILD_EXTRA_OPTIONS:-}" read -ra BAZEL_EXTRA_TEST_OPTIONS <<< "${BAZEL_EXTRA_TEST_OPTIONS:-}" diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 36c438bf01132..30edcf59ea5d3 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -86,7 +86,7 @@ VOLUMES=( -v "${ENVOY_DOCKER_BUILD_DIR}":"${BUILD_DIR_MOUNT_DEST}" -v "${SOURCE_DIR}":"${SOURCE_DIR_MOUNT_DEST}") -if ! is_windows && [[ -n "$ENVOY_DOCKER_IN_DOCKER" || -n "$ENVOY_SHARED_TMP_DIR" ]]; then +if [[ -n "$ENVOY_DOCKER_IN_DOCKER" || -n "$ENVOY_SHARED_TMP_DIR" ]]; then # Create a "shared" directory that has the same path in/outside the container # This allows the host docker engine to see artefacts using a temporary path created inside the container, # at the same path. diff --git a/ci/setup_cache.sh b/ci/setup_cache.sh deleted file mode 100755 index ca910ec1a090c..0000000000000 --- a/ci/setup_cache.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -set -e - -if [[ -n "${GCP_SERVICE_ACCOUNT_KEY:0:1}" ]]; then - # mktemp will create a tempfile with u+rw permission minus umask, it will not be readable by all - # users by default. - GCP_SERVICE_ACCOUNT_KEY_FILE=$(mktemp -t gcp_service_account.XXXXXX.json) - - gcp_service_account_cleanup() { - echo "Deleting service account key file..." - rm -rf "${GCP_SERVICE_ACCOUNT_KEY_FILE}" - } - - trap gcp_service_account_cleanup EXIT - - echo "Setting GCP_SERVICE_ACCOUNT_KEY is deprecated, please place your decoded GCP key in " \ - "an exported/shared tmp directory and add it to BAZEL_BUILD_EXTRA_OPTIONS, eg: " >&2 - # shellcheck disable=SC2086 - echo "$ export ENVOY_SHARED_TMP_DIR=/tmp/envoy-shared" \ - "$ ENVOY_RBE_KEY_PATH=$(mktemp -p \"${ENVOY_SHARED_TMP_DIR}\" -t gcp_service_account.XXXXXX.json)" \ - "$ bash -c 'echo \"$(GcpServiceAccountKey)\"' | base64 --decode > \"${ENVOY_RBE_KEY_PATH}\"" \ - "$ export BAZEL_BUILD_EXTRA_OPTIONS+=\" --google_credentials=${ENVOY_RBE_KEY_PATH}\"" >&2 - bash -c 'echo "${GCP_SERVICE_ACCOUNT_KEY}"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_FILE}" - export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_FILE}" -fi - -if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then - echo "Setting BAZEL_REMOTE_CACHE is deprecated, please use BAZEL_BUILD_EXTRA_OPTIONS " \ - "or use a user.bazelrc config " >&2 - export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_cache=${BAZEL_REMOTE_CACHE}" - echo "Set up bazel remote read/write cache at ${BAZEL_REMOTE_CACHE}." - if [[ -n "${BAZEL_REMOTE_INSTANCE}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_instance_name=${BAZEL_REMOTE_INSTANCE}" - echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." - fi -fi diff --git a/ci/windows_ci_steps.sh b/ci/windows_ci_steps.sh index 58fd0a9a81d58..c16d7392602ac 100755 --- a/ci/windows_ci_steps.sh +++ b/ci/windows_ci_steps.sh @@ -11,9 +11,6 @@ trap finish EXIT echo "disk space at beginning of build:" df -h -# shellcheck source=ci/setup_cache.sh -. "$(dirname "$0")"/setup_cache.sh - [ -z "${ENVOY_SRCDIR}" ] && export ENVOY_SRCDIR=/c/source read -ra BAZEL_STARTUP_OPTIONS <<< "${BAZEL_STARTUP_OPTIONS:-}" diff --git a/distribution/dockerhub/BUILD b/distribution/dockerhub/BUILD index cd6321175ee6a..599775efdf688 100644 --- a/distribution/dockerhub/BUILD +++ b/distribution/dockerhub/BUILD @@ -1,5 +1,6 @@ load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_gencontent", "envoy_py_namespace") +load("//tools/base:envoy_python.bzl", "envoy_gencontent") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/BUILD b/tools/BUILD index ec8b6d14f8e23..8dbb978d51ef4 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -5,7 +5,7 @@ load( "envoy_package", "envoy_py_test_binary", ) -load("//tools/base:envoy_python.bzl", "envoy_py_namespace") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/base/envoy_python.bzl b/tools/base/envoy_python.bzl index 7578d54f19fe5..a652943e4f48a 100644 --- a/tools/base/envoy_python.bzl +++ b/tools/base/envoy_python.bzl @@ -10,22 +10,6 @@ ENVOY_PYTOOL_NAMESPACE = [ "//tools:py-init", ] -def envoy_py_namespace(): - """Adding this to a build, injects a namespaced __init__.py, this allows namespaced - packages - eg envoy.base.utils to co-exist with packages created from the repo.""" - native.genrule( - name = "py-init-file", - outs = ["__init__.py"], - cmd = """ - echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" > $@ - """, - ) - py_library( - name = "py-init", - srcs = [":py-init-file"], - visibility = ["//visibility:public"], - ) - def envoy_pytool_binary( name, data = None, diff --git a/tools/code/BUILD b/tools/code/BUILD index 4de0a77a4b2ea..9c5fd6c0e3796 100644 --- a/tools/code/BUILD +++ b/tools/code/BUILD @@ -6,7 +6,8 @@ load( "READFILTER_FUZZ_FILTERS", "READFILTER_NOFUZZ_FILTERS", ) -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace") +load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 5d5a388502854..c97f59c223d5d 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -1,7 +1,8 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@envoy_repo//:path.bzl", "PATH") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/distribution/BUILD b/tools/distribution/BUILD index f809f3637d45f..cad485b87e1c8 100644 --- a/tools/distribution/BUILD +++ b/tools/distribution/BUILD @@ -1,6 +1,7 @@ load("@base_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/docs/BUILD b/tools/docs/BUILD index 7d2870c162ee2..290501c9f4495 100644 --- a/tools/docs/BUILD +++ b/tools/docs/BUILD @@ -1,6 +1,7 @@ load("@base_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/proto_format/BUILD b/tools/proto_format/BUILD index 8ce574c9e623d..597d2f191b5de 100644 --- a/tools/proto_format/BUILD +++ b/tools/proto_format/BUILD @@ -3,7 +3,8 @@ load("@envoy_repo//:path.bzl", "PATH") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_py_data", "envoy_pytool_binary") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/protodoc/BUILD b/tools/protodoc/BUILD index f562c431ed3e0..db4224fd4309b 100644 --- a/tools/protodoc/BUILD +++ b/tools/protodoc/BUILD @@ -1,8 +1,9 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@com_google_protobuf//:protobuf.bzl", "py_proto_library") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_jinja_env", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary", "envoy_pytool_library") +load("//tools/base:envoy_python.bzl", "envoy_genjson", "envoy_jinja_env", "envoy_py_data", "envoy_pytool_binary", "envoy_pytool_library") load("//tools/protodoc:protodoc.bzl", "protodoc_rule") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/protoprint/BUILD b/tools/protoprint/BUILD index 720e41c1a7ec4..37659b927bcf5 100644 --- a/tools/protoprint/BUILD +++ b/tools/protoprint/BUILD @@ -2,8 +2,9 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") load("//bazel:envoy_build_system.bzl", "envoy_package") -load("//tools/base:envoy_python.bzl", "envoy_py_data", "envoy_py_namespace", "envoy_pytool_binary") +load("//tools/base:envoy_python.bzl", "envoy_py_data", "envoy_pytool_binary") load("//tools/protoprint:protoprint.bzl", "protoprint_rule") +load("//tools/python:namespace.bzl", "envoy_py_namespace") licenses(["notice"]) # Apache 2 diff --git a/tools/python/BUILD b/tools/python/BUILD new file mode 100644 index 0000000000000..779d1695d3b7c --- /dev/null +++ b/tools/python/BUILD @@ -0,0 +1 @@ +licenses(["notice"]) # Apache 2 diff --git a/tools/python/namespace.bzl b/tools/python/namespace.bzl new file mode 100644 index 0000000000000..7be06755127b5 --- /dev/null +++ b/tools/python/namespace.bzl @@ -0,0 +1,17 @@ +load("@rules_python//python:defs.bzl", "py_library") + +def envoy_py_namespace(): + """Adding this to a build, injects a namespaced __init__.py, this allows namespaced + packages - eg envoy.base.utils to co-exist with packages created from the repo.""" + native.genrule( + name = "py-init-file", + outs = ["__init__.py"], + cmd = """ + echo "__path__ = __import__('pkgutil').extend_path(__path__, __name__)" > $@ + """, + ) + py_library( + name = "py-init", + srcs = [":py-init-file"], + visibility = ["//visibility:public"], + ) From e2b528af262d2d442a35fb0f101815b339993d28 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 18 Oct 2023 10:24:35 -0400 Subject: [PATCH 20/34] Prevent recursion during premature reset check (#30270) Prevent doConnectionClose to be called recursively when connection with active requests is disconnected due to premature reset check. Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 3 +++ source/common/http/conn_manager_impl.cc | 2 +- test/integration/multiplexed_integration_test.cc | 5 ++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..e7847aa56d8ea 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,9 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index f567d659f7022..4b5c43b2e3b24 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -677,7 +677,7 @@ void ConnectionManagerImpl::maybeDrainDueToPrematureResets() { return; } - if (drain_state_ == DrainState::NotDraining) { + if (read_callbacks_->connection().state() == Network::Connection::State::Open) { stats_.named_.downstream_rq_too_many_premature_resets_.inc(); doConnectionClose(Network::ConnectionCloseType::Abort, absl::nullopt, "too_many_premature_resets"); diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 3aca9af441b40..f0468865e9b0d 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2368,7 +2368,7 @@ TEST_P(Http2FrameIntegrationTest, ResettingDeferredRequestsTriggersPrematureRese TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { // Use large number of requests to ensure close is detected while there are // still some deferred streams. - const int kRequestsSentPerIOCycle = 1000; + const int kRequestsSentPerIOCycle = 20000; config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); // Ensure premature reset detection does not get in the way config_helper_.addRuntimeOverride("overload.premature_reset_total_stream_count", "1001"); @@ -2376,8 +2376,7 @@ TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { std::string buffer; for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { - auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/", - {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(i), "a", "/"); absl::StrAppend(&buffer, std::string(request)); } From 6d3149d9834b6c1a5ea95f2830e37bfa1dc8bfd0 Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Wed, 18 Oct 2023 18:38:46 +0000 Subject: [PATCH 21/34] Lengthen the timeout Signed-off-by: Yan Avlasov --- test/integration/multiplexed_integration_test.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index f0468865e9b0d..f2e7df6b2b98f 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2385,8 +2385,9 @@ TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { // Drop the downstream connection tcp_client_->close(); // Test that Envoy can clean-up deferred streams - test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", - kRequestsSentPerIOCycle); + // Make the timeout longer to accommodate non optimized builds + test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", kRequestsSentPerIOCycle, + TestUtility::DefaultTimeout * 3); } INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FrameIntegrationTest, From 4a9a24caa72b1960750932096e7a30f63a20819b Mon Sep 17 00:00:00 2001 From: "Vikas Choudhary (vikasc)" Date: Sat, 21 Oct 2023 00:31:36 +0530 Subject: [PATCH 22/34] Fix intermittent cpu spike in grpc async client (#30123) In grpc async client, if timer expiry handler function gets fired where time to next_expiry is less than 1 sec, a loop gets created where timer gets enabled with 0 secs expiry again and again until 'now' becomes as next_expiry. This causes random cpu spikes. If there is HPA configured on cpu, this will result in scale up and scale down on proxies. I added some logs while debugging it. Sharing here might help understanding the issue more clearly: ``` AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:198] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer next_expire: 966826645315069, now 966825877367850 [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:208] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer enable timer: 0 [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:193] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:198] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer next_expire: 966826645315069, now 966825877389741 [2023-09-08 10:27:32.640][22][info][grpc] [external/envoy/source/common/grpc/async_client_manager_impl.cc:208] AsyncClientManagerImpl::evictEntriesAndResetEvictionTimer enable timer: 0 ``` When this condition hits, logs get filled because of the loop I mentioned in the beginning. Fix is simple that in the `if` condition for expiry consider this round off thing as well. Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: Signed-off-by: Vikas Choudhary fix tests to match older codebase Signed-off-by: Vikas Choudhary Signed-off-by: Vikas Choudhary (vikasc) --- changelogs/current.yaml | 3 ++ .../common/grpc/async_client_manager_impl.cc | 13 +++-- .../grpc/async_client_manager_impl_test.cc | 51 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e7847aa56d8ea..9022d01ce408a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -11,6 +11,9 @@ bug_fixes: - area: http change: | Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: grpc + change: | + Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/grpc/async_client_manager_impl.cc b/source/common/grpc/async_client_manager_impl.cc index 949ae4bd75391..4b9dc4f861256 100644 --- a/source/common/grpc/async_client_manager_impl.cc +++ b/source/common/grpc/async_client_manager_impl.cc @@ -1,5 +1,7 @@ #include "source/common/grpc/async_client_manager_impl.h" +#include + #include "envoy/config/core/v3/grpc_service.pb.h" #include "envoy/stats/scope.h" @@ -187,13 +189,18 @@ void AsyncClientManagerImpl::RawAsyncClientCache::evictEntriesAndResetEvictionTi // Evict all the entries that have expired. while (!lru_list_.empty()) { MonotonicTime next_expire = lru_list_.back().accessed_time_ + EntryTimeoutInterval; - if (now >= next_expire) { + std::chrono::seconds time_to_next_expire_sec = + std::chrono::duration_cast(next_expire - now); + // since 'now' and 'next_expire' are in nanoseconds, the following condition is to + // check if the difference between them is less than 1 second. If we don't do this, the + // timer will be enabled with 0 seconds, which will cause the timer to fire immediately. + // This will cause cpu spike. + if (time_to_next_expire_sec.count() <= 0) { // Erase the expired entry. lru_map_.erase(lru_list_.back().config_); lru_list_.pop_back(); } else { - cache_eviction_timer_->enableTimer( - std::chrono::duration_cast(next_expire - now)); + cache_eviction_timer_->enableTimer(time_to_next_expire_sec); return; } } diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index 121f8cbc533d5..ee1d2008b20c2 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -1,3 +1,4 @@ +#include #include #include "envoy/config/core/v3/grpc_service.pb.h" @@ -105,6 +106,56 @@ TEST_F(RawAsyncClientCacheTest, GetExpiredButNotEvictedCacheEntry) { EXPECT_EQ(client_cache_.getCache(foo_service).get(), nullptr); } +class RawAsyncClientCacheTestBusyLoop : public testing::Test { +public: + RawAsyncClientCacheTestBusyLoop() { + timer_ = new Event::MockTimer(); + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb) { + return timer_; + })); + client_cache_ = std::make_unique(dispatcher_); + EXPECT_CALL(*timer_, enableTimer(testing::Not(std::chrono::milliseconds(0)), _)) + .Times(testing::AtLeast(1)); + } + + void waitForMilliSeconds(int ms) { + for (int i = 0; i < ms; i++) { + time_system_.advanceTimeAndRun(std::chrono::milliseconds(1), dispatcher_, + Event::Dispatcher::RunType::NonBlock); + } + } + +protected: + Event::SimulatedTimeSystem time_system_; + NiceMock dispatcher_; + Event::MockTimer* timer_; + std::unique_ptr client_cache_; +}; + +TEST_F(RawAsyncClientCacheTestBusyLoop, MultipleCacheEntriesEvictionBusyLoop) { + envoy::config::core::v3::GrpcService grpc_service; + RawAsyncClientSharedPtr foo_client = std::make_shared(); + // two entries are added to the cache + for (int i = 1; i <= 2; i++) { + grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(i)); + client_cache_->setCache(grpc_service, foo_client); + } + // waiting for 49.2 secs to make sure that for the entry which is not accessed, time to expire is + // less than 1 second, ~0.8 secs + waitForMilliSeconds(49200); + + // Access first cache entry to so that evictEntriesAndResetEvictionTimer() gets called. + // Since we are getting first entry, access time of first entry will be updated to current time. + grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(1)); + EXPECT_EQ(client_cache_->getCache(grpc_service).get(), foo_client.get()); + + // Verifying that though the time to expire for second entry ~0.8 sec, it is considered as expired + // to avoid the busy loop which could happen if timer gets enabled with 0(0.8 rounded off to 0) + // duration. + grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(2)); + EXPECT_EQ(client_cache_->getCache(grpc_service).get(), nullptr); +} + class AsyncClientManagerImplTest : public testing::Test { public: AsyncClientManagerImplTest() From 2827b0b720fd46c964d2eb6c1f36832244181c8a Mon Sep 17 00:00:00 2001 From: "Vikas Choudhary (vikasc)" Date: Wed, 18 Oct 2023 06:02:31 +0530 Subject: [PATCH 23/34] Check upstreamInfo's filter state as well in grpc access logs (#30057) Commit Message: At client side, if metadata exchange filter is an upstream filter, received filter state gets stored in the upstream streaminfo. While emitting access logs, streaminfo passed to the extractCommonAccessLogProperties() is the downstream side streaminfo. filter_state_objects_to_log keys must be searched in the usptream streaminfo's filter state as well. Additional Description: Risk Level: low Testing: done Docs Changes: no Release Notes: yes Signed-off-by: Vikas Choudhary Signed-off-by: Vikas Choudhary (vikasc) --- changelogs/current.yaml | 4 + .../grpc/grpc_access_log_utils.cc | 34 ++++-- .../grpc/grpc_access_log_utils.h | 3 + test/extensions/access_loggers/grpc/BUILD | 1 + .../grpc/grpc_access_log_utils_test.cc | 104 ++++++++++++++++++ 5 files changed, 136 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9022d01ce408a..71616eba4b605 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -5,6 +5,10 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: access_log + change: | + When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried + to find the keys configured in filter_state_objects_to_log. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc index 7155a13ee5fea..9a25f65dadfba 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc @@ -2,6 +2,7 @@ #include "envoy/data/accesslog/v3/accesslog.pb.h" #include "envoy/extensions/access_loggers/grpc/v3/als.pb.h" +#include "envoy/stream_info/filter_state.h" #include "envoy/upstream/upstream.h" #include "source/common/network/utility.h" @@ -300,16 +301,11 @@ void Utility::extractCommonAccessLogProperties( } for (const auto& key : config.filter_state_objects_to_log()) { - if (auto state = stream_info.filterState().getDataReadOnlyGeneric(key); state != nullptr) { - ProtobufTypes::MessagePtr serialized_proto = state->serializeAsProto(); - if (serialized_proto != nullptr) { - auto& filter_state_objects = *common_access_log.mutable_filter_state_objects(); - ProtobufWkt::Any& any = filter_state_objects[key]; - if (dynamic_cast(serialized_proto.get()) != nullptr) { - any.Swap(dynamic_cast(serialized_proto.get())); - } else { - any.PackFrom(*serialized_proto); - } + if (!(extractFilterStateData(stream_info.filterState(), key, common_access_log))) { + if (stream_info.upstreamInfo().has_value() && + stream_info.upstreamInfo()->upstreamFilterState() != nullptr) { + extractFilterStateData(*(stream_info.upstreamInfo()->upstreamFilterState()), key, + common_access_log); } } } @@ -342,6 +338,24 @@ void Utility::extractCommonAccessLogProperties( common_access_log.set_access_log_type(access_log_type); } +bool extractFilterStateData(const StreamInfo::FilterState& filter_state, const std::string& key, + envoy::data::accesslog::v3::AccessLogCommon& common_access_log) { + if (auto state = filter_state.getDataReadOnlyGeneric(key); state != nullptr) { + ProtobufTypes::MessagePtr serialized_proto = state->serializeAsProto(); + if (serialized_proto != nullptr) { + auto& filter_state_objects = *common_access_log.mutable_filter_state_objects(); + ProtobufWkt::Any& any = filter_state_objects[key]; + if (dynamic_cast(serialized_proto.get()) != nullptr) { + any.Swap(dynamic_cast(serialized_proto.get())); + } else { + any.PackFrom(*serialized_proto); + } + } + return true; + } + return false; +} + } // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h index 9f4f1e07fbd5d..beec1e719a8f5 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h @@ -24,6 +24,9 @@ class Utility { const StreamInfo::StreamInfo& stream_info); }; +bool extractFilterStateData(const StreamInfo::FilterState& filter_state, const std::string& key, + envoy::data::accesslog::v3::AccessLogCommon& common_access_log); + } // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions diff --git a/test/extensions/access_loggers/grpc/BUILD b/test/extensions/access_loggers/grpc/BUILD index 423c8734457f8..05d5477338a7e 100644 --- a/test/extensions/access_loggers/grpc/BUILD +++ b/test/extensions/access_loggers/grpc/BUILD @@ -35,6 +35,7 @@ envoy_extension_cc_test( extension_names = ["envoy.access_loggers.http_grpc"], deps = [ "//source/extensions/access_loggers/grpc:grpc_access_log_utils", + "//source/extensions/filters/common/expr:cel_state_lib", "//test/mocks/local_info:local_info_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", diff --git a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc index 782cf5178956f..372b7512e631b 100644 --- a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc +++ b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc @@ -1,6 +1,9 @@ #include "envoy/data/accesslog/v3/accesslog.pb.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/stream_info/filter_state_impl.h" #include "source/extensions/access_loggers/grpc/grpc_access_log_utils.h" +#include "source/extensions/filters/common/expr/cel_state.h" #include "test/mocks/stream_info/mocks.h" @@ -10,6 +13,8 @@ namespace AccessLoggers { namespace GrpcCommon { namespace { +using Filters::Common::Expr::CelStatePrototype; +using Filters::Common::Expr::CelStateType; using testing::_; using testing::Return; @@ -53,6 +58,105 @@ TEST(UtilityResponseFlagsToAccessLogResponseFlagsTest, All) { EXPECT_EQ(common_access_log_expected.DebugString(), common_access_log.DebugString()); } +// key is present only in downstream streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromDownstream) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("downstream_peer"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto state = std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + state->setValue("value_from_downstream_peer"); + stream_info.filter_state_->setData("downstream_peer", std::move(state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("downstream_peer"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("downstream_peer"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["downstream_peer"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_downstream_peer"); +} + +// key is present only in the upstream streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromUpstream) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("upstream_peer"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto state = std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + auto filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); + state->setValue("value_from_upstream_peer"); + filter_state->setData("upstream_peer", std::move(state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("upstream_peer"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("upstream_peer"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["upstream_peer"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_upstream_peer"); +} + +// key is present in both the streamInfo's filter state +TEST(UtilityExtractCommonAccessLogPropertiesTest, + FilterStateFromDownstreamIfSameKeyInBothStreamInfo) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(true)); + envoy::data::accesslog::v3::AccessLogCommon common_access_log; + envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig config; + config.mutable_filter_state_objects_to_log()->Add("same_key"); + CelStatePrototype prototype(true, CelStateType::Bytes, "", + StreamInfo::FilterState::LifeSpan::FilterChain); + auto downstream_state = + std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + downstream_state->setValue("value_from_downstream_peer"); + stream_info.filter_state_->setData("same_key", std::move(downstream_state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + auto upstream_state = + std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); + auto filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); + upstream_state->setValue("value_from_upstream_peer"); + filter_state->setData("same_key", std::move(upstream_state), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); + + Utility::extractCommonAccessLogProperties( + common_access_log, *Http::StaticEmptyHeaders::get().request_headers.get(), stream_info, + config, envoy::data::accesslog::v3::AccessLogType::TcpConnectionEnd); + + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->contains("same_key"), true); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->count("same_key"), 1); + ASSERT_EQ(common_access_log.mutable_filter_state_objects()->size(), 1); + auto any = (*(common_access_log.mutable_filter_state_objects()))["same_key"]; + ProtobufWkt::BytesValue gotState; + any.UnpackTo(&gotState); + EXPECT_EQ(gotState.value(), "value_from_downstream_peer"); +} + } // namespace } // namespace GrpcCommon } // namespace AccessLoggers From dcfd6f7efa3df573d9bbe6b8a2191441887e9bd0 Mon Sep 17 00:00:00 2001 From: Keith Mattix II Date: Thu, 26 Oct 2023 12:20:35 -0500 Subject: [PATCH 24/34] Add var to force docker save (#30502) Signed-off-by: Keith Mattix II --- ci/do_ci.sh | 2 +- ci/run_envoy_docker.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 484a1038474c7..aaf9be5d4d7e1 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -623,7 +623,7 @@ case $CI_TARGET in fi PLATFORMS="$(IFS=, ; echo "${_PLATFORMS[*]}")" export DOCKER_PLATFORM="$PLATFORMS" - if [[ -z "${DOCKERHUB_PASSWORD}" && "${#_PLATFORMS[@]}" -eq 1 ]]; then + if [[ -z "${DOCKERHUB_PASSWORD}" && "${#_PLATFORMS[@]}" -eq 1 && -z $ENVOY_DOCKER_SAVE_IMAGE ]]; then # if you are not pushing the images and there is only one platform # then load to Docker (ie local build) export DOCKER_LOAD_IMAGES=1 diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 30edcf59ea5d3..a8a5fe1bf5760 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -121,6 +121,7 @@ docker run --rm \ -e CI_TARGET_BRANCH \ -e DOCKERHUB_USERNAME \ -e DOCKERHUB_PASSWORD \ + -e ENVOY_DOCKER_SAVE_IMAGE \ -e ENVOY_STDLIB \ -e BUILD_REASON \ -e BAZEL_REMOTE_INSTANCE \ From c658fab86a174fda07961e196edb97f9da9b5a50 Mon Sep 17 00:00:00 2001 From: David Goffredo Date: Wed, 8 Nov 2023 09:55:50 -0500 Subject: [PATCH 25/34] datadog: honor extracted sampling decisions (backport #30577 onto v1.27) (#30750) Signed-off-by: David Goffredo --- changelogs/current.yaml | 4 + source/extensions/tracers/datadog/tracer.cc | 39 ++-- source/extensions/tracers/datadog/tracer.h | 21 ++- .../extensions/tracers/datadog/tracer_test.cc | 168 ++++++++++++++++++ 4 files changed, 213 insertions(+), 19 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 71616eba4b605..29a3cd3f5f832 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -18,6 +18,10 @@ bug_fixes: - area: grpc change: | Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug that caused the Datadog tracing extension to drop traces that + should be kept on account of an extracted sampling decision. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/tracers/datadog/tracer.cc b/source/extensions/tracers/datadog/tracer.cc index ac898f317b481..c3acc38a15eff 100644 --- a/source/extensions/tracers/datadog/tracer.cc +++ b/source/extensions/tracers/datadog/tracer.cc @@ -20,6 +20,7 @@ #include "datadog/sampling_priority.h" #include "datadog/span_config.h" #include "datadog/trace_segment.h" +#include "datadog/tracer_config.h" namespace Envoy { namespace Extensions { @@ -94,8 +95,27 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext span_config.resource = operation_name; span_config.start = estimateTime(stream_info.startTime()); - datadog::tracing::Tracer& tracer = *thread_local_tracer.tracer; TraceContextReader reader{trace_context}; + datadog::tracing::Span span = + extract_or_create_span(*thread_local_tracer.tracer, span_config, reader); + + // If we did not extract a sampling decision, and if Envoy is telling us to + // drop the trace, then we treat that as a "user drop" (manual override). + // + // If Envoy is telling us to keep the trace, then we leave it up to the + // tracer's internal sampler (which might decide to drop the trace anyway). + if (!span.trace_segment().sampling_decision().has_value() && !tracing_decision.traced) { + span.trace_segment().override_sampling_priority( + int(datadog::tracing::SamplingPriority::USER_DROP)); + } + + return std::make_unique(std::move(span)); +} + +datadog::tracing::Span +Tracer::extract_or_create_span(datadog::tracing::Tracer& tracer, + const datadog::tracing::SpanConfig& span_config, + const datadog::tracing::DictReader& reader) { datadog::tracing::Expected maybe_span = tracer.extract_span(reader, span_config); if (datadog::tracing::Error* error = maybe_span.if_error()) { @@ -111,23 +131,10 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext int(error->code), error->message); } - maybe_span = tracer.create_span(span_config); - } - - ASSERT(maybe_span); - datadog::tracing::Span& span = *maybe_span; - - // If Envoy is telling us to drop the trace, then we treat that as a - // "user drop" (manual override). - // - // If Envoy is telling us to keep the trace, then we leave it up to the - // tracer's internal sampler (which might decide to drop the trace anyway). - if (!tracing_decision.traced) { - span.trace_segment().override_sampling_priority( - int(datadog::tracing::SamplingPriority::USER_DROP)); + return tracer.create_span(span_config); } - return std::make_unique(std::move(span)); + return std::move(*maybe_span); } } // namespace Datadog diff --git a/source/extensions/tracers/datadog/tracer.h b/source/extensions/tracers/datadog/tracer.h index 9e822cb9a0df3..670f382fc3053 100644 --- a/source/extensions/tracers/datadog/tracer.h +++ b/source/extensions/tracers/datadog/tracer.h @@ -10,7 +10,18 @@ #include "source/extensions/tracers/datadog/tracer_stats.h" #include "datadog/tracer.h" -#include "datadog/tracer_config.h" + +namespace datadog { +namespace tracing { + +class DictReader; +class FinalizedTracerConfig; +class Span; +struct SpanConfig; +struct TracerConfig; + +} // namespace tracing +} // namespace datadog namespace Envoy { namespace Extensions { @@ -73,8 +84,8 @@ class Tracer : public Tracing::Driver, private Logger::Loggable thread_local_slot_; }; diff --git a/test/extensions/tracers/datadog/tracer_test.cc b/test/extensions/tracers/datadog/tracer_test.cc index 8a4e276fd3daa..7cd37a8a33291 100644 --- a/test/extensions/tracers/datadog/tracer_test.cc +++ b/test/extensions/tracers/datadog/tracer_test.cc @@ -1,3 +1,5 @@ +#include + #include "envoy/tracing/trace_reason.h" #include "source/common/tracing/null_span_impl.h" @@ -7,11 +9,14 @@ #include "test/mocks/stream_info/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "datadog/error.h" #include "datadog/expected.h" +#include "datadog/optional.h" +#include "datadog/propagation_style.h" #include "datadog/sampling_priority.h" #include "datadog/trace_segment.h" #include "datadog/tracer_config.h" @@ -23,6 +28,30 @@ namespace Tracers { namespace Datadog { namespace { +class EnvVarGuard { +public: + EnvVarGuard(const std::string& name, const std::string& value) : name_(name) { + if (const char* const previous = std::getenv(name.c_str())) { + previous_value_ = previous; + } + const int overwrite = 1; // Yes, overwrite it. + TestEnvironment::setEnvVar(name, value, overwrite); + } + + ~EnvVarGuard() { + if (previous_value_) { + const int overwrite = 1; // Yes, overwrite it. + TestEnvironment::setEnvVar(name_, *previous_value_, overwrite); + } else { + TestEnvironment::unsetEnvVar(name_); + } + } + +private: + std::string name_; + datadog::tracing::Optional previous_value_; +}; + class DatadogTracerTest : public testing::Test { public: DatadogTracerTest() { @@ -203,6 +232,145 @@ TEST_F(DatadogTracerTest, ExtractionFailure) { ASSERT_TRUE(maybe_dd_span); } +TEST_F(DatadogTracerTest, EnvoySamplingVersusExtractedSampling) { + // Verify that sampling decisions extracted from incoming requests are honored + // regardless of the sampling decision made by Envoy (i.e. `bool + // Tracing::Decision::traced`). + // + // We test two styles of extraction: OpenTelemetry's W3C "tracecontext" style + // and Datadog's "datadog" style. When trace context is extracted in either of + // these styles, a sampling decision might be present. If a sampling decision + // is present, then the resulting sampling priority in the extracted trace + // must be the same as that which was extracted. + // + // If a sampling decision is not present in the extracted trace context, then + // an Envoy decision of "drop" is honored. An Envoy decision of "keep" + // delegates the sampling decision to the underlying Datadog tracer, which + // will not make a sampling decision immediately. + + struct Case { + int line; + datadog::tracing::Optional extracted_sampling_priority; + bool envoy_decision_keep; + datadog::tracing::PropagationStyle extraction_style; + // `resulting_sampling_priority` is the sampling priority that results from + // trace context extraction. + // It's not necessarily the sampling priority that would be sent to the + // Datadog Agent. + // If `resulting_sampling_priority` is null, then that means that the tracer + // does not make an initial sampling decision, though it will make one by + // the time is sends spans to the Datadog Agent or injects trace context + // into an outgoing request. + datadog::tracing::Optional resulting_sampling_priority; + } cases[] = { + {__LINE__, datadog::tracing::nullopt, true, datadog::tracing::PropagationStyle::DATADOG, + datadog::tracing::nullopt}, + // Note that the `resulting_sampling_priority` in this case is an artifact + // of "traceparent" always containing a sampling decision in its flags. See + // the main body of the test, below, for more information. + {__LINE__, datadog::tracing::nullopt, true, datadog::tracing::PropagationStyle::W3C, 0}, + // This is the only case, at least in this test, where Envoy's decision + // affects the resulting sampling priority. + {__LINE__, datadog::tracing::nullopt, false, datadog::tracing::PropagationStyle::DATADOG, -1}, + {__LINE__, datadog::tracing::nullopt, false, datadog::tracing::PropagationStyle::W3C, 0}, + + {__LINE__, -1, true, datadog::tracing::PropagationStyle::DATADOG, -1}, + {__LINE__, -1, true, datadog::tracing::PropagationStyle::W3C, -1}, + {__LINE__, -1, false, datadog::tracing::PropagationStyle::DATADOG, -1}, + {__LINE__, -1, false, datadog::tracing::PropagationStyle::W3C, -1}, + + {__LINE__, 0, true, datadog::tracing::PropagationStyle::DATADOG, 0}, + {__LINE__, 0, true, datadog::tracing::PropagationStyle::W3C, 0}, + {__LINE__, 0, false, datadog::tracing::PropagationStyle::DATADOG, 0}, + {__LINE__, 0, false, datadog::tracing::PropagationStyle::W3C, 0}, + + {__LINE__, 1, true, datadog::tracing::PropagationStyle::DATADOG, 1}, + {__LINE__, 1, true, datadog::tracing::PropagationStyle::W3C, 1}, + {__LINE__, 1, false, datadog::tracing::PropagationStyle::DATADOG, 1}, + {__LINE__, 1, false, datadog::tracing::PropagationStyle::W3C, 1}, + + {__LINE__, 2, true, datadog::tracing::PropagationStyle::DATADOG, 2}, + {__LINE__, 2, true, datadog::tracing::PropagationStyle::W3C, 2}, + {__LINE__, 2, false, datadog::tracing::PropagationStyle::DATADOG, 2}, + {__LINE__, 2, false, datadog::tracing::PropagationStyle::W3C, 2}, + }; + + for (const Case& test_case : cases) { + std::ostringstream failure_context; + failure_context << "Failure occurred for test entry on line " << test_case.line; + + std::string style_name; + if (test_case.extraction_style == datadog::tracing::PropagationStyle::DATADOG) { + style_name = "datadog"; + } else { + ASSERT_EQ(test_case.extraction_style, datadog::tracing::PropagationStyle::W3C) + << failure_context.str(); + style_name = "tracecontext"; + } + + EnvVarGuard guard{"DD_TRACE_PROPAGATION_STYLE", style_name}; + datadog::tracing::TracerConfig config; + config.defaults.service = "envoy"; + Tracer tracer("fake_cluster", "test_host", config, cluster_manager_, *store_.rootScope(), + thread_local_slot_allocator_); + + Tracing::Decision envoy_decision; + envoy_decision.reason = Tracing::Reason::Sampling; + envoy_decision.traced = test_case.envoy_decision_keep; + + const std::string operation_name = "do.thing"; + + Tracing::TestTraceContextImpl context{{}}; + if (test_case.extraction_style == datadog::tracing::PropagationStyle::DATADOG) { + context.context_map_["x-datadog-trace-id"] = "123"; + context.context_map_["x-datadog-parent-id"] = "456"; + if (test_case.extracted_sampling_priority) { + context.context_map_["x-datadog-sampling-priority"] = + std::to_string(*test_case.extracted_sampling_priority); + } + } else { + ASSERT_EQ(test_case.extraction_style, datadog::tracing::PropagationStyle::W3C) + << failure_context.str(); + std::string flags; + if (test_case.extracted_sampling_priority) { + const int priority = *test_case.extracted_sampling_priority; + flags = priority <= 0 ? "00" : "01"; + context.context_map_["tracestate"] = "dd=s:" + std::to_string(priority); + } else { + // There's no such thing as the absence of a sampling decision with + // "traceparent," so default to "drop." + flags = "00"; + } + context.context_map_["traceparent"] = + "00-0000000000000000000000000000007b-00000000000001c8-" + flags; + } + + const Tracing::SpanPtr span = tracer.startSpan(Tracing::MockConfig{}, context, stream_info_, + operation_name, envoy_decision); + ASSERT_TRUE(span) << failure_context.str(); + const auto as_dd_span_wrapper = dynamic_cast(span.get()); + EXPECT_NE(nullptr, as_dd_span_wrapper) << failure_context.str(); + + const datadog::tracing::Optional& maybe_dd_span = + as_dd_span_wrapper->impl(); + ASSERT_TRUE(maybe_dd_span) << failure_context.str(); + const datadog::tracing::Span& dd_span = *maybe_dd_span; + + const datadog::tracing::Optional decision = + dd_span.trace_segment().sampling_decision(); + if (test_case.resulting_sampling_priority) { + // We expect that the tracer made a sampling decision immediately, and + // that it has the expected sampling priority. + ASSERT_NE(datadog::tracing::nullopt, decision) << failure_context.str(); + EXPECT_EQ(*test_case.resulting_sampling_priority, decision->priority) + << failure_context.str(); + } else { + // We expect that the tracer did not immediately make a sampling decision. + EXPECT_EQ(datadog::tracing::nullopt, decision) << failure_context.str(); + } + } +} + } // namespace } // namespace Datadog } // namespace Tracers From 00a78942e343cee953df07ad6930ed33625c328a Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 16 Nov 2023 05:26:02 +0100 Subject: [PATCH 26/34] [bp/1.27] Datadog: restore "resource.name" tag (#30503) (#30892) This is the backport of #30503 Risk Level: low Testing: local unit tests Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Fixes: #30235 Signed-off-by: David Goffredo --- .../tracers/datadog/demo/docker-compose.yaml | 2 ++ source/extensions/tracers/datadog/demo/envoy | 2 +- source/extensions/tracers/datadog/span.cc | 16 ++++++++++++++-- test/extensions/tracers/datadog/span_test.cc | 12 +++++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/source/extensions/tracers/datadog/demo/docker-compose.yaml b/source/extensions/tracers/datadog/demo/docker-compose.yaml index 40b81aa75c333..1f42ec52daa19 100644 --- a/source/extensions/tracers/datadog/demo/docker-compose.yaml +++ b/source/extensions/tracers/datadog/demo/docker-compose.yaml @@ -8,9 +8,11 @@ services: dd-agent: volumes: - '/var/run/docker.sock:/var/run/docker.sock:ro' + - '/run/user:/run/user:ro' - '/proc/:/host/proc/:ro' - '/sys/fs/cgroup/:/host/sys/fs/cgroup:ro' environment: + - DOCKER_HOST - DD_API_KEY - DD_APM_ENABLED=true - DD_LOG_LEVEL=ERROR diff --git a/source/extensions/tracers/datadog/demo/envoy b/source/extensions/tracers/datadog/demo/envoy index 2a29dc40f391c..a84b13ed97e03 100755 --- a/source/extensions/tracers/datadog/demo/envoy +++ b/source/extensions/tracers/datadog/demo/envoy @@ -1,4 +1,4 @@ #!/bin/sh here=$(dirname "$0") -"$(bazelisk info bazel-genfiles)"/source/exe/envoy-static --config-path "$here"/envoy.yaml "$@" +"$here"/../../../../../bazel-bin/source/exe/envoy-static --config-path "$here"/envoy.yaml "$@" diff --git a/source/extensions/tracers/datadog/span.cc b/source/extensions/tracers/datadog/span.cc index 45922a20e620f..d027419152187 100644 --- a/source/extensions/tracers/datadog/span.cc +++ b/source/extensions/tracers/datadog/span.cc @@ -41,7 +41,9 @@ void Span::setOperation(absl::string_view operation) { return; } - span_->set_name(operation); + // What Envoy calls the operation name more closely corresponds to what + // Datadog calls the resource name. + span_->set_resource_name(operation); } void Span::setTag(absl::string_view name, absl::string_view value) { @@ -49,7 +51,17 @@ void Span::setTag(absl::string_view name, absl::string_view value) { return; } - span_->set_tag(name, value); + // The special "resource.name" tag is a holdover from when the Datadog tracer + // was OpenTracing-based, and so there was no way to set the Datadog resource + // name directly. + // In Envoy, it's still the case that there's no way to set the Datadog + // resource name directly; so, here if the tag name is "resource.name", we + // actually set the resource name instead of setting a tag. + if (name == "resource.name") { + span_->set_resource_name(value); + } else { + span_->set_tag(name, value); + } } void Span::log(SystemTime, const std::string&) { diff --git a/test/extensions/tracers/datadog/span_test.cc b/test/extensions/tracers/datadog/span_test.cc index 70b4fafd93525..aec618037cb55 100644 --- a/test/extensions/tracers/datadog/span_test.cc +++ b/test/extensions/tracers/datadog/span_test.cc @@ -128,7 +128,10 @@ TEST_F(DatadogTracerSpanTest, SetOperation) { ASSERT_NE(nullptr, data_ptr); const datadog::tracing::SpanData& data = *data_ptr; - EXPECT_EQ("gastric bypass", data.name); + // Setting the operation name actually sets the resource name, because Envoy's + // notion of operation name more closely matches Datadog's notion of resource + // name. + EXPECT_EQ("gastric bypass", data.resource); } TEST_F(DatadogTracerSpanTest, SetTag) { @@ -136,6 +139,7 @@ TEST_F(DatadogTracerSpanTest, SetTag) { span.setTag("foo", "bar"); span.setTag("boom", "bam"); span.setTag("foo", "new"); + span.setTag("resource.name", "vespene gas"); span.finishSpan(); ASSERT_EQ(1, collector_->chunks.size()); @@ -152,6 +156,12 @@ TEST_F(DatadogTracerSpanTest, SetTag) { found = data.tags.find("boom"); ASSERT_NE(data.tags.end(), found); EXPECT_EQ("bam", found->second); + + // The "resource.name" tag is special. It doesn't set a tag, but instead the + // span's resource name. + found = data.tags.find("resource.name"); + ASSERT_EQ(data.tags.end(), found); + EXPECT_EQ("vespene gas", data.resource); } TEST_F(DatadogTracerSpanTest, InjectContext) { From 502913c4f07f4e6690f9d8f072fdb18bf80a7839 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 9 Nov 2023 06:12:42 +0800 Subject: [PATCH 27/34] buffer: separate the BufferFragement release and drain tracker (#28770) Fixes #28760 Signed-off-by: He Jie Xu --- source/common/buffer/buffer_impl.h | 21 +++++++++- test/common/buffer/owned_impl_test.cc | 42 ++++++++++++++++++- .../user_space/io_handle_impl_test.cc | 20 +++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/source/common/buffer/buffer_impl.h b/source/common/buffer/buffer_impl.h index 058657dc5abd3..efaf6fa5ef388 100644 --- a/source/common/buffer/buffer_impl.h +++ b/source/common/buffer/buffer_impl.h @@ -90,7 +90,7 @@ class Slice { : capacity_(fragment.size()), storage_(nullptr), base_(static_cast(const_cast(fragment.data()))), reservable_(fragment.size()) { - addDrainTracker([&fragment]() { fragment.done(); }); + releasor_ = [&fragment]() { fragment.done(); }; } Slice(Slice&& rhs) noexcept { @@ -101,6 +101,7 @@ class Slice { reservable_ = rhs.reservable_; drain_trackers_ = std::move(rhs.drain_trackers_); account_ = std::move(rhs.account_); + releasor_.swap(rhs.releasor_); rhs.capacity_ = 0; rhs.base_ = nullptr; @@ -119,6 +120,11 @@ class Slice { reservable_ = rhs.reservable_; drain_trackers_ = std::move(rhs.drain_trackers_); account_ = std::move(rhs.account_); + if (releasor_) { + releasor_(); + } + releasor_ = rhs.releasor_; + rhs.releasor_ = nullptr; rhs.capacity_ = 0; rhs.base_ = nullptr; @@ -129,7 +135,12 @@ class Slice { return *this; } - ~Slice() { callAndClearDrainTrackersAndCharges(); } + ~Slice() { + callAndClearDrainTrackersAndCharges(); + if (releasor_) { + releasor_(); + } + } /** * @return true if the data in the slice is mutable @@ -307,6 +318,9 @@ class Slice { void transferDrainTrackersTo(Slice& destination) { destination.drain_trackers_.splice(destination.drain_trackers_.end(), drain_trackers_); ASSERT(drain_trackers_.empty()); + // The releasor needn't to be transferred, and actually if there is releasor, this + // slice can't coalesce. Then there won't be a chance to calling this method. + ASSERT(releasor_ == nullptr); } /** @@ -397,6 +411,9 @@ class Slice { /** Account associated with this slice. This may be null. When * coalescing with another slice, we do not transfer over their account. */ BufferMemoryAccountSharedPtr account_; + + /** The releasor for the BufferFragment */ + std::function releasor_; }; class OwnedImpl; diff --git a/test/common/buffer/owned_impl_test.cc b/test/common/buffer/owned_impl_test.cc index c575f5ad9719e..85fbdcf7a61f6 100644 --- a/test/common/buffer/owned_impl_test.cc +++ b/test/common/buffer/owned_impl_test.cc @@ -77,6 +77,44 @@ TEST_F(OwnedImplTest, AddBufferFragmentWithCleanup) { EXPECT_TRUE(release_callback_called_); } +TEST_F(OwnedImplTest, MoveBufferFragment) { + Buffer::OwnedImpl buffer1; + testing::MockFunction + release_callback_tracker; + std::string frag_input("a"); + BufferFragmentImpl frag(frag_input.c_str(), frag_input.size(), + release_callback_tracker.AsStdFunction()); + buffer1.addBufferFragment(frag); + + Buffer::OwnedImpl buffer2; + buffer2.move(buffer1); + + EXPECT_EQ(0, buffer1.length()); + EXPECT_EQ(1, buffer2.length()); + + EXPECT_CALL(release_callback_tracker, Call(_, _, _)); + buffer2.drain(buffer2.length()); +} + +TEST_F(OwnedImplTest, MoveBufferFragmentWithReleaseDrainTracker) { + Buffer::OwnedImpl buffer1; + testing::MockFunction + release_callback_tracker; + std::string frag_input("a"); + BufferFragmentImpl frag(frag_input.c_str(), frag_input.size(), + release_callback_tracker.AsStdFunction()); + buffer1.addBufferFragment(frag); + + Buffer::OwnedImpl buffer2; + buffer2.move(buffer1, true); + + EXPECT_EQ(0, buffer1.length()); + EXPECT_EQ(1, buffer2.length()); + + EXPECT_CALL(release_callback_tracker, Call(_, _, _)); + buffer2.drain(buffer2.length()); +} + TEST_F(OwnedImplTest, AddEmptyFragment) { char input[] = "hello world"; BufferFragmentImpl frag1(input, 11, [](const void*, size_t, const BufferFragmentImpl*) {}); @@ -667,10 +705,10 @@ TEST_F(OwnedImplTest, LinearizeDrainTracking) { testing::MockFunction done_tracker; EXPECT_CALL(tracker1, Call()); EXPECT_CALL(drain_tracker, Call(3 * LargeChunk + 108 * SmallChunk, 16384)); - EXPECT_CALL(release_callback_tracker, Call(_, _, _)); EXPECT_CALL(tracker2, Call()); - EXPECT_CALL(release_callback_tracker2, Call(_, _, _)); + EXPECT_CALL(release_callback_tracker, Call(_, _, _)); EXPECT_CALL(tracker3, Call()); + EXPECT_CALL(release_callback_tracker2, Call(_, _, _)); EXPECT_CALL(drain_tracker, Call(2 * LargeChunk + 107 * SmallChunk, 16384)); EXPECT_CALL(drain_tracker, Call(LargeChunk + 106 * SmallChunk, 16384)); EXPECT_CALL(tracker4, Call()); diff --git a/test/extensions/io_socket/user_space/io_handle_impl_test.cc b/test/extensions/io_socket/user_space/io_handle_impl_test.cc index 17aeb8b7a99c6..b3bf35512ef81 100644 --- a/test/extensions/io_socket/user_space/io_handle_impl_test.cc +++ b/test/extensions/io_socket/user_space/io_handle_impl_test.cc @@ -412,6 +412,26 @@ TEST_F(IoHandleImplTest, ShutDownOptionsNotSupported) { ASSERT_DEBUG_DEATH(io_handle_peer_->shutdown(ENVOY_SHUT_RDWR), ""); } +// This test is ensure the memory created by BufferFragment won't be released +// after the write. +TEST_F(IoHandleImplTest, WriteBufferFragement) { + Buffer::OwnedImpl buf("a"); + bool released = false; + auto buf_frag = Buffer::OwnedBufferFragmentImpl::create( + std::string(255, 'b'), [&released](const Buffer::OwnedBufferFragmentImpl* fragment) { + released = true; + delete fragment; + }); + buf.addBufferFragment(*buf_frag.release()); + + auto result = io_handle_->write(buf); + EXPECT_FALSE(released); + EXPECT_EQ(0, buf.length()); + io_handle_peer_->read(buf, absl::nullopt); + buf.drain(buf.length()); + EXPECT_TRUE(released); +} + TEST_F(IoHandleImplTest, WriteByMove) { Buffer::OwnedImpl buf("0123456789"); auto result = io_handle_peer_->write(buf); From 1cea1f307036be956cb8d5aa94077c61013c71bd Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Wed, 15 Nov 2023 12:02:22 +0000 Subject: [PATCH 28/34] add changelog Signed-off-by: He Jie Xu --- changelogs/current.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 29a3cd3f5f832..7a71d5669308f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -12,6 +12,10 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. - area: http change: | Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. From 787cda1d3f6731e4dfb45bf607fef54e686ea7d8 Mon Sep 17 00:00:00 2001 From: Ashish Banerjee Date: Tue, 28 Nov 2023 17:24:28 -0500 Subject: [PATCH 29/34] Up-port tap/extproc changes from 1.26 to 1.27 On this branch, I created a diff of all commits that we created on top of Envoy 1.26 for extproc and tap. The last commit from upstream envoy was dbbe864852395ee5640b110bb25bff8e17b0c6da, off of which the first extproc commit (b1e99e5397568f87cf15f34b96f4cd79f41e633d) was written. Several extproc commits ensued, and then on top of the last extproc commit was a single commit (4d15e7f10d9fed5e0a855b56674f1a08244f541f) to backport upstream tap changes onto the v1.26.x + extproc branch. Given this, I created a patch by running git diff --patch dbbe864852395ee5640b110bb25bff8e17b0c6da...4d15e7f10d9fed5e0a855b56674f1a08244f541f > extproc-tap.patch This resulted in a single large patch file which I applied on top of `v1.27.1`, which is where this branch starts. Tap merged fine (1 merge conflict), while extproc was more difficult - lots of merge conflicts in the following files: api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto changelogs/current.yaml source/common/runtime/runtime_features.cc source/extensions/filters/common/expr/evaluator.cc source/extensions/filters/http/ext_proc/ext_proc.h source/extensions/filters/http/ext_proc/processor_state.h source/extensions/transport_sockets/tap/config.cc test/extensions/filters/http/ext_proc/config_test.cc test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc test/extensions/filters/http/ext_proc/filter_test.cc I tried to resolve them myself, and to be frank, I think I did a pretty damn good job. But there are still a few compile errors that I don't know how to resolve, so I'm going to push this branch from here and see if I can pull in some help. The good news is that a lot of it compiles - everything in `source/` compiles successfully, including `//source/exe:envoy-static`! The bad news is that some tests don't compile, and I also cannot be assured that any of the extproc tests will pass. (I could write a fancy bazel command to run some of the tests that _do_ compile, but I'd rather enjoy my evening.) The tests were some of the hardest conflicts to resolve, and clearly there is still some work here left to be done, so this part remains a work in progress. --- api/envoy/config/tap/v3/common.proto | 6 +- .../filters/http/ext_proc/v3/ext_proc.proto | 40 +- .../ext_proc/v3/external_processor.proto | 11 +- changelogs/current.yaml | 19 +- source/common/http/filter_manager.cc | 7 + source/common/runtime/runtime_features.cc | 4 + source/extensions/common/tap/BUILD | 1 + source/extensions/common/tap/tap.h | 21 + .../extensions/common/tap/tap_config_base.cc | 32 +- .../extensions/common/tap/tap_config_base.h | 2 +- .../extensions/filters/common/expr/context.cc | 8 +- .../filters/common/expr/evaluator.cc | 2 +- .../filters/common/expr/evaluator.h | 2 +- source/extensions/filters/http/ext_proc/BUILD | 25 +- .../filters/http/ext_proc/config.cc | 7 +- .../extensions/filters/http/ext_proc/config.h | 6 +- .../filters/http/ext_proc/ext_proc.cc | 247 ++++++++- .../filters/http/ext_proc/ext_proc.h | 113 +++- .../filters/http/ext_proc/processor_state.h | 71 ++- source/extensions/filters/http/tap/config.cc | 16 +- .../filters/http/tap/tap_config_impl.cc | 5 +- .../filters/http/tap/tap_config_impl.h | 3 +- .../transport_sockets/tap/config.cc | 11 +- .../transport_sockets/tap/tap_config_impl.h | 7 +- test/extensions/common/tap/BUILD | 3 + test/extensions/common/tap/admin_test.cc | 91 ++++ test/extensions/filters/http/ext_proc/BUILD | 19 + .../filters/http/ext_proc/config_test.cc | 34 +- .../ext_proc/ext_proc_integration_test.cc | 46 ++ .../filters/http/ext_proc/filter_test.cc | 486 +++++++++++++++++- .../filters/http/ext_proc/ordering_test.cc | 2 +- 31 files changed, 1278 insertions(+), 69 deletions(-) diff --git a/api/envoy/config/tap/v3/common.proto b/api/envoy/config/tap/v3/common.proto index 1884bd57d3d17..126993d0f7b42 100644 --- a/api/envoy/config/tap/v3/common.proto +++ b/api/envoy/config/tap/v3/common.proto @@ -4,6 +4,7 @@ package envoy.config.tap.v3; import "envoy/config/common/matcher/v3/matcher.proto"; import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/grpc_service.proto"; import "envoy/config/route/v3/route_components.proto"; @@ -183,7 +184,7 @@ message OutputConfig { } // Tap output sink configuration. -// [#next-free-field: 6] +// [#next-free-field: 7] message OutputSink { option (udpa.annotations.versioning).previous_message_type = "envoy.service.tap.v2alpha.OutputSink"; @@ -259,6 +260,9 @@ message OutputSink { // been configured to receive tap configuration from some other source (e.g., static // file, XDS, etc.) configuring the buffered admin output type will fail. BufferedAdminSink buffered_admin = 5; + + // Tap output filter will be defined by an extension type + core.v3.TypedExtensionConfig custom_sink = 6; } } diff --git a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto index db1d83471cf1c..b6494bea63275 100644 --- a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto +++ b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto @@ -28,8 +28,6 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // **Current Implementation Status:** // All options and processing modes are implemented except for the following: // -// * Request and response attributes are not sent and not processed. -// * Dynamic metadata in responses from the external processor is ignored. // * "async mode" is not implemented. // The filter communicates with an external gRPC service called an "external processor" @@ -99,7 +97,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // ` object in a namespace matching the filter // name. // -// [#next-free-field: 15] +// [#next-free-field: 17] message ExternalProcessor { // Configuration for the gRPC service that the filter will communicate with. // The filter supports both the "Envoy" and "Google" gRPC clients. @@ -200,6 +198,11 @@ message ExternalProcessor { // :ref:`mode_override `. // If not set, ``mode_override`` API in the response message will be ignored. bool allow_mode_override = 14; + + reserved 15; + + // Options related to the sending and receiving of dynamic metadata + MetadataOptions metadata_options = 16; } // The HeaderForwardingRules structure specifies what headers are @@ -224,6 +227,32 @@ message HeaderForwardingRules { type.matcher.v3.ListStringMatcher disallowed_headers = 2; } +// The MetadataOptions structure defines options for the sending and receiving of +// dynamic metadata. Specifically, which namespaces to send to the server, whether +// metadata returned by the server may be written, and how that metadata may be written. +message MetadataOptions { + message MetadataNamespaces { + // Specifies a list of metadata namespaces whose values, if present, + // will be passed to the ext_proc service as an opaque *protobuf::Struct*. + repeated string untyped = 1; + + // Specifies a list of metadata namespaces whose values, if present, + // will be passed to the ext_proc service as a *protobuf::Any*. This allows + // envoy and the external processing server to share the protobuf message + // definition for safe parsing. + repeated string typed = 2; + } + + // Describes which typed or untyped dynamic metadata namespaces to forward to + // the external processing server. + MetadataNamespaces forwarding_namespaces = 1; + + // Describes which typed or untyped dynamic metadata namespaces to accept from + // the external processing server. Set to empty or leave unset to disallow writing + // any received dynamic metadata. Receiving of typed metadata is not supported. + MetadataNamespaces receiving_namespaces = 2; +} + // Extra settings that may be added to per-route configuration for a // virtual host or cluster. message ExtProcPerRoute { @@ -242,7 +271,7 @@ message ExtProcPerRoute { } // Overrides that may be set on a per-route basis -// [#next-free-field: 6] +// [#next-free-field: 7] message ExtProcOverrides { // Set a different processing mode for this route than the default. ProcessingMode processing_mode = 1; @@ -263,4 +292,7 @@ message ExtProcOverrides { // Set a different gRPC service for this route than the default. config.core.v3.GrpcService grpc_service = 5; + + // Options related to the sending and receiving of dynamic metadata + MetadataOptions metadata_options = 6; } diff --git a/api/envoy/service/ext_proc/v3/external_processor.proto b/api/envoy/service/ext_proc/v3/external_processor.proto index 666e652962553..2fcb473e051ec 100644 --- a/api/envoy/service/ext_proc/v3/external_processor.proto +++ b/api/envoy/service/ext_proc/v3/external_processor.proto @@ -56,7 +56,7 @@ service ExternalProcessor { // This represents the different types of messages that Envoy can send // to an external processing server. -// [#next-free-field: 8] +// [#next-free-field: 9] message ProcessingRequest { // Specify whether the filter that sent this request is running in synchronous // or asynchronous mode. The choice of synchronous or asynchronous mode @@ -115,6 +115,9 @@ message ProcessingRequest { // in the filter configuration. HttpTrailers response_trailers = 7; } + + // Dynamic metadata associated with the request. + config.core.v3.Metadata metadata_context = 8; } // For every ProcessingRequest received by the server with the ``async_mode`` field @@ -158,9 +161,9 @@ message ProcessingResponse { ImmediateResponse immediate_response = 7; } - // [#not-implemented-hide:] - // Optional metadata that will be emitted as dynamic metadata to be consumed by the next - // filter. This metadata will be placed in the namespace ``envoy.filters.http.ext_proc``. + // Optional metadata that will be emitted as dynamic metadata to be consumed by + // following filters. This metadata will be placed in the namespace(s) specified by the top-level + // field name(s) of the struct. google.protobuf.Struct dynamic_metadata = 8; // Override how parts of the HTTP request and response are processed diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a6ce592912136..b32d49f300d56 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -19,12 +19,15 @@ behavior_changes: By default this limit is disabled. bug_fixes: -- area: connection limit - change: | - fixed a use-after-free bug in the connection limit filter. -- area: tls - change: | - fixed a bug where handshake may fail when both private key provider and cert validation are set. -- area: docker/publishing +# *Changes expected to improve the state of the world and are unlikely to have negative effects* + +removed_config_or_runtime: +# *Normally occurs at the end of the* :ref:`deprecation period ` + +new_features: +- area: tap change: | - Update base images to resolve various glibc vulnerabilities. + added :ref:`custom_sink ` type to enable writing tap data + out to a custom sink extension. + +deprecated: diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 5f71e1cfb5f4b..17d52c02e43ad 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -867,6 +867,13 @@ FilterManager::commonDecodePrefix(ActiveStreamDecoderFilter* filter, } void DownstreamFilterManager::onLocalReply(StreamFilterBase::LocalReplyData& data) { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.on_local_reply_createfilterchain")) { + // To ensure we have filters over which we can iterate and call onLocalReply. + // If the filter chain already exists this will be a no-op. + createFilterChain(); + } + state_.under_on_local_reply_ = true; filter_manager_callbacks_.onLocalReply(data.code_); diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 60d594c964319..d0844afc5eb55 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -43,6 +43,9 @@ RUNTIME_GUARD(envoy_reloadable_features_enable_intermediate_ca); RUNTIME_GUARD(envoy_reloadable_features_enable_update_listener_socket_options); RUNTIME_GUARD(envoy_reloadable_features_expand_agnostic_stream_lifetime); RUNTIME_GUARD(envoy_reloadable_features_finish_reading_on_decode_trailers); +RUNTIME_GUARD(envoy_reloadable_features_fix_hash_key); +RUNTIME_GUARD(envoy_reloadable_features_ext_authz_http_send_original_xff); +RUNTIME_GUARD(envoy_reloadable_features_ext_proc_disable_response_processing_on_local_reply); RUNTIME_GUARD(envoy_reloadable_features_format_ports_as_numbers); RUNTIME_GUARD(envoy_reloadable_features_handle_uppercase_scheme); RUNTIME_GUARD(envoy_reloadable_features_http1_allow_codec_error_response_after_1xx_headers); @@ -64,6 +67,7 @@ RUNTIME_GUARD(envoy_reloadable_features_oauth_header_passthrough_fix); RUNTIME_GUARD(envoy_reloadable_features_oauth_make_token_cookie_httponly); RUNTIME_GUARD(envoy_reloadable_features_oauth_use_standard_max_age_value); RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding); +RUNTIME_GUARD(envoy_reloadable_features_on_local_reply_createfilterchain); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); RUNTIME_GUARD(envoy_reloadable_features_overload_manager_error_unknown_action); RUNTIME_GUARD(envoy_reloadable_features_prohibit_route_refresh_after_response_headers_sent); diff --git a/source/extensions/common/tap/BUILD b/source/extensions/common/tap/BUILD index 452fa65c61611..68e8617581a86 100644 --- a/source/extensions/common/tap/BUILD +++ b/source/extensions/common/tap/BUILD @@ -28,6 +28,7 @@ envoy_cc_library( ":tap_interface", "//source/common/common:assert_lib", "//source/common/common:hex_lib", + "//source/common/config:utility_lib", "//source/extensions/common/matcher:matcher_lib", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", "@envoy_api//envoy/data/tap/v3:pkg_cc_proto", diff --git a/source/extensions/common/tap/tap.h b/source/extensions/common/tap/tap.h index 107152f9d08bc..9f3a86966eab2 100644 --- a/source/extensions/common/tap/tap.h +++ b/source/extensions/common/tap/tap.h @@ -76,6 +76,27 @@ class Sink { }; using SinkPtr = std::unique_ptr; +using SinkContext = + absl::variant, + std::reference_wrapper>; + +/** + * Abstract tap sink factory. Produces a factory that can instantiate SinkPtr objects + */ +class TapSinkFactory : public Config::TypedFactory { +public: + ~TapSinkFactory() override = default; + std::string category() const override { return "envoy.tap.sinks"; } + + /** + * Create a Sink that can be used for writing out data produced by the tap filter. + * @param config supplies the protobuf configuration for the sink factory + * @param cluster_manager is a ClusterManager from the HTTP/transport socket context + */ + virtual SinkPtr createSinkPtr(const Protobuf::Message& config, SinkContext context) PURE; +}; + +using TapSinkFactoryPtr = std::unique_ptr; /** * Generic configuration for a tap extension (filter, transport socket, etc.). diff --git a/source/extensions/common/tap/tap_config_base.cc b/source/extensions/common/tap/tap_config_base.cc index 5d91f81bf9279..7f75f4636f777 100644 --- a/source/extensions/common/tap/tap_config_base.cc +++ b/source/extensions/common/tap/tap_config_base.cc @@ -3,9 +3,11 @@ #include "envoy/config/tap/v3/common.pb.h" #include "envoy/data/tap/v3/common.pb.h" #include "envoy/data/tap/v3/wrapper.pb.h" +#include "envoy/server/transport_socket_config.h" #include "source/common/common/assert.h" #include "source/common/common/fmt.h" +#include "source/common/config/utility.h" #include "source/common/protobuf/utility.h" #include "source/extensions/common/matcher/matcher.h" @@ -45,12 +47,13 @@ bool Utility::addBufferToProtoBytes(envoy::data::tap::v3::Body& output_body, } TapConfigBaseImpl::TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Common::Tap::Sink* admin_streamer) + Common::Tap::Sink* admin_streamer, SinkContext context) : max_buffered_rx_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( proto_config.output_config(), max_buffered_rx_bytes, DefaultMaxBufferedBytes)), max_buffered_tx_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( proto_config.output_config(), max_buffered_tx_bytes, DefaultMaxBufferedBytes)), streaming_(proto_config.output_config().streaming()) { + using ProtoOutputSink = envoy::config::tap::v3::OutputSink; auto& sinks = proto_config.output_config().sinks(); ASSERT(sinks.size() == 1); @@ -86,6 +89,33 @@ TapConfigBaseImpl::TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& pr sink_ = std::make_unique(sinks[0].file_per_tap()); sink_to_use_ = sink_.get(); break; + case ProtoOutputSink::OutputSinkTypeCase::kCustomSink: { + TapSinkFactory& tap_sink_factory = + Envoy::Config::Utility::getAndCheckFactory(sinks[0].custom_sink()); + + // extract message validation visitor from the context and use it to define config + ProtobufTypes::MessagePtr config; + using TsfContextRef = + std::reference_wrapper; + using HttpContextRef = std::reference_wrapper; + if (absl::holds_alternative(context)) { + Server::Configuration::TransportSocketFactoryContext& tsf_context = + absl::get(context).get(); + config = Config::Utility::translateAnyToFactoryConfig(sinks[0].custom_sink().typed_config(), + tsf_context.messageValidationVisitor(), + tap_sink_factory); + } else { + Server::Configuration::FactoryContext& http_context = + absl::get(context).get(); + config = Config::Utility::translateAnyToFactoryConfig( + sinks[0].custom_sink().typed_config(), + http_context.messageValidationContext().staticValidationVisitor(), tap_sink_factory); + } + + sink_ = tap_sink_factory.createSinkPtr(*config, context); + sink_to_use_ = sink_.get(); + break; + } case envoy::config::tap::v3::OutputSink::OutputSinkTypeCase::kStreamingGrpc: PANIC("not implemented"); case envoy::config::tap::v3::OutputSink::OutputSinkTypeCase::OUTPUT_SINK_TYPE_NOT_SET: diff --git a/source/extensions/common/tap/tap_config_base.h b/source/extensions/common/tap/tap_config_base.h index 77a997929b745..aca6eb1cf485e 100644 --- a/source/extensions/common/tap/tap_config_base.h +++ b/source/extensions/common/tap/tap_config_base.h @@ -103,7 +103,7 @@ class TapConfigBaseImpl : public virtual TapConfig { protected: TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Common::Tap::Sink* admin_streamer); + Common::Tap::Sink* admin_streamer, SinkContext context); private: // This is the default setting for both RX/TX max buffered bytes. (This means that per tap, the diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc index e8ddca7d3208f..fcaaeb801b820 100644 --- a/source/extensions/filters/common/expr/context.cc +++ b/source/extensions/filters/common/expr/context.cc @@ -152,7 +152,13 @@ absl::optional ResponseWrapper::operator[](CelValue key) const { } auto value = key.StringOrDie().value(); if (value == Code) { - auto code = info_.responseCode(); + absl::optional code; + uint32_t maybecode; + if (info_.responseCode().has_value()) { + code.emplace(info_.responseCode().value()); + } else if (headers_.value_ != nullptr && absl::SimpleAtoi(headers_.value_->getStatusValue(), &maybecode)) { + code.emplace(maybecode); + } if (code.has_value()) { return CelValue::CreateInt64(code.value()); } diff --git a/source/extensions/filters/common/expr/evaluator.cc b/source/extensions/filters/common/expr/evaluator.cc index f7f965e9b3b9a..27cbc92f56003 100644 --- a/source/extensions/filters/common/expr/evaluator.cc +++ b/source/extensions/filters/common/expr/evaluator.cc @@ -84,7 +84,7 @@ ActivationPtr createActivation(const StreamInfo::StreamInfo& info, response_trailers); } -BuilderPtr createBuilder(Protobuf::Arena* arena) { +Extensions::Filters::Common::Expr::BuilderPtr createBuilder(Protobuf::Arena* arena) { ASSERT_IS_MAIN_OR_TEST_THREAD(); google::api::expr::runtime::InterpreterOptions options; diff --git a/source/extensions/filters/common/expr/evaluator.h b/source/extensions/filters/common/expr/evaluator.h index 656a9c7e676f5..b97ebc3e3f9b2 100644 --- a/source/extensions/filters/common/expr/evaluator.h +++ b/source/extensions/filters/common/expr/evaluator.h @@ -72,7 +72,7 @@ using BuilderInstanceSharedPtr = std::shared_ptr; // Creates an expression builder. The optional arena is used to enable constant folding // for intermediate evaluation results. // Throws an exception if fails to construct an expression builder. -BuilderPtr createBuilder(Protobuf::Arena* arena); +Filters::Common::Expr::BuilderPtr createBuilder(Protobuf::Arena* arena); // Gets the singleton expression builder. Must be called on the main thread. BuilderInstanceSharedPtr getBuilder(Server::Configuration::CommonFactoryContext& context); diff --git a/source/extensions/filters/http/ext_proc/BUILD b/source/extensions/filters/http/ext_proc/BUILD index 2c2397d6d3044..d152edb1c7e80 100644 --- a/source/extensions/filters/http/ext_proc/BUILD +++ b/source/extensions/filters/http/ext_proc/BUILD @@ -19,6 +19,12 @@ envoy_cc_library( "ext_proc.h", "processor_state.h", ], + copts = select({ + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), deps = [ ":client_interface", ":mutation_utils_lib", @@ -31,23 +37,40 @@ envoy_cc_library( "//source/common/runtime:runtime_features_lib", "//source/extensions/filters/common/mutation_rules:mutation_rules_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", + "//source/extensions/filters/common/expr:evaluator_lib", "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", + "@com_google_cel_cpp//eval/public:builtin_func_registrar", + "@com_google_cel_cpp//eval/public:cel_expr_builder_factory", "@envoy_api//envoy/config/common/mutation_rules/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto", "@envoy_api//envoy/service/ext_proc/v3:pkg_cc_proto", - ], + ] + select( + { + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "@com_google_cel_cpp//parser", + ], + }, + ), ) envoy_cc_extension( name = "config", srcs = ["config.cc"], hdrs = ["config.h"], + copts = select({ + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), deps = [ ":client_lib", ":ext_proc", "//source/extensions/filters/http/common:factory_base_lib", + "//source/extensions/filters/common/expr:evaluator_lib", "@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/ext_proc/config.cc b/source/extensions/filters/http/ext_proc/config.cc index 7a0240e84e033..bf57eb7dafdca 100644 --- a/source/extensions/filters/http/ext_proc/config.cc +++ b/source/extensions/filters/http/ext_proc/config.cc @@ -11,13 +11,16 @@ namespace ExternalProcessing { Http::FilterFactoryCb ExternalProcessingFilterConfig::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { + if (expr_builder_ == nullptr) { + expr_builder_ = Extensions::Filters::Common::Expr::createBuilder(nullptr); + } const uint32_t message_timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, message_timeout, DefaultMessageTimeoutMs); const uint32_t max_message_timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, max_message_timeout, DefaultMaxMessageTimeoutMs); const auto filter_config = std::make_shared(proto_config, std::chrono::milliseconds(message_timeout_ms), - max_message_timeout_ms, context.scope(), stats_prefix); + max_message_timeout_ms, context.scope(), stats_prefix, *expr_builder_); return [filter_config, grpc_service = proto_config.grpc_service(), &context](Http::FilterChainFactoryCallbacks& callbacks) { @@ -46,7 +49,7 @@ ExternalProcessingFilterConfig::createFilterFactoryFromProtoWithServerContextTyp PROTOBUF_GET_MS_OR_DEFAULT(proto_config, max_message_timeout, DefaultMaxMessageTimeoutMs); const auto filter_config = std::make_shared(proto_config, std::chrono::milliseconds(message_timeout_ms), - max_message_timeout_ms, server_context.scope(), stats_prefix); + max_message_timeout_ms, server_context.scope(), stats_prefix, *expr_builder_); return [filter_config, grpc_service = proto_config.grpc_service(), &server_context](Http::FilterChainFactoryCallbacks& callbacks) { diff --git a/source/extensions/filters/http/ext_proc/config.h b/source/extensions/filters/http/ext_proc/config.h index a2912466eb6b1..98c92a2f4a14a 100644 --- a/source/extensions/filters/http/ext_proc/config.h +++ b/source/extensions/filters/http/ext_proc/config.h @@ -6,6 +6,7 @@ #include "envoy/extensions/filters/http/ext_proc/v3/ext_proc.pb.validate.h" #include "source/extensions/filters/http/common/factory_base.h" +#include "source/extensions/filters/common/expr/evaluator.h" namespace Envoy { namespace Extensions { @@ -23,13 +24,16 @@ class ExternalProcessingFilterConfig static constexpr uint64_t DefaultMessageTimeoutMs = 200; static constexpr uint64_t DefaultMaxMessageTimeoutMs = 0; + // Expression builder must outlive the compiled expression. + Filters::Common::Expr::BuilderPtr expr_builder_; + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; Router::RouteSpecificFilterConfigConstSharedPtr createRouteSpecificFilterConfigTyped( const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& proto_config, - Server::Configuration::ServerFactoryContext& context, + Server::Configuration::ServerFactoryContext&, ProtobufMessage::ValidationVisitor& validator) override; Http::FilterFactoryCb createFilterFactoryFromProtoWithServerContextTyped( diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 4adb8c5b73c10..0023d4c0a6a11 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -1,5 +1,6 @@ #include "source/extensions/filters/http/ext_proc/ext_proc.h" +#include #include "envoy/config/common/mutation_rules/v3/mutation_rules.pb.h" #include "source/common/http/utility.h" @@ -7,6 +8,11 @@ #include "source/extensions/filters/http/ext_proc/mutation_utils.h" #include "absl/strings/str_format.h" +#include + +#if defined(USE_CEL_PARSER) +#include "parser/parser.h" +#endif namespace Envoy { namespace Extensions { @@ -113,13 +119,58 @@ ExtProcLoggingInfo::grpcCalls(envoy::config::core::v3::TrafficDirection traffic_ : encoding_processor_grpc_calls_; } +absl::flat_hash_map +FilterConfig::initExpressions(const Protobuf::RepeatedPtrField& matchers) const { + absl::flat_hash_map expressions; +#if defined(USE_CEL_PARSER) + for (const auto& matcher : matchers) { + auto parse_status = google::api::expr::parser::Parse(matcher); + if (!parse_status.ok()) { + throw EnvoyException("Unable to parse descriptor expression: " + + parse_status.status().ToString()); + } + expressions.emplace(matcher, Extensions::Filters::Common::Expr::createExpression( + builder_, parse_status.value().expr())); + } +#else + ENVOY_LOG(warn, "CEL expression parsing is not available for use in this environment." + " Attempted to parse " + + std::to_string(matchers.size()) + " expressions"); +#endif + return expressions; +} + FilterConfigPerRoute::FilterConfigPerRoute(const ExtProcPerRoute& config) : disabled_(config.disabled()) { - if (config.has_overrides()) { + if (!config.has_overrides()) { + return; + } + + const auto& overrides = config.overrides(); + if (overrides.has_processing_mode()) { processing_mode_ = config.overrides().processing_mode(); } - if (config.overrides().has_grpc_service()) { - grpc_service_ = config.overrides().grpc_service(); + if (overrides.has_grpc_service()) { + grpc_service_ = overrides.grpc_service(); + } + + if (!overrides.has_metadata_options()) { + return; + } + + const auto& md_opts = overrides.metadata_options(); + if (md_opts.has_forwarding_namespaces()) { + untyped_forwarding_namespaces_ = + std::vector(md_opts.forwarding_namespaces().untyped().begin(), + md_opts.forwarding_namespaces().untyped().end()); + typed_forwarding_namespaces_ = + std::vector(md_opts.forwarding_namespaces().typed().begin(), + md_opts.forwarding_namespaces().typed().end()); + } + if (md_opts.has_receiving_namespaces()) { + untyped_receiving_namespaces_ = + std::vector(md_opts.receiving_namespaces().untyped().begin(), + md_opts.receiving_namespaces().untyped().end()); } } @@ -129,6 +180,15 @@ void FilterConfigPerRoute::merge(const FilterConfigPerRoute& src) { if (src.grpcService().has_value()) { grpc_service_ = src.grpcService(); } + if (src.untypedForwardingMetadataNamespaces().has_value()) { + untyped_forwarding_namespaces_ = src.untypedForwardingMetadataNamespaces(); + } + if (src.typedForwardingMetadataNamespaces().has_value()) { + typed_forwarding_namespaces_ = src.typedForwardingMetadataNamespaces(); + } + if (src.untypedReceivingMetadataNamespaces().has_value()) { + untyped_receiving_namespaces_ = src.untypedReceivingMetadataNamespaces(); + } } void Filter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) { @@ -206,7 +266,9 @@ void Filter::onDestroy() { } FilterHeadersStatus Filter::onHeaders(ProcessorState& state, - Http::RequestOrResponseHeaderMap& headers, bool end_stream) { + Http::RequestOrResponseHeaderMap& headers, + bool end_stream, + absl::optional proto) { switch (openStream()) { case StreamOpenState::Error: return FilterHeadersStatus::StopIteration; @@ -220,10 +282,14 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, state.setHeaders(&headers); state.setHasNoBody(end_stream); ProcessingRequest req; + addDynamicMetadata(state, req); auto* headers_req = state.mutableHeaders(req); MutationUtils::headersToProto(headers, config_->allowedHeaders(), config_->disallowedHeaders(), *headers_req->mutable_headers()); headers_req->set_end_of_stream(end_stream); + if (proto.has_value()) { + (*headers_req->mutable_attributes())[FilterName] = proto.value(); + } state.onStartProcessorCall(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout(), ProcessorState::CallbackState::HeadersCallback); ENVOY_LOG(debug, "Sending headers message"); @@ -233,6 +299,47 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, return FilterHeadersStatus::StopIteration; } +const absl::optional +Filter::evaluateAttributes(Filters::Common::Expr::ActivationPtr activation, + const absl::flat_hash_map& expr) { + absl::optional proto; + if (expr.size() > 0) { + proto.emplace(google::protobuf::Struct{}); + for (const auto& hash_entry: expr) { + ProtobufWkt::Arena arena; + const auto result = hash_entry.second.get()->Evaluate(*activation, &arena); + if (!result.ok()) { + // TODO: Stats? + continue; + } + + if (result.value().IsError()) { + ENVOY_LOG(trace, "error parsing cel expression {}", hash_entry.first); + continue; + } + + google::protobuf::Value value; + switch (result.value().type()) { + case google::api::expr::runtime::CelValue::Type::kBool: + value.set_bool_value(result.value().BoolOrDie()); + break; + case google::api::expr::runtime::CelValue::Type::kNullType: + value.set_null_value(ProtobufWkt::NullValue{}); + break; + case google::api::expr::runtime::CelValue::Type::kDouble: + value.set_number_value(result.value().DoubleOrDie()); + break; + default: + value.set_string_value(Filters::Common::Expr::print(result.value())); + } + + (*(proto.value()).mutable_fields())[hash_entry.first] = value; + } + } + + return proto; +} + FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_stream) { ENVOY_LOG(trace, "decodeHeaders: end_stream = {}", end_stream); mergePerRouteConfig(); @@ -245,7 +352,11 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st return FilterHeadersStatus::Continue; } - const auto status = onHeaders(decoding_state_, headers, end_stream); + auto activation_ptr = Filters::Common::Expr::createActivation(decoding_state_.streamInfo(), + &headers, nullptr, nullptr); + auto proto = evaluateAttributes(std::move(activation_ptr), config_->requestExpr()); + + const auto status = onHeaders(decoding_state_, headers, end_stream, proto); ENVOY_LOG(trace, "decodeHeaders returning {}", static_cast(status)); return status; } @@ -543,18 +654,26 @@ FilterHeadersStatus Filter::encodeHeaders(ResponseHeaderMap& headers, bool end_s encoding_state_.setCompleteBodyAvailable(true); } - if (processing_complete_ || !encoding_state_.sendHeaders()) { + if (processing_complete_ || !encoding_state_.sendHeaders() || encoding_state_.inLocalReply()) { ENVOY_LOG(trace, "encodeHeaders: Continue"); return FilterHeadersStatus::Continue; } - const auto status = onHeaders(encoding_state_, headers, end_stream); + auto activation_ptr = Filters::Common::Expr::createActivation(encoding_state_.streamInfo(), + nullptr, &headers, nullptr); + auto proto = evaluateAttributes(std::move(activation_ptr), config_->responseExpr()); + + const auto status = onHeaders(encoding_state_, headers, end_stream, proto); ENVOY_LOG(trace, "encodeHeaders returns {}", static_cast(status)); return status; } FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_stream) { ENVOY_LOG(trace, "encodeData({}): end_stream = {}", data.length(), end_stream); + if (encoding_state_.inLocalReply()) { + ENVOY_LOG(trace, "encodeData: Continue"); + return FilterDataStatus::Continue; + } const auto status = onData(encoding_state_, data, end_stream); ENVOY_LOG(trace, "encodeData returning {}", static_cast(status)); return status; @@ -562,6 +681,10 @@ FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_stream) { FilterTrailersStatus Filter::encodeTrailers(ResponseTrailerMap& trailers) { ENVOY_LOG(trace, "encodeTrailers"); + if (encoding_state_.inLocalReply()) { + ENVOY_LOG(trace, "encodeTrailers: Continue"); + return FilterTrailersStatus::Continue; + } const auto status = onTrailers(encoding_state_, trailers); ENVOY_LOG(trace, "encodeTrailers returning {}", static_cast(status)); return status; @@ -573,6 +696,7 @@ void Filter::sendBodyChunk(ProcessorState& state, const Buffer::Instance& data, state.onStartProcessorCall(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout(), new_state); ProcessingRequest req; + addDynamicMetadata(state, req); auto* body_req = state.mutableBody(req); body_req->set_end_of_stream(end_stream); body_req->set_body(data.toString()); @@ -593,6 +717,7 @@ void Filter::sendBufferedData(ProcessorState& state, ProcessorState::CallbackSta void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers) { ProcessingRequest req; + addDynamicMetadata(state, req); auto* trailers_req = state.mutableTrailers(req); MutationUtils::headersToProto(trailers, config_->allowedHeaders(), config_->disallowedHeaders(), *trailers_req->mutable_trailers()); @@ -632,6 +757,83 @@ void Filter::onNewTimeout(const ProtobufWkt::Duration& override_message_timeout) stats_.override_message_timeout_received_.inc(); } +void Filter::addDynamicMetadata(ProcessorState& state, ProcessingRequest& req) { + // get the callbacks from the ProcessorState. This will be the appropriate + // callbacks for the current state of the filter + auto* cb = state.callbacks(); + envoy::config::core::v3::Metadata forwarding_metadata; + + // If metadata_context_namespaces is specified, pass matching filter metadata to the ext_proc + // service. If metadata key is set in both the connection and request metadata then the value + // will be the request metadata value. The metadata will only be searched for the callbacks + // corresponding to the traffic direction at the time of the external processing request. + const auto& request_metadata = cb->streamInfo().dynamicMetadata().filter_metadata(); + for (const auto& context_key : state.untypedForwardingMetadataNamespaces()) { + if (const auto metadata_it = request_metadata.find(context_key); + metadata_it != request_metadata.end()) { + (*forwarding_metadata.mutable_filter_metadata())[metadata_it->first] = metadata_it->second; + } else if (cb->connection().has_value()) { + const auto& connection_metadata = + cb->connection().value().get().streamInfo().dynamicMetadata().filter_metadata(); + if (const auto metadata_it = connection_metadata.find(context_key); + metadata_it != connection_metadata.end()) { + (*forwarding_metadata.mutable_filter_metadata())[metadata_it->first] = metadata_it->second; + } + } + } + + // If typed_metadata_context_namespaces is specified, pass matching typed filter metadata to the + // ext_proc service. If metadata key is set in both the connection and request metadata then + // the value will be the request metadata value. The metadata will only be searched for the + // callbacks corresponding to the traffic direction at the time of the external processing + // request. + const auto& request_typed_metadata = cb->streamInfo().dynamicMetadata().typed_filter_metadata(); + for (const auto& context_key : state.typedForwardingMetadataNamespaces()) { + if (const auto metadata_it = request_typed_metadata.find(context_key); + metadata_it != request_typed_metadata.end()) { + (*forwarding_metadata.mutable_typed_filter_metadata())[metadata_it->first] = + metadata_it->second; + } else if (cb->connection().has_value()) { + const auto& connection_typed_metadata = + cb->connection().value().get().streamInfo().dynamicMetadata().typed_filter_metadata(); + if (const auto metadata_it = connection_typed_metadata.find(context_key); + metadata_it != connection_typed_metadata.end()) { + (*forwarding_metadata.mutable_typed_filter_metadata())[metadata_it->first] = + metadata_it->second; + } + } + } + + *req.mutable_metadata_context() = forwarding_metadata; +} + +void Filter::setDynamicMetadata(Http::StreamFilterCallbacks* cb, const ProcessorState& state, + std::unique_ptr& response) { + bool has_receiving_namespaces = state.untypedReceivingMetadataNamespaces().size() > 0; + if (!(has_receiving_namespaces && response->has_dynamic_metadata())) { + return; + } + + if (response->has_dynamic_metadata()) { + auto response_metadata = response->dynamic_metadata().fields(); + auto receiving_namespaces = state.untypedReceivingMetadataNamespaces(); + for (const auto& context_key : response_metadata) { + if (auto metadata_it = std::find(receiving_namespaces.begin(), receiving_namespaces.end(), + context_key.first); + metadata_it != receiving_namespaces.end()) + cb->streamInfo().setDynamicMetadata(context_key.first, + response_metadata.at(context_key.first).struct_value()); + } + } +} + +void Filter::setEncoderDynamicMetadata(std::unique_ptr& response) { + setDynamicMetadata(encoder_callbacks_, encoding_state_, response); +} +void Filter::setDecoderDynamicMetadata(std::unique_ptr& response) { + setDynamicMetadata(decoder_callbacks_, decoding_state_, response); +} + void Filter::onReceiveMessage(std::unique_ptr&& r) { if (processing_complete_) { ENVOY_LOG(debug, "Ignoring stream message received after processing complete"); @@ -661,24 +863,31 @@ void Filter::onReceiveMessage(std::unique_ptr&& r) { absl::Status processing_status; switch (response->response_case()) { case ProcessingResponse::ResponseCase::kRequestHeaders: + setDecoderDynamicMetadata(response); processing_status = decoding_state_.handleHeadersResponse(response->request_headers()); break; case ProcessingResponse::ResponseCase::kResponseHeaders: + setEncoderDynamicMetadata(response); processing_status = encoding_state_.handleHeadersResponse(response->response_headers()); break; case ProcessingResponse::ResponseCase::kRequestBody: + setDecoderDynamicMetadata(response); processing_status = decoding_state_.handleBodyResponse(response->request_body()); break; case ProcessingResponse::ResponseCase::kResponseBody: + setEncoderDynamicMetadata(response); processing_status = encoding_state_.handleBodyResponse(response->response_body()); break; case ProcessingResponse::ResponseCase::kRequestTrailers: + setDecoderDynamicMetadata(response); processing_status = decoding_state_.handleTrailersResponse(response->request_trailers()); break; case ProcessingResponse::ResponseCase::kResponseTrailers: + setEncoderDynamicMetadata(response); processing_status = encoding_state_.handleTrailersResponse(response->response_trailers()); break; case ProcessingResponse::ResponseCase::kImmediateResponse: + setDecoderDynamicMetadata(response); // We won't be sending anything more to the stream after we // receive this message. ENVOY_LOG(debug, "Sending immediate response"); @@ -879,6 +1088,30 @@ void Filter::mergePerRouteConfig() { ENVOY_LOG(trace, "Setting new GrpcService from per-route configuration"); grpc_service_ = *merged_config->grpcService(); } + if (merged_config->untypedForwardingMetadataNamespaces()) { + ENVOY_LOG(trace, + "Setting new untyped forwarding metadata namespaces from per-route configuration"); + decoding_state_.setUntypedForwardingMetadataNamespaces( + *merged_config->untypedForwardingMetadataNamespaces()); + encoding_state_.setUntypedForwardingMetadataNamespaces( + *merged_config->untypedForwardingMetadataNamespaces()); + } + if (merged_config->typedForwardingMetadataNamespaces()) { + ENVOY_LOG(trace, + "Setting new typed forwarding metadata namespaces from per-route configuration"); + decoding_state_.setTypedForwardingMetadataNamespaces( + *merged_config->typedForwardingMetadataNamespaces()); + encoding_state_.setTypedForwardingMetadataNamespaces( + *merged_config->typedForwardingMetadataNamespaces()); + } + if (merged_config->untypedReceivingMetadataNamespaces()) { + ENVOY_LOG(trace, + "Setting new untyped receiving metadata namespaces from per-route configuration"); + decoding_state_.setUntypedReceivingMetadataNamespaces( + *merged_config->untypedReceivingMetadataNamespaces()); + encoding_state_.setUntypedReceivingMetadataNamespaces( + *merged_config->untypedReceivingMetadataNamespaces()); + } } std::string responseCaseToString(const ProcessingResponse::ResponseCase response_case) { diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 5c8e347a06a63..51bf059655624 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -21,11 +21,15 @@ #include "source/common/common/logger.h" #include "source/common/common/matchers.h" #include "source/common/protobuf/protobuf.h" +#include "parser/parser.h" +#include "source/extensions/filters/common/expr/evaluator.h" #include "source/extensions/filters/common/mutation_rules/mutation_rules.h" #include "source/extensions/filters/http/common/pass_through_filter.h" #include "source/extensions/filters/http/ext_proc/client.h" #include "source/extensions/filters/http/ext_proc/processor_state.h" +#include "source/common/runtime/runtime_features.h" + namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -121,12 +125,13 @@ class ExtProcLoggingInfo : public Envoy::StreamInfo::FilterState::Object { Upstream::HostDescriptionConstSharedPtr upstream_host_; }; -class FilterConfig { +class FilterConfig : public Logger::Loggable { public: FilterConfig(const envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor& config, const std::chrono::milliseconds message_timeout, const uint32_t max_message_timeout_ms, Stats::Scope& scope, - const std::string& stats_prefix) + const std::string& stats_prefix, + Extensions::Filters::Common::Expr::Builder& builder) : failure_mode_allow_(config.failure_mode_allow()), disable_clear_route_cache_(config.disable_clear_route_cache()), message_timeout_(message_timeout), max_message_timeout_ms_(max_message_timeout_ms), @@ -135,7 +140,19 @@ class FilterConfig { filter_metadata_(config.filter_metadata()), allow_mode_override_(config.allow_mode_override()), allowed_headers_(initHeaderMatchers(config.forward_rules().allowed_headers())), - disallowed_headers_(initHeaderMatchers(config.forward_rules().disallowed_headers())) {} + disallowed_headers_(initHeaderMatchers(config.forward_rules().disallowed_headers())), + builder_(builder), + request_expr_(initExpressions(config.request_attributes())), + response_expr_(initExpressions(config.response_attributes())), + untyped_forwarding_namespaces_( + config.metadata_options().forwarding_namespaces().untyped().begin(), + config.metadata_options().forwarding_namespaces().untyped().end()), + typed_forwarding_namespaces_( + config.metadata_options().forwarding_namespaces().typed().begin(), + config.metadata_options().forwarding_namespaces().typed().end()), + untyped_receiving_namespaces_( + config.metadata_options().receiving_namespaces().untyped().begin(), + config.metadata_options().receiving_namespaces().untyped().end()) {} bool failureModeAllow() const { return failure_mode_allow_; } @@ -164,6 +181,28 @@ class FilterConfig { const Envoy::ProtobufWkt::Struct& filterMetadata() const { return filter_metadata_; } + const absl::flat_hash_map& + requestExpr() const { + return request_expr_; + } + + const absl::flat_hash_map& + responseExpr() const { + return response_expr_; + } + + const std::vector& untypedForwardingMetadataNamespaces() const { + return untyped_forwarding_namespaces_; + } + + const std::vector& typedForwardingMetadataNamespaces() const { + return typed_forwarding_namespaces_; + } + + const std::vector& untypedReceivingMetadataNamespaces() const { + return untyped_receiving_namespaces_; + } + private: ExtProcFilterStats generateStats(const std::string& prefix, const std::string& filter_stats_prefix, Stats::Scope& scope) { @@ -181,6 +220,9 @@ class FilterConfig { return header_matchers; } + absl::flat_hash_map + initExpressions(const Protobuf::RepeatedPtrField& matchers) const; + const bool failure_mode_allow_; const bool disable_clear_route_cache_; const std::chrono::milliseconds message_timeout_; @@ -196,6 +238,17 @@ class FilterConfig { const std::vector allowed_headers_; // Empty disallowed_header_ means disallow nothing, i.e, allow all. const std::vector disallowed_headers_; + + Extensions::Filters::Common::Expr::Builder& builder_; + + const absl::flat_hash_map + request_expr_; + const absl::flat_hash_map + response_expr_; + + const std::vector untyped_forwarding_namespaces_; + const std::vector typed_forwarding_namespaces_; + const std::vector untyped_receiving_namespaces_; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -216,10 +269,24 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { return grpc_service_; } + const absl::optional>& untypedForwardingMetadataNamespaces() const { + return untyped_forwarding_namespaces_; + } + const absl::optional>& typedForwardingMetadataNamespaces() const { + return typed_forwarding_namespaces_; + } + const absl::optional>& untypedReceivingMetadataNamespaces() const { + return untyped_receiving_namespaces_; + } + private: bool disabled_; absl::optional processing_mode_; absl::optional grpc_service_; + + absl::optional> untyped_forwarding_namespaces_; + absl::optional> typed_forwarding_namespaces_; + absl::optional> untyped_receiving_namespaces_; }; class Filter : public Logger::Loggable, @@ -241,8 +308,14 @@ class Filter : public Logger::Loggable, Filter(const FilterConfigSharedPtr& config, ExternalProcessorClientPtr&& client, const envoy::config::core::v3::GrpcService& grpc_service) : config_(config), client_(std::move(client)), stats_(config->stats()), - grpc_service_(grpc_service), decoding_state_(*this, config->processingMode()), - encoding_state_(*this, config->processingMode()) {} + grpc_service_(grpc_service), decoding_state_(*this, config->processingMode(), + config->untypedForwardingMetadataNamespaces(), + config->typedForwardingMetadataNamespaces(), + config->untypedReceivingMetadataNamespaces()), + encoding_state_(*this, config->processingMode(), + config->untypedForwardingMetadataNamespaces(), + config->typedForwardingMetadataNamespaces(), + config->untypedReceivingMetadataNamespaces()) {} const FilterConfig& config() const { return *config_; } @@ -283,6 +356,19 @@ class Filter : public Logger::Loggable, void sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers); + Http::LocalErrorStatus onLocalReply(const LocalReplyData&) override { + + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.ext_proc_disable_response_processing_on_local_reply")) { + ENVOY_LOG(trace, "local reply; will skip ext_proc response processing requests"); + encoding_state_.inLocalReply(true); + } + return Http::LocalErrorStatus::Continue; + } + + const ProcessorState& encodingState() { return encoding_state_; } + const ProcessorState& decodingState() { return decoding_state_; } + private: void mergePerRouteConfig(); StreamOpenState openStream(); @@ -293,11 +379,26 @@ class Filter : public Logger::Loggable, void sendImmediateResponse(const envoy::service::ext_proc::v3::ImmediateResponse& response); Http::FilterHeadersStatus onHeaders(ProcessorState& state, - Http::RequestOrResponseHeaderMap& headers, bool end_stream); + Http::RequestOrResponseHeaderMap& headers, + bool end_stream, + absl::optional proto); + + const absl::optional + evaluateAttributes(Filters::Common::Expr::ActivationPtr activation, + const absl::flat_hash_map& expr); // Return a pair of whether to terminate returning the current result. std::pair sendStreamChunk(ProcessorState& state, bool end_stream); Http::FilterDataStatus onData(ProcessorState& state, Buffer::Instance& data, bool end_stream); Http::FilterTrailersStatus onTrailers(ProcessorState& state, Http::HeaderMap& trailers); + void + setDynamicMetadata(Http::StreamFilterCallbacks* cb, const ProcessorState& state, + std::unique_ptr& response); + void setEncoderDynamicMetadata( + std::unique_ptr& response); + void setDecoderDynamicMetadata( + std::unique_ptr& response); + void addDynamicMetadata(ProcessorState& state, + envoy::service::ext_proc::v3::ProcessingRequest& req); const FilterConfigSharedPtr config_; const ExternalProcessorClientPtr client_; diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index c921cdb322c27..164f5f4476dbd 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -80,10 +80,19 @@ class ProcessorState : public Logger::Loggable { }; explicit ProcessorState(Filter& filter, - envoy::config::core::v3::TrafficDirection traffic_direction) + envoy::config::core::v3::TrafficDirection traffic_direction, + std::vector untyped_forwarding_namespaces, + std::vector typed_forwarding_namespaces, + std::vector untyped_receiving_namespaces) : filter_(filter), watermark_requested_(false), paused_(false), no_body_(false), complete_body_available_(false), trailers_available_(false), body_replaced_(false), - partial_body_processed_(false), traffic_direction_(traffic_direction) {} + partial_body_processed_(false), traffic_direction_(traffic_direction), + untyped_forwarding_namespaces_(untyped_forwarding_namespaces.begin(), + untyped_forwarding_namespaces.end()), + typed_forwarding_namespaces_(typed_forwarding_namespaces.begin(), + typed_forwarding_namespaces.end()), + untyped_receiving_namespaces_(untyped_receiving_namespaces.begin(), + untyped_receiving_namespaces.end()) {} ProcessorState(const ProcessorState&) = delete; virtual ~ProcessorState() = default; ProcessorState& operator=(const ProcessorState&) = delete; @@ -107,6 +116,28 @@ class ProcessorState : public Logger::Loggable { virtual void setProcessingMode( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode) PURE; + + const std::vector& untypedForwardingMetadataNamespaces() const { + return untyped_forwarding_namespaces_; + }; + void setUntypedForwardingMetadataNamespaces(const std::vector ns) { + untyped_forwarding_namespaces_ = std::vector(ns.begin(), ns.end()); + }; + + const std::vector& typedForwardingMetadataNamespaces() const { + return typed_forwarding_namespaces_; + }; + void setTypedForwardingMetadataNamespaces(const std::vector ns) { + typed_forwarding_namespaces_ = std::vector(ns.begin(), ns.end()); + }; + + const std::vector& untypedReceivingMetadataNamespaces() const { + return untyped_receiving_namespaces_; + }; + void setUntypedReceivingMetadataNamespaces(const std::vector ns) { + untyped_receiving_namespaces_ = std::vector(ns.begin(), ns.end()); + }; + bool sendHeaders() const { return send_headers_; } bool sendTrailers() const { return send_trailers_; } envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode bodyMode() const { @@ -165,6 +196,9 @@ class ProcessorState : public Logger::Loggable { virtual envoy::service::ext_proc::v3::HttpTrailers* mutableTrailers(envoy::service::ext_proc::v3::ProcessingRequest& request) const PURE; + virtual StreamInfo::StreamInfo& streamInfo() PURE; + virtual Http::StreamFilterCallbacks* callbacks() PURE; + protected: void setBodyMode( envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode); @@ -209,6 +243,10 @@ class ProcessorState : public Logger::Loggable { absl::optional call_start_time_ = absl::nullopt; const envoy::config::core::v3::TrafficDirection traffic_direction_; + std::vector untyped_forwarding_namespaces_; + std::vector typed_forwarding_namespaces_; + std::vector untyped_receiving_namespaces_; + private: virtual void clearRouteCache(const envoy::service::ext_proc::v3::CommonResponse&) {} }; @@ -216,8 +254,13 @@ class ProcessorState : public Logger::Loggable { class DecodingProcessorState : public ProcessorState { public: explicit DecodingProcessorState( - Filter& filter, const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode) - : ProcessorState(filter, envoy::config::core::v3::TrafficDirection::INBOUND) { + Filter& filter, const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode, + std::vector untyped_forwarding_namespaces, + std::vector typed_forwarding_namespaces, + std::vector untyped_receiving_namespaces) + : ProcessorState(filter, envoy::config::core::v3::TrafficDirection::INBOUND, + untyped_forwarding_namespaces, typed_forwarding_namespaces, + untyped_receiving_namespaces) { setProcessingModeInternal(mode); } DecodingProcessorState(const DecodingProcessorState&) = delete; @@ -276,6 +319,9 @@ class DecodingProcessorState : public ProcessorState { void requestWatermark() override; void clearWatermark() override; + StreamInfo::StreamInfo& streamInfo() override { return decoder_callbacks_->streamInfo(); } + Http::StreamFilterCallbacks* callbacks() override { return decoder_callbacks_; } + private: void setProcessingModeInternal( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode); @@ -289,8 +335,13 @@ class DecodingProcessorState : public ProcessorState { class EncodingProcessorState : public ProcessorState { public: explicit EncodingProcessorState( - Filter& filter, const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode) - : ProcessorState(filter, envoy::config::core::v3::TrafficDirection::OUTBOUND) { + Filter& filter, const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode, + std::vector untyped_forwarding_namespaces, + std::vector typed_forwarding_namespaces, + std::vector untyped_receiving_namespaces) + : ProcessorState(filter, envoy::config::core::v3::TrafficDirection::OUTBOUND, + untyped_forwarding_namespaces, typed_forwarding_namespaces, + untyped_receiving_namespaces) { setProcessingModeInternal(mode); } EncodingProcessorState(const EncodingProcessorState&) = delete; @@ -349,11 +400,19 @@ class EncodingProcessorState : public ProcessorState { void requestWatermark() override; void clearWatermark() override; + void inLocalReply(bool in_local_reply) { in_local_reply_ = in_local_reply; }; + bool inLocalReply() const { return in_local_reply_; }; + + StreamInfo::StreamInfo& streamInfo() override { return encoder_callbacks_->streamInfo(); } + Http::StreamFilterCallbacks* callbacks() override { return encoder_callbacks_; } + private: void setProcessingModeInternal( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode); Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; + + bool in_local_reply_{}; }; } // namespace ExternalProcessing diff --git a/source/extensions/filters/http/tap/config.cc b/source/extensions/filters/http/tap/config.cc index 5c573924f768e..c9051d7c68b62 100644 --- a/source/extensions/filters/http/tap/config.cc +++ b/source/extensions/filters/http/tap/config.cc @@ -15,21 +15,27 @@ namespace TapFilter { class HttpTapConfigFactoryImpl : public Extensions::Common::Tap::TapConfigFactory { public: + HttpTapConfigFactoryImpl(Server::Configuration::FactoryContext& context) + : factory_context_(context) {} // TapConfigFactory Extensions::Common::Tap::TapConfigSharedPtr createConfigFromProto(const envoy::config::tap::v3::TapConfig& proto_config, Extensions::Common::Tap::Sink* admin_streamer) override { - return std::make_shared(std::move(proto_config), admin_streamer); + return std::make_shared(std::move(proto_config), admin_streamer, + factory_context_); } + +private: + Server::Configuration::FactoryContext& factory_context_; }; Http::FilterFactoryCb TapFilterFactory::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::tap::v3::Tap& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { - FilterConfigSharedPtr filter_config( - new FilterConfigImpl(proto_config, stats_prefix, std::make_unique(), - context.scope(), context.admin(), context.singletonManager(), - context.threadLocal(), context.mainThreadDispatcher())); + FilterConfigSharedPtr filter_config(new FilterConfigImpl( + proto_config, stats_prefix, std::make_unique(context), + context.scope(), context.admin(), context.singletonManager(), context.threadLocal(), + context.mainThreadDispatcher())); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(filter_config); callbacks.addStreamFilter(filter); diff --git a/source/extensions/filters/http/tap/tap_config_impl.cc b/source/extensions/filters/http/tap/tap_config_impl.cc index b3d844c7a48e1..2aa2653a4ffe8 100644 --- a/source/extensions/filters/http/tap/tap_config_impl.cc +++ b/source/extensions/filters/http/tap/tap_config_impl.cc @@ -29,8 +29,9 @@ fillHeaderList(Protobuf::RepeatedPtrField* } // namespace HttpTapConfigImpl::HttpTapConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Common::Tap::Sink* admin_streamer) - : TapCommon::TapConfigBaseImpl(std::move(proto_config), admin_streamer) {} + Common::Tap::Sink* admin_streamer, + Server::Configuration::FactoryContext& context) + : TapCommon::TapConfigBaseImpl(std::move(proto_config), admin_streamer, context) {} HttpPerRequestTapperPtr HttpTapConfigImpl::createPerRequestTapper(uint64_t stream_id) { return std::make_unique(shared_from_this(), stream_id); diff --git a/source/extensions/filters/http/tap/tap_config_impl.h b/source/extensions/filters/http/tap/tap_config_impl.h index d79ef4cc4842a..5938d38076fc7 100644 --- a/source/extensions/filters/http/tap/tap_config_impl.h +++ b/source/extensions/filters/http/tap/tap_config_impl.h @@ -19,7 +19,8 @@ class HttpTapConfigImpl : public Extensions::Common::Tap::TapConfigBaseImpl, public std::enable_shared_from_this { public: HttpTapConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Extensions::Common::Tap::Sink* admin_streamer); + Extensions::Common::Tap::Sink* admin_streamer, + Server::Configuration::FactoryContext& context); // TapFilter::HttpTapConfig HttpPerRequestTapperPtr createPerRequestTapper(uint64_t stream_id) override; diff --git a/source/extensions/transport_sockets/tap/config.cc b/source/extensions/transport_sockets/tap/config.cc index 3b565bee92e96..c8267771e854a 100644 --- a/source/extensions/transport_sockets/tap/config.cc +++ b/source/extensions/transport_sockets/tap/config.cc @@ -17,18 +17,21 @@ namespace Tap { class SocketTapConfigFactoryImpl : public Extensions::Common::Tap::TapConfigFactory { public: - SocketTapConfigFactoryImpl(TimeSource& time_source) : time_source_(time_source) {} + SocketTapConfigFactoryImpl(TimeSource& time_source, + Server::Configuration::TransportSocketFactoryContext& context) + : time_source_(time_source), factory_context_(context) {} // TapConfigFactory Extensions::Common::Tap::TapConfigSharedPtr createConfigFromProto(const envoy::config::tap::v3::TapConfig& proto_config, Extensions::Common::Tap::Sink* admin_streamer) override { return std::make_shared(std::move(proto_config), admin_streamer, - time_source_); + time_source_, factory_context_); } private: TimeSource& time_source_; + Server::Configuration::TransportSocketFactoryContext& factory_context_; }; Network::UpstreamTransportSocketFactoryPtr @@ -49,7 +52,7 @@ UpstreamTapSocketConfigFactory::createTransportSocketFactory( return std::make_unique( outer_config, std::make_unique( - server_context.mainThreadDispatcher().timeSource()), + server_context.mainThreadDispatcher().timeSource(), context), server_context.admin(), server_context.singletonManager(), server_context.threadLocal(), server_context.mainThreadDispatcher(), std::move(inner_transport_factory)); } @@ -72,7 +75,7 @@ DownstreamTapSocketConfigFactory::createTransportSocketFactory( return std::make_unique( outer_config, std::make_unique( - server_context.mainThreadDispatcher().timeSource()), + server_context.mainThreadDispatcher().timeSource(), context), server_context.admin(), server_context.singletonManager(), server_context.threadLocal(), server_context.mainThreadDispatcher(), std::move(inner_transport_factory)); } diff --git a/source/extensions/transport_sockets/tap/tap_config_impl.h b/source/extensions/transport_sockets/tap/tap_config_impl.h index cf715c3df9733..38556025c1408 100644 --- a/source/extensions/transport_sockets/tap/tap_config_impl.h +++ b/source/extensions/transport_sockets/tap/tap_config_impl.h @@ -3,6 +3,7 @@ #include "envoy/config/tap/v3/common.pb.h" #include "envoy/data/tap/v3/transport.pb.h" #include "envoy/event/timer.h" +#include "envoy/server/transport_socket_config.h" #include "source/extensions/common/tap/tap_config_base.h" #include "source/extensions/transport_sockets/tap/tap_config.h" @@ -51,8 +52,10 @@ class SocketTapConfigImpl : public Extensions::Common::Tap::TapConfigBaseImpl, public std::enable_shared_from_this { public: SocketTapConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Extensions::Common::Tap::Sink* admin_streamer, TimeSource& time_system) - : Extensions::Common::Tap::TapConfigBaseImpl(std::move(proto_config), admin_streamer), + Extensions::Common::Tap::Sink* admin_streamer, TimeSource& time_system, + Server::Configuration::TransportSocketFactoryContext& context) + : Extensions::Common::Tap::TapConfigBaseImpl(std::move(proto_config), admin_streamer, + context), time_source_(time_system) {} // SocketTapConfig diff --git a/test/extensions/common/tap/BUILD b/test/extensions/common/tap/BUILD index f65ad733eaf55..dea02d4f722a2 100644 --- a/test/extensions/common/tap/BUILD +++ b/test/extensions/common/tap/BUILD @@ -26,9 +26,12 @@ envoy_cc_test( srcs = envoy_select_admin_functionality(["admin_test.cc"]), deps = [ "//source/extensions/common/tap:admin", + "//source/extensions/common/tap:tap_config_base", "//test/mocks/server:admin_mocks", "//test/mocks/server:admin_stream_mocks", + "//test/mocks/server:server_mocks", "//test/test_common:logging_lib", + "//test/test_common:registry_lib", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/common/tap/admin_test.cc b/test/extensions/common/tap/admin_test.cc index 2a98078939717..e8ad7aaf659df 100644 --- a/test/extensions/common/tap/admin_test.cc +++ b/test/extensions/common/tap/admin_test.cc @@ -5,10 +5,13 @@ #include "source/extensions/common/tap/admin.h" #include "source/extensions/common/tap/tap.h" +#include "source/extensions/common/tap/tap_config_base.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/admin_stream.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/logging.h" +#include "test/test_common/registry.h" #include "gtest/gtest.h" @@ -20,6 +23,7 @@ using ::testing::_; using ::testing::AtLeast; using ::testing::Between; using ::testing::DoAll; +using ::testing::Invoke; using ::testing::Return; using ::testing::ReturnRef; using ::testing::SaveArg; @@ -146,6 +150,93 @@ config_id: test_config_id StrictMock sink_; }; +using Extensions::Common::Tap::TapSinkFactory; +class MockTapSinkFactory : public TapSinkFactory { +public: + MockTapSinkFactory() {} + ~MockTapSinkFactory() override{}; + + MOCK_METHOD(SinkPtr, createSinkPtr, (const Protobuf::Message& config, SinkContext), (override)); + + MOCK_METHOD(std::string, name, (), (const, override)); + MOCK_METHOD(ProtobufTypes::MessagePtr, createEmptyConfigProto, (), (override)); +}; + +class TestConfigImpl : public TapConfigBaseImpl { +public: + TestConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, + Extensions::Common::Tap::Sink* admin_streamer, SinkContext context) + : TapConfigBaseImpl(std::move(proto_config), admin_streamer, context) {} +}; + +TEST(TypedExtensionConfigTest, AddTestConfigHttpContext) { + + const std::string tap_config_yaml = + R"EOF( + match: + any_match: true + output_config: + sinks: + - format: PROTO_BINARY + custom_sink: + name: custom_sink + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue +)EOF"; + envoy::config::tap::v3::TapConfig tap_config; + TestUtility::loadFromYaml(tap_config_yaml, tap_config); + + MockTapSinkFactory factory_impl; + EXPECT_CALL(factory_impl, name).Times(AtLeast(1)); + EXPECT_CALL(factory_impl, createEmptyConfigProto) + .WillRepeatedly(Invoke([]() -> ProtobufTypes::MessagePtr { + return std::make_unique(); + })); + EXPECT_CALL( + factory_impl, + createSinkPtr( + _, + testing::VariantWith>(_))); + Registry::InjectFactory factory(factory_impl); + + NiceMock factory_context; + TestConfigImpl(tap_config, NULL, factory_context); +} + +TEST(TypedExtensionConfigTest, AddTestConfigTransportSocketContext) { + + const std::string tap_config_yaml = + R"EOF( + match: + any_match: true + output_config: + sinks: + - format: PROTO_BINARY + custom_sink: + name: custom_sink + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue +)EOF"; + envoy::config::tap::v3::TapConfig tap_config; + TestUtility::loadFromYaml(tap_config_yaml, tap_config); + + MockTapSinkFactory factory_impl; + EXPECT_CALL(factory_impl, name).Times(AtLeast(1)); + EXPECT_CALL(factory_impl, createEmptyConfigProto) + .WillRepeatedly(Invoke([]() -> ProtobufTypes::MessagePtr { + return std::make_unique(); + })); + EXPECT_CALL( + factory_impl, + createSinkPtr( + _, testing::VariantWith< + std::reference_wrapper>(_))); + Registry::InjectFactory factory(factory_impl); + + NiceMock factory_context; + TestConfigImpl(tap_config, NULL, factory_context); +} + // Make sure warn if using a pipe address for the admin handler. TEST_F(AdminHandlerTest, AdminWithPipeSocket) { EXPECT_LOG_CONTAINS( diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 4f6af31bc0f78..afa9f8e1b3fae 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -30,6 +30,12 @@ envoy_extension_cc_test( name = "filter_test", size = "small", srcs = ["filter_test.cc"], + copts = select({ + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), extension_names = ["envoy.filters.http.ext_proc"], deps = [ ":mock_server_lib", @@ -50,6 +56,7 @@ envoy_extension_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:overload_manager_mocks", + "//test/proto:helloworld_proto_cc_proto", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/service/ext_proc/v3:pkg_cc_proto", @@ -117,6 +124,12 @@ envoy_extension_cc_test( name = "ext_proc_integration_test", size = "large", # This test can take a while under tsan. srcs = ["ext_proc_integration_test.cc"], + copts = select({ + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), extension_names = ["envoy.filters.http.ext_proc"], shard_count = 2, tags = [ @@ -138,6 +151,12 @@ envoy_extension_cc_test( name = "streaming_integration_test", size = "large", srcs = ["streaming_integration_test.cc"], + copts = select({ + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), extension_names = ["envoy.filters.http.ext_proc"], tags = [ "cpu:3", diff --git a/test/extensions/filters/http/ext_proc/config_test.cc b/test/extensions/filters/http/ext_proc/config_test.cc index 914e454ebd5b2..86ad2ad521bd2 100644 --- a/test/extensions/filters/http/ext_proc/config_test.cc +++ b/test/extensions/filters/http/ext_proc/config_test.cc @@ -21,8 +21,12 @@ TEST(HttpExtProcConfigTest, CorrectConfig) { target_uri: ext_proc_server stat_prefix: google failure_mode_allow: true - request_attributes: 'Foo, Bar, Baz' - response_attributes: More + request_attributes: + - 'Foo' + - 'Bar' + - 'Baz' + response_attributes: + - 'More' processing_mode: request_header_mode: send response_header_mode: skip @@ -32,6 +36,15 @@ TEST(HttpExtProcConfigTest, CorrectConfig) { response_trailer_mode: send filter_metadata: hello: "world" + metadata_options: + forwarding_namespaces: + typed: + - ns1 + untyped: + - ns2 + receiving_namespaces: + untyped: + - ns2 )EOF"; ExternalProcessingFilterConfig factory; @@ -53,8 +66,12 @@ TEST(HttpExtProcConfigTest, CorrectConfigServerContext) { target_uri: ext_proc_server stat_prefix: google failure_mode_allow: true - request_attributes: 'Foo, Bar, Baz' - response_attributes: More + request_attributes: + - 'Foo' + - 'Bar' + - 'Baz' + response_attributes: + - 'More' processing_mode: request_header_mode: send response_header_mode: skip @@ -64,6 +81,15 @@ TEST(HttpExtProcConfigTest, CorrectConfigServerContext) { response_trailer_mode: send filter_metadata: hello: "world" + metadata_options: + forwarding_namespaces: + typed: + - ns1 + untyped: + - ns2 + receiving_namespaces: + untyped: + - ns2 )EOF"; ExternalProcessingFilterConfig factory; diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 762606cca9a73..2bd4d46d34e23 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -73,6 +73,9 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, scoped_runtime_.mergeValues( {{"envoy_reloadable_features_immediate_response_use_filter_mutation_rule", filter_mutation_rule_}}); + scoped_runtime_.mergeValues( + {{"envoy_reloadable_features_ext_proc_disable_response_processing_on_local_reply", + disable_on_local_reply_}}); config_helper_.addConfigModifier([this, valid_grpc_server]( envoy::config::bootstrap::v3::Bootstrap& bootstrap) { @@ -415,6 +418,7 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, TestScopedRuntime scoped_runtime_; std::string header_raw_value_{"false"}; std::string filter_mutation_rule_{"false"}; + std::string disable_on_local_reply_{"false"}; }; INSTANTIATE_TEST_SUITE_P( @@ -2681,5 +2685,47 @@ TEST_P(ExtProcIntegrationTest, HeaderMutationResultSizeFailWithResponseTrailer) ASSERT_TRUE(response->waitForReset()); EXPECT_FALSE(response->complete()); } +#if defined(USE_CEL_PARSER) +// Test the filter using the default configuration by connecting to +// an ext_proc server that responds to the request_headers message +// by requesting to modify the request headers. +TEST_P(ExtProcIntegrationTest, GetAndSetRequestResponseAttributes) { + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SEND); + proto_config_.mutable_request_attributes()->Add("request.path"); + proto_config_.mutable_request_attributes()->Add("request.method"); + proto_config_.mutable_request_attributes()->Add("request.scheme"); + proto_config_.mutable_request_attributes()->Add("connection.mtls"); + proto_config_.mutable_response_attributes()->Add("response.code"); + proto_config_.mutable_response_attributes()->Add("response.code_details"); + + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage( + *grpc_upstreams_[0], true, [](const HttpHeaders& req, HeadersResponse&) { + EXPECT_EQ(req.attributes().size(), 1); + auto proto_struct = req.attributes().at("envoy.filters.http.ext_proc"); + EXPECT_EQ(proto_struct.fields().at("request.path").string_value(), "/"); + EXPECT_EQ(proto_struct.fields().at("request.method").string_value(), "GET"); + EXPECT_EQ(proto_struct.fields().at("request.scheme").string_value(), "http"); + EXPECT_EQ(proto_struct.fields().at("connection.mtls").bool_value(), false); + return true; + }); + + handleUpstreamRequest(); + + processResponseHeadersMessage( + *grpc_upstreams_[0], false, [](const HttpHeaders& req, HeadersResponse&) { + EXPECT_EQ(req.attributes().size(), 1); + auto proto_struct = req.attributes().at("envoy.filters.http.ext_proc"); + EXPECT_EQ(proto_struct.fields().at("response.code").string_value(), "200"); + EXPECT_EQ(proto_struct.fields().at("response.code_details").string_value(), "via_upstream"); + return true; + }); + + verifyDownstreamResponse(*response, 200); +} +#endif } // namespace Envoy diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 72db2ad264b21..034d6ed589b68 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -28,6 +28,7 @@ #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/proto/helloworld.pb.h" #include "test/test_common/printers.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -85,7 +86,6 @@ class HttpFilterTest : public testing::Test { EXPECT_CALL(decoder_callbacks_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); EXPECT_CALL(decoder_callbacks_, route()).WillRepeatedly(Return(route_)); EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); - EXPECT_CALL(async_client_stream_info_, bytesSent()).WillRepeatedly(Return(100)); EXPECT_CALL(async_client_stream_info_, bytesReceived()).WillRepeatedly(Return(200)); EXPECT_CALL(async_client_stream_info_, upstreamClusterInfo()); @@ -95,6 +95,16 @@ class HttpFilterTest : public testing::Test { std::dynamic_pointer_cast( async_client_stream_info_.upstreamInfo()); EXPECT_CALL(testing::Const(*mock_upstream_info), upstreamHost()); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, dynamicMetadata()).WillRepeatedly(ReturnRef(dynamic_metadata_)); + EXPECT_CALL(stream_info_, setDynamicMetadata(_, _)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &HttpFilterTest::doSetDynamicMetadata)); + + EXPECT_CALL(decoder_callbacks_, connection()) + .WillRepeatedly(Return(OptRef{connection_})); + EXPECT_CALL(encoder_callbacks_, connection()) + .WillRepeatedly(Return(OptRef{connection_})); // Pointing dispatcher_.time_system_ to a SimulatedTimeSystem object. test_time_ = new Envoy::Event::SimulatedTimeSystem(); @@ -119,7 +129,7 @@ class HttpFilterTest : public testing::Test { if (!yaml.empty()) { TestUtility::loadFromYaml(yaml, proto_config); } - config_.reset(new FilterConfig(proto_config, 200ms, 10000, *stats_store_.rootScope(), "")); + config_.reset(new FilterConfig(proto_config, 200ms, 10000, *stats_store_.rootScope(), "", std::make_shared(Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)))); filter_ = std::make_unique(config_, std::move(client_), proto_config.grpc_service()); filter_->setEncoderFilterCallbacks(encoder_callbacks_); EXPECT_CALL(encoder_callbacks_, encoderBufferLimit()).WillRepeatedly(Return(BufferSize)); @@ -180,6 +190,10 @@ class HttpFilterTest : public testing::Test { return stream; } + void doSetDynamicMetadata(const std::string& ns, const ProtobufWkt::Struct& val){ + (*dynamic_metadata_.mutable_filter_metadata())[ns] = val; + }; + void doSend(ProcessingRequest&& request, Unused) { last_request_ = std::move(request); } bool doSendClose() { return !server_closed_stream_; } @@ -467,7 +481,7 @@ class HttpFilterTest : public testing::Test { ExternalProcessorCallbacks* stream_callbacks_ = nullptr; ProcessingRequest last_request_; bool server_closed_stream_ = false; - NiceMock stats_store_; + testing::NiceMock stats_store_; FilterConfigSharedPtr config_; std::shared_ptr filter_; testing::NiceMock dispatcher_; @@ -483,6 +497,8 @@ class HttpFilterTest : public testing::Test { std::vector timers_; TestScopedRuntime scoped_runtime_; Envoy::Event::SimulatedTimeSystem* test_time_; + envoy::config::core::v3::Metadata dynamic_metadata_; + testing::NiceMock connection_; }; // Using the default configuration, test the filter with a processor that @@ -2946,7 +2962,468 @@ TEST_F(HttpFilterTest, ResponseTrailerMutationExceedSizeLimit) { EXPECT_EQ(1, config_->stats().rejected_header_mutations_.value()); } -class HttpFilter2Test : public HttpFilterTest, public Http::HttpConnectionManagerImplMixin {}; +TEST_F(HttpFilterTest, MetadataOptionsOverride) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SKIP" + response_header_mode: "SEND" + request_body_mode: "NONE" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + metadata_options: + forwarding_namespaces: + untyped: + - untyped_ns_1 + typed: + - typed_ns_1 + receiving_namespaces: + untyped: + - untyped_receiving_ns_1 + )EOF"); + ExtProcPerRoute override_cfg; + const std::string override_yaml = R"EOF( + overrides: + metadata_options: + forwarding_namespaces: + untyped: + - untyped_ns_2 + typed: + - typed_ns_2 + receiving_namespaces: + untyped: + - untyped_receiving_ns_2 + )EOF"; + TestUtility::loadFromYaml(override_yaml, override_cfg); + + FilterConfigPerRoute route_config(override_cfg); + + EXPECT_CALL(decoder_callbacks_, traversePerFilterConfig(_)) + .WillOnce( + testing::Invoke([&](std::function cb) { + cb(route_config); + })); + + response_headers_.addCopy(LowerCaseString(":status"), "200"); + response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + response_headers_.addCopy(LowerCaseString("content-length"), "3"); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(false, absl::nullopt); + + EXPECT_EQ(filter_->encodingState().untypedForwardingMetadataNamespaces().size(), 1); + EXPECT_EQ(filter_->encodingState().untypedForwardingMetadataNamespaces()[0], "untyped_ns_2"); + EXPECT_EQ(filter_->decodingState().untypedForwardingMetadataNamespaces().size(), 1); + EXPECT_EQ(filter_->decodingState().untypedForwardingMetadataNamespaces()[0], "untyped_ns_2"); + + EXPECT_EQ(filter_->encodingState().typedForwardingMetadataNamespaces().size(), 1); + EXPECT_EQ(filter_->encodingState().typedForwardingMetadataNamespaces()[0], "typed_ns_2"); + EXPECT_EQ(filter_->decodingState().typedForwardingMetadataNamespaces().size(), 1); + EXPECT_EQ(filter_->decodingState().typedForwardingMetadataNamespaces()[0], "typed_ns_2"); + + EXPECT_EQ(filter_->encodingState().untypedReceivingMetadataNamespaces().size(), 1); + EXPECT_EQ(filter_->encodingState().untypedReceivingMetadataNamespaces()[0], + "untyped_receiving_ns_2"); + EXPECT_EQ(filter_->decodingState().untypedReceivingMetadataNamespaces().size(), 1); + EXPECT_EQ(filter_->decodingState().untypedReceivingMetadataNamespaces()[0], + "untyped_receiving_ns_2"); + + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + filter_->onDestroy(); +} + +// Verify that the filter sets the processing request with dynamic metadata +// including when the metadata is on the connection stream info +TEST_F(HttpFilterTest, SendDynamicMetadata) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SKIP" + response_header_mode: "SEND" + request_body_mode: "NONE" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + metadata_options: + forwarding_namespaces: + untyped: + - connection.and.request.have.data + - connection.has.data + - request.has.data + - neither.have.data + - untyped.and.typed.connection.data + - typed.connection.data + - untyped.connection.data + typed: + - untyped.and.typed.connection.data + - typed.connection.data + - typed.request.data + - untyped.connection.data + )EOF"); + + const std::string request_yaml = R"EOF( + filter_metadata: + connection.and.request.have.data: + data: request + request.has.data: + data: request + typed_filter_metadata: + typed.request.data: + '@type': type.googleapis.com/helloworld.HelloRequest + name: request_typed + )EOF"; + + const std::string connection_yaml = R"EOF( + filter_metadata: + connection.and.request.have.data: + data: connection_untyped + connection.has.data: + data: connection_untyped + untyped.and.typed.connection.data: + data: connection_untyped + untyped.connection.data: + data: connection_untyped + not.selected.data: + data: connection_untyped + typed_filter_metadata: + untyped.and.typed.connection.data: + '@type': type.googleapis.com/helloworld.HelloRequest + name: connection_typed + typed.connection.data: + '@type': type.googleapis.com/helloworld.HelloRequest + name: connection_typed + not.selected.data: + '@type': type.googleapis.com/helloworld.HelloRequest + name: connection_typed + )EOF"; + + envoy::config::core::v3::Metadata connection_metadata; + TestUtility::loadFromYaml(request_yaml, dynamic_metadata_); + TestUtility::loadFromYaml(connection_yaml, connection_metadata); + connection_.stream_info_.metadata_ = connection_metadata; + + Buffer::OwnedImpl empty_chunk; + + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + + // ensure the metadata that is attached to the processing request is identical to + // the metadata we specified above + EXPECT_EQ("request", last_request_.metadata_context() + .filter_metadata() + .at("connection.and.request.have.data") + .fields() + .at("data") + .string_value()); + + EXPECT_EQ("request", last_request_.metadata_context() + .filter_metadata() + .at("request.has.data") + .fields() + .at("data") + .string_value()); + + EXPECT_EQ("connection_untyped", last_request_.metadata_context() + .filter_metadata() + .at("connection.has.data") + .fields() + .at("data") + .string_value()); + + EXPECT_EQ("connection_untyped", last_request_.metadata_context() + .filter_metadata() + .at("untyped.and.typed.connection.data") + .fields() + .at("data") + .string_value()); + + EXPECT_EQ(0, last_request_.metadata_context().filter_metadata().count("neither.have.data")); + + EXPECT_EQ(0, last_request_.metadata_context().filter_metadata().count("not.selected.data")); + + EXPECT_EQ(0, last_request_.metadata_context().filter_metadata().count("typed.connection.data")); + + helloworld::HelloRequest hello; + last_request_.metadata_context() + .typed_filter_metadata() + .at("typed.connection.data") + .UnpackTo(&hello); + EXPECT_EQ("connection_typed", hello.name()); + + last_request_.metadata_context() + .typed_filter_metadata() + .at("untyped.and.typed.connection.data") + .UnpackTo(&hello); + EXPECT_EQ("connection_typed", hello.name()); + + EXPECT_EQ( + 0, last_request_.metadata_context().typed_filter_metadata().count("untyped.connection.data")); + + EXPECT_EQ(0, last_request_.metadata_context().typed_filter_metadata().count("not.selected.data")); + + processResponseHeaders(false, absl::nullopt); + + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + filter_->onDestroy(); +} + +// Verify that when returning an response with dynamic_metadata field set, the filter emits +// dynamic metadata. +TEST_F(HttpFilterTest, EmitDynamicMetadata) { + // Configure the filter to only pass response headers to ext server. + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SKIP" + response_header_mode: "SEND" + request_body_mode: "NONE" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + metadata_options: + receiving_namespaces: + untyped: + - envoy.filters.http.ext_proc + )EOF"); + + Buffer::OwnedImpl empty_chunk; + + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + + processResponseHeaders(false, [](const HttpHeaders&, ProcessingResponse& resp, HeadersResponse&) { + ProtobufWkt::Struct foobar; + (*foobar.mutable_fields())["foo"].set_string_value("bar"); + auto metadata_mut = resp.mutable_dynamic_metadata()->mutable_fields(); + auto mut_struct = (*metadata_mut)["envoy.filters.http.ext_proc"].mutable_struct_value(); + *mut_struct = foobar; + }); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + EXPECT_EQ("bar", dynamic_metadata_.filter_metadata() + .at("envoy.filters.http.ext_proc") + .fields() + .at("foo") + .string_value()); + + filter_->onDestroy(); +} + +// Verify that when returning an response with dynamic_metadata field set, the filter emits +// dynamic metadata to namespaces other than its own. +TEST_F(HttpFilterTest, EmitDynamicMetadataArbitraryNamespace) { + // Configure the filter to only pass response headers to ext server. + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SKIP" + response_header_mode: "SEND" + request_body_mode: "NONE" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + metadata_options: + receiving_namespaces: + untyped: + - envoy.filters.http.ext_authz + )EOF"); + + Buffer::OwnedImpl empty_chunk; + + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + + processResponseHeaders(false, [](const HttpHeaders&, ProcessingResponse& resp, HeadersResponse&) { + ProtobufWkt::Struct foobar; + (*foobar.mutable_fields())["foo"].set_string_value("bar"); + auto metadata_mut = resp.mutable_dynamic_metadata()->mutable_fields(); + auto mut_struct = (*metadata_mut)["envoy.filters.http.ext_authz"].mutable_struct_value(); + *mut_struct = foobar; + }); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + EXPECT_EQ("bar", dynamic_metadata_.filter_metadata() + .at("envoy.filters.http.ext_authz") + .fields() + .at("foo") + .string_value()); + + filter_->onDestroy(); +} + +// Verify that when returning an response with dynamic_metadata field set, the +// filter does not emit metadata when no allowed namespaces are configured. +TEST_F(HttpFilterTest, DisableEmitDynamicMetadata) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SEND" + response_header_mode: "SEND" + request_body_mode: "NONE" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + )EOF"); + + Buffer::OwnedImpl empty_chunk; + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(false, absl::nullopt); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + + processResponseHeaders(false, [](const HttpHeaders&, ProcessingResponse& resp, HeadersResponse&) { + ProtobufWkt::Struct foobar; + (*foobar.mutable_fields())["foo"].set_string_value("bar"); + auto metadata_mut = resp.mutable_dynamic_metadata()->mutable_fields(); + auto mut_struct = (*metadata_mut)["envoy.filters.http.ext_proc"].mutable_struct_value(); + *mut_struct = foobar; + }); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + EXPECT_EQ(0, dynamic_metadata_.filter_metadata().size()); + + filter_->onDestroy(); +} + +// Verify that when returning an response with dynamic_metadata field set, the +// filter does not emit metadata to namespaces which are not allowed. +TEST_F(HttpFilterTest, DisableEmittingDynamicMetadataToDisallowedNamespaces) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SEND" + response_header_mode: "SEND" + request_body_mode: "NONE" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + metadata_options: + receiving_namespaces: + untyped: + - envoy.filters.http.ext_proc + )EOF"); + + Buffer::OwnedImpl empty_chunk; + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(false, absl::nullopt); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + + processResponseHeaders(false, [](const HttpHeaders&, ProcessingResponse& resp, HeadersResponse&) { + ProtobufWkt::Struct foobar; + (*foobar.mutable_fields())["foo"].set_string_value("bar"); + auto metadata_mut = resp.mutable_dynamic_metadata()->mutable_fields(); + auto mut_struct = (*metadata_mut)["envoy.filters.http.ext_authz"].mutable_struct_value(); + *mut_struct = foobar; + }); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + EXPECT_EQ(0, dynamic_metadata_.filter_metadata().size()); + + filter_->onDestroy(); +} + +// Verify that when returning an response with dynamic_metadata field set, the filter emits +// dynamic metadata and later emissions overwrite earlier ones. +TEST_F(HttpFilterTest, EmitDynamicMetadataUseLast) { + // Configure the filter to only pass response headers to ext server. + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SEND" + response_header_mode: "SEND" + request_body_mode: "NONE" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + metadata_options: + receiving_namespaces: + untyped: + - envoy.filters.http.ext_proc + )EOF"); + + Buffer::OwnedImpl empty_chunk; + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(false, [](const HttpHeaders&, ProcessingResponse& resp, HeadersResponse&) { + ProtobufWkt::Struct batbaz; + (*batbaz.mutable_fields())["bat"].set_string_value("baz"); + auto metadata_mut = resp.mutable_dynamic_metadata()->mutable_fields(); + auto mut_struct = (*metadata_mut)["envoy.filters.http.ext_proc"].mutable_struct_value(); + *mut_struct = batbaz; + }); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + + processResponseHeaders(false, [](const HttpHeaders&, ProcessingResponse& resp, HeadersResponse&) { + ProtobufWkt::Struct foobar; + (*foobar.mutable_fields())["foo"].set_string_value("bar"); + auto metadata_mut = resp.mutable_dynamic_metadata()->mutable_fields(); + auto mut_struct = (*metadata_mut)["envoy.filters.http.ext_proc"].mutable_struct_value(); + *mut_struct = foobar; + }); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_chunk, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + + EXPECT_FALSE(dynamic_metadata_.filter_metadata() + .at("envoy.filters.http.ext_proc") + .fields() + .contains("bat")); + + EXPECT_EQ("bar", dynamic_metadata_.filter_metadata() + .at("envoy.filters.http.ext_proc") + .fields() + .at("foo") + .string_value()); + + filter_->onDestroy(); +} + +class HttpFilter2Test : public HttpFilterTest, + public ::Envoy::Http::HttpConnectionManagerImplMixin {}; // Test proves that when decodeData(data, end_stream=true) is called before request headers response // is returned, ext_proc filter will buffer the data in the ActiveStream buffer without triggering a @@ -3143,7 +3620,6 @@ TEST_F(HttpFilter2Test, LastEncodeDataCallExceedsStreamBufferLimitWouldJustRaise Buffer::OwnedImpl fake_input("hello"); conn_manager_->onData(fake_input, false); } - } // namespace } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/ordering_test.cc b/test/extensions/filters/http/ext_proc/ordering_test.cc index b9587e92b026f..56d6c446521e0 100644 --- a/test/extensions/filters/http/ext_proc/ordering_test.cc +++ b/test/extensions/filters/http/ext_proc/ordering_test.cc @@ -71,7 +71,7 @@ class OrderingTest : public testing::Test { (*cb)(proto_config); } config_.reset(new FilterConfig(proto_config, kMessageTimeout, kMaxMessageTimeoutMs, - *stats_store_.rootScope(), "")); + *stats_store_.rootScope(), "", std::make_shared(Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)))); filter_ = std::make_unique(config_, std::move(client_), proto_config.grpc_service()); filter_->setEncoderFilterCallbacks(encoder_callbacks_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); From 5e4cfe40eb77942ed24a71b4dccdc9c1956f2dfc Mon Sep 17 00:00:00 2001 From: Ashish Banerjee Date: Tue, 28 Nov 2023 23:42:11 +0000 Subject: [PATCH 30/34] Fix a few tests that won't compile The tests compile now. Not sure if this is the correct fix though so a second pair of eyes would be great here. --- test/extensions/filters/http/ext_proc/filter_test.cc | 3 ++- test/extensions/filters/http/ext_proc/ordering_test.cc | 2 +- .../http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 034d6ed589b68..79ca62bf198d7 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -129,7 +129,8 @@ class HttpFilterTest : public testing::Test { if (!yaml.empty()) { TestUtility::loadFromYaml(yaml, proto_config); } - config_.reset(new FilterConfig(proto_config, 200ms, 10000, *stats_store_.rootScope(), "", std::make_shared(Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)))); + config_.reset(new FilterConfig(proto_config, 200ms, 10000, *stats_store_.rootScope(), "", + Envoy::Extensions::Filters::Common::Expr::BuilderInstance(Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)).builder())); filter_ = std::make_unique(config_, std::move(client_), proto_config.grpc_service()); filter_->setEncoderFilterCallbacks(encoder_callbacks_); EXPECT_CALL(encoder_callbacks_, encoderBufferLimit()).WillRepeatedly(Return(BufferSize)); diff --git a/test/extensions/filters/http/ext_proc/ordering_test.cc b/test/extensions/filters/http/ext_proc/ordering_test.cc index 56d6c446521e0..333765af5730c 100644 --- a/test/extensions/filters/http/ext_proc/ordering_test.cc +++ b/test/extensions/filters/http/ext_proc/ordering_test.cc @@ -71,7 +71,7 @@ class OrderingTest : public testing::Test { (*cb)(proto_config); } config_.reset(new FilterConfig(proto_config, kMessageTimeout, kMaxMessageTimeoutMs, - *stats_store_.rootScope(), "", std::make_shared(Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)))); + *stats_store_.rootScope(), "", Envoy::Extensions::Filters::Common::Expr::BuilderInstance(Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)).builder())); filter_ = std::make_unique(config_, std::move(client_), proto_config.grpc_service()); filter_->setEncoderFilterCallbacks(encoder_callbacks_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); diff --git a/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc index 8ca81765cb27f..e0cbc4da98600 100644 --- a/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc +++ b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc @@ -66,7 +66,8 @@ DEFINE_PROTO_FUZZER( try { config = std::make_shared( proto_config, std::chrono::milliseconds(200), 200, *stats_store.rootScope(), - "ext_proc_prefix"); + "ext_proc_prefix", + Envoy::Extensions::Filters::Common::Expr::BuilderInstance(Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)).builder()); } catch (const EnvoyException& e) { ENVOY_LOG_MISC(debug, "EnvoyException during ext_proc filter config validation: {}", e.what()); return; From d87ee62abf0d1e0a5a82f9bcf31b346518768415 Mon Sep 17 00:00:00 2001 From: Ashish Banerjee Date: Wed, 29 Nov 2023 13:03:09 +0000 Subject: [PATCH 31/34] Fix a segfault in `config_test` The fix was to simply copy the builder initialisation code from `createFilterFactoryFromProtoTyped` into `createFilterFactoryFromProtoWithServerContextTyped` --- source/extensions/filters/http/ext_proc/config.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/extensions/filters/http/ext_proc/config.cc b/source/extensions/filters/http/ext_proc/config.cc index bf57eb7dafdca..63ea302672b02 100644 --- a/source/extensions/filters/http/ext_proc/config.cc +++ b/source/extensions/filters/http/ext_proc/config.cc @@ -11,9 +11,9 @@ namespace ExternalProcessing { Http::FilterFactoryCb ExternalProcessingFilterConfig::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { - if (expr_builder_ == nullptr) { - expr_builder_ = Extensions::Filters::Common::Expr::createBuilder(nullptr); - } + if (expr_builder_ == nullptr) { + expr_builder_ = Extensions::Filters::Common::Expr::createBuilder(nullptr); + } const uint32_t message_timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, message_timeout, DefaultMessageTimeoutMs); const uint32_t max_message_timeout_ms = @@ -43,6 +43,9 @@ Http::FilterFactoryCb ExternalProcessingFilterConfig::createFilterFactoryFromProtoWithServerContextTyped( const envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor& proto_config, const std::string& stats_prefix, Server::Configuration::ServerFactoryContext& server_context) { + if (expr_builder_ == nullptr) { + expr_builder_ = Extensions::Filters::Common::Expr::createBuilder(nullptr); + } const uint32_t message_timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, message_timeout, DefaultMessageTimeoutMs); const uint32_t max_message_timeout_ms = From 497b7aab5775056729dcff6bd5059e929c07c346 Mon Sep 17 00:00:00 2001 From: ashish b <108897222+ashishb-solo@users.noreply.github.com> Date: Sat, 19 Aug 2023 13:04:42 -0400 Subject: [PATCH 32/34] Tap sink typed extension (#28808) Signed-off-by: Ashish Banerjee --- api/envoy/config/tap/v3/common.proto | 6 +- changelogs/current.yaml | 4 + source/extensions/common/tap/BUILD | 1 + source/extensions/common/tap/tap.h | 21 +++++ .../extensions/common/tap/tap_config_base.cc | 32 ++++++- .../extensions/common/tap/tap_config_base.h | 2 +- source/extensions/filters/http/tap/config.cc | 16 +++- .../filters/http/tap/tap_config_impl.cc | 5 +- .../filters/http/tap/tap_config_impl.h | 3 +- .../transport_sockets/tap/config.cc | 11 ++- .../transport_sockets/tap/tap_config_impl.h | 7 +- test/extensions/common/tap/BUILD | 3 + test/extensions/common/tap/admin_test.cc | 91 +++++++++++++++++++ 13 files changed, 185 insertions(+), 17 deletions(-) diff --git a/api/envoy/config/tap/v3/common.proto b/api/envoy/config/tap/v3/common.proto index 1884bd57d3d17..126993d0f7b42 100644 --- a/api/envoy/config/tap/v3/common.proto +++ b/api/envoy/config/tap/v3/common.proto @@ -4,6 +4,7 @@ package envoy.config.tap.v3; import "envoy/config/common/matcher/v3/matcher.proto"; import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/grpc_service.proto"; import "envoy/config/route/v3/route_components.proto"; @@ -183,7 +184,7 @@ message OutputConfig { } // Tap output sink configuration. -// [#next-free-field: 6] +// [#next-free-field: 7] message OutputSink { option (udpa.annotations.versioning).previous_message_type = "envoy.service.tap.v2alpha.OutputSink"; @@ -259,6 +260,9 @@ message OutputSink { // been configured to receive tap configuration from some other source (e.g., static // file, XDS, etc.) configuring the buffered admin output type will fail. BufferedAdminSink buffered_admin = 5; + + // Tap output filter will be defined by an extension type + core.v3.TypedExtensionConfig custom_sink = 6; } } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 7a71d5669308f..d0b8ca262f429 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -31,5 +31,9 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: tap + change: | + added :ref:`custom_sink ` type to enable writing tap data + out to a custom sink extension. deprecated: diff --git a/source/extensions/common/tap/BUILD b/source/extensions/common/tap/BUILD index 452fa65c61611..68e8617581a86 100644 --- a/source/extensions/common/tap/BUILD +++ b/source/extensions/common/tap/BUILD @@ -28,6 +28,7 @@ envoy_cc_library( ":tap_interface", "//source/common/common:assert_lib", "//source/common/common:hex_lib", + "//source/common/config:utility_lib", "//source/extensions/common/matcher:matcher_lib", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", "@envoy_api//envoy/data/tap/v3:pkg_cc_proto", diff --git a/source/extensions/common/tap/tap.h b/source/extensions/common/tap/tap.h index 107152f9d08bc..9f3a86966eab2 100644 --- a/source/extensions/common/tap/tap.h +++ b/source/extensions/common/tap/tap.h @@ -76,6 +76,27 @@ class Sink { }; using SinkPtr = std::unique_ptr; +using SinkContext = + absl::variant, + std::reference_wrapper>; + +/** + * Abstract tap sink factory. Produces a factory that can instantiate SinkPtr objects + */ +class TapSinkFactory : public Config::TypedFactory { +public: + ~TapSinkFactory() override = default; + std::string category() const override { return "envoy.tap.sinks"; } + + /** + * Create a Sink that can be used for writing out data produced by the tap filter. + * @param config supplies the protobuf configuration for the sink factory + * @param cluster_manager is a ClusterManager from the HTTP/transport socket context + */ + virtual SinkPtr createSinkPtr(const Protobuf::Message& config, SinkContext context) PURE; +}; + +using TapSinkFactoryPtr = std::unique_ptr; /** * Generic configuration for a tap extension (filter, transport socket, etc.). diff --git a/source/extensions/common/tap/tap_config_base.cc b/source/extensions/common/tap/tap_config_base.cc index 5d91f81bf9279..7f75f4636f777 100644 --- a/source/extensions/common/tap/tap_config_base.cc +++ b/source/extensions/common/tap/tap_config_base.cc @@ -3,9 +3,11 @@ #include "envoy/config/tap/v3/common.pb.h" #include "envoy/data/tap/v3/common.pb.h" #include "envoy/data/tap/v3/wrapper.pb.h" +#include "envoy/server/transport_socket_config.h" #include "source/common/common/assert.h" #include "source/common/common/fmt.h" +#include "source/common/config/utility.h" #include "source/common/protobuf/utility.h" #include "source/extensions/common/matcher/matcher.h" @@ -45,12 +47,13 @@ bool Utility::addBufferToProtoBytes(envoy::data::tap::v3::Body& output_body, } TapConfigBaseImpl::TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Common::Tap::Sink* admin_streamer) + Common::Tap::Sink* admin_streamer, SinkContext context) : max_buffered_rx_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( proto_config.output_config(), max_buffered_rx_bytes, DefaultMaxBufferedBytes)), max_buffered_tx_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( proto_config.output_config(), max_buffered_tx_bytes, DefaultMaxBufferedBytes)), streaming_(proto_config.output_config().streaming()) { + using ProtoOutputSink = envoy::config::tap::v3::OutputSink; auto& sinks = proto_config.output_config().sinks(); ASSERT(sinks.size() == 1); @@ -86,6 +89,33 @@ TapConfigBaseImpl::TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& pr sink_ = std::make_unique(sinks[0].file_per_tap()); sink_to_use_ = sink_.get(); break; + case ProtoOutputSink::OutputSinkTypeCase::kCustomSink: { + TapSinkFactory& tap_sink_factory = + Envoy::Config::Utility::getAndCheckFactory(sinks[0].custom_sink()); + + // extract message validation visitor from the context and use it to define config + ProtobufTypes::MessagePtr config; + using TsfContextRef = + std::reference_wrapper; + using HttpContextRef = std::reference_wrapper; + if (absl::holds_alternative(context)) { + Server::Configuration::TransportSocketFactoryContext& tsf_context = + absl::get(context).get(); + config = Config::Utility::translateAnyToFactoryConfig(sinks[0].custom_sink().typed_config(), + tsf_context.messageValidationVisitor(), + tap_sink_factory); + } else { + Server::Configuration::FactoryContext& http_context = + absl::get(context).get(); + config = Config::Utility::translateAnyToFactoryConfig( + sinks[0].custom_sink().typed_config(), + http_context.messageValidationContext().staticValidationVisitor(), tap_sink_factory); + } + + sink_ = tap_sink_factory.createSinkPtr(*config, context); + sink_to_use_ = sink_.get(); + break; + } case envoy::config::tap::v3::OutputSink::OutputSinkTypeCase::kStreamingGrpc: PANIC("not implemented"); case envoy::config::tap::v3::OutputSink::OutputSinkTypeCase::OUTPUT_SINK_TYPE_NOT_SET: diff --git a/source/extensions/common/tap/tap_config_base.h b/source/extensions/common/tap/tap_config_base.h index 77a997929b745..aca6eb1cf485e 100644 --- a/source/extensions/common/tap/tap_config_base.h +++ b/source/extensions/common/tap/tap_config_base.h @@ -103,7 +103,7 @@ class TapConfigBaseImpl : public virtual TapConfig { protected: TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Common::Tap::Sink* admin_streamer); + Common::Tap::Sink* admin_streamer, SinkContext context); private: // This is the default setting for both RX/TX max buffered bytes. (This means that per tap, the diff --git a/source/extensions/filters/http/tap/config.cc b/source/extensions/filters/http/tap/config.cc index 5c573924f768e..c9051d7c68b62 100644 --- a/source/extensions/filters/http/tap/config.cc +++ b/source/extensions/filters/http/tap/config.cc @@ -15,21 +15,27 @@ namespace TapFilter { class HttpTapConfigFactoryImpl : public Extensions::Common::Tap::TapConfigFactory { public: + HttpTapConfigFactoryImpl(Server::Configuration::FactoryContext& context) + : factory_context_(context) {} // TapConfigFactory Extensions::Common::Tap::TapConfigSharedPtr createConfigFromProto(const envoy::config::tap::v3::TapConfig& proto_config, Extensions::Common::Tap::Sink* admin_streamer) override { - return std::make_shared(std::move(proto_config), admin_streamer); + return std::make_shared(std::move(proto_config), admin_streamer, + factory_context_); } + +private: + Server::Configuration::FactoryContext& factory_context_; }; Http::FilterFactoryCb TapFilterFactory::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::tap::v3::Tap& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { - FilterConfigSharedPtr filter_config( - new FilterConfigImpl(proto_config, stats_prefix, std::make_unique(), - context.scope(), context.admin(), context.singletonManager(), - context.threadLocal(), context.mainThreadDispatcher())); + FilterConfigSharedPtr filter_config(new FilterConfigImpl( + proto_config, stats_prefix, std::make_unique(context), + context.scope(), context.admin(), context.singletonManager(), context.threadLocal(), + context.mainThreadDispatcher())); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(filter_config); callbacks.addStreamFilter(filter); diff --git a/source/extensions/filters/http/tap/tap_config_impl.cc b/source/extensions/filters/http/tap/tap_config_impl.cc index b3d844c7a48e1..2aa2653a4ffe8 100644 --- a/source/extensions/filters/http/tap/tap_config_impl.cc +++ b/source/extensions/filters/http/tap/tap_config_impl.cc @@ -29,8 +29,9 @@ fillHeaderList(Protobuf::RepeatedPtrField* } // namespace HttpTapConfigImpl::HttpTapConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Common::Tap::Sink* admin_streamer) - : TapCommon::TapConfigBaseImpl(std::move(proto_config), admin_streamer) {} + Common::Tap::Sink* admin_streamer, + Server::Configuration::FactoryContext& context) + : TapCommon::TapConfigBaseImpl(std::move(proto_config), admin_streamer, context) {} HttpPerRequestTapperPtr HttpTapConfigImpl::createPerRequestTapper(uint64_t stream_id) { return std::make_unique(shared_from_this(), stream_id); diff --git a/source/extensions/filters/http/tap/tap_config_impl.h b/source/extensions/filters/http/tap/tap_config_impl.h index d79ef4cc4842a..5938d38076fc7 100644 --- a/source/extensions/filters/http/tap/tap_config_impl.h +++ b/source/extensions/filters/http/tap/tap_config_impl.h @@ -19,7 +19,8 @@ class HttpTapConfigImpl : public Extensions::Common::Tap::TapConfigBaseImpl, public std::enable_shared_from_this { public: HttpTapConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Extensions::Common::Tap::Sink* admin_streamer); + Extensions::Common::Tap::Sink* admin_streamer, + Server::Configuration::FactoryContext& context); // TapFilter::HttpTapConfig HttpPerRequestTapperPtr createPerRequestTapper(uint64_t stream_id) override; diff --git a/source/extensions/transport_sockets/tap/config.cc b/source/extensions/transport_sockets/tap/config.cc index 3b565bee92e96..c8267771e854a 100644 --- a/source/extensions/transport_sockets/tap/config.cc +++ b/source/extensions/transport_sockets/tap/config.cc @@ -17,18 +17,21 @@ namespace Tap { class SocketTapConfigFactoryImpl : public Extensions::Common::Tap::TapConfigFactory { public: - SocketTapConfigFactoryImpl(TimeSource& time_source) : time_source_(time_source) {} + SocketTapConfigFactoryImpl(TimeSource& time_source, + Server::Configuration::TransportSocketFactoryContext& context) + : time_source_(time_source), factory_context_(context) {} // TapConfigFactory Extensions::Common::Tap::TapConfigSharedPtr createConfigFromProto(const envoy::config::tap::v3::TapConfig& proto_config, Extensions::Common::Tap::Sink* admin_streamer) override { return std::make_shared(std::move(proto_config), admin_streamer, - time_source_); + time_source_, factory_context_); } private: TimeSource& time_source_; + Server::Configuration::TransportSocketFactoryContext& factory_context_; }; Network::UpstreamTransportSocketFactoryPtr @@ -49,7 +52,7 @@ UpstreamTapSocketConfigFactory::createTransportSocketFactory( return std::make_unique( outer_config, std::make_unique( - server_context.mainThreadDispatcher().timeSource()), + server_context.mainThreadDispatcher().timeSource(), context), server_context.admin(), server_context.singletonManager(), server_context.threadLocal(), server_context.mainThreadDispatcher(), std::move(inner_transport_factory)); } @@ -72,7 +75,7 @@ DownstreamTapSocketConfigFactory::createTransportSocketFactory( return std::make_unique( outer_config, std::make_unique( - server_context.mainThreadDispatcher().timeSource()), + server_context.mainThreadDispatcher().timeSource(), context), server_context.admin(), server_context.singletonManager(), server_context.threadLocal(), server_context.mainThreadDispatcher(), std::move(inner_transport_factory)); } diff --git a/source/extensions/transport_sockets/tap/tap_config_impl.h b/source/extensions/transport_sockets/tap/tap_config_impl.h index cf715c3df9733..38556025c1408 100644 --- a/source/extensions/transport_sockets/tap/tap_config_impl.h +++ b/source/extensions/transport_sockets/tap/tap_config_impl.h @@ -3,6 +3,7 @@ #include "envoy/config/tap/v3/common.pb.h" #include "envoy/data/tap/v3/transport.pb.h" #include "envoy/event/timer.h" +#include "envoy/server/transport_socket_config.h" #include "source/extensions/common/tap/tap_config_base.h" #include "source/extensions/transport_sockets/tap/tap_config.h" @@ -51,8 +52,10 @@ class SocketTapConfigImpl : public Extensions::Common::Tap::TapConfigBaseImpl, public std::enable_shared_from_this { public: SocketTapConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, - Extensions::Common::Tap::Sink* admin_streamer, TimeSource& time_system) - : Extensions::Common::Tap::TapConfigBaseImpl(std::move(proto_config), admin_streamer), + Extensions::Common::Tap::Sink* admin_streamer, TimeSource& time_system, + Server::Configuration::TransportSocketFactoryContext& context) + : Extensions::Common::Tap::TapConfigBaseImpl(std::move(proto_config), admin_streamer, + context), time_source_(time_system) {} // SocketTapConfig diff --git a/test/extensions/common/tap/BUILD b/test/extensions/common/tap/BUILD index f65ad733eaf55..dea02d4f722a2 100644 --- a/test/extensions/common/tap/BUILD +++ b/test/extensions/common/tap/BUILD @@ -26,9 +26,12 @@ envoy_cc_test( srcs = envoy_select_admin_functionality(["admin_test.cc"]), deps = [ "//source/extensions/common/tap:admin", + "//source/extensions/common/tap:tap_config_base", "//test/mocks/server:admin_mocks", "//test/mocks/server:admin_stream_mocks", + "//test/mocks/server:server_mocks", "//test/test_common:logging_lib", + "//test/test_common:registry_lib", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/common/tap/admin_test.cc b/test/extensions/common/tap/admin_test.cc index 2a98078939717..f25c56a4340b2 100644 --- a/test/extensions/common/tap/admin_test.cc +++ b/test/extensions/common/tap/admin_test.cc @@ -5,10 +5,13 @@ #include "source/extensions/common/tap/admin.h" #include "source/extensions/common/tap/tap.h" +#include "source/extensions/common/tap/tap_config_base.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/admin_stream.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/logging.h" +#include "test/test_common/registry.h" #include "gtest/gtest.h" @@ -20,6 +23,7 @@ using ::testing::_; using ::testing::AtLeast; using ::testing::Between; using ::testing::DoAll; +using ::testing::Invoke; using ::testing::Return; using ::testing::ReturnRef; using ::testing::SaveArg; @@ -146,6 +150,93 @@ config_id: test_config_id StrictMock sink_; }; +using Extensions::Common::Tap::TapSinkFactory; +class MockTapSinkFactory : public TapSinkFactory { +public: + MockTapSinkFactory() {} + ~MockTapSinkFactory() override{}; + + MOCK_METHOD(SinkPtr, createSinkPtr, (const Protobuf::Message& config, SinkContext), (override)); + + MOCK_METHOD(std::string, name, (), (const, override)); + MOCK_METHOD(ProtobufTypes::MessagePtr, createEmptyConfigProto, (), (override)); +}; + +class TestConfigImpl : public TapConfigBaseImpl { +public: + TestConfigImpl(const envoy::config::tap::v3::TapConfig& proto_config, + Extensions::Common::Tap::Sink* admin_streamer, SinkContext context) + : TapConfigBaseImpl(std::move(proto_config), admin_streamer, context) {} +}; + +TEST(TypedExtensionConfigTest, AddTestConfigHttpContext) { + + const std::string tap_config_yaml = + R"EOF( + match: + any_match: true + output_config: + sinks: + - format: PROTO_BINARY + custom_sink: + name: custom_sink + typed_config: + "@type": type.googleapis.cm/google.protobuf.StringValue +)EOF"; + envoy::config::tap::v3::TapConfig tap_config; + TestUtility::loadFromYaml(tap_config_yaml, tap_config); + + MockTapSinkFactory factory_impl; + EXPECT_CALL(factory_impl, name).Times(AtLeast(1)); + EXPECT_CALL(factory_impl, createEmptyConfigProto) + .WillRepeatedly(Invoke([]() -> ProtobufTypes::MessagePtr { + return std::make_unique(); + })); + EXPECT_CALL( + factory_impl, + createSinkPtr( + _, + testing::VariantWith>(_))); + Registry::InjectFactory factory(factory_impl); + + NiceMock factory_context; + TestConfigImpl(tap_config, NULL, factory_context); +} + +TEST(TypedExtensionConfigTest, AddTestConfigTransportSocketContext) { + + const std::string tap_config_yaml = + R"EOF( + match: + any_match: true + output_config: + sinks: + - format: PROTO_BINARY + custom_sink: + name: custom_sink + typed_config: + "@type": type.googleapis.cm/google.protobuf.StringValue +)EOF"; + envoy::config::tap::v3::TapConfig tap_config; + TestUtility::loadFromYaml(tap_config_yaml, tap_config); + + MockTapSinkFactory factory_impl; + EXPECT_CALL(factory_impl, name).Times(AtLeast(1)); + EXPECT_CALL(factory_impl, createEmptyConfigProto) + .WillRepeatedly(Invoke([]() -> ProtobufTypes::MessagePtr { + return std::make_unique(); + })); + EXPECT_CALL( + factory_impl, + createSinkPtr( + _, testing::VariantWith< + std::reference_wrapper>(_))); + Registry::InjectFactory factory(factory_impl); + + NiceMock factory_context; + TestConfigImpl(tap_config, NULL, factory_context); +} + // Make sure warn if using a pipe address for the admin handler. TEST_F(AdminHandlerTest, AdminWithPipeSocket) { EXPECT_LOG_CONTAINS( From 77c8233e538cfd4706e0c279d4aa0291d0a73347 Mon Sep 17 00:00:00 2001 From: ashish b <108897222+ashishb-solo@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:26:44 -0500 Subject: [PATCH 33/34] Update source/extensions/filters/http/ext_proc/ext_proc.cc --- source/extensions/filters/http/ext_proc/ext_proc.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 0023d4c0a6a11..669103d5b9ce8 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -1,6 +1,5 @@ #include "source/extensions/filters/http/ext_proc/ext_proc.h" -#include #include "envoy/config/common/mutation_rules/v3/mutation_rules.pb.h" #include "source/common/http/utility.h" From cf7bec0ef33b364ec045503cf3a0e43b999dd237 Mon Sep 17 00:00:00 2001 From: ashish b <108897222+ashishb-solo@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:27:07 -0500 Subject: [PATCH 34/34] Update source/extensions/filters/http/ext_proc/ext_proc.cc --- source/extensions/filters/http/ext_proc/ext_proc.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 669103d5b9ce8..eaad8f9f35545 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -7,7 +7,6 @@ #include "source/extensions/filters/http/ext_proc/mutation_utils.h" #include "absl/strings/str_format.h" -#include #if defined(USE_CEL_PARSER) #include "parser/parser.h"