From 07f0133c16efb697086e328997e0be11b203b4ba Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 27 Oct 2023 02:07:02 +0100 Subject: [PATCH 01/72] ci/release: Add version history sync workflow (#30507) Signed-off-by: Ryan Northey --- .github/workflows/envoy-release.yml | 79 ++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 976a45b4c2b5..c355d17f98b4 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -19,14 +19,7 @@ on: type: choice options: - create-release - summary: - type: boolean - default: true - description: Use changelog summary (required to publish release) - author: - description: >- - Author: User/email, eg 'Myname ' - (used by create-release, default: `changelogs/summary.md` last committer) + - sync-version-histories pr: type: boolean default: true @@ -37,6 +30,19 @@ on: type: boolean default: false description: WIP + author: + description: >- + Author: User/email, eg 'Myname ' + (used by create-release, default: `changelogs/summary.md` last committer) + summary: + type: boolean + default: true + description: Use changelog summary (required to publish release) + +env: + COMMITTER_NAME: publish-envoy[bot] + COMMITTER_EMAIL: 140627008+publish-envoy[bot]@users.noreply.github.com + jobs: ## Triggerable actions @@ -77,12 +83,9 @@ jobs: with: email: ${{ inputs.author }} - run: | - git config --global user.name $COMMITTER_NAME - git config --global user.email $COMMITTER_EMAIL + git config --global user.name ${{ env.COMMITTER_NAME }} + git config --global user.email ${{ env.COMMITTER_EMAIL }} name: Configure committer - env: - COMMITTER_NAME: publish-envoy[bot] - COMMITTER_EMAIL: 140627008+publish-envoy[bot]@users.noreply.github.com - run: | BAZEL_ARGS=(-- -l debug -v debug) BAZEL_RUN_ARGS=(--config=ci) @@ -120,6 +123,49 @@ jobs: repo: Release ${{ steps.release.outputs.version }} GITHUB_TOKEN: ${{ steps.checkout.outputs.token }} + sync_version_histories: + runs-on: ubuntu-22.04 + if: github.event_name == 'workflow_dispatch' && inputs.task == 'sync-version-histories' + name: Sync version histories + steps: + - id: checkout + name: Checkout Envoy repository + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.0.35 + with: + app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} + - run: | + BRANCHNAME="${GITHUB_REF_NAME#release/}" + echo "name=${BRANCHNAME}" >> $GITHUB_OUTPUT + echo "full_name=release/sync/${BRANCHNAME}" >> $GITHUB_OUTPUT + name: Get branch name + id: branch + env: + GITHUB_REF_NAME: ${{ github.ref_name }} + - run: | + git config --global user.name ${{ env.COMMITTER_NAME }} + git config --global user.email ${{ env.COMMITTER_NAME }} + name: Configure committer + - run: | + bazel run --config=ci @envoy_repo//:sync + name: Sync version histories + - name: Create a PR + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.0.35 + with: + base: ${{ github.ref_name }} + commit: false + body: | + Created by Envoy publish bot for @${{ github.actor }} + branch: ${{ steps.branch.outputs.full_name }} + diff-upload: version-histories-${{ steps.branch.outputs.name }} + diff-show: true + dry-run: ${{ ! inputs.pr }} + GITHUB_TOKEN: ${{ steps.checkout.outputs.token }} + title: >- + ${{ steps.branch.outputs.name != 'main' && '[${{ steps.branch.outputs.name }}]' || '' }} + repo: Sync version histories + + ## Triggered actions # On release to `main`: @@ -147,8 +193,8 @@ jobs: version="$(cut -d- -f1 < VERSION.txt | cut -d. -f-2)" release_branch="release/v${version}" commit_sha="$(git rev-parse HEAD)" - git config --global user.name "$COMMITTER_NAME" - git config --global user.email "$COMMITTER_EMAIL" + git config --global user.name "${{ env.COMMITTER_NAME }}" + git config --global user.email "${{ env.COMMITTER_EMAIL }}" echo "Creating ${release_branch} from ${commit_sha}" git checkout -b "$release_branch" bazel run @envoy_repo//:dev -- --patch @@ -156,6 +202,3 @@ jobs: git commit . -m "repo: Remove mobile ci for release branch" git log git push origin "$release_branch" - env: - COMMITTER_NAME: publish-envoy[bot] - COMMITTER_EMAIL: 140627008+publish-envoy[bot]@users.noreply.github.com From 712143549379c8a5d5dbe0047bad3a7966e4ddee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 08:44:55 +0100 Subject: [PATCH 02/72] build(deps): bump orjson from 3.9.9 to 3.9.10 in /tools/base (#30546) Bumps [orjson](https://github.com/ijl/orjson) from 3.9.9 to 3.9.10. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.9...3.9.10) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 102 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 2a991b19d840..c19e2d44f0c9 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -884,57 +884,57 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.9 \ - --hash=sha256:02e693843c2959befdd82d1ebae8b05ed12d1cb821605d5f9fe9f98ca5c9fd2b \ - --hash=sha256:06f0c024a75e8ba5d9101facb4fb5a028cdabe3cdfe081534f2a9de0d5062af2 \ - --hash=sha256:0a1a4d9e64597e550428ba091e51a4bcddc7a335c8f9297effbfa67078972b5c \ - --hash=sha256:0d2cd6ef4726ef1b8c63e30d8287225a383dbd1de3424d287b37c1906d8d2855 \ - --hash=sha256:0f89dc338a12f4357f5bf1b098d3dea6072fb0b643fd35fec556f4941b31ae27 \ - --hash=sha256:12b83e0d8ba4ca88b894c3e00efc59fe6d53d9ffb5dbbb79d437a466fc1a513d \ - --hash=sha256:1ef06431f021453a47a9abb7f7853f04f031d31fbdfe1cc83e3c6aadde502cce \ - --hash=sha256:1f352117eccac268a59fedac884b0518347f5e2b55b9f650c2463dd1e732eb61 \ - --hash=sha256:24301f2d99d670ded4fb5e2f87643bc7428a54ba49176e38deb2887e42fe82fb \ - --hash=sha256:31d676bc236f6e919d100fb85d0a99812cff1ebffaa58106eaaec9399693e227 \ - --hash=sha256:335406231f9247f985df045f0c0c8f6b6d5d6b3ff17b41a57c1e8ef1a31b4d04 \ - --hash=sha256:397a185e5dd7f8ebe88a063fe13e34d61d394ebb8c70a443cee7661b9c89bda7 \ - --hash=sha256:4a308aeac326c2bafbca9abbae1e1fcf682b06e78a54dad0347b760525838d85 \ - --hash=sha256:50232572dd300c49f134838c8e7e0917f29a91f97dbd608d23f2895248464b7f \ - --hash=sha256:512e5a41af008e76451f5a344941d61f48dddcf7d7ddd3073deb555de64596a6 \ - --hash=sha256:5424ecbafe57b2de30d3b5736c5d5835064d522185516a372eea069b92786ba6 \ - --hash=sha256:543b36df56db195739c70d645ecd43e49b44d5ead5f8f645d2782af118249b37 \ - --hash=sha256:678ffb5c0a6b1518b149cc328c610615d70d9297e351e12c01d0beed5d65360f \ - --hash=sha256:6fcf06c69ccc78e32d9f28aa382ab2ab08bf54b696dbe00ee566808fdf05da7d \ - --hash=sha256:75b805549cbbcb963e9c9068f1a05abd0ea4c34edc81f8d8ef2edb7e139e5b0f \ - --hash=sha256:8038ba245d0c0a6337cfb6747ea0c51fe18b0cf1a4bc943d530fd66799fae33d \ - --hash=sha256:879d2d1f6085c9c0831cec6716c63aaa89e41d8e036cabb19a315498c173fcc6 \ - --hash=sha256:8cba20c9815c2a003b8ca4429b0ad4aa87cb6649af41365821249f0fd397148e \ - --hash=sha256:8e7877256b5092f1e4e48fc0f1004728dc6901e7a4ffaa4acb0a9578610aa4ce \ - --hash=sha256:906cac73b7818c20cf0f6a7dde5a6f009c52aecc318416c7af5ea37f15ca7e66 \ - --hash=sha256:920814e02e3dd7af12f0262bbc18b9fe353f75a0d0c237f6a67d270da1a1bb44 \ - --hash=sha256:957a45fb201c61b78bcf655a16afbe8a36c2c27f18a998bd6b5d8a35e358d4ad \ - --hash=sha256:9a4402e7df1b5c9a4c71c7892e1c8f43f642371d13c73242bda5964be6231f95 \ - --hash=sha256:9d9b5440a5d215d9e1cfd4aee35fd4101a8b8ceb8329f549c16e3894ed9f18b5 \ - --hash=sha256:a3bf6ca6bce22eb89dd0650ef49c77341440def966abcb7a2d01de8453df083a \ - --hash=sha256:a71b0cc21f2c324747bc77c35161e0438e3b5e72db6d3b515310457aba743f7f \ - --hash=sha256:ab7bae2b8bf17620ed381e4101aeeb64b3ba2a45fc74c7617c633a923cb0f169 \ - --hash=sha256:ae72621f216d1d990468291b1ec153e1b46e0ed188a86d54e0941f3dabd09ee8 \ - --hash=sha256:b20becf50d4aec7114dc902b58d85c6431b3a59b04caa977e6ce67b6fee0e159 \ - --hash=sha256:b28c1a65cd13fff5958ab8b350f0921121691464a7a1752936b06ed25c0c7b6e \ - --hash=sha256:b97a67c47840467ccf116136450c50b6ed4e16a8919c81a4b4faef71e0a2b3f4 \ - --hash=sha256:bd55ea5cce3addc03f8fb0705be0cfed63b048acc4f20914ce5e1375b15a293b \ - --hash=sha256:c4eb31a8e8a5e1d9af5aa9e247c2a52ad5cf7e968aaa9aaefdff98cfcc7f2e37 \ - --hash=sha256:c63eca397127ebf46b59c9c1fb77b30dd7a8fc808ac385e7a58a7e64bae6e106 \ - --hash=sha256:c959550e0705dc9f59de8fca1a316da0d9b115991806b217c82931ac81d75f74 \ - --hash=sha256:cffb77cf0cd3cbf20eb603f932e0dde51b45134bdd2d439c9f57924581bb395b \ - --hash=sha256:d1c01cf4b8e00c7e98a0a7cf606a30a26c32adf2560be2d7d5d6766d6f474b31 \ - --hash=sha256:d3f56e41bc79d30fdf077073072f2377d2ebf0b946b01f2009ab58b08907bc28 \ - --hash=sha256:e159b97f5676dcdac0d0f75ec856ef5851707f61d262851eb41a30e8fadad7c9 \ - --hash=sha256:e98ca450cb4fb176dd572ce28c6623de6923752c70556be4ef79764505320acb \ - --hash=sha256:eb50d869b3c97c7c5187eda3759e8eb15deb1271d694bc5d6ba7040db9e29036 \ - --hash=sha256:ece2d8ed4c34903e7f1b64fb1e448a00e919a4cdb104fc713ad34b055b665fca \ - --hash=sha256:f28090060a31f4d11221f9ba48b2273b0d04b702f4dcaa197c38c64ce639cc51 \ - --hash=sha256:f692e7aabad92fa0fff5b13a846fb586b02109475652207ec96733a085019d80 \ - --hash=sha256:f708ca623287186e5876256cb30599308bce9b2757f90d917b7186de54ce6547 +orjson==3.9.10 \ + --hash=sha256:06ad5543217e0e46fd7ab7ea45d506c76f878b87b1b4e369006bdb01acc05a83 \ + --hash=sha256:0a73160e823151f33cdc05fe2cea557c5ef12fdf276ce29bb4f1c571c8368a60 \ + --hash=sha256:1234dc92d011d3554d929b6cf058ac4a24d188d97be5e04355f1b9223e98bbe9 \ + --hash=sha256:1d0dc4310da8b5f6415949bd5ef937e60aeb0eb6b16f95041b5e43e6200821fb \ + --hash=sha256:2a11b4b1a8415f105d989876a19b173f6cdc89ca13855ccc67c18efbd7cbd1f8 \ + --hash=sha256:2e2ecd1d349e62e3960695214f40939bbfdcaeaaa62ccc638f8e651cf0970e5f \ + --hash=sha256:3a2ce5ea4f71681623f04e2b7dadede3c7435dfb5e5e2d1d0ec25b35530e277b \ + --hash=sha256:3e892621434392199efb54e69edfff9f699f6cc36dd9553c5bf796058b14b20d \ + --hash=sha256:3fb205ab52a2e30354640780ce4587157a9563a68c9beaf52153e1cea9aa0921 \ + --hash=sha256:4689270c35d4bb3102e103ac43c3f0b76b169760aff8bcf2d401a3e0e58cdb7f \ + --hash=sha256:49f8ad582da6e8d2cf663c4ba5bf9f83cc052570a3a767487fec6af839b0e777 \ + --hash=sha256:4bd176f528a8151a6efc5359b853ba3cc0e82d4cd1fab9c1300c5d957dc8f48c \ + --hash=sha256:4cf7837c3b11a2dfb589f8530b3cff2bd0307ace4c301e8997e95c7468c1378e \ + --hash=sha256:4fd72fab7bddce46c6826994ce1e7de145ae1e9e106ebb8eb9ce1393ca01444d \ + --hash=sha256:5148bab4d71f58948c7c39d12b14a9005b6ab35a0bdf317a8ade9a9e4d9d0bd5 \ + --hash=sha256:5869e8e130e99687d9e4be835116c4ebd83ca92e52e55810962446d841aba8de \ + --hash=sha256:602a8001bdf60e1a7d544be29c82560a7b49319a0b31d62586548835bbe2c862 \ + --hash=sha256:61804231099214e2f84998316f3238c4c2c4aaec302df12b21a64d72e2a135c7 \ + --hash=sha256:666c6fdcaac1f13eb982b649e1c311c08d7097cbda24f32612dae43648d8db8d \ + --hash=sha256:674eb520f02422546c40401f4efaf8207b5e29e420c17051cddf6c02783ff5ca \ + --hash=sha256:7ec960b1b942ee3c69323b8721df2a3ce28ff40e7ca47873ae35bfafeb4555ca \ + --hash=sha256:7f433be3b3f4c66016d5a20e5b4444ef833a1f802ced13a2d852c637f69729c1 \ + --hash=sha256:7f8fb7f5ecf4f6355683ac6881fd64b5bb2b8a60e3ccde6ff799e48791d8f864 \ + --hash=sha256:81a3a3a72c9811b56adf8bcc829b010163bb2fc308877e50e9910c9357e78521 \ + --hash=sha256:858379cbb08d84fe7583231077d9a36a1a20eb72f8c9076a45df8b083724ad1d \ + --hash=sha256:8b9ba0ccd5a7f4219e67fbbe25e6b4a46ceef783c42af7dbc1da548eb28b6531 \ + --hash=sha256:92af0d00091e744587221e79f68d617b432425a7e59328ca4c496f774a356071 \ + --hash=sha256:9ebbdbd6a046c304b1845e96fbcc5559cd296b4dfd3ad2509e33c4d9ce07d6a1 \ + --hash=sha256:9edd2856611e5050004f4722922b7b1cd6268da34102667bd49d2a2b18bafb81 \ + --hash=sha256:a353bf1f565ed27ba71a419b2cd3db9d6151da426b61b289b6ba1422a702e643 \ + --hash=sha256:b5b7d4a44cc0e6ff98da5d56cde794385bdd212a86563ac321ca64d7f80c80d1 \ + --hash=sha256:b90f340cb6397ec7a854157fac03f0c82b744abdd1c0941a024c3c29d1340aff \ + --hash=sha256:c18a4da2f50050a03d1da5317388ef84a16013302a5281d6f64e4a3f406aabc4 \ + --hash=sha256:c338ed69ad0b8f8f8920c13f529889fe0771abbb46550013e3c3d01e5174deef \ + --hash=sha256:c5a02360e73e7208a872bf65a7554c9f15df5fe063dc047f79738998b0506a14 \ + --hash=sha256:c62b6fa2961a1dcc51ebe88771be5319a93fd89bd247c9ddf732bc250507bc2b \ + --hash=sha256:c812312847867b6335cfb264772f2a7e85b3b502d3a6b0586aa35e1858528ab1 \ + --hash=sha256:c943b35ecdf7123b2d81d225397efddf0bce2e81db2f3ae633ead38e85cd5ade \ + --hash=sha256:ce0a29c28dfb8eccd0f16219360530bc3cfdf6bf70ca384dacd36e6c650ef8e8 \ + --hash=sha256:cf80b550092cc480a0cbd0750e8189247ff45457e5a023305f7ef1bcec811616 \ + --hash=sha256:cff7570d492bcf4b64cc862a6e2fb77edd5e5748ad715f487628f102815165e9 \ + --hash=sha256:d2c1e559d96a7f94a4f581e2a32d6d610df5840881a8cba8f25e446f4d792df3 \ + --hash=sha256:deeb3922a7a804755bbe6b5be9b312e746137a03600f488290318936c1a2d4dc \ + --hash=sha256:e28a50b5be854e18d54f75ef1bb13e1abf4bc650ab9d635e4258c58e71eb6ad5 \ + --hash=sha256:e99c625b8c95d7741fe057585176b1b8783d46ed4b8932cf98ee145c4facf499 \ + --hash=sha256:ec6f18f96b47299c11203edfbdc34e1b69085070d9a3d1f302810cc23ad36bf3 \ + --hash=sha256:ed8bc367f725dfc5cabeed1ae079d00369900231fbb5a5280cf0736c30e2adf7 \ + --hash=sha256:ee5926746232f627a3be1cc175b2cfad24d0170d520361f4ce3fa2fd83f09e1d \ + --hash=sha256:f295efcd47b6124b01255d1491f9e46f17ef40d3d7eabf7364099e463fb45f0f \ + --hash=sha256:fb0b361d73f6b8eeceba47cd37070b5e6c9de5beaeaa63a1cb35c7e1a73ef088 # via # -r requirements.in # envoy-base-utils From 3ee131a730649b18f8f49e809ef15b8dc7d75f11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 07:48:35 +0000 Subject: [PATCH 03/72] build(deps): bump openpolicyagent/opa from 0.57.1-istio to 0.58.0-istio in /examples/ext_authz (#30549) build(deps): bump openpolicyagent/opa in /examples/ext_authz Bumps openpolicyagent/opa from 0.57.1-istio to 0.58.0-istio. --- updated-dependencies: - dependency-name: openpolicyagent/opa dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/ext_authz/Dockerfile-opa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ext_authz/Dockerfile-opa b/examples/ext_authz/Dockerfile-opa index 7c5e544b987c..ff19250effc1 100644 --- a/examples/ext_authz/Dockerfile-opa +++ b/examples/ext_authz/Dockerfile-opa @@ -1 +1 @@ -FROM openpolicyagent/opa:0.57.1-istio@sha256:f76fb8c743d36265a58eae0dcc95a5587699c25a85afb0797dd6be88e77e3653 +FROM openpolicyagent/opa:0.58.0-istio@sha256:f53e69eeee948b1d725877751720864221f6353e515211d54455f08b5abad671 From 1771b2797433d1c35826b4b243652ffd01a87d2a Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 27 Oct 2023 11:40:00 +0200 Subject: [PATCH 04/72] docs: fix the license URL of brotli (#30556) Signed-off-by: Michael Kaufmann --- bazel/repository_locations.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index cf6b50a3f8b0..6a1acfb4816e 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -679,7 +679,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( release_date = "2023-08-31", cpe = "cpe:2.3:a:google:brotli:*", license = "MIT", - license_url = "https://github.com/google/brotli/blob/{version}/LICENSE", + license_url = "https://github.com/google/brotli/blob/v{version}/LICENSE", ), com_github_facebook_zstd = dict( project_name = "zstd", From 861f87d6df1775c7a5cfb242ca04d595c8ebe5e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:38:39 +0100 Subject: [PATCH 05/72] build(deps): bump envoy-base-utils from 0.4.27 to 0.5.0 in /tools/base (#30557) Bumps [envoy-base-utils](https://github.com/envoyproxy/toolshed) from 0.4.27 to 0.5.0. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/commits) --- updated-dependencies: - dependency-name: envoy-base-utils dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index c19e2d44f0c9..33648a485a00 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -473,9 +473,9 @@ docutils==0.19 \ # envoy-docs-sphinx-runner # sphinx # sphinx-rtd-theme -envoy-base-utils==0.4.27 \ - --hash=sha256:908d7fdcf9ca2dae01cccdd6d2f5b9002e5ef8610f4437aba86af5a5f3a248a8 \ - --hash=sha256:e5a89a3c96cb424995705c884f2d0fac111f0519d2590da16fc8b5bfeba11469 +envoy-base-utils==0.5.0 \ + --hash=sha256:a185279fc2f6c49ba2b2a1d02ab2a361733ee4a5c3f41a225cbd2dd349369458 \ + --hash=sha256:f71e4bdcea86539f273388f1cb6210b40ec5d05f49aaacb7ef776a60fb60f107 # via # -r requirements.in # envoy-code-check From e7159f4e4ac902e63e2abc81f0dc5c5a3766b93d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:44:51 +0100 Subject: [PATCH 06/72] build(deps): bump envoyproxy/toolshed from actions-v0.0.35 to 0.1.0 (#30558) * build(deps): bump envoyproxy/toolshed from actions-v0.0.35 to 0.1.0 Bumps [envoyproxy/toolshed](https://github.com/envoyproxy/toolshed) from actions-v0.0.35 to 0.1.0. This release includes the previously tagged commit. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/actions-v0.0.35...actions-v0.1.0) --- updated-dependencies: - dependency-name: envoyproxy/toolshed dependency-type: direct:production ... Signed-off-by: dependabot[bot] * update-ci-action Signed-off-by: Ryan Northey --------- Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Northey --- .github/actions/do_ci/action.yml | 4 ++-- .github/workflows/_cache_docker.yml | 2 +- .github/workflows/_ci.yml | 10 +++++----- .github/workflows/_stage_publish.yml | 2 +- .github/workflows/_workflow-start.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/commands.yml | 2 +- .github/workflows/envoy-dependency.yml | 14 +++++++------- .github/workflows/envoy-release.yml | 12 ++++++------ .github/workflows/envoy-sync.yml | 2 +- .github/workflows/mobile-android_tests.yml | 4 ++-- .github/workflows/workflow-complete.yml | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/actions/do_ci/action.yml b/.github/actions/do_ci/action.yml index 374a7371aea5..55275ed22c04 100644 --- a/.github/actions/do_ci/action.yml +++ b/.github/actions/do_ci/action.yml @@ -51,14 +51,14 @@ inputs: runs: using: composite steps: - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.0.35 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.0 name: 'Run CI target ${{ inputs.target }}' with: catch-errors: ${{ inputs.catch-errors }} container-command: ${{ inputs.command_prefix }} command-prefix: ${{ inputs.command_ci }} command: ${{ inputs.target }} - env: ${{ inputs.env }} + source: ${{ inputs.env }} error-match: ${{ inputs.error-match }} notice-match: ${{ inputs.notice-match }} warning-match: ${{ inputs.warning-match }} diff --git a/.github/workflows/_cache_docker.yml b/.github/workflows/_cache_docker.yml index 673f67ef72bb..535656ff16eb 100644 --- a/.github/workflows/_cache_docker.yml +++ b/.github/workflows/_cache_docker.yml @@ -37,7 +37,7 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.0.35 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.0 name: Prime Docker cache (${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}) with: image_tag: "${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}" diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index bbf16933978d..2fdbb6de8630 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -110,7 +110,7 @@ jobs: steps: - if: ${{ inputs.cache_build_image }} name: Restore Docker cache (${{ inputs.cache_build_image }}) - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.0 with: image_tag: ${{ inputs.cache_build_image }} @@ -123,7 +123,7 @@ jobs: - if: ${{ steps.context.outputs.use_appauth == 'true' }} name: Fetch token for app auth id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.0 with: app_id: ${{ secrets.app_id }} key: ${{ secrets.app_key }} @@ -153,7 +153,7 @@ jobs: run: git config --global --add safe.directory /__w/envoy/envoy - if: ${{ inputs.diskspace_hack }} - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 - run: | echo "disk space at beginning of build:" df -h @@ -161,7 +161,7 @@ jobs: - if: ${{ inputs.run_pre }} name: Run pre action ${{ inputs.run_pre && format('({0})', inputs.run_pre) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.0 with: uses: ${{ inputs.run_pre }} with: ${{ inputs.run_pre_with }} @@ -186,7 +186,7 @@ jobs: - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.0 with: uses: ${{ inputs.run_post }} with: ${{ inputs.run_post_with }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 81aba66daed3..e85a328c3dee 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -116,7 +116,7 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.0.35 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.0 with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index 05025292544a..878253534f9d 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -29,7 +29,7 @@ jobs: - if: ${{ steps.env.outputs.trusted != 'true' }} name: Start status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.0 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: ${{ inputs.workflow_name }} diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 112a43acad17..042a119fb88b 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Pre-cleanup - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 81f643c2443b..f1fca647979b 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: actions: write checks: read steps: - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.0.35 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.0 with: token: ${{ secrets.GITHUB_TOKEN }} azp_org: cncf diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index ab25e02a3d97..ff3907d2f466 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -43,13 +43,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.0 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: version name: Shorten (possible) SHA - uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.0 with: string: ${{ inputs.version }} length: 7 @@ -64,13 +64,13 @@ jobs: TARGET: ${{ inputs.task == 'bazel' && 'update' || 'api-update' }} TASK: ${{ inputs.task == 'bazel' && 'bazel' || 'api/bazel' }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.0.35 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.0 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.version.outputs.string }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 with: base: main body: | @@ -97,7 +97,7 @@ jobs: steps: - name: Fetch token for app auth id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.0 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} @@ -137,7 +137,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.0 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -166,7 +166,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index c355d17f98b4..0af7b5d57542 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,7 +55,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.0 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -79,7 +79,7 @@ jobs: GITHUB_REF_NAME: ${{ github.ref_name }} - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.0 with: email: ${{ inputs.author }} - run: | @@ -105,7 +105,7 @@ jobs: env: AUTHOR: ${{ inputs.author }} - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 with: base: ${{ github.ref_name }} commit: false @@ -130,7 +130,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.0 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -150,7 +150,7 @@ jobs: bazel run --config=ci @envoy_repo//:sync name: Sync version histories - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 with: base: ${{ github.ref_name }} commit: false @@ -180,7 +180,7 @@ jobs: steps: - name: Fetch token for app auth id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.0 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index c01e875d209f..e856d41e1717 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -28,7 +28,7 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.0.35 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.0 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index be15734f8477..0f3378ba02b6 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -34,7 +34,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy @@ -68,7 +68,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml index 8cf3824deae8..2ddc6acee449 100644 --- a/.github/workflows/workflow-complete.yml +++ b/.github/workflows/workflow-complete.yml @@ -53,7 +53,7 @@ jobs: echo "state=${STATE}" >> "$GITHUB_OUTPUT" id: job - name: Complete status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.0.35 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.0 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: Verify/examples From 0b2d1ce7f8e71c91f7c30f5ba4011f5d80226147 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:44:16 +0100 Subject: [PATCH 07/72] build(deps): bump envoyproxy/toolshed from actions-v0.1.0 to 0.1.1 (#30560) Bumps [envoyproxy/toolshed](https://github.com/envoyproxy/toolshed) from actions-v0.1.0 to 0.1.1. This release includes the previously tagged commit. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/actions-v0.1.0...actions-v0.1.1) --- updated-dependencies: - dependency-name: envoyproxy/toolshed dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_cache_docker.yml | 2 +- .github/workflows/_ci.yml | 10 +++++----- .github/workflows/_stage_publish.yml | 2 +- .github/workflows/_workflow-start.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/commands.yml | 2 +- .github/workflows/envoy-dependency.yml | 14 +++++++------- .github/workflows/envoy-release.yml | 12 ++++++------ .github/workflows/envoy-sync.yml | 2 +- .github/workflows/mobile-android_tests.yml | 4 ++-- .github/workflows/workflow-complete.yml | 2 +- 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/_cache_docker.yml b/.github/workflows/_cache_docker.yml index 535656ff16eb..355e56b39a20 100644 --- a/.github/workflows/_cache_docker.yml +++ b/.github/workflows/_cache_docker.yml @@ -37,7 +37,7 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.0 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.1 name: Prime Docker cache (${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}) with: image_tag: "${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}" diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 2fdbb6de8630..91072b9cd5d3 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -110,7 +110,7 @@ jobs: steps: - if: ${{ inputs.cache_build_image }} name: Restore Docker cache (${{ inputs.cache_build_image }}) - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.1 with: image_tag: ${{ inputs.cache_build_image }} @@ -123,7 +123,7 @@ jobs: - if: ${{ steps.context.outputs.use_appauth == 'true' }} name: Fetch token for app auth id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.1 with: app_id: ${{ secrets.app_id }} key: ${{ secrets.app_key }} @@ -153,7 +153,7 @@ jobs: run: git config --global --add safe.directory /__w/envoy/envoy - if: ${{ inputs.diskspace_hack }} - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 - run: | echo "disk space at beginning of build:" df -h @@ -161,7 +161,7 @@ jobs: - if: ${{ inputs.run_pre }} name: Run pre action ${{ inputs.run_pre && format('({0})', inputs.run_pre) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.1 with: uses: ${{ inputs.run_pre }} with: ${{ inputs.run_pre_with }} @@ -186,7 +186,7 @@ jobs: - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.1 with: uses: ${{ inputs.run_post }} with: ${{ inputs.run_post_with }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index e85a328c3dee..55474ea66e8d 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -116,7 +116,7 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.0 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.1 with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index 878253534f9d..ddc2a2fffd8c 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -29,7 +29,7 @@ jobs: - if: ${{ steps.env.outputs.trusted != 'true' }} name: Start status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.1 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: ${{ inputs.workflow_name }} diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 042a119fb88b..9ef46d30acbe 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Pre-cleanup - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index f1fca647979b..7d8d26134200 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: actions: write checks: read steps: - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.0 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.1 with: token: ${{ secrets.GITHUB_TOKEN }} azp_org: cncf diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index ff3907d2f466..5cb84a473b8c 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -43,13 +43,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: version name: Shorten (possible) SHA - uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.1 with: string: ${{ inputs.version }} length: 7 @@ -64,13 +64,13 @@ jobs: TARGET: ${{ inputs.task == 'bazel' && 'update' || 'api-update' }} TASK: ${{ inputs.task == 'bazel' && 'bazel' || 'api/bazel' }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.0 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.1 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.version.outputs.string }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 with: base: main body: | @@ -97,7 +97,7 @@ jobs: steps: - name: Fetch token for app auth id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.1 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} @@ -137,7 +137,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.1 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -166,7 +166,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 0af7b5d57542..a266e23eeb5c 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,7 +55,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -79,7 +79,7 @@ jobs: GITHUB_REF_NAME: ${{ github.ref_name }} - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.1 with: email: ${{ inputs.author }} - run: | @@ -105,7 +105,7 @@ jobs: env: AUTHOR: ${{ inputs.author }} - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 with: base: ${{ github.ref_name }} commit: false @@ -130,7 +130,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -150,7 +150,7 @@ jobs: bazel run --config=ci @envoy_repo//:sync name: Sync version histories - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 with: base: ${{ github.ref_name }} commit: false @@ -180,7 +180,7 @@ jobs: steps: - name: Fetch token for app auth id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.1 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index e856d41e1717..9a3b1304ca3d 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -28,7 +28,7 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.0 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.1 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index 0f3378ba02b6..6f2770619eda 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -34,7 +34,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy @@ -68,7 +68,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml index 2ddc6acee449..5308c0cc53cc 100644 --- a/.github/workflows/workflow-complete.yml +++ b/.github/workflows/workflow-complete.yml @@ -53,7 +53,7 @@ jobs: echo "state=${STATE}" >> "$GITHUB_OUTPUT" id: job - name: Complete status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.0 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.1 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: Verify/examples From d6e7e5904ce5598c3ad7ae5a20e7e54b9d60a49f Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 27 Oct 2023 13:01:03 +0100 Subject: [PATCH 08/72] github/ci: Assorted cleanups (#30540) Signed-off-by: Ryan Northey --- .github/actions/do_ci/action.yml | 75 ------------------- .../actions/publish/release/setup/action.yml | 2 +- .../actions/verify/examples/setup/action.yml | 6 +- .github/workflows/_ci.yml | 69 +++++++++-------- .github/workflows/envoy-dependency.yml | 19 ++--- .github/workflows/envoy-prechecks.yml | 1 - .github/workflows/envoy-release.yml | 75 ++++++++++--------- tools/base/requirements.in | 2 +- 8 files changed, 88 insertions(+), 161 deletions(-) delete mode 100644 .github/actions/do_ci/action.yml diff --git a/.github/actions/do_ci/action.yml b/.github/actions/do_ci/action.yml deleted file mode 100644 index 55275ed22c04..000000000000 --- a/.github/actions/do_ci/action.yml +++ /dev/null @@ -1,75 +0,0 @@ -inputs: - target: - required: true - type: string - rbe: - type: boolean - default: true - managed: - type: boolean - default: true - - auth_bazel_rbe: - type: string - default: '' - - bazel_extra: - type: string - default: - bazel_rbe_jobs: - type: number - default: 75 - - command_prefix: - type: string - default: ./ci/run_envoy_docker.sh - command_ci: - type: string - default: ./ci/do_ci.sh - catch-errors: - type: boolean - default: false - error-match: - type: string - default: | - ERROR - warning-match: - type: string - default: | - WARNING - notice-match: - type: string - default: | - NOTICE - - env: - type: string - - GITHUB_TOKEN: - required: true - -runs: - using: composite - steps: - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.0 - name: 'Run CI target ${{ inputs.target }}' - with: - catch-errors: ${{ inputs.catch-errors }} - container-command: ${{ inputs.command_prefix }} - command-prefix: ${{ inputs.command_ci }} - command: ${{ inputs.target }} - source: ${{ inputs.env }} - error-match: ${{ inputs.error-match }} - notice-match: ${{ inputs.notice-match }} - warning-match: ${{ inputs.warning-match }} - env: - GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} - ENVOY_DOCKER_BUILD_DIR: ${{ runner.temp }} - ENVOY_RBE: ${{ inputs.rbe != 'false' && 1 || '' }} - GCP_SERVICE_ACCOUNT_KEY: ${{ inputs.rbe && inputs.auth_bazel_rbe || '' }} - BAZEL_BUILD_EXTRA_OPTIONS: >- - --config=remote-ci - ${{ inputs.bazel_extra }} - ${{ inputs.rbe != 'false' && format('--jobs={0}', inputs.bazel_rbe_jobs) || '' }} - BAZEL_FAKE_SCM_REVISION: ${{ github.event_name == 'pull_request' && 'e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9' || '' }} - CI_TARGET_BRANCH: ${{ github.event_name == 'pull_request' && github.event.base.ref || github.ref }} diff --git a/.github/actions/publish/release/setup/action.yml b/.github/actions/publish/release/setup/action.yml index 4e0935710d2d..9660078fceb2 100644 --- a/.github/actions/publish/release/setup/action.yml +++ b/.github/actions/publish/release/setup/action.yml @@ -16,7 +16,7 @@ runs: env: REF: ${{ inputs.ref }} shell: bash - - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.0.10 + - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.1.1 id: fetch with: url: "${{ steps.url.outputs.base }}/release.signed.tar.zst" diff --git a/.github/actions/verify/examples/setup/action.yml b/.github/actions/verify/examples/setup/action.yml index 18f3205721ce..7384eb281d0d 100644 --- a/.github/actions/verify/examples/setup/action.yml +++ b/.github/actions/verify/examples/setup/action.yml @@ -16,15 +16,15 @@ runs: env: REF: ${{ inputs.ref }} shell: bash - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.0.10 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.1.1 with: url: "${{ steps.url.outputs.base }}/envoy.tar" variant: dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.0.10 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.1.1 with: url: "${{ steps.url.outputs.base }}/envoy-contrib.tar" variant: contrib-dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.0.10 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.1.1 with: url: "${{ steps.url.outputs.base }}/envoy-google-vrp.tar" variant: google-vrp-dev diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 91072b9cd5d3..5c6de10324d0 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -15,7 +15,9 @@ on: managed: type: boolean default: true - + runs-on: + default: ubuntu-22.04 + type: string auth_bazel_rbe: type: string default: '' @@ -105,7 +107,7 @@ concurrency: jobs: do_ci: if: ${{ ! inputs.skip }} - runs-on: ubuntu-22.04 + runs-on: ${{ inputs.runs-on }} name: ${{ inputs.command_ci }} ${{ inputs.target }} steps: - if: ${{ inputs.cache_build_image }} @@ -114,29 +116,18 @@ 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.1.1 - with: - app_id: ${{ secrets.app_id }} - key: ${{ secrets.app_key }} - - - uses: actions/checkout@v4 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + id: checkout name: Checkout Envoy repository with: - fetch-depth: ${{ ! inputs.trusted && inputs.repo_fetch_depth || 0 }} - # WARNING: This allows untrusted code to run!!! - # If this is set, then anything before or after in the job should be regarded as - # compromised. - ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} - token: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} + app_id: ${{ inputs.trusted && secrets.app_id || '' }} + app_key: ${{ inputs.trusted && secrets.app_key || '' }} + config: | + fetch-depth: ${{ ! inputs.trusted && inputs.repo_fetch_depth || 0 }} + # WARNING: This allows untrusted code to run!!! + # If this is set, then anything before or after in the job should be regarded as + # compromised. + ref: ${{ ! inputs.trusted && inputs.repo_ref || github.ref }} # If we are in a trusted CI run then the provided commit _must_ be either the latest for # this branch, or an antecdent. @@ -148,6 +139,7 @@ jobs: git checkout "${{ inputs.repo_ref }}" if: ${{ inputs.trusted }} name: Check provided ref + shell: bash - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy @@ -158,6 +150,7 @@ jobs: echo "disk space at beginning of build:" df -h name: "Check disk space at beginning" + shell: bash - if: ${{ inputs.run_pre }} name: Run pre action ${{ inputs.run_pre && format('({0})', inputs.run_pre) || '' }} @@ -166,23 +159,28 @@ jobs: uses: ${{ inputs.run_pre }} with: ${{ inputs.run_pre_with }} - - uses: ./.github/actions/do_ci - name: Do CI + - uses: envoyproxy/toolshed/gh-actions/github/run@5a3993152f00cc3f7c364d97b2a339fff606b0fc + name: 'Run CI target ${{ inputs.target }}' with: - target: ${{ inputs.target }} - rbe: ${{ inputs.rbe }} - managed: ${{ inputs.managed }} - auth_bazel_rbe: ${{ inputs.auth_bazel_rbe }} - bazel_extra: ${{ inputs.bazel_extra }} - bazel_rbe_jobs: ${{ inputs.bazel_rbe_jobs }} - command_prefix: ${{ inputs.command_prefix }} - command_ci: ${{ inputs.command_ci }} catch-errors: ${{ inputs.catch-errors }} + container-command: ${{ inputs.command_prefix }} + command-prefix: ${{ inputs.command_ci }} + command: ${{ inputs.target }} + source: ${{ inputs.env }} error-match: ${{ inputs.error-match }} notice-match: ${{ inputs.notice-match }} warning-match: ${{ inputs.warning-match }} - env: ${{ inputs.env }} - GITHUB_TOKEN: ${{ steps.context.outputs.use_appauth == 'true' && steps.appauth.outputs.token || secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ steps.checkout.outputs.token != '' && steps.checkout.outputs.token || secrets.GITHUB_TOKEN }} + ENVOY_DOCKER_BUILD_DIR: ${{ runner.temp }} + ENVOY_RBE: ${{ inputs.rbe != 'false' && 1 || '' }} + GCP_SERVICE_ACCOUNT_KEY: ${{ inputs.rbe && inputs.auth_bazel_rbe || '' }} + BAZEL_BUILD_EXTRA_OPTIONS: >- + --config=remote-ci + ${{ inputs.bazel_extra }} + ${{ inputs.rbe != 'false' && format('--jobs={0}', inputs.bazel_rbe_jobs) || '' }} + BAZEL_FAKE_SCM_REVISION: ${{ github.event_name == 'pull_request' && 'e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9' || '' }} + CI_TARGET_BRANCH: ${{ github.event_name == 'pull_request' && github.event.base.ref || github.ref }} - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} @@ -197,3 +195,4 @@ jobs: echo du -ch "${{ runner.temp }}" | grep -E "[0-9]{2,}M|[0-9]G" name: "Check disk space at end" + shell: bash diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 5cb84a473b8c..facb70d4499c 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -95,18 +95,15 @@ jobs: name: Update build image (PR) runs-on: ubuntu-22.04 steps: - - name: Fetch token for app auth - id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.1 - with: - app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} - key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: actions/checkout@v4 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + id: checkout name: Checkout Envoy repository with: - path: envoy - fetch-depth: 0 - token: ${{ steps.appauth.outputs.token }} + config: | + path: envoy + fetch-depth: 0 + app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} + app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - uses: actions/checkout@v4 name: Checkout Envoy build tools repository with: @@ -178,5 +175,5 @@ jobs: Signed-off-by: ${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}> title: 'deps: Bump build images -> `${{ steps.build-tools.outputs.tag_short }}`' - GITHUB_TOKEN: ${{ steps.appauth.outputs.token }} + GITHUB_TOKEN: ${{ steps.checkout.outputs.token }} working-directory: envoy diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 142f55f93a9d..856a1187cdb3 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -16,7 +16,6 @@ on: - 'WORKSPACE' - '.github/workflows/envoy-prechecks.yml' - '.github/workflows/_*.yml' - - '.github/actions/do_ci/action.yml' concurrency: group: ${{ github.event.inputs.head_ref || github.run_id }}-${{ github.workflow }} diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index a266e23eeb5c..256bf633ac14 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -59,6 +59,8 @@ jobs: with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} + committer-name: ${{ env.COMMITTER_NAME }} + committer-email: ${{ env.COMMITTER_EMAIL }} - run: | if [[ ! -s "changelogs/summary.md" ]]; then if [[ "${{ inputs.summary }}" == "false" ]]; then @@ -68,6 +70,9 @@ jobs: echo "::error::Changelog summary (changelogs/summary.md) is empty!" exit 1 fi + COMMITTER=$(git log -n 1 --format='%an <%ae>' -- changelogs/summary.md) + echo "committer=${COMMITTER}" >> $GITHUB_OUTPUT + id: changelog name: Check changelog summary - run: | BRANCHNAME="${GITHUB_REF_NAME#release/}" @@ -82,28 +87,30 @@ jobs: uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.1 with: email: ${{ inputs.author }} + - uses: envoyproxy/toolshed/gh-actions/github/run@ffa33da04ea0b9528f666a49ff2f336fedf9fca4 + name: Create release + with: + source: | + BAZEL_ARGS=(--) + BAZEL_RUN_ARGS=(--config=ci) + if [[ -n "${{ inputs.author }}" ]]; then + BAZEL_ARGS+=( + "--release-author=${{ inputs.author }}" + "--signoff=${{ steps.changelog.outputs.committer }}") + else + BAZEL_ARGS+=("--release-author=${{ steps.changelog.outputs.committer }}") + fi + command: >- + bazel + run + "${BAZEL_RUN_ARGS[@]}" + @envoy_repo//:release + "${BAZEL_ARGS[@]}" - run: | - git config --global user.name ${{ env.COMMITTER_NAME }} - git config --global user.email ${{ env.COMMITTER_EMAIL }} - name: Configure committer - - run: | - BAZEL_ARGS=(-- -l debug -v debug) - BAZEL_RUN_ARGS=(--config=ci) - CHANGELOG_COMMITTER="$(git log -n 1 --format="%an <%ae>" -- changelogs/summary.md)" - if [[ -n "$AUTHOR" ]]; then - BAZEL_ARGS+=( - --release-author="${AUTHOR}" - --release-signoff="${CHANGELOG_COMMITTER}") - else - BAZEL_ARGS+=(--release-author="${CHANGELOG_COMMITTER}") - fi - bazel run "${BAZEL_RUN_ARGS[@]}" @envoy_repo//:release "${BAZEL_ARGS[@]}" VERSION=$(cat VERSION.txt) echo "version=v${VERSION}" >> $GITHUB_OUTPUT - name: Create release + name: Release version id: release - env: - AUTHOR: ${{ inputs.author }} - name: Create a PR uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 with: @@ -134,6 +141,8 @@ jobs: with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} + committer-name: ${{ env.COMMITTER_NAME }} + committer-email: ${{ env.COMMITTER_EMAIL }} - run: | BRANCHNAME="${GITHUB_REF_NAME#release/}" echo "name=${BRANCHNAME}" >> $GITHUB_OUTPUT @@ -142,16 +151,19 @@ jobs: id: branch env: GITHUB_REF_NAME: ${{ github.ref_name }} - - run: | - git config --global user.name ${{ env.COMMITTER_NAME }} - git config --global user.email ${{ env.COMMITTER_NAME }} - name: Configure committer - - run: | - bazel run --config=ci @envoy_repo//:sync + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.1 name: Sync version histories + with: + command: >- + bazel + run + --config=ci @envoy_repo//:sync + -- + --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 with: + append-commit-message: true base: ${{ github.ref_name }} commit: false body: | @@ -178,23 +190,18 @@ jobs: if: github.event_name == 'release' && endsWith(github.ref, '.0') name: Create release branch steps: - - name: Fetch token for app auth - id: appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.1.1 - with: - app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} - key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: actions/checkout@v4 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 with: - token: ${{ steps.appauth.outputs.token }} + app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} + committer-name: ${{ env.COMMITTER_NAME }} + committer-email: ${{ env.COMMITTER_EMAIL }} - name: Create release branch run: | version="$(cut -d- -f1 < VERSION.txt | cut -d. -f-2)" release_branch="release/v${version}" commit_sha="$(git rev-parse HEAD)" - git config --global user.name "${{ env.COMMITTER_NAME }}" - git config --global user.email "${{ env.COMMITTER_EMAIL }}" echo "Creating ${release_branch} from ${commit_sha}" git checkout -b "$release_branch" bazel run @envoy_repo//:dev -- --patch diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 160e6f0af407..c13d3a06eee0 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -10,7 +10,7 @@ colorama coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 -envoy.base.utils>=0.4.27 +envoy.base.utils>=0.5.0 envoy.code.check>=0.5.8 envoy.dependency.check>=0.1.10 envoy.distribution.release>=0.0.9 From a3d74407c75ebcf56bb1720903606730a859d2ff Mon Sep 17 00:00:00 2001 From: "publish-envoy[bot]" <140627008+publish-envoy[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:25:48 +0100 Subject: [PATCH 09/72] repo: Sync version histories (#30561) repo: Sync version histories Signed-off-by: publish-envoy[bot] <140627008+publish-envoy[bot]@users.noreply.github.com> --- changelogs/1.25.11.yaml | 7 +++++++ changelogs/1.26.6.yaml | 10 ++++++++++ changelogs/1.27.2.yaml | 10 ++++++++++ docs/inventories/v1.28/objects.inv | Bin 0 -> 164171 bytes docs/versions.yaml | 1 + 5 files changed, 28 insertions(+) create mode 100644 changelogs/1.25.11.yaml create mode 100644 changelogs/1.26.6.yaml create mode 100644 changelogs/1.27.2.yaml create mode 100644 docs/inventories/v1.28/objects.inv diff --git a/changelogs/1.25.11.yaml b/changelogs/1.25.11.yaml new file mode 100644 index 000000000000..4beae10fad69 --- /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 000000000000..a5caeaa72fa5 --- /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/1.27.2.yaml b/changelogs/1.27.2.yaml new file mode 100644 index 000000000000..91d3633c0154 --- /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/docs/inventories/v1.28/objects.inv b/docs/inventories/v1.28/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..c454862b2315130bb7c402d16f03517eb4ba674f GIT binary patch literal 164171 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 Date: Fri, 27 Oct 2023 17:14:36 +0200 Subject: [PATCH 10/72] opentelemetry tracer: add support for environment resource detector (#29547) Commit Message: Allow specifying resource detectors for the OpenTelemetry tracer via a new configuration resource_detectors. The resource detector reads from the env variable OTEL_RESOURCE_ATTRIBUTES which is defined by the OTel specification. The detector returns a resource object populated with the detected attributes, which is sent as part of the OTLP request. Additional Description: This PR adds the "foundation" for building other resource detectors in Envoy. It is based on the OTel collector implementation. Users can configure multiple resource detectors, and they work together to "merge" all the detected attributes into a single resource object, which is then part of the OTLP message exported. Risk Level: Low Testing: Multiple unit tests, that cover all new code/scenarios. I also did manual testing, running Envoy locally with the OTel tracer + env resource detector enabled. Resource attributes detected from my environment is successfully exported as seen in the Jaeger screenshot. resource-detectors-env-jaeger Docs Changes: Not sure if I should add/where. Happy to do it. Release Notes: N/A Platform Specific Features: N/A [Optional Runtime guard:] N/A [Optional Fixes #28929] Here is how the new config is used: tracing: provider: name: envoy.tracers.opentelemetry typed_config: "@type": type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig grpc_service: envoy_grpc: cluster_name: opentelemetry_collector timeout: 0.250s service_name: envoy-gRPC-exporter resource_detectors: # --> NEW CONFIG - name: envoy.tracers.opentelemetry.resource_detectors.environment typed_config: "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.resource_detectors.v3.EnvironmentResourceDetectorConfig Signed-off-by: Joao Grassi --- api/BUILD | 1 + api/envoy/config/trace/v3/opentelemetry.proto | 5 + .../opentelemetry/resource_detectors/v3/BUILD | 9 + .../v3/environment_resource_detector.proto | 25 ++ api/versioning/BUILD | 1 + changelogs/current.yaml | 4 +- .../opentelemetry/resource_detectors.rst | 10 + docs/root/api-v3/config/trace/trace.rst | 1 + source/extensions/extensions_build_config.bzl | 6 + source/extensions/extensions_metadata.yaml | 7 + source/extensions/tracers/opentelemetry/BUILD | 1 + .../opentelemetry_tracer_impl.cc | 15 +- .../opentelemetry/opentelemetry_tracer_impl.h | 6 + .../opentelemetry/resource_detectors/BUILD | 27 ++ .../resource_detectors/environment/BUILD | 33 ++ .../resource_detectors/environment/config.cc | 35 ++ .../resource_detectors/environment/config.h | 46 ++ .../environment_resource_detector.cc | 60 +++ .../environment_resource_detector.h | 38 ++ .../resource_detectors/resource_detector.h | 80 ++++ .../resource_detectors/resource_provider.cc | 110 +++++ .../resource_detectors/resource_provider.h | 44 ++ .../tracers/opentelemetry/tracer.cc | 31 +- .../extensions/tracers/opentelemetry/tracer.h | 5 +- .../opentelemetry_tracer_impl_test.cc | 25 +- .../opentelemetry/resource_detectors/BUILD | 21 + .../resource_detectors/environment/BUILD | 37 ++ .../environment/config_test.cc | 36 ++ .../environment_resource_detector_test.cc | 109 +++++ .../resource_provider_test.cc | 424 ++++++++++++++++++ tools/extensions/extensions_schema.yaml | 1 + tools/spelling/spelling_dictionary.txt | 1 + 32 files changed, 1233 insertions(+), 21 deletions(-) create mode 100644 api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/BUILD create mode 100644 api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.proto create mode 100644 docs/root/api-v3/config/trace/opentelemetry/resource_detectors.rst create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/BUILD create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/environment/config.cc create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/environment/config.h create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.h create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc create mode 100644 source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h create mode 100644 test/extensions/tracers/opentelemetry/resource_detectors/BUILD create mode 100644 test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD create mode 100644 test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc create mode 100644 test/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector_test.cc create mode 100644 test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc diff --git a/api/BUILD b/api/BUILD index 76facfe2dda1..d40a6c4d7470 100644 --- a/api/BUILD +++ b/api/BUILD @@ -306,6 +306,7 @@ proto_library( "//envoy/extensions/stat_sinks/graphite_statsd/v3:pkg", "//envoy/extensions/stat_sinks/open_telemetry/v3:pkg", "//envoy/extensions/stat_sinks/wasm/v3:pkg", + "//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg", "//envoy/extensions/transport_sockets/alts/v3:pkg", "//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg", "//envoy/extensions/transport_sockets/internal_upstream/v3:pkg", diff --git a/api/envoy/config/trace/v3/opentelemetry.proto b/api/envoy/config/trace/v3/opentelemetry.proto index 7ae6a964bd72..5d9c9202cb5a 100644 --- a/api/envoy/config/trace/v3/opentelemetry.proto +++ b/api/envoy/config/trace/v3/opentelemetry.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package envoy.config.trace.v3; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/grpc_service.proto"; import "envoy/config/core/v3/http_service.proto"; @@ -43,4 +44,8 @@ message OpenTelemetryConfig { // The name for the service. This will be populated in the ResourceSpan Resource attributes. // If it is not provided, it will default to "unknown_service:envoy". string service_name = 2; + + // An ordered list of resource detectors + // [#extension-category: envoy.tracers.opentelemetry.resource_detectors] + repeated core.v3.TypedExtensionConfig resource_detectors = 4; } diff --git a/api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/BUILD b/api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.proto b/api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.proto new file mode 100644 index 000000000000..df62fc2d9e42 --- /dev/null +++ b/api/envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package envoy.extensions.tracers.opentelemetry.resource_detectors.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.tracers.opentelemetry.resource_detectors.v3"; +option java_outer_classname = "EnvironmentResourceDetectorProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/tracers/opentelemetry/resource_detectors/v3;resource_detectorsv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Environment Resource Detector config] + +// Configuration for the Environment Resource detector extension. +// The resource detector reads from the ``OTEL_RESOURCE_ATTRIBUTES`` +// environment variable, as per the OpenTelemetry specification. +// +// See: +// +// `OpenTelemetry specification `_ +// +// [#extension: envoy.tracers.opentelemetry.resource_detectors.environment] +message EnvironmentResourceDetectorConfig { +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 9f8638e33ee2..9ad67e06e99c 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -245,6 +245,7 @@ proto_library( "//envoy/extensions/stat_sinks/graphite_statsd/v3:pkg", "//envoy/extensions/stat_sinks/open_telemetry/v3:pkg", "//envoy/extensions/stat_sinks/wasm/v3:pkg", + "//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg", "//envoy/extensions/transport_sockets/alts/v3:pkg", "//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg", "//envoy/extensions/transport_sockets/internal_upstream/v3:pkg", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cb74cc6886e8..cff26a84b048 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -73,7 +73,6 @@ new_features: change: | added :ref:`per_endpoint_stats ` to get some metrics for each endpoint in a cluster. - - area: jwt change: | The jwt filter can now serialize non-primitive custom claims when maping claims to headers. @@ -90,5 +89,8 @@ new_features: returns an error or cannot be reached with :ref:`status_on_error ` configuration flag. +- area: tracing + change: | + Added support for configuring resource detectors on the OpenTelemetry tracer. deprecated: diff --git a/docs/root/api-v3/config/trace/opentelemetry/resource_detectors.rst b/docs/root/api-v3/config/trace/opentelemetry/resource_detectors.rst new file mode 100644 index 000000000000..87790ac145ec --- /dev/null +++ b/docs/root/api-v3/config/trace/opentelemetry/resource_detectors.rst @@ -0,0 +1,10 @@ +OpenTelemetry Resource Detectors +================================ + +Resource detectors that can be configured with the OpenTelemetry Tracer: + +.. toctree:: + :glob: + :maxdepth: 3 + + ../../../extensions/tracers/opentelemetry/resource_detectors/v3/* diff --git a/docs/root/api-v3/config/trace/trace.rst b/docs/root/api-v3/config/trace/trace.rst index 8f8d039a18d8..6cccb4f67d1b 100644 --- a/docs/root/api-v3/config/trace/trace.rst +++ b/docs/root/api-v3/config/trace/trace.rst @@ -12,3 +12,4 @@ HTTP tracers :maxdepth: 2 v3/* + opentelemetry/resource_detectors diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index f73bf64356c9..a75696fbf8c8 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -265,6 +265,12 @@ EXTENSIONS = { "envoy.tracers.skywalking": "//source/extensions/tracers/skywalking:config", "envoy.tracers.opentelemetry": "//source/extensions/tracers/opentelemetry:config", + # + # OpenTelemetry Resource Detectors + # + + "envoy.tracers.opentelemetry.resource_detectors.environment": "//source/extensions/tracers/opentelemetry/resource_detectors/environment:config", + # # Transport sockets # diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 7098afed83ad..2471dc695903 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1676,3 +1676,10 @@ envoy.filters.network.set_filter_state: status: alpha type_urls: - envoy.extensions.filters.network.set_filter_state.v3.Config +envoy.tracers.opentelemetry.resource_detectors.environment: + categories: + - envoy.tracers.opentelemetry.resource_detectors + security_posture: unknown + status: wip + type_urls: + - envoy.extensions.tracers.opentelemetry.resource_detectors.v3.EnvironmentResourceDetectorConfig diff --git a/source/extensions/tracers/opentelemetry/BUILD b/source/extensions/tracers/opentelemetry/BUILD index 58d0a20ba5b7..6122aa34d05a 100644 --- a/source/extensions/tracers/opentelemetry/BUILD +++ b/source/extensions/tracers/opentelemetry/BUILD @@ -41,6 +41,7 @@ envoy_cc_library( "//source/common/config:utility_lib", "//source/common/tracing:http_tracer_lib", "//source/extensions/tracers/common:factory_base_lib", + "//source/extensions/tracers/opentelemetry/resource_detectors:resource_detector_lib", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", "@opentelemetry_proto//:trace_cc_proto", ], diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc index 52e40c5cffbc..54f41ca2da12 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc @@ -10,6 +10,8 @@ #include "source/common/tracing/http_tracer_impl.h" #include "source/extensions/tracers/opentelemetry/grpc_trace_exporter.h" #include "source/extensions/tracers/opentelemetry/http_trace_exporter.h" +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "source/extensions/tracers/opentelemetry/span_context_extractor.h" #include "source/extensions/tracers/opentelemetry/trace_exporter.h" @@ -25,11 +27,19 @@ namespace OpenTelemetry { Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, Server::Configuration::TracerFactoryContext& context) + : Driver(opentelemetry_config, context, ResourceProviderImpl{}) {} + +Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, + Server::Configuration::TracerFactoryContext& context, + const ResourceProvider& resource_provider) : tls_slot_ptr_(context.serverFactoryContext().threadLocal().allocateSlot()), tracing_stats_{OPENTELEMETRY_TRACER_STATS( POOL_COUNTER_PREFIX(context.serverFactoryContext().scope(), "tracing.opentelemetry"))} { auto& factory_context = context.serverFactoryContext(); + Resource resource = resource_provider.getResource(opentelemetry_config, context); + ResourceConstSharedPtr resource_ptr = std::make_shared(std::move(resource)); + if (opentelemetry_config.has_grpc_service() && opentelemetry_config.has_http_service()) { throw EnvoyException( "OpenTelemetry Tracer cannot have both gRPC and HTTP exporters configured. " @@ -37,7 +47,8 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr } // Create the tracer in Thread Local Storage. - tls_slot_ptr_->set([opentelemetry_config, &factory_context, this](Event::Dispatcher& dispatcher) { + tls_slot_ptr_->set([opentelemetry_config, &factory_context, this, + resource_ptr](Event::Dispatcher& dispatcher) { OpenTelemetryTraceExporterPtr exporter; if (opentelemetry_config.has_grpc_service()) { Grpc::AsyncClientFactoryPtr&& factory = @@ -52,7 +63,7 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr } TracerPtr tracer = std::make_unique( std::move(exporter), factory_context.timeSource(), factory_context.api().randomGenerator(), - factory_context.runtime(), dispatcher, tracing_stats_, opentelemetry_config.service_name()); + factory_context.runtime(), dispatcher, tracing_stats_, resource_ptr); return std::make_shared(std::move(tracer)); }); diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h index 5083cff22f6e..d197ba2d5f97 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h @@ -8,6 +8,8 @@ #include "source/common/common/logger.h" #include "source/common/singleton/const_singleton.h" #include "source/extensions/tracers/common/factory_base.h" +#include "source/extensions/tracers/opentelemetry/grpc_trace_exporter.h" +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h" #include "source/extensions/tracers/opentelemetry/tracer.h" namespace Envoy { @@ -31,6 +33,10 @@ class Driver : Logger::Loggable, public Tracing::Driver { Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, Server::Configuration::TracerFactoryContext& context); + Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, + Server::Configuration::TracerFactoryContext& context, + const ResourceProvider& resource_provider); + // Tracing::Driver Tracing::SpanPtr startSpan(const Tracing::Config& config, Tracing::TraceContext& trace_context, const StreamInfo::StreamInfo& stream_info, diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/BUILD b/source/extensions/tracers/opentelemetry/resource_detectors/BUILD new file mode 100644 index 000000000000..c8b064de43e4 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/BUILD @@ -0,0 +1,27 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "resource_detector_lib", + srcs = [ + "resource_provider.cc", + ], + hdrs = [ + "resource_detector.h", + "resource_provider.h", + ], + deps = [ + "//envoy/config:typed_config_interface", + "//envoy/server:tracer_config_interface", + "//source/common/common:logger_lib", + "//source/common/config:utility_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD b/source/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD new file mode 100644 index 000000000000..3a0026dbd0dd --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD @@ -0,0 +1,33 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":environment_resource_detector_lib", + "//envoy/registry", + "//source/common/config:utility_lib", + "@envoy_api//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "environment_resource_detector_lib", + srcs = ["environment_resource_detector.cc"], + hdrs = ["environment_resource_detector.h"], + deps = [ + "//source/common/config:datasource_lib", + "//source/extensions/tracers/opentelemetry/resource_detectors:resource_detector_lib", + "@envoy_api//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/environment/config.cc b/source/extensions/tracers/opentelemetry/resource_detectors/environment/config.cc new file mode 100644 index 000000000000..5216a959ce1d --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/environment/config.cc @@ -0,0 +1,35 @@ +#include "source/extensions/tracers/opentelemetry/resource_detectors/environment/config.h" + +#include "envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.pb.h" +#include "envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.pb.validate.h" + +#include "source/common/config/utility.h" +#include "source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +ResourceDetectorPtr EnvironmentResourceDetectorFactory::createResourceDetector( + const Protobuf::Message& message, Server::Configuration::TracerFactoryContext& context) { + + auto mptr = Envoy::Config::Utility::translateAnyToFactoryConfig( + dynamic_cast(message), context.messageValidationVisitor(), *this); + + const auto& proto_config = MessageUtil::downcastAndValidate< + const envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + EnvironmentResourceDetectorConfig&>(*mptr, context.messageValidationVisitor()); + + return std::make_unique(proto_config, context); +} + +/** + * Static registration for the Env resource detector factory. @see RegisterFactory. + */ +REGISTER_FACTORY(EnvironmentResourceDetectorFactory, ResourceDetectorFactory); + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/environment/config.h b/source/extensions/tracers/opentelemetry/resource_detectors/environment/config.h new file mode 100644 index 000000000000..a2bf1f72025f --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/environment/config.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.pb.h" + +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * Config registration for the Environment resource detector. @see ResourceDetectorFactory. + */ +class EnvironmentResourceDetectorFactory : public ResourceDetectorFactory { +public: + /** + * @brief Create a Resource Detector that reads from the OTEL_RESOURCE_ATTRIBUTES + * environment variable. + * + * @param message The resource detector configuration. + * @param context The tracer factory context. + * @return ResourceDetectorPtr + */ + ResourceDetectorPtr + createResourceDetector(const Protobuf::Message& message, + Server::Configuration::TracerFactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { + return "envoy.tracers.opentelemetry.resource_detectors.environment"; + } +}; + +DECLARE_FACTORY(EnvironmentResourceDetectorFactory); + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc new file mode 100644 index 000000000000..3c69e32b76f3 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc @@ -0,0 +1,60 @@ +#include "environment_resource_detector.h" + +#include +#include + +#include "source/common/config/datasource.h" +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +constexpr absl::string_view kOtelResourceAttributesEnv = "OTEL_RESOURCE_ATTRIBUTES"; + +/** + * @brief Detects a resource from the OTEL_RESOURCE_ATTRIBUTES environment variable + * Based on the OTel C++ SDK: + * https://github.com/open-telemetry/opentelemetry-cpp/blob/v1.11.0/sdk/src/resource/resource_detector.cc + * + * @return Resource A resource with the attributes from the OTEL_RESOURCE_ATTRIBUTES environment + * variable. + */ +Resource EnvironmentResourceDetector::detect() { + envoy::config::core::v3::DataSource ds; + ds.set_environment_variable(kOtelResourceAttributesEnv); + + Resource resource; + resource.schemaUrl_ = ""; + std::string attributes_str = ""; + + attributes_str = Config::DataSource::read(ds, true, context_.serverFactoryContext().api()); + + if (attributes_str.empty()) { + throw EnvoyException( + fmt::format("The OpenTelemetry environment resource detector is configured but the '{}'" + " environment variable is empty.", + kOtelResourceAttributesEnv)); + } + + for (const auto& pair : StringUtil::splitToken(attributes_str, ",")) { + const auto keyValue = StringUtil::splitToken(pair, "="); + if (keyValue.size() != 2) { + throw EnvoyException( + fmt::format("The OpenTelemetry environment resource detector is configured but the '{}'" + " environment variable has an invalid format.", + kOtelResourceAttributesEnv)); + } + + const std::string key = std::string(keyValue[0]); + const std::string value = std::string(keyValue[1]); + resource.attributes_[key] = value; + } + return resource; +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.h b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.h new file mode 100644 index 000000000000..78327b047840 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.h @@ -0,0 +1,38 @@ +#pragma once + +#include "envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.pb.h" +#include "envoy/server/factory_context.h" + +#include "source/common/common/logger.h" +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * @brief A resource detector that extracts attributes from the OTEL_RESOURCE_ATTRIBUTES environment + * variable. + * @see + * https://github.com/open-telemetry/opentelemetry-specification/blob/v1.24.0/specification/resource/sdk.md#detecting-resource-information-from-the-environment + * + */ +class EnvironmentResourceDetector : public ResourceDetector, Logger::Loggable { +public: + EnvironmentResourceDetector(const envoy::extensions::tracers::opentelemetry::resource_detectors:: + v3::EnvironmentResourceDetectorConfig& config, + Server::Configuration::TracerFactoryContext& context) + : config_(config), context_(context) {} + Resource detect() override; + +private: + const envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + EnvironmentResourceDetectorConfig config_; + Server::Configuration::TracerFactoryContext& context_; +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h b/source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h new file mode 100644 index 000000000000..69894b917680 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include "envoy/config/typed_config.h" +#include "envoy/server/tracer_config.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * @brief A string key-value map that stores the resource attributes. + */ +using ResourceAttributes = std::map; + +/** + * @brief A Resource represents the entity producing telemetry as Attributes. + * For example, a process producing telemetry that is running in a container on Kubernetes + * has a Pod name, it is in a namespace and possibly is part of a Deployment which also has a name. + * See: + * https://github.com/open-telemetry/opentelemetry-specification/blob/v1.26.0/specification/resource/sdk.md + */ +struct Resource { + std::string schemaUrl_{""}; + ResourceAttributes attributes_{}; + + virtual ~Resource() = default; +}; + +using ResourceConstSharedPtr = std::shared_ptr; + +/** + * @brief The base type for all resource detectors + * + */ +class ResourceDetector { +public: + virtual ~ResourceDetector() = default; + + /** + * @brief Load attributes and returns a Resource object + * populated with them and a possible SchemaUrl. + * @return Resource + */ + virtual Resource detect() PURE; +}; + +using ResourceDetectorPtr = std::unique_ptr; + +/* + * A factory for creating resource detectors. + */ +class ResourceDetectorFactory : public Envoy::Config::TypedFactory { +public: + ~ResourceDetectorFactory() override = default; + + /** + * @brief Creates a resource detector based on the configuration type provided. + * + * @param message The resource detector configuration. + * @param context The tracer factory context. + * @return ResourceDetectorPtr A resource detector based on the configuration type provided. + */ + virtual ResourceDetectorPtr + createResourceDetector(const Protobuf::Message& message, + Server::Configuration::TracerFactoryContext& context) PURE; + + std::string category() const override { return "envoy.tracers.opentelemetry.resource_detectors"; } +}; + +using ResourceDetectorTypedFactoryPtr = std::unique_ptr; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc new file mode 100644 index 000000000000..a8f106cc3729 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc @@ -0,0 +1,110 @@ +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h" + +#include + +#include "source/common/common/logger.h" +#include "source/common/config/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace { +bool isEmptyResource(const Resource& resource) { return resource.attributes_.empty(); } + +Resource createInitialResource(const std::string& service_name) { + Resource resource{}; + + // Creates initial resource with the static service.name attribute. + resource.attributes_[std::string(kServiceNameKey.data(), kServiceNameKey.size())] = + service_name.empty() ? std::string{kDefaultServiceName} : service_name; + + return resource; +} + +/** + * @brief Resolves the new schema url when merging two resources. + * This function implements the algorithm as defined in the OpenTelemetry Resource SDK + * specification. @see + * https://github.com/open-telemetry/opentelemetry-specification/blob/v1.24.0/specification/resource/sdk.md#merge + * + * @param old_schema_url The old resource's schema URL. + * @param updating_schema_url The updating resource's schema URL. + * @return std::string The calculated schema URL. + */ +std::string resolveSchemaUrl(const std::string& old_schema_url, + const std::string& updating_schema_url) { + if (old_schema_url.empty()) { + return updating_schema_url; + } + if (updating_schema_url.empty()) { + return old_schema_url; + } + if (old_schema_url == updating_schema_url) { + return old_schema_url; + } + // The OTel spec leaves this case (when both have value but are different) unspecified. + ENVOY_LOG_MISC(info, "Resource schemaUrl conflict. Fall-back to old schema url: {}", + old_schema_url); + return old_schema_url; +} + +/** + * @brief Updates an old resource with a new one. This function implements + * the Merge operation defined in the OpenTelemetry Resource SDK specification. + * @see + * https://github.com/open-telemetry/opentelemetry-specification/blob/v1.24.0/specification/resource/sdk.md#merge + * + * @param old_resource The old resource. + * @param updating_resource The new resource. + */ +void mergeResource(Resource& old_resource, const Resource& updating_resource) { + // The schemaUrl is merged, regardless if the resources being merged + // have attributes or not. This behavior is compliant with the OTel spec. + // see: https://github.com/envoyproxy/envoy/pull/29547#discussion_r1344540427 + old_resource.schemaUrl_ = resolveSchemaUrl(old_resource.schemaUrl_, updating_resource.schemaUrl_); + + if (isEmptyResource(updating_resource)) { + return; + } + for (auto const& attr : updating_resource.attributes_) { + old_resource.attributes_.insert_or_assign(attr.first, attr.second); + } +} +} // namespace + +Resource ResourceProviderImpl::getResource( + const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, + Server::Configuration::TracerFactoryContext& context) const { + + Resource resource = createInitialResource(opentelemetry_config.service_name()); + + const auto& detectors_configs = opentelemetry_config.resource_detectors(); + + for (const auto& detector_config : detectors_configs) { + ResourceDetectorPtr detector; + auto* factory = Envoy::Config::Utility::getFactory(detector_config); + + if (!factory) { + throw EnvoyException( + fmt::format("Resource detector factory not found: '{}'", detector_config.name())); + } + + detector = factory->createResourceDetector(detector_config.typed_config(), context); + + if (!detector) { + throw EnvoyException( + fmt::format("Resource detector could not be created: '{}'", detector_config.name())); + } + + Resource detected_resource = detector->detect(); + mergeResource(resource, detected_resource); + } + return resource; +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h new file mode 100644 index 000000000000..9ecf6420c31d --- /dev/null +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h @@ -0,0 +1,44 @@ +#pragma once + +#include "envoy/config/trace/v3/opentelemetry.pb.h" + +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +constexpr absl::string_view kServiceNameKey = "service.name"; +constexpr absl::string_view kDefaultServiceName = "unknown_service:envoy"; + +class ResourceProvider : public Logger::Loggable { +public: + virtual ~ResourceProvider() = default; + + /** + * @brief Iterates through all loaded resource detectors and merge all the returned + * resources into one. Resource merging is done according to the OpenTelemetry + * resource SDK specification. @see + * https://github.com/open-telemetry/opentelemetry-specification/blob/v1.24.0/specification/resource/sdk.md#merge. + * + * @param opentelemetry_config The OpenTelemetry configuration, which contains the configured + * resource detectors. + * @param context The tracer factory context. + * @return Resource const The merged resource. + */ + virtual Resource + getResource(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, + Server::Configuration::TracerFactoryContext& context) const PURE; +}; + +class ResourceProviderImpl : public ResourceProvider { +public: + Resource getResource(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, + Server::Configuration::TracerFactoryContext& context) const override; +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/tracer.cc b/source/extensions/tracers/opentelemetry/tracer.cc index 683d5ea87d5f..5d55725906d2 100644 --- a/source/extensions/tracers/opentelemetry/tracer.cc +++ b/source/extensions/tracers/opentelemetry/tracer.cc @@ -19,8 +19,6 @@ namespace OpenTelemetry { constexpr absl::string_view kTraceParent = "traceparent"; constexpr absl::string_view kTraceState = "tracestate"; constexpr absl::string_view kDefaultVersion = "00"; -constexpr absl::string_view kServiceNameKey = "service.name"; -constexpr absl::string_view kDefaultServiceName = "unknown_service:envoy"; using opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest; @@ -110,12 +108,9 @@ void Span::setTag(absl::string_view name, absl::string_view value) { Tracer::Tracer(OpenTelemetryTraceExporterPtr exporter, Envoy::TimeSource& time_source, Random::RandomGenerator& random, Runtime::Loader& runtime, Event::Dispatcher& dispatcher, OpenTelemetryTracerStats tracing_stats, - const std::string& service_name) + const ResourceConstSharedPtr resource) : exporter_(std::move(exporter)), time_source_(time_source), random_(random), runtime_(runtime), - tracing_stats_(tracing_stats), service_name_(service_name) { - if (service_name.empty()) { - service_name_ = std::string{kDefaultServiceName}; - } + tracing_stats_(tracing_stats), resource_(resource) { flush_timer_ = dispatcher.createTimer([this]() -> void { tracing_stats_.timer_flushed_.inc(); flushSpans(); @@ -134,14 +129,20 @@ void Tracer::flushSpans() { ExportTraceServiceRequest request; // A request consists of ResourceSpans. ::opentelemetry::proto::trace::v1::ResourceSpans* resource_span = request.add_resource_spans(); - opentelemetry::proto::common::v1::KeyValue key_value = - opentelemetry::proto::common::v1::KeyValue(); - opentelemetry::proto::common::v1::AnyValue value_proto = - opentelemetry::proto::common::v1::AnyValue(); - value_proto.set_string_value(std::string{service_name_}); - key_value.set_key(std::string{kServiceNameKey}); - *key_value.mutable_value() = value_proto; - (*resource_span->mutable_resource()->add_attributes()) = key_value; + resource_span->set_schema_url(resource_->schemaUrl_); + + // add resource attributes + for (auto const& att : resource_->attributes_) { + opentelemetry::proto::common::v1::KeyValue key_value = + opentelemetry::proto::common::v1::KeyValue(); + opentelemetry::proto::common::v1::AnyValue value_proto = + opentelemetry::proto::common::v1::AnyValue(); + value_proto.set_string_value(std::string{att.second}); + key_value.set_key(std::string{att.first}); + *key_value.mutable_value() = value_proto; + (*resource_span->mutable_resource()->add_attributes()) = key_value; + } + ::opentelemetry::proto::trace::v1::ScopeSpans* scope_span = resource_span->add_scope_spans(); for (const auto& pending_span : span_buffer_) { (*scope_span->add_spans()) = pending_span; diff --git a/source/extensions/tracers/opentelemetry/tracer.h b/source/extensions/tracers/opentelemetry/tracer.h index 07d38ef22c8e..74bdb55952b8 100644 --- a/source/extensions/tracers/opentelemetry/tracer.h +++ b/source/extensions/tracers/opentelemetry/tracer.h @@ -11,6 +11,7 @@ #include "source/common/common/logger.h" #include "source/extensions/tracers/common/factory_base.h" #include "source/extensions/tracers/opentelemetry/grpc_trace_exporter.h" +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "absl/strings/escaping.h" @@ -35,7 +36,7 @@ class Tracer : Logger::Loggable { public: Tracer(OpenTelemetryTraceExporterPtr exporter, Envoy::TimeSource& time_source, Random::RandomGenerator& random, Runtime::Loader& runtime, Event::Dispatcher& dispatcher, - OpenTelemetryTracerStats tracing_stats, const std::string& service_name); + OpenTelemetryTracerStats tracing_stats, const ResourceConstSharedPtr resource); void sendSpan(::opentelemetry::proto::trace::v1::Span& span); @@ -64,7 +65,7 @@ class Tracer : Logger::Loggable { Runtime::Loader& runtime_; Event::TimerPtr flush_timer_; OpenTelemetryTracerStats tracing_stats_; - std::string service_name_; + const ResourceConstSharedPtr resource_; }; /** diff --git a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc index 4954854efd3d..300e92cf8d5a 100644 --- a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc +++ b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc @@ -27,6 +27,14 @@ using testing::NiceMock; using testing::Return; using testing::ReturnRef; +class MockResourceProvider : public ResourceProvider { +public: + MOCK_METHOD(Resource, getResource, + (const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, + Server::Configuration::TracerFactoryContext& context), + (const)); +}; + class OpenTelemetryDriverTest : public testing::Test { public: OpenTelemetryDriverTest() = default; @@ -44,7 +52,13 @@ class OpenTelemetryDriverTest : public testing::Test { .WillByDefault(Return(ByMove(std::move(mock_client_factory)))); ON_CALL(factory_context, scope()).WillByDefault(ReturnRef(scope_)); - driver_ = std::make_unique(opentelemetry_config, context_); + Resource resource; + resource.attributes_.insert(std::pair("key1", "val1")); + + auto mock_resource_provider = NiceMock(); + EXPECT_CALL(mock_resource_provider, getResource(_, _)).WillRepeatedly(Return(resource)); + + driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); } void setupValidDriver() { @@ -183,6 +197,9 @@ TEST_F(OpenTelemetryDriverTest, ParseSpanContextFromHeadersTest) { key: "service.name" value: string_value: "unknown_service:envoy" + key: "key1" + value: + string_value: "val1" scope_spans: spans: trace_id: "AAA" @@ -550,6 +567,9 @@ TEST_F(OpenTelemetryDriverTest, ExportOTLPSpanWithAttributes) { key: "service.name" value: string_value: "unknown_service:envoy" + key: "key1" + value: + string_value: "val1" scope_spans: spans: trace_id: "AAA" @@ -659,6 +679,9 @@ TEST_F(OpenTelemetryDriverTest, ExportSpanWithCustomServiceName) { key: "service.name" value: string_value: "test-service-name" + key: "key1" + value: + string_value: "val1" scope_spans: spans: trace_id: "AAA" diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/BUILD b/test/extensions/tracers/opentelemetry/resource_detectors/BUILD new file mode 100644 index 000000000000..b91bdda9b2e2 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/resource_detectors/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "resource_provider_test", + srcs = ["resource_provider_test.cc"], + deps = [ + "//envoy/registry", + "//source/extensions/tracers/opentelemetry/resource_detectors:resource_detector_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD b/test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD new file mode 100644 index 000000000000..2e6598200c38 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD @@ -0,0 +1,37 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.tracers.opentelemetry.resource_detectors.environment"], + deps = [ + "//envoy/registry", + "//source/extensions/tracers/opentelemetry/resource_detectors/environment:config", + "//source/extensions/tracers/opentelemetry/resource_detectors/environment:environment_resource_detector_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "environment_resource_detector_test", + srcs = ["environment_resource_detector_test.cc"], + extension_names = ["envoy.tracers.opentelemetry.resource_detectors.environment"], + deps = [ + "//source/extensions/tracers/opentelemetry/resource_detectors/environment:environment_resource_detector_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc new file mode 100644 index 000000000000..7e9ada0850eb --- /dev/null +++ b/test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc @@ -0,0 +1,36 @@ +#include "envoy/registry/registry.h" + +#include "source/extensions/tracers/opentelemetry/resource_detectors/environment/config.h" + +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Test create resource detector via factory +TEST(EnvironmentResourceDetectorFactoryTest, Basic) { + auto* factory = Registry::FactoryRegistry::getFactory( + "envoy.tracers.opentelemetry.resource_detectors.environment"); + ASSERT_NE(factory, nullptr); + + envoy::config::core::v3::TypedExtensionConfig typed_config; + const std::string yaml = R"EOF( + name: envoy.tracers.opentelemetry.resource_detectors.environment + typed_config: + "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.resource_detectors.v3.EnvironmentResourceDetectorConfig + )EOF"; + TestUtility::loadFromYaml(yaml, typed_config); + + NiceMock context; + EXPECT_NE(factory->createResourceDetector(typed_config.typed_config(), context), nullptr); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector_test.cc new file mode 100644 index 000000000000..e88f0dd5e72a --- /dev/null +++ b/test/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector_test.cc @@ -0,0 +1,109 @@ +#include + +#include "envoy/extensions/tracers/opentelemetry/resource_detectors/v3/environment_resource_detector.pb.h" +#include "envoy/registry/registry.h" + +#include "source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.h" + +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +const std::string kOtelResourceAttributesEnv = "OTEL_RESOURCE_ATTRIBUTES"; + +// Test detector when env variable is not present +TEST(EnvironmentResourceDetectorTest, EnvVariableNotPresent) { + NiceMock context; + + envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + EnvironmentResourceDetectorConfig config; + + auto detector = std::make_unique(config, context); + EXPECT_THROW_WITH_MESSAGE(detector->detect(), EnvoyException, + "Environment variable doesn't exist: OTEL_RESOURCE_ATTRIBUTES"); +} + +// Test detector when env variable is present but contains an empty value +TEST(EnvironmentResourceDetectorTest, EnvVariablePresentButEmpty) { + NiceMock context; + TestEnvironment::setEnvVar(kOtelResourceAttributesEnv, "", 1); + Envoy::Cleanup cleanup([]() { TestEnvironment::unsetEnvVar(kOtelResourceAttributesEnv); }); + + envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + EnvironmentResourceDetectorConfig config; + + auto detector = std::make_unique(config, context); + +#ifdef WIN32 + EXPECT_THROW_WITH_MESSAGE(detector->detect(), EnvoyException, + "Environment variable doesn't exist: OTEL_RESOURCE_ATTRIBUTES"); +#else + EXPECT_THROW_WITH_MESSAGE(detector->detect(), EnvoyException, + "The OpenTelemetry environment resource detector is configured but the " + "'OTEL_RESOURCE_ATTRIBUTES'" + " environment variable is empty."); +#endif +} + +// Test detector with valid values in the env variable +TEST(EnvironmentResourceDetectorTest, EnvVariablePresentAndWithAttributes) { + NiceMock context; + TestEnvironment::setEnvVar(kOtelResourceAttributesEnv, "key1=val1,key2=val2", 1); + Envoy::Cleanup cleanup([]() { TestEnvironment::unsetEnvVar(kOtelResourceAttributesEnv); }); + ResourceAttributes expected_attributes = {{"key1", "val1"}, {"key2", "val2"}}; + + Api::ApiPtr api = Api::createApiForTest(); + EXPECT_CALL(context.server_factory_context_, api()).WillRepeatedly(ReturnRef(*api)); + + envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + EnvironmentResourceDetectorConfig config; + + auto detector = std::make_unique(config, context); + Resource resource = detector->detect(); + + EXPECT_EQ(resource.schemaUrl_, ""); + EXPECT_EQ(2, resource.attributes_.size()); + + for (auto& actual : resource.attributes_) { + auto expected = expected_attributes.find(actual.first); + + EXPECT_TRUE(expected != expected_attributes.end()); + EXPECT_EQ(expected->second, actual.second); + } +} + +// Test detector with invalid values mixed with valid ones in the env variable +TEST(EnvironmentResourceDetectorTest, EnvVariablePresentAndWithAttributesWrongFormat) { + NiceMock context; + TestEnvironment::setEnvVar(kOtelResourceAttributesEnv, "key1=val1,key2val2,key3/val3, , key", 1); + Envoy::Cleanup cleanup([]() { TestEnvironment::unsetEnvVar(kOtelResourceAttributesEnv); }); + ResourceAttributes expected_attributes = {{"key1", "val"}}; + + Api::ApiPtr api = Api::createApiForTest(); + EXPECT_CALL(context.server_factory_context_, api()).WillRepeatedly(ReturnRef(*api)); + + envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + EnvironmentResourceDetectorConfig config; + + auto detector = std::make_unique(config, context); + + EXPECT_THROW_WITH_MESSAGE(detector->detect(), EnvoyException, + "The OpenTelemetry environment resource detector is configured but the " + "'OTEL_RESOURCE_ATTRIBUTES'" + " environment variable has an invalid format."); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc new file mode 100644 index 000000000000..8f49c4d93ca7 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc @@ -0,0 +1,424 @@ +#include + +#include "envoy/registry/registry.h" + +#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h" + +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/environment.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { +namespace { + +class SampleDetector : public ResourceDetector { +public: + MOCK_METHOD(Resource, detect, ()); +}; + +class DetectorFactoryA : public ResourceDetectorFactory { +public: + MOCK_METHOD(ResourceDetectorPtr, createResourceDetector, + (const Protobuf::Message& message, + Server::Configuration::TracerFactoryContext& context)); + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return "envoy.tracers.opentelemetry.resource_detectors.a"; } +}; + +class DetectorFactoryB : public ResourceDetectorFactory { +public: + MOCK_METHOD(ResourceDetectorPtr, createResourceDetector, + (const Protobuf::Message& message, + Server::Configuration::TracerFactoryContext& context)); + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return "envoy.tracers.opentelemetry.resource_detectors.b"; } +}; + +const std::string kOtelResourceAttributesEnv = "OTEL_RESOURCE_ATTRIBUTES"; + +class ResourceProviderTest : public testing::Test { +public: + ResourceProviderTest() { + resource_a_.attributes_.insert(std::pair("key1", "val1")); + resource_b_.attributes_.insert(std::pair("key2", "val2")); + } + NiceMock context_; + Resource resource_a_; + Resource resource_b_; +}; + +// Verifies a resource with the static service name is returned when no detectors are configured +TEST_F(ResourceProviderTest, NoResourceDetectorsConfigured) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config, context_); + + EXPECT_EQ(resource.schemaUrl_, ""); + + // Only the service name was added to the resource + EXPECT_EQ(1, resource.attributes_.size()); +} + +// Verifies a resource with the default service name is returned when no detectors + static service +// name are configured +TEST_F(ResourceProviderTest, ServiceNameNotProvided) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config, context_); + + EXPECT_EQ(resource.schemaUrl_, ""); + + // service.name receives the unknown value when not configured + EXPECT_EQ(1, resource.attributes_.size()); + auto service_name = resource.attributes_.find("service.name"); + EXPECT_EQ("unknown_service:envoy", service_name->second); +} + +// Verifies it is possible to configure multiple resource detectors +TEST_F(ResourceProviderTest, MultipleResourceDetectorsConfigured) { + auto detector_a = std::make_unique>(); + EXPECT_CALL(*detector_a, detect()).WillOnce(Return(resource_a_)); + + auto detector_b = std::make_unique>(); + EXPECT_CALL(*detector_b, detect()).WillOnce(Return(resource_b_)); + + DetectorFactoryA factory_a; + Registry::InjectFactory factory_a_registration(factory_a); + + DetectorFactoryB factory_b; + Registry::InjectFactory factory_b_registration(factory_b); + + EXPECT_CALL(factory_a, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_a)))); + EXPECT_CALL(factory_b, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_b)))); + + // Expected merged attributes from all detectors + ResourceAttributes expected_attributes = { + {"service.name", "my-service"}, {"key1", "val1"}, {"key2", "val2"}}; + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + resource_detectors: + - name: envoy.tracers.opentelemetry.resource_detectors.a + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + - name: envoy.tracers.opentelemetry.resource_detectors.b + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config, context_); + + EXPECT_EQ(resource.schemaUrl_, ""); + + // The resource should contain all 3 merged attributes + // service.name + 1 for each detector + EXPECT_EQ(3, resource.attributes_.size()); + + for (auto& actual : resource.attributes_) { + auto expected = expected_attributes.find(actual.first); + + EXPECT_TRUE(expected != expected_attributes.end()); + EXPECT_EQ(expected->second, actual.second); + } +} + +// Verifies Envoy fails when an unknown resource detector is configured +TEST_F(ResourceProviderTest, UnknownResourceDetectors) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + resource_detectors: + - name: envoy.tracers.opentelemetry.resource_detectors.UnkownResourceDetector + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + EXPECT_THROW_WITH_MESSAGE( + resource_provider.getResource(opentelemetry_config, context_), EnvoyException, + "Resource detector factory not found: " + "'envoy.tracers.opentelemetry.resource_detectors.UnkownResourceDetector'"); +} + +// Verifies Envoy fails when an error occurs while instantiating a resource detector +TEST_F(ResourceProviderTest, ProblemCreatingResourceDetector) { + DetectorFactoryA factory; + Registry::InjectFactory factory_registration(factory); + + // Simulating having a problem when creating the resource detector + EXPECT_CALL(factory, createResourceDetector(_, _)).WillOnce(Return(testing::ByMove(nullptr))); + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-clusterdetector_a + timeout: 0.250s + service_name: my-service + resource_detectors: + - name: envoy.tracers.opentelemetry.resource_detectors.a + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + )EOF"; + + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + EXPECT_THROW_WITH_MESSAGE(resource_provider.getResource(opentelemetry_config, context_), + EnvoyException, + "Resource detector could not be created: " + "'envoy.tracers.opentelemetry.resource_detectors.a'"); +} + +// Test merge when old schema url is empty but updating is not +TEST_F(ResourceProviderTest, OldSchemaEmptyUpdatingSet) { + std::string expected_schema_url = "my.schema/v1"; + Resource old_resource = resource_a_; + + // Updating resource is empty (no attributes) + Resource updating_resource; + updating_resource.schemaUrl_ = expected_schema_url; + + auto detector_a = std::make_unique>(); + EXPECT_CALL(*detector_a, detect()).WillOnce(Return(old_resource)); + + auto detector_b = std::make_unique>(); + EXPECT_CALL(*detector_b, detect()).WillOnce(Return(updating_resource)); + + DetectorFactoryA factory_a; + Registry::InjectFactory factory_a_registration(factory_a); + + DetectorFactoryB factory_b; + Registry::InjectFactory factory_b_registration(factory_b); + + EXPECT_CALL(factory_a, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_a)))); + EXPECT_CALL(factory_b, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_b)))); + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + resource_detectors: + - name: envoy.tracers.opentelemetry.resource_detectors.a + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + - name: envoy.tracers.opentelemetry.resource_detectors.b + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config, context_); + + // OTel spec says the updating schema should be used + EXPECT_EQ(expected_schema_url, resource.schemaUrl_); +} + +// Test merge when old schema url is not empty but updating is +TEST_F(ResourceProviderTest, OldSchemaSetUpdatingEmpty) { + std::string expected_schema_url = "my.schema/v1"; + Resource old_resource = resource_a_; + old_resource.schemaUrl_ = expected_schema_url; + + Resource updating_resource = resource_b_; + updating_resource.schemaUrl_ = ""; + + auto detector_a = std::make_unique>(); + EXPECT_CALL(*detector_a, detect()).WillOnce(Return(old_resource)); + + auto detector_b = std::make_unique>(); + EXPECT_CALL(*detector_b, detect()).WillOnce(Return(updating_resource)); + + DetectorFactoryA factory_a; + Registry::InjectFactory factory_a_registration(factory_a); + + DetectorFactoryB factory_b; + Registry::InjectFactory factory_b_registration(factory_b); + + EXPECT_CALL(factory_a, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_a)))); + EXPECT_CALL(factory_b, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_b)))); + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + resource_detectors: + - name: envoy.tracers.opentelemetry.resource_detectors.a + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + - name: envoy.tracers.opentelemetry.resource_detectors.b + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config, context_); + + // OTel spec says the updating schema should be used + EXPECT_EQ(expected_schema_url, resource.schemaUrl_); +} + +// Test merge when both old and updating schema url are set and equal +TEST_F(ResourceProviderTest, OldAndUpdatingSchemaAreEqual) { + std::string expected_schema_url = "my.schema/v1"; + Resource old_resource = resource_a_; + old_resource.schemaUrl_ = expected_schema_url; + + Resource updating_resource = resource_b_; + updating_resource.schemaUrl_ = expected_schema_url; + + auto detector_a = std::make_unique>(); + EXPECT_CALL(*detector_a, detect()).WillOnce(Return(old_resource)); + + auto detector_b = std::make_unique>(); + EXPECT_CALL(*detector_b, detect()).WillOnce(Return(updating_resource)); + + DetectorFactoryA factory_a; + Registry::InjectFactory factory_a_registration(factory_a); + + DetectorFactoryB factory_b; + Registry::InjectFactory factory_b_registration(factory_b); + + EXPECT_CALL(factory_a, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_a)))); + EXPECT_CALL(factory_b, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_b)))); + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + resource_detectors: + - name: envoy.tracers.opentelemetry.resource_detectors.a + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + - name: envoy.tracers.opentelemetry.resource_detectors.b + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config, context_); + + EXPECT_EQ(expected_schema_url, resource.schemaUrl_); +} + +// Test merge when both old and updating schema url are set but different +TEST_F(ResourceProviderTest, OldAndUpdatingSchemaAreDifferent) { + std::string expected_schema_url = "my.schema/v1"; + Resource old_resource = resource_a_; + old_resource.schemaUrl_ = expected_schema_url; + + Resource updating_resource = resource_b_; + updating_resource.schemaUrl_ = "my.schema/v2"; + + auto detector_a = std::make_unique>(); + EXPECT_CALL(*detector_a, detect()).WillOnce(Return(old_resource)); + + auto detector_b = std::make_unique>(); + EXPECT_CALL(*detector_b, detect()).WillOnce(Return(updating_resource)); + + DetectorFactoryA factory_a; + Registry::InjectFactory factory_a_registration(factory_a); + + DetectorFactoryB factory_b; + Registry::InjectFactory factory_b_registration(factory_b); + + EXPECT_CALL(factory_a, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_a)))); + EXPECT_CALL(factory_b, createResourceDetector(_, _)) + .WillOnce(Return(testing::ByMove(std::move(detector_b)))); + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + resource_detectors: + - name: envoy.tracers.opentelemetry.resource_detectors.a + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + - name: envoy.tracers.opentelemetry.resource_detectors.b + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config, context_); + + // OTel spec says Old schema should be used + EXPECT_EQ(expected_schema_url, resource.schemaUrl_); +} + +} // namespace +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml index 36181fb61786..e45938acb534 100644 --- a/tools/extensions/extensions_schema.yaml +++ b/tools/extensions/extensions_schema.yaml @@ -136,6 +136,7 @@ categories: - envoy.http.early_header_mutation - envoy.http.custom_response - envoy.router.cluster_specifier_plugin +- envoy.tracers.opentelemetry.resource_detectors status_values: - name: stable diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 3a73591424f9..ba9fe8bc3477 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -38,6 +38,7 @@ DOM Gasd GiB IPTOS +OTEL Repick Reserializer SION From 8df72d0b85f202185f1a821af58f73609d570707 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Fri, 27 Oct 2023 15:17:15 +0000 Subject: [PATCH 11/72] mobile/ci: Switch to Linux for running Android apps (#30414) Signed-off-by: Fredy Wijaya --- .github/workflows/mobile-android_build.yml | 98 ++++++++++++++-------- mobile/ci/linux_ci_setup.sh | 11 +++ mobile/ci/start_android_emulator.sh | 10 ++- 3 files changed, 80 insertions(+), 39 deletions(-) create mode 100755 mobile/ci/linux_ci_setup.sh diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index b45265f84916..ca135f7bf9ed 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -44,7 +44,7 @@ jobs: run: | cd mobile ./bazelw build \ - --config=mobile-remote-ci \ + --config=mobile-remote-release-clang \ --fat_apk_cpu=x86_64 \ --linkopt=-fuse-ld=lld \ //:android_dist @@ -58,7 +58,7 @@ jobs: contents: read packages: read name: java_helloworld - runs-on: macos-12 + runs-on: envoy-x64-small timeout-minutes: 50 steps: - uses: actions/checkout@v4 @@ -68,10 +68,16 @@ jobs: java-package: jdk architecture: x64 distribution: zulu - - run: | + - name: 'Install dependencies' + run: | cd mobile - ./ci/mac_ci_setup.sh --android - name: 'Install dependencies' + ./ci/linux_ci_setup.sh + # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ + - name: Enable KVM group permissions + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' with: @@ -81,22 +87,23 @@ jobs: # Return to using: # cd mobile && ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/java/hello_world:hello_envoy # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start java app' + - name: 'Start Java app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | cd mobile ./bazelw build \ - --config=mobile-remote-ci-macos \ + --config=mobile-remote-release-clang \ --fat_apk_cpu=x86_64 \ + --linkopt=-fuse-ld=lld \ //examples/java/hello_world:hello_envoy - adb install -r --no-incremental bazel-bin/examples/java/hello_world/hello_envoy.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoy/.MainActivity + "${ANDROID_HOME}"/platform-tools/adb install -r --no-incremental bazel-bin/examples/java/hello_world/hello_envoy.apk + "${ANDROID_HOME}"/platform-tools/adb shell am start -n io.envoyproxy.envoymobile.helloenvoy/.MainActivity - name: 'Check connectivity' run: | - timeout 30 adb logcat -e "received headers with status 301" -m 1 || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat -e "received headers with status 301" -m 1 || { echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat || { echo "Failed dumping adb logcat" >&2 } exit 1 @@ -111,7 +118,7 @@ jobs: contents: read packages: read name: kotlin_helloworld - runs-on: macos-12 + runs-on: envoy-x64-small timeout-minutes: 50 steps: - uses: actions/checkout@v4 @@ -124,7 +131,13 @@ jobs: - name: 'Install dependencies' run: | cd mobile - ./ci/mac_ci_setup.sh --android + ./ci/linux_ci_setup.sh + # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ + - name: Enable KVM group permissions + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' with: @@ -134,22 +147,23 @@ jobs: # Return to using: # ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/kotlin/hello_world:hello_envoy_kt # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start kotlin app' + - name: 'Start Kotlin app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | cd mobile ./bazelw build \ - --config=mobile-remote-ci-macos \ + --config=mobile-remote-release-clang \ --fat_apk_cpu=x86_64 \ + --linkopt=-fuse-ld=lld \ //examples/kotlin/hello_world:hello_envoy_kt - adb install -r --no-incremental bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity + "${ANDROID_HOME}"/platform-tools/adb install -r --no-incremental bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk + "${ANDROID_HOME}"/platform-tools/adb shell am start -n io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity - name: 'Check connectivity' run: | - timeout 30 adb logcat -e "received headers with status 200" -m 1 || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat -e "received headers with status 200" -m 1 || { echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat || { echo "Failed dumping adb logcat" >&2 } exit 1 @@ -164,7 +178,7 @@ jobs: contents: read packages: read name: kotlin_baseline_app - runs-on: macos-12 + runs-on: envoy-x64-small timeout-minutes: 50 steps: - uses: actions/checkout@v4 @@ -177,7 +191,13 @@ jobs: - name: 'Install dependencies' run: | cd mobile - ./ci/mac_ci_setup.sh --android + ./ci/linux_ci_setup.sh + # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ + - name: Enable KVM group permissions + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' with: @@ -187,22 +207,23 @@ jobs: # Return to using: # ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/kotlin/hello_world:hello_envoy_kt # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start kotlin app' + - name: 'Start Kotlin app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | cd mobile ./bazelw build \ - --config=mobile-remote-ci-macos \ + --config=mobile-remote-release-clang \ --fat_apk_cpu=x86_64 \ + --linkopt=-fuse-ld=lld \ //test/kotlin/apps/baseline:hello_envoy_kt - adb install -r --no-incremental bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity + "${ANDROID_HOME}"/platform-tools/adb install -r --no-incremental bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk + "${ANDROID_HOME}"/platform-tools/adb shell am start -n io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity - name: 'Check connectivity' run: | - timeout 30 adb logcat -e "received headers with status 301" -m 1 || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat -e "received headers with status 301" -m 1 || { echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat || { echo "Failed dumping adb logcat" >&2 } exit 1 @@ -217,7 +238,7 @@ jobs: contents: read packages: read name: kotlin_experimental_app - runs-on: macos-12 + runs-on: envoy-x64-small timeout-minutes: 50 steps: - uses: actions/checkout@v4 @@ -230,7 +251,13 @@ jobs: - name: 'Install dependencies' run: | cd mobile - ./ci/mac_ci_setup.sh --android + ./ci/linux_ci_setup.sh + # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ + - name: Enable KVM group permissions + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' with: @@ -240,23 +267,24 @@ jobs: # Return to using: # ./bazelw mobile-install --fat_apk_cpu=x86_64 --start_app //examples/kotlin/hello_world:hello_envoy_kt # When https://github.com/envoyproxy/envoy-mobile/issues/853 is fixed. - - name: 'Start kotlin app' + - name: 'Start Kotlin app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | cd mobile ./bazelw build \ - --config=mobile-remote-ci-macos \ + --config=mobile-remote-release-clang \ --fat_apk_cpu=x86_64 \ --define envoy_mobile_listener=enabled \ + --linkopt=-fuse-ld=lld \ //test/kotlin/apps/experimental:hello_envoy_kt - adb install -r --no-incremental bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity + "${ANDROID_HOME}"/platform-tools/adb install -r --no-incremental bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk + "${ANDROID_HOME}"/platform-tools/adb shell am start -n io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity - name: 'Check connectivity' run: | - timeout 30 adb logcat -e "received headers with status 200" -m 1 || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat -e "received headers with status 200" -m 1 || { echo "Failed checking for headers in adb logcat" >&2 - timeout 30 adb logcat || { + timeout 30 "${ANDROID_HOME}"/platform-tools/adb logcat || { echo "Failed dumping adb logcat" >&2 } exit 1 diff --git a/mobile/ci/linux_ci_setup.sh b/mobile/ci/linux_ci_setup.sh new file mode 100755 index 000000000000..c74829272178 --- /dev/null +++ b/mobile/ci/linux_ci_setup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +# Set up necessary Android SDK and NDK. +ANDROID_HOME=$ANDROID_SDK_ROOT +SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" +$SDKMANAGER --uninstall "ndk-bundle" +echo "y" | $SDKMANAGER "ndk;21.4.7075529" +$SDKMANAGER --install "build-tools;30.0.3" +echo "ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/21.4.7075529" >> "$GITHUB_ENV" diff --git a/mobile/ci/start_android_emulator.sh b/mobile/ci/start_android_emulator.sh index 0ba2e4c4d36b..8b582c343304 100755 --- a/mobile/ci/start_android_emulator.sh +++ b/mobile/ci/start_android_emulator.sh @@ -16,8 +16,8 @@ check_emulator_status() { done } -echo "y" | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --install 'system-images;android-30;google_atd;x86_64' --channel=3 -echo "no" | "${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager" create avd -n test_android_emulator -k 'system-images;android-30;google_atd;x86_64' --device pixel_4 --force +echo "y" | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --install 'system-images;android-30;google_apis;x86_64' --channel=3 +echo "no" | "${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager" create avd -n test_android_emulator -k 'system-images;android-30;google_apis;x86_64' --device pixel_4 --force "${ANDROID_HOME}"/emulator/emulator -accel-check # This is only available on macOS. if [[ -n $(which system_profiler) ]]; then @@ -25,8 +25,10 @@ if [[ -n $(which system_profiler) ]]; then fi # shellcheck disable=SC2094 -nohup "${ANDROID_HOME}/emulator/emulator" -partition-size 1024 -avd test_android_emulator -no-snapshot-load > nohup.out 2>&1 | tail -f nohup.out & { - check_emulator_status +nohup "${ANDROID_HOME}/emulator/emulator" -no-window -accel on -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -avd test_android_emulator > nohup.out 2>&1 | tail -f nohup.out & { + if [[ "$(uname -s)" == "Darwin" ]]; then + check_emulator_status + fi # shellcheck disable=SC2016 "${ANDROID_HOME}/platform-tools/adb" wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\''\r'\'') ]]; do sleep 1; done; input keyevent 82' } From dc61e68651e2eb209633d17ea67ea75d016e6987 Mon Sep 17 00:00:00 2001 From: Adam Kotwasinski Date: Fri, 27 Oct 2023 08:17:56 -0700 Subject: [PATCH 12/72] =?UTF-8?q?kafka:=20introduce=20filter-config=20obje?= =?UTF-8?q?ct=20+=20push=20broker=20build=20parts=20into=20=E2=80=A6=20(#3?= =?UTF-8?q?0545)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kafka: introduce filter-config object + push broker build parts into its own file Signed-off-by: Adam Kotwasinski --- contrib/contrib_build_config.bzl | 2 +- contrib/kafka/filters/network/source/BUILD | 45 ++++----------- .../kafka/filters/network/source/broker/BUILD | 56 +++++++++++++++++++ .../filters/network/source/broker/config.cc | 10 ++-- .../filters/network/source/broker/filter.cc | 6 +- .../filters/network/source/broker/filter.h | 4 +- .../network/source/broker/filter_config.h | 36 ++++++++++++ contrib/kafka/filters/network/test/BUILD | 2 +- .../kafka/filters/network/test/broker/BUILD | 6 +- .../test/broker/filter_protocol_test.cc | 2 +- 10 files changed, 119 insertions(+), 50 deletions(-) create mode 100644 contrib/kafka/filters/network/source/broker/BUILD create mode 100644 contrib/kafka/filters/network/source/broker/filter_config.h diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index f2cdaea8bfdc..11109e3878dd 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -15,7 +15,7 @@ CONTRIB_EXTENSIONS = { # "envoy.filters.network.client_ssl_auth": "//contrib/client_ssl_auth/filters/network/source:config", - "envoy.filters.network.kafka_broker": "//contrib/kafka/filters/network/source:kafka_broker_config_lib", + "envoy.filters.network.kafka_broker": "//contrib/kafka/filters/network/source/broker:config_lib", "envoy.filters.network.kafka_mesh": "//contrib/kafka/filters/network/source/mesh:config_lib", "envoy.filters.network.mysql_proxy": "//contrib/mysql_proxy/filters/network/source:config", "envoy.filters.network.postgres_proxy": "//contrib/postgres_proxy/filters/network/source:config", diff --git a/contrib/kafka/filters/network/source/BUILD b/contrib/kafka/filters/network/source/BUILD index ec50a777c50d..a7e075125bfe 100644 --- a/contrib/kafka/filters/network/source/BUILD +++ b/contrib/kafka/filters/network/source/BUILD @@ -2,7 +2,6 @@ load("@base_pip3//:requirements.bzl", "requirement") load("@rules_python//python:defs.bzl", "py_binary", "py_library") load( "//bazel:envoy_build_system.bzl", - "envoy_cc_contrib_extension", "envoy_cc_library", "envoy_contrib_package", ) @@ -11,39 +10,7 @@ licenses(["notice"]) # Apache 2 envoy_contrib_package() -# Kafka network filter. -# Broker filter public docs: https://envoyproxy.io/docs/envoy/latest/configuration/listeners/network_filters/kafka_broker_filter - -envoy_cc_contrib_extension( - name = "kafka_broker_config_lib", - srcs = ["broker/config.cc"], - hdrs = ["broker/config.h"], - deps = [ - ":kafka_broker_filter_lib", - "//source/extensions/filters/network:well_known_names", - "//source/extensions/filters/network/common:factory_base_lib", - "@envoy_api//contrib/envoy/extensions/filters/network/kafka_broker/v3:pkg_cc_proto", - ], -) - -envoy_cc_library( - name = "kafka_broker_filter_lib", - srcs = ["broker/filter.cc"], - hdrs = [ - "broker/filter.h", - "external/request_metrics.h", - "external/response_metrics.h", - ], - deps = [ - ":kafka_request_codec_lib", - ":kafka_response_codec_lib", - "//envoy/buffer:buffer_interface", - "//envoy/network:connection_interface", - "//envoy/network:filter_interface", - "//source/common/common:assert_lib", - "//source/common/common:minimal_logger_lib", - ], -) +# Common code for Kafka filters (Kafka type abstractions, protocol, metrics, etc.). envoy_cc_library( name = "abstract_codec_lib", @@ -201,6 +168,16 @@ py_library( srcs = ["protocol/generator.py"], ) +envoy_cc_library( + name = "kafka_metrics_lib", + hdrs = [ + "external/request_metrics.h", + "external/response_metrics.h", + ], + deps = [ + ], +) + envoy_cc_library( name = "parser_lib", hdrs = ["parser.h"], diff --git a/contrib/kafka/filters/network/source/broker/BUILD b/contrib/kafka/filters/network/source/broker/BUILD new file mode 100644 index 000000000000..3dfc7604add6 --- /dev/null +++ b/contrib/kafka/filters/network/source/broker/BUILD @@ -0,0 +1,56 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +# Kafka-broker network filter. +# Broker filter public docs: https://envoyproxy.io/docs/envoy/latest/configuration/listeners/network_filters/kafka_broker_filter + +envoy_cc_contrib_extension( + name = "config_lib", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":filter_config_lib", + ":filter_lib", + "//source/extensions/filters/network:well_known_names", + "//source/extensions/filters/network/common:factory_base_lib", + "@envoy_api//contrib/envoy/extensions/filters/network/kafka_broker/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "filter_config_lib", + srcs = [], + hdrs = [ + "filter_config.h", + ], + deps = [ + "@envoy_api//contrib/envoy/extensions/filters/network/kafka_broker/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "filter_lib", + srcs = ["filter.cc"], + hdrs = [ + "filter.h", + ], + deps = [ + ":filter_config_lib", + "//contrib/kafka/filters/network/source:kafka_metrics_lib", + "//contrib/kafka/filters/network/source:kafka_request_codec_lib", + "//contrib/kafka/filters/network/source:kafka_response_codec_lib", + "//envoy/buffer:buffer_interface", + "//envoy/network:connection_interface", + "//envoy/network:filter_interface", + "//source/common/common:assert_lib", + "//source/common/common:minimal_logger_lib", + ], +) diff --git a/contrib/kafka/filters/network/source/broker/config.cc b/contrib/kafka/filters/network/source/broker/config.cc index 459ce5fd20d8..532407a67480 100644 --- a/contrib/kafka/filters/network/source/broker/config.cc +++ b/contrib/kafka/filters/network/source/broker/config.cc @@ -5,6 +5,7 @@ #include "envoy/stats/scope.h" #include "contrib/kafka/filters/network/source/broker/filter.h" +#include "contrib/kafka/filters/network/source/broker/filter_config.h" namespace Envoy { namespace Extensions { @@ -15,13 +16,10 @@ namespace Broker { Network::FilterFactoryCb KafkaConfigFactory::createFilterFactoryFromProtoTyped( const KafkaBrokerProtoConfig& proto_config, Server::Configuration::FactoryContext& context) { - ASSERT(!proto_config.stat_prefix().empty()); - - const std::string& stat_prefix = proto_config.stat_prefix(); - - return [&context, stat_prefix](Network::FilterManager& filter_manager) -> void { + const BrokerFilterConfig filter_config{proto_config}; + return [&context, filter_config](Network::FilterManager& filter_manager) -> void { Network::FilterSharedPtr filter = - std::make_shared(context.scope(), context.timeSource(), stat_prefix); + std::make_shared(context.scope(), context.timeSource(), filter_config); filter_manager.addFilter(filter); }; } diff --git a/contrib/kafka/filters/network/source/broker/filter.cc b/contrib/kafka/filters/network/source/broker/filter.cc index 855226780ebd..8440fcf876a8 100644 --- a/contrib/kafka/filters/network/source/broker/filter.cc +++ b/contrib/kafka/filters/network/source/broker/filter.cc @@ -70,9 +70,9 @@ absl::flat_hash_map& KafkaMetricsFacadeImpl::getRequestA } KafkaBrokerFilter::KafkaBrokerFilter(Stats::Scope& scope, TimeSource& time_source, - const std::string& stat_prefix) - : KafkaBrokerFilter{ - std::make_shared(scope, time_source, stat_prefix)} {}; + const BrokerFilterConfig& filter_config) + : KafkaBrokerFilter{std::make_shared(scope, time_source, + filter_config.stat_prefix_)} {}; KafkaBrokerFilter::KafkaBrokerFilter(const KafkaMetricsFacadeSharedPtr& metrics) : metrics_{metrics}, response_decoder_{new ResponseDecoder({metrics})}, diff --git a/contrib/kafka/filters/network/source/broker/filter.h b/contrib/kafka/filters/network/source/broker/filter.h index 207115838000..519dee77a7aa 100644 --- a/contrib/kafka/filters/network/source/broker/filter.h +++ b/contrib/kafka/filters/network/source/broker/filter.h @@ -6,6 +6,7 @@ #include "source/common/common/logger.h" #include "absl/container/flat_hash_map.h" +#include "contrib/kafka/filters/network/source/broker/filter_config.h" #include "contrib/kafka/filters/network/source/external/request_metrics.h" #include "contrib/kafka/filters/network/source/external/response_metrics.h" #include "contrib/kafka/filters/network/source/parser.h" @@ -138,7 +139,8 @@ class KafkaBrokerFilter : public Network::Filter, private Logger::Loggable Date: Fri, 27 Oct 2023 16:20:11 +0100 Subject: [PATCH 13/72] deps: Bump `com_github_aignas_rules_shellcheck` -> 0.2.4 (#30543) Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 8 ++++---- ci/do_ci.sh | 8 ++++---- ci/windows_ci_steps.sh | 14 +++++++------- examples/locality-load-balancing/verify.sh | 2 +- .../transport_sockets/tls/test_data/certs.sh | 2 +- tools/vscode/refresh_compdb.sh | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6a1acfb4816e..f6c51daf44f0 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -176,11 +176,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Shellcheck rules for bazel", project_desc = "Now you do not need to depend on the system shellcheck version in your bazel-managed (mono)repos.", project_url = "https://github.com/aignas/rules_shellcheck", - version = "0.1.1", - sha256 = "4e7cc56d344d0adfd20283f7ad8cb4fba822c0b15ce122665b00dd87a27a74b6", + version = "0.2.4", + sha256 = "ce4d0e7a9beb1fb3f0d37424465060491a91dae68de1ef1c92ee57d94c773b46", strip_prefix = "rules_shellcheck-{version}", - urls = ["https://github.com/aignas/rules_shellcheck/archive/v{version}.tar.gz"], - release_date = "2022-05-30", + urls = ["https://github.com/aignas/rules_shellcheck/archive/{version}.tar.gz"], + release_date = "2023-10-27", use_category = ["build"], cpe = "N/A", license = "MIT", diff --git a/ci/do_ci.sh b/ci/do_ci.sh index fb008e6ed362..03f6e57b5ea2 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -163,7 +163,7 @@ function bazel_binary_build() { # The COMPILE_TYPE variable is redundant in this case and is only here for # readability. It is already set in the .bazelrc config for sizeopt. COMPILE_TYPE="opt" - CONFIG_ARGS="--config=sizeopt" + CONFIG_ARGS=("--config=sizeopt") elif [[ "${BINARY_TYPE}" == "fastbuild" ]]; then COMPILE_TYPE="fastbuild" fi @@ -181,7 +181,7 @@ function bazel_binary_build() { # This is a workaround for https://github.com/bazelbuild/bazel/issues/11834 [[ -n "${ENVOY_RBE}" ]] && rm -rf bazel-bin/"${ENVOY_BIN}"* - bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_TARGET}" ${CONFIG_ARGS} + bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_TARGET}" "${CONFIG_ARGS[@]}" collect_build_profile "${BINARY_TYPE}"_build # Copy the built envoy binary somewhere that we can access outside of the @@ -191,14 +191,14 @@ function bazel_binary_build() { if [[ "${COMPILE_TYPE}" == "dbg" || "${COMPILE_TYPE}" == "opt" ]]; then # Generate dwp file for debugging since we used split DWARF to reduce binary # size - bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_DEBUG_INFORMATION}" ${CONFIG_ARGS} + bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" "${BUILD_DEBUG_INFORMATION}" "${CONFIG_ARGS[@]}" # Copy the debug information cp -f bazel-bin/"${ENVOY_BIN}".dwp "${FINAL_DELIVERY_DIR}"/envoy.dwp fi # Validation tools for the tools image. bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" \ - //test/tools/schema_validator:schema_validator_tool ${CONFIG_ARGS} + //test/tools/schema_validator:schema_validator_tool "${CONFIG_ARGS[@]}" # Build su-exec utility bazel build "${BAZEL_BUILD_OPTIONS[@]}" --remote_download_toplevel -c "${COMPILE_TYPE}" external:su-exec diff --git a/ci/windows_ci_steps.sh b/ci/windows_ci_steps.sh index c16d7392602a..8881be13dc99 100755 --- a/ci/windows_ci_steps.sh +++ b/ci/windows_ci_steps.sh @@ -74,13 +74,13 @@ fi if [[ $1 == "//source/exe:envoy-static" ]]; then BUILD_ENVOY_STATIC=1 shift - TEST_TARGETS=$* + TEST_TARGETS=("${@}") elif [[ $# -gt 0 ]]; then BUILD_ENVOY_STATIC=0 - TEST_TARGETS=$* + TEST_TARGETS=("$@") else BUILD_ENVOY_STATIC=1 - TEST_TARGETS='//test/...' + TEST_TARGETS=('//test/...') fi # Complete envoy-static build @@ -97,8 +97,8 @@ if [[ $BUILD_ENVOY_STATIC -eq 1 ]]; then fi # Test invocations of known-working tests on Windows -if [[ $TEST_TARGETS == "//test/..." ]]; then - bazel "${BAZEL_STARTUP_OPTIONS[@]}" test "${BAZEL_BUILD_OPTIONS[@]}" $TEST_TARGETS --test_tag_filters=-skip_on_windows,-fails_on_${FAIL_GROUP} --build_tests_only +if [[ "${TEST_TARGETS[*]}" == "//test/..." ]]; then + bazel "${BAZEL_STARTUP_OPTIONS[@]}" test "${BAZEL_BUILD_OPTIONS[@]}" "${TEST_TARGETS[@]}" --test_tag_filters=-skip_on_windows,-fails_on_${FAIL_GROUP} --build_tests_only # Build tests that are known flaky or failing to ensure no compilation regressions bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" //test/... --test_tag_filters=fails_on_${FAIL_GROUP} --build_tests_only @@ -108,8 +108,8 @@ if [[ $TEST_TARGETS == "//test/..." ]]; then # not triggered by envoy-static or //test/... targets and not deliberately tagged skip_on_windows bazel "${BAZEL_STARTUP_OPTIONS[@]}" build "${BAZEL_BUILD_OPTIONS[@]}" //bazel/... --build_tag_filters=-skip_on_windows fi -elif [[ -n "$TEST_TARGETS" ]]; then - bazel "${BAZEL_STARTUP_OPTIONS[@]}" test "${BAZEL_BUILD_OPTIONS[@]}" $TEST_TARGETS --build_tests_only +elif [[ -n "${TEST_TARGETS[*]}" ]]; then + bazel "${BAZEL_STARTUP_OPTIONS[@]}" test "${BAZEL_BUILD_OPTIONS[@]}" "${TEST_TARGETS[@]}" --build_tests_only fi # Summarize known unbuildable or inapplicable tests (example) diff --git a/examples/locality-load-balancing/verify.sh b/examples/locality-load-balancing/verify.sh index c6a2855df8ca..7f6727811257 100755 --- a/examples/locality-load-balancing/verify.sh +++ b/examples/locality-load-balancing/verify.sh @@ -79,7 +79,7 @@ make_healthy backend-local-1-1 make_healthy backend-local-2-1 run_log "Scale backend-local-1 to 5 replicas." -"${DOCKER_COMPOSE[@]}" -p ${NAME} up --scale backend-local-1=5 -d --build +"${DOCKER_COMPOSE[@]}" -p "${NAME}" up --scale backend-local-1=5 -d --build wait_for 5 check_health backend-local-1-2 healthy wait_for 5 check_health backend-local-1-3 healthy wait_for 5 check_health backend-local-1-4 healthy diff --git a/test/extensions/transport_sockets/tls/test_data/certs.sh b/test/extensions/transport_sockets/tls/test_data/certs.sh index 75a63ba41605..a63bbddacec9 100755 --- a/test/extensions/transport_sockets/tls/test_data/certs.sh +++ b/test/extensions/transport_sockets/tls/test_data/certs.sh @@ -114,7 +114,7 @@ generate_cert_chain() { ca_name="i$((x - 1))" fi echo "$x: $certname $ca_name" - generate_ca $certname $ca_name + generate_ca "$certname" "$ca_name" done for x in {1..3}; do cat "i${x}_cert.pem" >> test_long_cert_chain.pem diff --git a/tools/vscode/refresh_compdb.sh b/tools/vscode/refresh_compdb.sh index 46a8f433f954..ff5d4363d191 100755 --- a/tools/vscode/refresh_compdb.sh +++ b/tools/vscode/refresh_compdb.sh @@ -8,7 +8,7 @@ command -v bazelisk &> /dev/null || bazel_or_isk=bazel [[ -z "${EXCLUDE_CONTRIB}" ]] || opts="--exclude_contrib" # Setting TEST_TMPDIR here so the compdb headers won't be overwritten by another bazel run -TEST_TMPDIR=${BUILD_DIR:-/tmp}/envoy-compdb tools/gen_compilation_database.py --vscode --bazel=$bazel_or_isk ${opts} +TEST_TMPDIR=${BUILD_DIR:-/tmp}/envoy-compdb tools/gen_compilation_database.py --vscode --bazel=$bazel_or_isk "${opts}" # Kill clangd to reload the compilation database pkill clangd || : From e545153ac45a0db519003f5cc6ab47835a367333 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 27 Oct 2023 17:03:48 +0100 Subject: [PATCH 14/72] ci: Use boosted vm for verify/examples (#30568) Signed-off-by: Ryan Northey --- .github/workflows/_stage_verify.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index a1a40d2b5fd4..d63c81984d9b 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -51,3 +51,4 @@ jobs: env: ${{ matrix.env }} trusted: ${{ inputs.trusted }} repo_ref: ${{ inputs.repo_ref }} + runs-on: envoy-x64-small From 94d3726b2e8d49ff0bb4d7bf7577de5b879340e3 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 27 Oct 2023 17:26:08 +0100 Subject: [PATCH 15/72] verify/examples: Remove diskspace hack (#30569) Signed-off-by: Ryan Northey --- .github/workflows/_stage_verify.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index d63c81984d9b..cbcbb771d756 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -31,7 +31,6 @@ jobs: managed: true cache_build_image: "" command_prefix: "" - diskspace_hack: true run_pre: ./.github/actions/verify/examples/setup run_pre_with: | bucket: envoy-${{ inputs.trusted && 'postsubmit' || 'pr' }} @@ -44,7 +43,6 @@ jobs: rbe: ${{ matrix.rbe }} managed: ${{ matrix.managed }} cache_build_image: ${{ matrix.cache_build_image }} - diskspace_hack: ${{ matrix.diskspace_hack }} command_prefix: ${{ matrix.command_prefix }} run_pre: ${{ matrix.run_pre }} run_pre_with: ${{ matrix.run_pre_with }} From 9843ca464974b89aff18e5fd02cced12c9ea152e Mon Sep 17 00:00:00 2001 From: code Date: Sat, 28 Oct 2023 00:39:15 +0800 Subject: [PATCH 16/72] unified interfaces to access request/response headers/trailers (#30553) Signed-off-by: wbpcode --- envoy/http/filter.h | 49 ++++++++++++++-------- source/common/http/async_client_impl.cc | 4 ++ source/common/http/async_client_impl.h | 12 ++++-- source/common/http/filter_manager.cc | 28 +++++++------ source/common/http/filter_manager.h | 8 ++-- test/common/http/filter_manager_test.cc | 55 +++++++++++++++++++++++++ test/mocks/http/mocks.h | 13 ++++-- 7 files changed, 130 insertions(+), 39 deletions(-) diff --git a/envoy/http/filter.h b/envoy/http/filter.h index 361eacc24474..5ccb8bec3706 100644 --- a/envoy/http/filter.h +++ b/envoy/http/filter.h @@ -441,6 +441,37 @@ class StreamFilterCallbacks { * @return absl::string_view the name of the filter as configured in the filter chain. */ virtual absl::string_view filterConfigName() const PURE; + + /** + * The downstream request headers if present. + */ + virtual RequestHeaderMapOptRef requestHeaders() PURE; + + /** + * The downstream request trailers if present. + */ + virtual RequestTrailerMapOptRef requestTrailers() PURE; + + /** + * Retrieves a pointer to the continue headers if present. + */ + virtual ResponseHeaderMapOptRef informationalHeaders() PURE; + + /** + * Retrieves a pointer to the response headers if present. + * Note that response headers might be set multiple times (e.g. if a local reply is issued after + * headers have been received but before headers have been encoded), so it is not safe in general + * to assume that any set of headers will be valid for the duration of the stream. + */ + virtual ResponseHeaderMapOptRef responseHeaders() PURE; + + /** + * Retrieves a pointer to the last response trailers if present. + * Note that response headers might be set multiple times (e.g. if a local reply is issued after + * headers have been received but before headers have been encoded), so it is not safe in general + * to assume that any set of headers will be valid for the duration of the stream. + */ + virtual ResponseTrailerMapOptRef responseTrailers() PURE; }; class DecoderFilterWatermarkCallbacks { @@ -608,12 +639,6 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, */ virtual void encode1xxHeaders(ResponseHeaderMapPtr&& headers) PURE; - /** - * Returns the headers provided to encode1xxHeaders. Returns absl::nullopt if - * no headers have been provided yet. - */ - virtual ResponseHeaderMapOptRef informationalHeaders() const PURE; - /** * Called with headers to be encoded, optionally indicating end of stream. * @@ -630,12 +655,6 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, virtual void encodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream, absl::string_view details) PURE; - /** - * Returns the headers provided to encodeHeaders. Returns absl::nullopt if no headers have been - * provided yet. - */ - virtual ResponseHeaderMapOptRef responseHeaders() const PURE; - /** * Called with data to be encoded, optionally indicating end of stream. * @param data supplies the data to be encoded. @@ -649,12 +668,6 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks, */ virtual void encodeTrailers(ResponseTrailerMapPtr&& trailers) PURE; - /** - * Returns the trailers provided to encodeTrailers. Returns absl::nullopt if no headers have been - * provided yet. - */ - virtual ResponseTrailerMapOptRef responseTrailers() const PURE; - /** * Called with metadata to be encoded. * diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 18167336065a..04ca4bb217d4 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -140,6 +140,8 @@ void AsyncStreamImpl::encodeTrailers(ResponseTrailerMapPtr&& trailers) { } void AsyncStreamImpl::sendHeaders(RequestHeaderMap& headers, bool end_stream) { + request_headers_ = &headers; + if (Http::Headers::get().MethodValues.Head == headers.getMethodValue()) { is_head_request_ = true; } @@ -182,6 +184,8 @@ void AsyncStreamImpl::sendData(Buffer::Instance& data, bool end_stream) { } void AsyncStreamImpl::sendTrailers(RequestTrailerMap& trailers) { + request_trailers_ = &trailers; + ASSERT(dispatcher().isThreadSafe()); // See explanation in sendData. if (local_closed_) { diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 4d5c80527a33..47e99aee70d3 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -208,13 +208,10 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, } // The async client won't pause if sending 1xx headers so simply swallow any. void encode1xxHeaders(ResponseHeaderMapPtr&&) override {} - ResponseHeaderMapOptRef informationalHeaders() const override { return {}; } void encodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream, absl::string_view details) override; - ResponseHeaderMapOptRef responseHeaders() const override { return {}; } void encodeData(Buffer::Instance& data, bool end_stream) override; void encodeTrailers(ResponseTrailerMapPtr&& trailers) override; - ResponseTrailerMapOptRef responseTrailers() const override { return {}; } void encodeMetadata(MetadataMapPtr&&) override {} void onDecoderFilterAboveWriteBufferHighWatermark() override { ++high_watermark_calls_; @@ -254,6 +251,13 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, void setUpstreamOverrideHost(absl::string_view) override {} absl::optional upstreamOverrideHost() const override { return {}; } absl::string_view filterConfigName() const override { return ""; } + RequestHeaderMapOptRef requestHeaders() override { return makeOptRefFromPtr(request_headers_); } + RequestTrailerMapOptRef requestTrailers() override { + return makeOptRefFromPtr(request_trailers_); + } + ResponseHeaderMapOptRef informationalHeaders() override { return {}; } + ResponseHeaderMapOptRef responseHeaders() override { return {}; } + ResponseTrailerMapOptRef responseTrailers() override { return {}; } // ScopeTrackedObject void dumpState(std::ostream& os, int indent_level) const override { @@ -275,6 +279,8 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, Buffer::InstancePtr buffered_body_; Buffer::BufferMemoryAccountSharedPtr account_{nullptr}; absl::optional buffer_limit_{absl::nullopt}; + RequestHeaderMap* request_headers_{}; + RequestTrailerMap* request_trailers_{}; bool encoded_response_headers_{}; bool is_grpc_request_{}; bool is_head_request_{false}; diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index fbe7277b9ab4..a3af10905664 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -352,6 +352,22 @@ OptRef ActiveStreamFilterBase::upstreamCallbacks( return parent_.filter_manager_callbacks_.upstreamCallbacks(); } +RequestHeaderMapOptRef ActiveStreamFilterBase::requestHeaders() { + return parent_.filter_manager_callbacks_.requestHeaders(); +} +RequestTrailerMapOptRef ActiveStreamFilterBase::requestTrailers() { + return parent_.filter_manager_callbacks_.requestTrailers(); +} +ResponseHeaderMapOptRef ActiveStreamFilterBase::informationalHeaders() { + return parent_.filter_manager_callbacks_.informationalHeaders(); +} +ResponseHeaderMapOptRef ActiveStreamFilterBase::responseHeaders() { + return parent_.filter_manager_callbacks_.responseHeaders(); +} +ResponseTrailerMapOptRef ActiveStreamFilterBase::responseTrailers() { + return parent_.filter_manager_callbacks_.responseTrailers(); +} + bool ActiveStreamDecoderFilter::canContinue() { // It is possible for the connection manager to respond directly to a request even while // a filter is trying to continue. If a response has already happened, we should not @@ -477,10 +493,6 @@ void ActiveStreamDecoderFilter::encode1xxHeaders(ResponseHeaderMapPtr&& headers) } } -ResponseHeaderMapOptRef ActiveStreamDecoderFilter::informationalHeaders() const { - return parent_.filter_manager_callbacks_.informationalHeaders(); -} - void ActiveStreamDecoderFilter::encodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream, absl::string_view details) { parent_.streamInfo().setResponseCodeDetails(details); @@ -488,10 +500,6 @@ void ActiveStreamDecoderFilter::encodeHeaders(ResponseHeaderMapPtr&& headers, bo parent_.encodeHeaders(nullptr, *parent_.filter_manager_callbacks_.responseHeaders(), end_stream); } -ResponseHeaderMapOptRef ActiveStreamDecoderFilter::responseHeaders() const { - return parent_.filter_manager_callbacks_.responseHeaders(); -} - void ActiveStreamDecoderFilter::encodeData(Buffer::Instance& data, bool end_stream) { parent_.encodeData(nullptr, data, end_stream, FilterManager::FilterIterationStartState::CanStartFromCurrent); @@ -502,10 +510,6 @@ void ActiveStreamDecoderFilter::encodeTrailers(ResponseTrailerMapPtr&& trailers) parent_.encodeTrailers(nullptr, *parent_.filter_manager_callbacks_.responseTrailers()); } -ResponseTrailerMapOptRef ActiveStreamDecoderFilter::responseTrailers() const { - return parent_.filter_manager_callbacks_.responseTrailers(); -} - void ActiveStreamDecoderFilter::encodeMetadata(MetadataMapPtr&& metadata_map_ptr) { parent_.encodeMetadata(nullptr, std::move(metadata_map_ptr)); } diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index b276327444d4..030c6709f047 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -106,6 +106,11 @@ struct ActiveStreamFilterBase : public virtual StreamFilterCallbacks, OptRef downstreamCallbacks() override; OptRef upstreamCallbacks() override; absl::string_view filterConfigName() const override { return filter_context_.config_name; } + RequestHeaderMapOptRef requestHeaders() override; + RequestTrailerMapOptRef requestTrailers() override; + ResponseHeaderMapOptRef informationalHeaders() override; + ResponseHeaderMapOptRef responseHeaders() override; + ResponseTrailerMapOptRef responseTrailers() override; // Functions to set or get iteration state. bool canIterate() { return iteration_state_ == IterationState::Continue; } @@ -218,13 +223,10 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, const absl::optional grpc_status, absl::string_view details) override; void encode1xxHeaders(ResponseHeaderMapPtr&& headers) override; - ResponseHeaderMapOptRef informationalHeaders() const override; void encodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream, absl::string_view details) override; - ResponseHeaderMapOptRef responseHeaders() const override; void encodeData(Buffer::Instance& data, bool end_stream) override; void encodeTrailers(ResponseTrailerMapPtr&& trailers) override; - ResponseTrailerMapOptRef responseTrailers() const override; void encodeMetadata(MetadataMapPtr&& metadata_map_ptr) override; void onDecoderFilterAboveWriteBufferHighWatermark() override; void onDecoderFilterBelowWriteBufferLowWatermark() override; diff --git a/test/common/http/filter_manager_test.cc b/test/common/http/filter_manager_test.cc index c1d3e041e996..865f98515ccd 100644 --- a/test/common/http/filter_manager_test.cc +++ b/test/common/http/filter_manager_test.cc @@ -69,6 +69,61 @@ class FilterManagerTest : public testing::Test { std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); }; +TEST_F(FilterManagerTest, RequestHeadersOrResponseHeadersAccess) { + initialize(); + + auto decoder_filter = std::make_shared>(); + auto encoder_filter = std::make_shared>(); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillOnce(Invoke([&](FilterChainManager& manager) -> bool { + auto decoder_factory = createDecoderFilterFactoryCb(decoder_filter); + manager.applyFilterFactoryCb({}, decoder_factory); + auto encoder_factory = createEncoderFilterFactoryCb(encoder_filter); + manager.applyFilterFactoryCb({}, encoder_factory); + return true; + })); + filter_manager_->createFilterChain(); + + RequestHeaderMapPtr request_headers{ + new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; + ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}}; + RequestTrailerMapPtr request_trailers{new TestRequestTrailerMapImpl{{"foo", "bar"}}}; + ResponseTrailerMapPtr response_trailers{new TestResponseTrailerMapImpl{{"foo", "bar"}}}; + ResponseHeaderMapPtr informational_headers{ + new TestResponseHeaderMapImpl{{":status", "100"}, {"foo", "bar"}}}; + + EXPECT_CALL(filter_manager_callbacks_, requestHeaders()) + .Times(2) + .WillRepeatedly(Return(makeOptRef(*request_headers))); + EXPECT_CALL(filter_manager_callbacks_, responseHeaders()) + .Times(2) + .WillRepeatedly(Return(makeOptRef(*response_headers))); + EXPECT_CALL(filter_manager_callbacks_, requestTrailers()) + .Times(2) + .WillRepeatedly(Return(makeOptRef(*request_trailers))); + EXPECT_CALL(filter_manager_callbacks_, responseTrailers()) + .Times(2) + .WillRepeatedly(Return(makeOptRef(*response_trailers))); + EXPECT_CALL(filter_manager_callbacks_, informationalHeaders()) + .Times(2) + .WillRepeatedly(Return(makeOptRef(*informational_headers))); + + EXPECT_EQ(decoder_filter->callbacks_->requestHeaders().ptr(), request_headers.get()); + EXPECT_EQ(decoder_filter->callbacks_->responseHeaders().ptr(), response_headers.get()); + EXPECT_EQ(decoder_filter->callbacks_->requestTrailers().ptr(), request_trailers.get()); + EXPECT_EQ(decoder_filter->callbacks_->responseTrailers().ptr(), response_trailers.get()); + EXPECT_EQ(decoder_filter->callbacks_->informationalHeaders().ptr(), informational_headers.get()); + + EXPECT_EQ(encoder_filter->callbacks_->requestHeaders().ptr(), request_headers.get()); + EXPECT_EQ(encoder_filter->callbacks_->responseHeaders().ptr(), response_headers.get()); + EXPECT_EQ(encoder_filter->callbacks_->requestTrailers().ptr(), request_trailers.get()); + EXPECT_EQ(encoder_filter->callbacks_->responseTrailers().ptr(), response_trailers.get()); + EXPECT_EQ(encoder_filter->callbacks_->informationalHeaders().ptr(), informational_headers.get()); + + filter_manager_->destroyFilters(); +} + // Verifies that the local reply persists the gRPC classification even if the request headers are // modified. TEST_F(FilterManagerTest, SendLocalReplyDuringDecodingGrpcClassiciation) { diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 7a26fb2fb2ed..2e3d2b86a094 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -269,6 +269,11 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, MOCK_METHOD(OptRef, downstreamCallbacks, ()); MOCK_METHOD(OptRef, upstreamCallbacks, ()); MOCK_METHOD(absl::string_view, filterConfigName, (), (const override)); + MOCK_METHOD(RequestHeaderMapOptRef, requestHeaders, ()); + MOCK_METHOD(RequestTrailerMapOptRef, requestTrailers, ()); + MOCK_METHOD(ResponseHeaderMapOptRef, informationalHeaders, ()); + MOCK_METHOD(ResponseHeaderMapOptRef, responseHeaders, ()); + MOCK_METHOD(ResponseTrailerMapOptRef, responseTrailers, ()); // Http::StreamDecoderFilterCallbacks // NOLINTNEXTLINE(readability-identifier-naming) @@ -278,15 +283,12 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, absl::string_view details); void encode1xxHeaders(ResponseHeaderMapPtr&& headers) override { encode1xxHeaders_(*headers); } - MOCK_METHOD(ResponseHeaderMapOptRef, informationalHeaders, (), (const)); void encodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream, absl::string_view details) override { stream_info_.setResponseCodeDetails(details); encodeHeaders_(*headers, end_stream); } - MOCK_METHOD(ResponseHeaderMapOptRef, responseHeaders, (), (const)); void encodeTrailers(ResponseTrailerMapPtr&& trailers) override { encodeTrailers_(*trailers); } - MOCK_METHOD(ResponseTrailerMapOptRef, responseTrailers, (), (const)); void encodeMetadata(MetadataMapPtr&& metadata_map) override { encodeMetadata_(std::move(metadata_map)); } @@ -361,6 +363,11 @@ class MockStreamEncoderFilterCallbacks : public StreamEncoderFilterCallbacks, MOCK_METHOD(OptRef, downstreamCallbacks, ()); MOCK_METHOD(OptRef, upstreamCallbacks, ()); MOCK_METHOD(absl::string_view, filterConfigName, (), (const override)); + MOCK_METHOD(RequestHeaderMapOptRef, requestHeaders, ()); + MOCK_METHOD(RequestTrailerMapOptRef, requestTrailers, ()); + MOCK_METHOD(ResponseHeaderMapOptRef, informationalHeaders, ()); + MOCK_METHOD(ResponseHeaderMapOptRef, responseHeaders, ()); + MOCK_METHOD(ResponseTrailerMapOptRef, responseTrailers, ()); // Http::StreamEncoderFilterCallbacks MOCK_METHOD(void, addEncodedData, (Buffer::Instance & data, bool streaming)); From 8f804787815a17debce1222a7b65ff67418475f9 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Fri, 27 Oct 2023 22:31:32 +0530 Subject: [PATCH 17/72] docs: add some clarification around the use of metadata in ExtAuthZ (#30563) Signed-off-by: Rohit Agrawal Co-authored-by: phlax --- .../filters/http/ext_authz/v3/ext_authz.proto | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index bba080473fa2..6cef61a44ec8 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -91,7 +91,10 @@ message ExtAuthz { type.v3.HttpStatus status_on_error = 7; // Specifies a list of metadata namespaces whose values, if present, will be passed to the - // ext_authz service. :ref:`filter_metadata ` is passed as an opaque ``protobuf::Struct``. + // ext_authz service. The :ref:`filter_metadata ` + // is passed as an opaque ``protobuf::Struct``. + // + // Please note that this field exclusively applies to the gRPC ext_authz service and has no effect on the HTTP service. // // For example, if the ``jwt_authn`` filter is used and :ref:`payload_in_metadata // ` is set, @@ -105,10 +108,13 @@ message ExtAuthz { repeated string metadata_context_namespaces = 8; // Specifies a list of metadata namespaces whose values, if present, will be passed to the - // ext_authz service. :ref:`typed_filter_metadata ` is passed as an ``protobuf::Any``. + // ext_authz service. :ref:`typed_filter_metadata ` + // is passed as a ``protobuf::Any``. + // + // Please note that this field exclusively applies to the gRPC ext_authz service and has no effect on the HTTP service. // - // It works in a way similar to ``metadata_context_namespaces`` but allows envoy and external authz server to share the protobuf message definition - // in order to do a safe parsing. + // It works in a way similar to ``metadata_context_namespaces`` but allows Envoy and ext_authz server to share + // the protobuf message definition in order to do a safe parsing. // repeated string typed_metadata_context_namespaces = 16; From 8c7e9acd75c1fe7d8a3a52f225f9d7fca56a4ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Sat, 28 Oct 2023 02:17:48 +0800 Subject: [PATCH 18/72] docs: fix indentation in the compressor filter configuration (#30515) Signed-off-by: spacewander --- .../compressor-filter-request-response.yaml | 77 +++++++++++++ .../_include/compressor-filter.yaml | 69 ++++++++++++ .../http/http_filters/compressor_filter.rst | 104 +++--------------- 3 files changed, 161 insertions(+), 89 deletions(-) create mode 100644 docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml create mode 100644 docs/root/configuration/http/http_filters/_include/compressor-filter.yaml diff --git a/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml b/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml new file mode 100644 index 000000000000..a7a0ff1e0046 --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml @@ -0,0 +1,77 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + # This filter is only enabled for responses. + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + request_direction_config: + common_config: + enabled: + default_value: false + runtime_key: request_compressor_enabled + compressor_library: + name: for_response + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 3 + window_bits: 10 + compression_level: BEST_COMPRESSION + compression_strategy: DEFAULT_STRATEGY + # This filter is only enabled for requests. + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + response_direction_config: + common_config: + enabled: + default_value: false + runtime_key: response_compressor_enabled + request_direction_config: + common_config: + enabled: + default_value: true + runtime_key: request_compressor_enabled + compressor_library: + name: for_request + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 9 + window_bits: 15 + compression_level: BEST_SPEED + compression_strategy: DEFAULT_STRATEGY + clusters: + - name: service + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service + port_value: 8000 diff --git a/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml b/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml new file mode 100644 index 000000000000..daf2afdd95a4 --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml @@ -0,0 +1,69 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + typed_per_filter_config: + envoy.filters.http.compression: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute + disabled: true + routes: + - match: { prefix: "/static" } + route: { cluster: service } + typed_per_filter_config: + envoy.filters.http.compression: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute + overrides: + response_direction_config: + - match: { prefix: "/" } + route: { cluster: service } + http_filters: + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + response_direction_config: + common_config: + min_content_length: 100 + content_type: + - text/html + - application/json + disable_on_etag_header: true + request_direction_config: + common_config: + enabled: + default_value: false + runtime_key: request_compressor_enabled + compressor_library: + name: text_optimized + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 3 + window_bits: 10 + compression_level: BEST_COMPRESSION + compression_strategy: DEFAULT_STRATEGY + clusters: + - name: service + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service + port_value: 8000 diff --git a/docs/root/configuration/http/http_filters/compressor_filter.rst b/docs/root/configuration/http/http_filters/compressor_filter.rst index 9c3c0dba9a31..9bcc66fb4796 100644 --- a/docs/root/configuration/http/http_filters/compressor_filter.rst +++ b/docs/root/configuration/http/http_filters/compressor_filter.rst @@ -26,32 +26,11 @@ compression only. Other compression libraries can be supported as extensions. An example configuration of the filter may look like the following: -.. code-block:: yaml - - http_filters: - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - response_direction_config: - common_config: - min_content_length: 100 - content_type: - - text/html - - application/json - disable_on_etag_header: true - request_direction_config: - common_config: - enabled: - default_value: false - runtime_key: request_compressor_enabled - compressor_library: - name: text_optimized - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 3 - window_bits: 10 - compression_level: BEST_COMPRESSION - compression_strategy: DEFAULT_STRATEGY +.. literalinclude:: _include/compressor-filter.yaml + :language: yaml + :linenos: + :lines: 33-56 + :caption: :download:`compressor-filter.yaml <_include/compressor-filter.yaml>` By *default* request compression is disabled, but when enabled it will be *skipped* if: @@ -132,27 +111,11 @@ Per-Route Configuration Response compression can be enabled and disabled on individual virtual hosts and routes. For example, to disable response compression for a particular virtual host, but enable response compression for its ``/static`` route: -.. code-block:: yaml - - route_config: - name: local_route - virtual_hosts: - - name: local_service - domains: ["*"] - typed_per_filter_config: - envoy.filters.http.compression: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute - disabled: true - routes: - - match: { prefix: "/static" } - route: { cluster: some_service } - typed_per_filter_config: - envoy.filters.http.compression: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute - overrides: - response_direction_config: - - match: { prefix: "/" } - route: { cluster: some_service } +.. literalinclude:: _include/compressor-filter.yaml + :language: yaml + :linenos: + :lines: 14-32 + :caption: :download:`compressor-filter.yaml <_include/compressor-filter.yaml>` Using different compressors for requests and responses -------------------------------------------------------- @@ -160,48 +123,11 @@ Using different compressors for requests and responses If different compression libraries are desired for requests and responses, it is possible to install multiple compressor filters enabled only for requests or responses. For instance: -.. code-block:: yaml - - http_filters: - # This filter is only enabled for responses. - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - request_direction_config: - common_config: - enabled: - default_value: false - runtime_key: request_compressor_enabled - compressor_library: - name: for_response - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 3 - window_bits: 10 - compression_level: BEST_COMPRESSION - compression_strategy: DEFAULT_STRATEGY - # This filter is only enabled for requests. - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - response_direction_config: - common_config: - enabled: - default_value: false - runtime_key: response_compressor_enabled - request_direction_config: - common_config: - enabled: - default_value: true - runtime_key: request_compressor_enabled - compressor_library: - name: for_request - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 9 - window_bits: 15 - compression_level: BEST_SPEED - compression_strategy: DEFAULT_STRATEGY +.. literalinclude:: _include/compressor-filter-request-response.yaml + :language: yaml + :linenos: + :lines: 25-64 + :caption: :download:`compressor-filter-request-response.yaml <_include/compressor-filter-request-response.yaml>` .. _compressor-statistics: From 5c6bec874d673f72c4255c04bda17333e5b19cf0 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 27 Oct 2023 20:13:40 +0100 Subject: [PATCH 19/72] =?UTF-8?q?Revert=20"docs:=20fix=20indentation=20in?= =?UTF-8?q?=20the=20compressor=20filter=20configuration=20=E2=80=A6=20(#30?= =?UTF-8?q?575)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "docs: fix indentation in the compressor filter configuration (#30515)" This reverts commit 8c7e9acd75c1fe7d8a3a52f225f9d7fca56a4ff9. Signed-off-by: Ryan Northey --- .../compressor-filter-request-response.yaml | 77 ------------- .../_include/compressor-filter.yaml | 69 ------------ .../http/http_filters/compressor_filter.rst | 104 +++++++++++++++--- 3 files changed, 89 insertions(+), 161 deletions(-) delete mode 100644 docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml delete mode 100644 docs/root/configuration/http/http_filters/_include/compressor-filter.yaml diff --git a/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml b/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml deleted file mode 100644 index a7a0ff1e0046..000000000000 --- a/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml +++ /dev/null @@ -1,77 +0,0 @@ -static_resources: - listeners: - - address: - socket_address: - address: 0.0.0.0 - port_value: 80 - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - codec_type: AUTO - stat_prefix: ingress_http - route_config: - name: local_route - virtual_hosts: - - name: app - domains: - - "*" - routes: - - match: - prefix: "/" - route: - cluster: service - http_filters: - # This filter is only enabled for responses. - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - request_direction_config: - common_config: - enabled: - default_value: false - runtime_key: request_compressor_enabled - compressor_library: - name: for_response - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 3 - window_bits: 10 - compression_level: BEST_COMPRESSION - compression_strategy: DEFAULT_STRATEGY - # This filter is only enabled for requests. - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - response_direction_config: - common_config: - enabled: - default_value: false - runtime_key: response_compressor_enabled - request_direction_config: - common_config: - enabled: - default_value: true - runtime_key: request_compressor_enabled - compressor_library: - name: for_request - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 9 - window_bits: 15 - compression_level: BEST_SPEED - compression_strategy: DEFAULT_STRATEGY - clusters: - - name: service - type: STRICT_DNS - lb_policy: ROUND_ROBIN - load_assignment: - cluster_name: service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: service - port_value: 8000 diff --git a/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml b/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml deleted file mode 100644 index daf2afdd95a4..000000000000 --- a/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml +++ /dev/null @@ -1,69 +0,0 @@ -static_resources: - listeners: - - address: - socket_address: - address: 0.0.0.0 - port_value: 80 - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - codec_type: AUTO - stat_prefix: ingress_http - route_config: - name: local_route - virtual_hosts: - - name: local_service - domains: ["*"] - typed_per_filter_config: - envoy.filters.http.compression: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute - disabled: true - routes: - - match: { prefix: "/static" } - route: { cluster: service } - typed_per_filter_config: - envoy.filters.http.compression: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute - overrides: - response_direction_config: - - match: { prefix: "/" } - route: { cluster: service } - http_filters: - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - response_direction_config: - common_config: - min_content_length: 100 - content_type: - - text/html - - application/json - disable_on_etag_header: true - request_direction_config: - common_config: - enabled: - default_value: false - runtime_key: request_compressor_enabled - compressor_library: - name: text_optimized - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 3 - window_bits: 10 - compression_level: BEST_COMPRESSION - compression_strategy: DEFAULT_STRATEGY - clusters: - - name: service - type: STRICT_DNS - lb_policy: ROUND_ROBIN - load_assignment: - cluster_name: service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: service - port_value: 8000 diff --git a/docs/root/configuration/http/http_filters/compressor_filter.rst b/docs/root/configuration/http/http_filters/compressor_filter.rst index 9bcc66fb4796..9c3c0dba9a31 100644 --- a/docs/root/configuration/http/http_filters/compressor_filter.rst +++ b/docs/root/configuration/http/http_filters/compressor_filter.rst @@ -26,11 +26,32 @@ compression only. Other compression libraries can be supported as extensions. An example configuration of the filter may look like the following: -.. literalinclude:: _include/compressor-filter.yaml - :language: yaml - :linenos: - :lines: 33-56 - :caption: :download:`compressor-filter.yaml <_include/compressor-filter.yaml>` +.. code-block:: yaml + + http_filters: + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + response_direction_config: + common_config: + min_content_length: 100 + content_type: + - text/html + - application/json + disable_on_etag_header: true + request_direction_config: + common_config: + enabled: + default_value: false + runtime_key: request_compressor_enabled + compressor_library: + name: text_optimized + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 3 + window_bits: 10 + compression_level: BEST_COMPRESSION + compression_strategy: DEFAULT_STRATEGY By *default* request compression is disabled, but when enabled it will be *skipped* if: @@ -111,11 +132,27 @@ Per-Route Configuration Response compression can be enabled and disabled on individual virtual hosts and routes. For example, to disable response compression for a particular virtual host, but enable response compression for its ``/static`` route: -.. literalinclude:: _include/compressor-filter.yaml - :language: yaml - :linenos: - :lines: 14-32 - :caption: :download:`compressor-filter.yaml <_include/compressor-filter.yaml>` +.. code-block:: yaml + + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + typed_per_filter_config: + envoy.filters.http.compression: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute + disabled: true + routes: + - match: { prefix: "/static" } + route: { cluster: some_service } + typed_per_filter_config: + envoy.filters.http.compression: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute + overrides: + response_direction_config: + - match: { prefix: "/" } + route: { cluster: some_service } Using different compressors for requests and responses -------------------------------------------------------- @@ -123,11 +160,48 @@ Using different compressors for requests and responses If different compression libraries are desired for requests and responses, it is possible to install multiple compressor filters enabled only for requests or responses. For instance: -.. literalinclude:: _include/compressor-filter-request-response.yaml - :language: yaml - :linenos: - :lines: 25-64 - :caption: :download:`compressor-filter-request-response.yaml <_include/compressor-filter-request-response.yaml>` +.. code-block:: yaml + + http_filters: + # This filter is only enabled for responses. + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + request_direction_config: + common_config: + enabled: + default_value: false + runtime_key: request_compressor_enabled + compressor_library: + name: for_response + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 3 + window_bits: 10 + compression_level: BEST_COMPRESSION + compression_strategy: DEFAULT_STRATEGY + # This filter is only enabled for requests. + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + response_direction_config: + common_config: + enabled: + default_value: false + runtime_key: response_compressor_enabled + request_direction_config: + common_config: + enabled: + default_value: true + runtime_key: request_compressor_enabled + compressor_library: + name: for_request + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 9 + window_bits: 15 + compression_level: BEST_SPEED + compression_strategy: DEFAULT_STRATEGY .. _compressor-statistics: From d84f436c9dd6006e6fc87e1c39ccb3ef572f34c8 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Fri, 27 Oct 2023 17:41:19 +0100 Subject: [PATCH 20/72] win/ci: Link the Envoy bin locally to prevent OOM Signed-off-by: Ryan Northey --- .bazelrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.bazelrc b/.bazelrc index fa3ec866f6fc..540554910b36 100644 --- a/.bazelrc +++ b/.bazelrc @@ -302,6 +302,7 @@ build:remote-windows --spawn_strategy=remote,local build:remote-windows --strategy=Javac=remote,local build:remote-windows --strategy=Closure=remote,local build:remote-windows --strategy=Genrule=remote,local +build:remote-windows --strategy=CppLink=local build:remote-windows --remote_timeout=7200 build:remote-windows --google_default_credentials=true build:remote-windows --remote_download_toplevel From fda4cca840892c7b843a22c4d3ad72ebba54c862 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 27 Oct 2023 13:01:03 +0100 Subject: [PATCH 21/72] win/ci: Shift to github Signed-off-by: Ryan Northey Signed-off-by: phlax --- .azure-pipelines/pipelines.yml | 4 - .azure-pipelines/stage/windows.yml | 125 ---------------------------- .azure-pipelines/stages.yml | 17 ---- .github/workflows/_ci.yml | 27 +++++- .github/workflows/envoy-windows.yml | 101 ++++++++++++++++++++++ 5 files changed, 125 insertions(+), 149 deletions(-) delete mode 100644 .azure-pipelines/stage/windows.yml create mode 100644 .github/workflows/envoy-windows.yml diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 99f458d07abd..8025b1813d65 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -82,8 +82,6 @@ stages: - env macBuildStageDeps: - env - windowsBuildStageDeps: - - env # Postsubmit main/release branches - ${{ if eq(variables.pipelinePostsubmit, true) }}: @@ -98,5 +96,3 @@ stages: - env macBuildStageDeps: - env - windowsBuildStageDeps: - - env diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml deleted file mode 100644 index fa2729b82254..000000000000 --- a/.azure-pipelines/stage/windows.yml +++ /dev/null @@ -1,125 +0,0 @@ - -parameters: - -# Auth -- name: authGCP - type: string - default: "" - -- name: runBuild - displayName: "Run build" - type: string - default: true - -jobs: -- job: release - displayName: Build and test - condition: | - and(not(canceled()), - eq(${{ parameters.runBuild }}, 'true')) - timeoutInMinutes: 180 - pool: - vmImage: "windows-2019" - steps: - - task: Cache@2 - inputs: - key: '"windows.release" | $(cacheKeyBazel)' - path: $(Build.StagingDirectory)/repository_cache - continueOnError: true - - - bash: | - set -e - ENVOY_SHARED_TMP_DIR="C:\\Users\\VSSADM~1\\AppData\\Local\\Temp\\bazel-shared" - mkdir -p "$ENVOY_SHARED_TMP_DIR" - GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -p "${ENVOY_SHARED_TMP_DIR}" -t gcp_service_account.XXXXXX.json) - bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" - export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" - export ENVOY_SHARED_TMP_DIR - ci/run_envoy_docker.sh ci/windows_ci_steps.sh - displayName: "Run Windows msvc-cl CI" - env: - CI_TARGET: "windows" - ENVOY_DOCKER_BUILD_DIR: "$(Build.StagingDirectory)" - ENVOY_RBE: "true" - BAZEL_BUILD_EXTRA_OPTIONS: >- - --config=remote-ci - --config=rbe-google - --config=remote-msvc-cl - --jobs=$(RbeJobs) - --flaky_test_attempts=2 - - - task: PublishTestResults@2 - inputs: - testResultsFiles: "**/bazel-out/**/testlogs/**/test.xml" - testRunTitle: "windows" - searchFolder: $(Build.StagingDirectory)/tmp - timeoutInMinutes: 10 - condition: not(canceled()) - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: "$(Build.StagingDirectory)/envoy" - artifactName: windows.release - timeoutInMinutes: 10 - condition: not(canceled()) - -- job: docker - displayName: Build Docker image - condition: and(not(canceled()), succeeded(), ne(stageDependencies.env.repo.outputs['changed.mobileOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.docsOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.examplesOnly'], 'true')) - strategy: - matrix: - windows2019: - imageName: 'windows-2019' - windowsBuildType: "windows" - windowsImageBase: "mcr.microsoft.com/windows/servercore" - windowsImageTag: "ltsc2019" - windows2022: - imageName: 'windows-2022' - windowsBuildType: "windows-ltsc2022" - windowsImageBase: "mcr.microsoft.com/windows/nanoserver" - windowsImageTag: "ltsc2022" - dependsOn: ["release"] - timeoutInMinutes: 120 - pool: - vmImage: $(imageName) - steps: - - task: DownloadBuildArtifacts@0 - inputs: - buildType: current - artifactName: "windows.release" - itemPattern: "windows.release/envoy_binary.tar.gz" - downloadType: single - targetPath: $(Build.StagingDirectory) - - bash: | - set -e - # Convert to Unix-style path so tar doesn't think drive letter is a hostname - STAGING_DIR="/$(echo '$(Build.StagingDirectory)' | tr -d ':' | tr '\\' '/')" - mkdir -p windows/amd64 && tar zxf "${STAGING_DIR}/windows.release/envoy_binary.tar.gz" -C ./windows/amd64 - ci/docker_ci.sh - workingDirectory: $(Build.SourcesDirectory) - env: - CI_BRANCH: $(Build.SourceBranch) - CI_SHA1: $(Build.SourceVersion) - DOCKERHUB_USERNAME: $(DockerUsername) - DOCKERHUB_PASSWORD: $(DockerPassword) - WINDOWS_BUILD_TYPE: $(windowsBuildType) - WINDOWS_IMAGE_BASE: $(windowsImageBase) - WINDOWS_IMAGE_TAG: $(windowsImageTag) - -- job: released - displayName: Complete - dependsOn: ["release", "docker"] - pool: - vmImage: $(agentUbuntu) - # This condition ensures that this (required) job passes if all of - # the preceeding jobs either pass or are skipped - # adapted from: - # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#job-to-job-dependencies-within-one-stage - condition: | - and( - eq(variables['Build.Reason'], 'PullRequest'), - in(dependencies.release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), - in(dependencies.docker.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')) - steps: - - checkout: none - - bash: | - echo "windows released" diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index ab3fbca2075a..a49a99f7d9f8 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -14,12 +14,6 @@ parameters: default: - env - prechecks -- name: windowsBuildStageDeps - displayName: "Windows stage dependencies" - type: object - default: - - env - - prechecks - name: checkStageDeps displayName: "Check stage dependencies" type: object @@ -170,14 +164,3 @@ stages: parameters: authGCP: $(GcpServiceAccountKey) runBuild: variables['RUN_BUILD'] - -- stage: windows - displayName: Windows - dependsOn: ${{ parameters.windowsBuildStageDeps }} - variables: - RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] - jobs: - - template: stage/windows.yml - parameters: - authGCP: $(GcpServiceAccountKey) - runBuild: variables['RUN_BUILD'] diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 5c6de10324d0..35d0a22849ae 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -5,6 +5,7 @@ on: secrets: app_id: app_key: + rbe-key: inputs: target: required: true @@ -18,6 +19,18 @@ on: runs-on: default: ubuntu-22.04 type: string + run-du: + default: true + type: boolean + temp-dir: + default: + type: string + upload-name: + default: + type: string + upload-path: + default: + type: string auth_bazel_rbe: type: string default: '' @@ -159,7 +172,7 @@ jobs: uses: ${{ inputs.run_pre }} with: ${{ inputs.run_pre_with }} - - uses: envoyproxy/toolshed/gh-actions/github/run@5a3993152f00cc3f7c364d97b2a339fff606b0fc + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.1 name: 'Run CI target ${{ inputs.target }}' with: catch-errors: ${{ inputs.catch-errors }} @@ -174,7 +187,7 @@ jobs: GITHUB_TOKEN: ${{ steps.checkout.outputs.token != '' && steps.checkout.outputs.token || secrets.GITHUB_TOKEN }} ENVOY_DOCKER_BUILD_DIR: ${{ runner.temp }} ENVOY_RBE: ${{ inputs.rbe != 'false' && 1 || '' }} - GCP_SERVICE_ACCOUNT_KEY: ${{ inputs.rbe && inputs.auth_bazel_rbe || '' }} + RBE_KEY: ${{ secrets.rbe-key }} BAZEL_BUILD_EXTRA_OPTIONS: >- --config=remote-ci ${{ inputs.bazel_extra }} @@ -193,6 +206,14 @@ jobs: echo "disk space at end of build:" df -h echo - du -ch "${{ runner.temp }}" | grep -E "[0-9]{2,}M|[0-9]G" + if [[ "${{ inputs.run-du }}" != "false" ]]; then + du -ch "${{ inputs.temp-dir || runner.temp }}" | grep -E "[0-9]{2,}M|[0-9]G" + fi name: "Check disk space at end" shell: bash + + - uses: actions/upload-artifact@v3 + if: ${{ inputs.upload-name && inputs.upload-path }} + with: + name: ${{ inputs.upload-name }} + path: ${{ inputs.upload-path }} diff --git a/.github/workflows/envoy-windows.yml b/.github/workflows/envoy-windows.yml new file mode 100644 index 000000000000..348a96d9e1c5 --- /dev/null +++ b/.github/workflows/envoy-windows.yml @@ -0,0 +1,101 @@ +name: Envoy/windows + +permissions: + contents: read + +on: + push: + branches: + - main + - release/v* + pull_request_target: + +concurrency: + group: ${{ github.event.inputs.head_ref || github.run_id }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + env: + uses: ./.github/workflows/_env.yml + with: + prime_build_image: false + check_mobile_run: false + + windows: + needs: + - env + strategy: + fail-fast: false + matrix: + include: + - target: ci/windows_ci_steps.sh + name: Windows 2019 + uses: ./.github/workflows/_ci.yml + name: CI ${{ matrix.name || matrix.target }} + secrets: + rbe-key: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} + with: + target: ${{ matrix.target }} + runs-on: envoy-win19-medium + command_ci: + temp-dir: 'C:\Users\runner\AppData\Local\Temp\bazel-shared' + run-du: false + upload-name: windows.release + upload-path: 'C:\Users\runner\AppData\Local\Temp\envoy' + env: | + export ENVOY_SHARED_TMP_DIR="C:\Users\runner\AppData\Local\Temp\bazel-shared" + export ENVOY_DOCKER_BUILD_DIR="C:\Users\runner\AppData\Local\Temp" + 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 \"${RBE_KEY}\" | base64 --decode > \"${GCP_SERVICE_ACCOUNT_KEY_PATH}\"" + _BAZEL_BUILD_EXTRA_OPTIONS=( + --config=remote-ci + --config=rbe-google + --config=remote-msvc-cl + --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH} + --jobs=75 + --flaky_test_attempts=2) + export BAZEL_BUILD_EXTRA_OPTIONS=${_BAZEL_BUILD_EXTRA_OPTIONS[*]} + + docker: + needs: + - env + - windows + strategy: + fail-fast: false + matrix: + include: + - target: windows2019 + name: Windows 2019 + runs-on: envoy-win19-medium + build-type: windows + image-base: mcr.microsoft.com/windows/servercore + image-tag: ltsc2019 + - target: windows2022 + name: Windows 2022 + runs-on: envoy-win22-medium + build-type: windows-ltsc2022 + image-base: mcr.microsoft.com/windows/nanoserver + image-tag: ltsc2022 + runs-on: ${{ matrix.runs-on }} + steps: + # TODO(phlax): checkout a more specific commit with safeguards + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v3 + with: + name: windows.release + - run: | + # Convert to Unix-style path so tar doesn't think drive letter is a hostname + STAGING_DIR="$(echo $PWD | tr -d ':' | tr '\\' '/')" + mkdir -p windows/amd64 && tar zxf "${STAGING_DIR}/envoy_binary.tar.gz" -C ./windows/amd64 + CI_SHA1=$(git rev-parse head) + export CI_SHA1 + ci/docker_ci.sh + shell: bash + env: + CI_BRANCH: ${{ github.ref }} + DOCKERHUB_USERNAME: ${{ needs.env.outputs.trusted == 'true' && secrets.DOCKERHUB_USERNAME || '' }} + DOCKERHUB_PASSWORD: ${{ needs.env.outputs.trusted == 'true' && secrets.DOCKERHUB_PASSWORD || '' }} + WINDOWS_BUILD_TYPE: ${{ matrix.build-type }} + WINDOWS_IMAGE_BASE: ${{ matrix.image-base }} + WINDOWS_IMAGE_TAG: ${{ matrix.image-tag }} From 2efbe23852f45e0dab2d8f85cd76c0a05ee0e55b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Oct 2023 15:44:47 +0100 Subject: [PATCH 22/72] build(deps): bump envoy-dependency-check from 0.1.10 to 0.1.11 in /tools/base (#30588) build(deps): bump envoy-dependency-check in /tools/base Bumps [envoy-dependency-check](https://github.com/envoyproxy/toolshed) from 0.1.10 to 0.1.11. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/0.1.10...0.1.11) --- updated-dependencies: - dependency-name: envoy-dependency-check dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 33648a485a00..6ae32732d784 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -32,9 +32,9 @@ aio-api-github==0.2.5 \ # -r requirements.in # envoy-base-utils # envoy-dependency-check -aio-api-nist==0.0.3 \ - --hash=sha256:3465d25e4ffdec35d824960e6d68fbff070f823fde55a40fa4eb53a7fd7d18ca \ - --hash=sha256:5ecf9f32e19ad8804bba1358dde93d1008029335009541dadc69c3823241b382 +aio-api-nist==0.0.4 \ + --hash=sha256:1f2909d60ed4fdb3a3ffc37ad6012666f34078b71648394be91f5e67bbf8b6ca \ + --hash=sha256:c948ee597b9e7cda7982e17bc4aca509b8aa68510899b42e2d382c10fb0d6f89 # via envoy-dependency-check aio-core==0.10.0 \ --hash=sha256:57e2d8dd8ee8779b0ebc2e2447492c0db8d7ed782e9ad1bb2662593740751acb \ @@ -491,9 +491,9 @@ envoy-code-check==0.5.8 \ --hash=sha256:03f32588cc9ed98ab6703cbca6f81df1527db71c3a0f962be6a6084ded40d528 \ --hash=sha256:2b12c51098c78d393823cf055a54e9308c37321d769041f01a2f35b04074d6f3 # via -r requirements.in -envoy-dependency-check==0.1.10 \ - --hash=sha256:4a637e0ed7184791b495041f9baf44567a95cbb979e1e5f26f6a8c33f724cf9e \ - --hash=sha256:e6ae41249f298c865a357edcd8e4850354f222ea4f0dd629c737706b23670c75 +envoy-dependency-check==0.1.11 \ + --hash=sha256:1c4e9f238787bda6d1270452538b361b3f33be3866640373161b70ac9c98c740 \ + --hash=sha256:3318930cf8632b3e9d0bfbd724f148c8eeb2b3e20784d92f62e16c6c706ba511 # via -r requirements.in envoy-distribution-distrotest==0.0.10 \ --hash=sha256:83e912c48da22eb3e514fc1142247d33eb7ed0d59e94eca2ffbd178a26fbf808 \ From 9743073deeb47749a36452693d72f113373a1264 Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 29 Oct 2023 10:07:05 +0000 Subject: [PATCH 23/72] github/actions: Fix action version (#30598) Signed-off-by: Ryan Northey --- .github/workflows/envoy-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 256bf633ac14..c3f16dc1e8f4 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -87,7 +87,7 @@ jobs: uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.1 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/gh-actions/github/run@ffa33da04ea0b9528f666a49ff2f336fedf9fca4 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.1 name: Create release with: source: | From 2afd97dcb9950ecc272f1bd77f76313be86f274c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 14:42:09 +0000 Subject: [PATCH 24/72] build(deps): bump envoyproxy/toolshed from actions-v0.1.1 to 0.1.2 (#30599) Bumps [envoyproxy/toolshed](https://github.com/envoyproxy/toolshed) from actions-v0.1.1 to 0.1.2. This release includes the previously tagged commit. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/actions-v0.1.1...actions-v0.1.2) --- updated-dependencies: - dependency-name: envoyproxy/toolshed dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_cache_docker.yml | 2 +- .github/workflows/_ci.yml | 12 ++++++------ .github/workflows/_stage_publish.yml | 2 +- .github/workflows/_workflow-start.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/commands.yml | 2 +- .github/workflows/envoy-dependency.yml | 14 +++++++------- .github/workflows/envoy-release.yml | 16 ++++++++-------- .github/workflows/envoy-sync.yml | 2 +- .github/workflows/mobile-android_tests.yml | 4 ++-- .github/workflows/workflow-complete.yml | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/_cache_docker.yml b/.github/workflows/_cache_docker.yml index 355e56b39a20..f777d9668ab3 100644 --- a/.github/workflows/_cache_docker.yml +++ b/.github/workflows/_cache_docker.yml @@ -37,7 +37,7 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.2 name: Prime Docker cache (${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}) with: image_tag: "${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}" diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 35d0a22849ae..e0b78a8f8595 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -125,11 +125,11 @@ jobs: steps: - if: ${{ inputs.cache_build_image }} name: Restore Docker cache (${{ inputs.cache_build_image }}) - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.2 with: image_tag: ${{ inputs.cache_build_image }} - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 id: checkout name: Checkout Envoy repository with: @@ -158,7 +158,7 @@ jobs: run: git config --global --add safe.directory /__w/envoy/envoy - if: ${{ inputs.diskspace_hack }} - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 - run: | echo "disk space at beginning of build:" df -h @@ -167,12 +167,12 @@ jobs: - if: ${{ inputs.run_pre }} name: Run pre action ${{ inputs.run_pre && format('({0})', inputs.run_pre) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.2 with: uses: ${{ inputs.run_pre }} with: ${{ inputs.run_pre_with }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.2 name: 'Run CI target ${{ inputs.target }}' with: catch-errors: ${{ inputs.catch-errors }} @@ -197,7 +197,7 @@ jobs: - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.2 with: uses: ${{ inputs.run_post }} with: ${{ inputs.run_post_with }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 55474ea66e8d..b896c77ea558 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -116,7 +116,7 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.2 with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index ddc2a2fffd8c..1be0e2004876 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -29,7 +29,7 @@ jobs: - if: ${{ steps.env.outputs.trusted != 'true' }} name: Start status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.2 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: ${{ inputs.workflow_name }} diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 9ef46d30acbe..f72b75167709 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Pre-cleanup - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 7d8d26134200..055649d8a0ab 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: actions: write checks: read steps: - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.2 with: token: ${{ secrets.GITHUB_TOKEN }} azp_org: cncf diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index facb70d4499c..e890b09dbe9d 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -43,13 +43,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: version name: Shorten (possible) SHA - uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.2 with: string: ${{ inputs.version }} length: 7 @@ -64,13 +64,13 @@ jobs: TARGET: ${{ inputs.task == 'bazel' && 'update' || 'api-update' }} TASK: ${{ inputs.task == 'bazel' && 'bazel' || 'api/bazel' }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.2 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.version.outputs.string }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 with: base: main body: | @@ -95,7 +95,7 @@ jobs: name: Update build image (PR) runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 id: checkout name: Checkout Envoy repository with: @@ -134,7 +134,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.2 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -163,7 +163,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index c3f16dc1e8f4..99c15c8537ca 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,7 +55,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -84,10 +84,10 @@ jobs: GITHUB_REF_NAME: ${{ github.ref_name }} - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.2 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.2 name: Create release with: source: | @@ -112,7 +112,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 with: base: ${{ github.ref_name }} commit: false @@ -137,7 +137,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -151,7 +151,7 @@ jobs: id: branch env: GITHUB_REF_NAME: ${{ github.ref_name }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.2 name: Sync version histories with: command: >- @@ -161,7 +161,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 with: append-commit-message: true base: ${{ github.ref_name }} @@ -191,7 +191,7 @@ jobs: name: Create release branch steps: - name: Checkout repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 9a3b1304ca3d..21cad96ac358 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -28,7 +28,7 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.1 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.2 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index 6f2770619eda..253018e621c5 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -34,7 +34,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy @@ -68,7 +68,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml index 5308c0cc53cc..3e97b7805f9d 100644 --- a/.github/workflows/workflow-complete.yml +++ b/.github/workflows/workflow-complete.yml @@ -53,7 +53,7 @@ jobs: echo "state=${STATE}" >> "$GITHUB_OUTPUT" id: job - name: Complete status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.1 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.2 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: Verify/examples From 489d41a83c340e42df31b22e8b264ae8a127fda6 Mon Sep 17 00:00:00 2001 From: IssaAbuKalbein <86603440+IssaAbuKalbein@users.noreply.github.com> Date: Sun, 29 Oct 2023 22:55:23 +0200 Subject: [PATCH 25/72] Fix segmentation fault in udp proxy filter (#30376) - proxy_filter.cc => Check host_info in onLoadDnsCacheComplete to verify that DNS lookup wasn't failed (because in case of failure "read_callbacks_->continueFilterChain();" will remove the session). - udp_proxy_filter.cc => Add the session before calling "onNewSession" (because "onNewSession" will remove the session if it failed to create upstream connection pool). Risk Level: low, fix a bug Testing: Unit Test Docs Changes: None Release Notes: None Platform Specific Features: None Signed-off-by: Issa Abu Kalbein Co-authored-by: Issa Abu Kalbein --- .../dynamic_forward_proxy/proxy_filter.cc | 12 ++- .../udp/udp_proxy/session_filters/filter.h | 3 +- .../filters/udp/udp_proxy/udp_proxy_filter.cc | 50 +++++++----- .../filters/udp/udp_proxy/udp_proxy_filter.h | 14 ++-- .../extensions/filters/udp/udp_proxy/mocks.cc | 1 + test/extensions/filters/udp/udp_proxy/mocks.h | 2 +- .../proxy_filter_integration_test.cc | 78 ++++++++++++------- .../proxy_filter_test.cc | 59 ++++++++++++-- 8 files changed, 151 insertions(+), 68 deletions(-) diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.cc index fdae4b4b5e83..fa0fc7f62c1d 100644 --- a/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter.cc @@ -106,13 +106,21 @@ ReadFilterStatus ProxyFilter::onData(Network::UdpRecvData& data) { return ReadFilterStatus::StopIteration; } -void ProxyFilter::onLoadDnsCacheComplete(const Common::DynamicForwardProxy::DnsHostInfoSharedPtr&) { +void ProxyFilter::onLoadDnsCacheComplete( + const Common::DynamicForwardProxy::DnsHostInfoSharedPtr& host_info) { ENVOY_LOG(debug, "load DNS cache complete, continuing"); + if (!host_info || !host_info->address()) { + ENVOY_LOG(debug, "empty DNS respose received"); + } + ASSERT(circuit_breaker_ != nullptr); circuit_breaker_.reset(); load_dns_cache_completed_ = true; - read_callbacks_->continueFilterChain(); + + if (!read_callbacks_->continueFilterChain()) { + return; + } while (!datagrams_buffer_.empty()) { BufferedDatagramPtr buffered_datagram = std::move(datagrams_buffer_.front()); diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h index 44ed8ab08790..d750b41d050b 100644 --- a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h +++ b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h @@ -43,8 +43,9 @@ class ReadFilterCallbacks : public FilterCallbacks { /** * If a read filter stopped filter iteration, continueFilterChain() can be called to continue the * filter chain. It will have onNewSession() called if it was not previously called. + * @return false if the session is removed and no longer valid, otherwise returns true. */ - virtual void continueFilterChain() PURE; + virtual bool continueFilterChain() PURE; }; class WriteFilterCallbacks : public FilterCallbacks {}; diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc index 935480811d1b..7594c861b954 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc @@ -173,11 +173,13 @@ UdpProxyFilter::ActiveSession* UdpProxyFilter::ClusterInfo::createSessionWithOpt } new_session->createFilterChain(); - new_session->onNewSession(); - auto new_session_ptr = new_session.get(); - sessions_.emplace(std::move(new_session)); + if (new_session->onNewSession()) { + auto new_session_ptr = new_session.get(); + sessions_.emplace(std::move(new_session)); + return new_session_ptr; + } - return new_session_ptr; + return nullptr; } Upstream::HostConstSharedPtr UdpProxyFilter::ClusterInfo::chooseHost( @@ -382,7 +384,7 @@ void UdpProxyFilter::UdpActiveSession::onReadReady() { cluster_.filter_.read_callbacks_->udpListener().flush(); } -void UdpProxyFilter::ActiveSession::onNewSession() { +bool UdpProxyFilter::ActiveSession::onNewSession() { for (auto& active_read_filter : read_filters_) { if (active_read_filter->initialized_) { // The filter may call continueFilterChain() in onNewSession(), causing next @@ -393,11 +395,11 @@ void UdpProxyFilter::ActiveSession::onNewSession() { active_read_filter->initialized_ = true; auto status = active_read_filter->read_filter_->onNewSession(); if (status == ReadFilterStatus::StopIteration) { - return; + return true; } } - createUpstream(); + return createUpstream(); } void UdpProxyFilter::ActiveSession::onData(Network::UdpRecvData& data) { @@ -467,7 +469,7 @@ void UdpProxyFilter::UdpActiveSession::writeUpstream(Network::UdpRecvData& data) } } -void UdpProxyFilter::ActiveSession::onContinueFilterChain(ActiveReadFilter* filter) { +bool UdpProxyFilter::ActiveSession::onContinueFilterChain(ActiveReadFilter* filter) { ASSERT(filter != nullptr); std::list::iterator entry = std::next(filter->entry()); @@ -479,18 +481,23 @@ void UdpProxyFilter::ActiveSession::onContinueFilterChain(ActiveReadFilter* filt (*entry)->initialized_ = true; auto status = (*entry)->read_filter_->onNewSession(); if (status == ReadFilterStatus::StopIteration) { - break; + return true; } } - createUpstream(); + if (!createUpstream()) { + cluster_.removeSession(this); + return false; + } + + return true; } -void UdpProxyFilter::UdpActiveSession::createUpstream() { +bool UdpProxyFilter::UdpActiveSession::createUpstream() { if (udp_socket_) { // A session filter may call on continueFilterChain(), after already creating the socket, // so we first check that the socket was not created already. - return; + return true; } if (!host_) { @@ -498,12 +505,13 @@ void UdpProxyFilter::UdpActiveSession::createUpstream() { if (host_ == nullptr) { ENVOY_LOG(debug, "cannot find any valid host."); cluster_.cluster_.info()->trafficStats()->upstream_cx_none_healthy_.inc(); - return; + return false; } } cluster_.addSession(host_.get(), this); createUdpSocket(host_); + return true; } void UdpProxyFilter::UdpActiveSession::createUdpSocket(const Upstream::HostConstSharedPtr& host) { @@ -793,26 +801,28 @@ UdpProxyFilter::TunnelingActiveSession::TunnelingActiveSession( ClusterInfo& cluster, Network::UdpRecvData::LocalPeerAddresses&& addresses) : ActiveSession(cluster, std::move(addresses), nullptr) {} -void UdpProxyFilter::TunnelingActiveSession::createUpstream() { +bool UdpProxyFilter::TunnelingActiveSession::createUpstream() { if (conn_pool_factory_) { // A session filter may call on continueFilterChain(), after already creating the upstream, // so we first check that the factory was not created already. - return; + return true; } conn_pool_factory_ = std::make_unique(); load_balancer_context_ = std::make_unique( cluster_.filter_.config_->hashPolicy(), addresses_.peer_, &udp_session_info_); - establishUpstreamConnection(); + return establishUpstreamConnection(); } -void UdpProxyFilter::TunnelingActiveSession::establishUpstreamConnection() { +bool UdpProxyFilter::TunnelingActiveSession::establishUpstreamConnection() { if (!createConnectionPool()) { ENVOY_LOG(debug, "failed to create upstream connection pool"); cluster_.cluster_stats_.sess_tunnel_failure_.inc(); - cluster_.removeSession(this); + return false; } + + return true; } bool UdpProxyFilter::TunnelingActiveSession::createConnectionPool() { @@ -900,9 +910,7 @@ void UdpProxyFilter::TunnelingActiveSession::onUpstreamEvent(Network::Connection event == Network::ConnectionEvent::LocalClose) { upstream_.reset(); - if (connecting) { - establishUpstreamConnection(); - } else { + if (!connecting || !establishUpstreamConnection()) { cluster_.removeSession(this); } } diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h index 8bf19ef77428..fc226272db62 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -457,7 +457,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, // SessionFilters::ReadFilterCallbacks uint64_t sessionId() const override { return parent_.sessionId(); }; StreamInfo::StreamInfo& streamInfo() override { return parent_.streamInfo(); }; - void continueFilterChain() override { parent_.onContinueFilterChain(this); } + bool continueFilterChain() override { return parent_.onContinueFilterChain(this); } void injectDatagramToFilterChain(Network::UdpRecvData& data) override { parent_.onInjectReadDatagramToFilterChain(this, data); } @@ -509,13 +509,13 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, return absl::nullopt; } - void onNewSession(); + bool onNewSession(); void onData(Network::UdpRecvData& data); void processUpstreamDatagram(Network::UdpRecvData& data); void writeDownstream(Network::UdpRecvData& data); void resetIdleTimer(); - virtual void createUpstream() PURE; + virtual bool createUpstream() PURE; virtual void writeUpstream(Network::UdpRecvData& data) PURE; virtual void onIdleTimer() PURE; @@ -525,7 +525,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, uint64_t sessionId() const { return session_id_; }; StreamInfo::StreamInfo& streamInfo() { return udp_session_info_; }; - void onContinueFilterChain(ActiveReadFilter* filter); + bool onContinueFilterChain(ActiveReadFilter* filter); void onInjectReadDatagramToFilterChain(ActiveReadFilter* filter, Network::UdpRecvData& data); void onInjectWriteDatagramToFilterChain(ActiveWriteFilter* filter, Network::UdpRecvData& data); @@ -595,7 +595,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, ~UdpActiveSession() override = default; // ActiveSession - void createUpstream() override; + bool createUpstream() override; void writeUpstream(Network::UdpRecvData& data) override; void onIdleTimer() override; @@ -644,7 +644,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, ~TunnelingActiveSession() override = default; // ActiveSession - void createUpstream() override; + bool createUpstream() override; void writeUpstream(Network::UdpRecvData& data) override; void onIdleTimer() override; @@ -666,7 +666,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, private: using BufferedDatagramPtr = std::unique_ptr; - void establishUpstreamConnection(); + bool establishUpstreamConnection(); bool createConnectionPool(); void maybeBufferDatagram(Network::UdpRecvData& data); void flushBuffer(); diff --git a/test/extensions/filters/udp/udp_proxy/mocks.cc b/test/extensions/filters/udp/udp_proxy/mocks.cc index b8a59bb23c66..7c654662a83e 100644 --- a/test/extensions/filters/udp/udp_proxy/mocks.cc +++ b/test/extensions/filters/udp/udp_proxy/mocks.cc @@ -15,6 +15,7 @@ namespace SessionFilters { MockReadFilterCallbacks::MockReadFilterCallbacks() { ON_CALL(*this, sessionId()).WillByDefault(Return(session_id_)); ON_CALL(*this, streamInfo()).WillByDefault(ReturnRef(stream_info_)); + ON_CALL(*this, continueFilterChain()).WillByDefault(Return(true)); } MockReadFilterCallbacks::~MockReadFilterCallbacks() = default; diff --git a/test/extensions/filters/udp/udp_proxy/mocks.h b/test/extensions/filters/udp/udp_proxy/mocks.h index ce23c9374e1a..f0bd4e7505ef 100644 --- a/test/extensions/filters/udp/udp_proxy/mocks.h +++ b/test/extensions/filters/udp/udp_proxy/mocks.h @@ -22,7 +22,7 @@ class MockReadFilterCallbacks : public ReadFilterCallbacks { MOCK_METHOD(uint64_t, sessionId, (), (const)); MOCK_METHOD(StreamInfo::StreamInfo&, streamInfo, ()); - MOCK_METHOD(void, continueFilterChain, ()); + MOCK_METHOD(bool, continueFilterChain, ()); MOCK_METHOD(void, injectDatagramToFilterChain, (Network::UdpRecvData & data)); uint64_t session_id_{1}; diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc index cf76b3ae7b21..d3a822c31bab 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -35,22 +35,25 @@ class DynamicForwardProxyIntegrationTest uint32_t max_buffered_bytes_; }; - void setup(absl::optional buffer_config = absl::nullopt, uint32_t max_hosts = 1024, + void setup(std::string upsteam_host = "localhost", + absl::optional buffer_config = absl::nullopt, uint32_t max_hosts = 1024, uint32_t max_pending_requests = 1024) { setUdpFakeUpstream(FakeUpstreamConfig::UdpConfig()); - config_helper_.addConfigModifier([this, buffer_config, max_hosts, max_pending_requests]( - envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - // Switch predefined cluster_0 to CDS filesystem sourcing. - bootstrap.mutable_dynamic_resources()->mutable_cds_config()->set_resource_api_version( - envoy::config::core::v3::ApiVersion::V3); - bootstrap.mutable_dynamic_resources() - ->mutable_cds_config() - ->mutable_path_config_source() - ->set_path(cds_helper_.cdsPath()); - bootstrap.mutable_static_resources()->clear_clusters(); - - std::string filter_config = fmt::format(R"EOF( + config_helper_.addConfigModifier( + [this, upsteam_host, buffer_config, max_hosts, + max_pending_requests](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Switch predefined cluster_0 to CDS filesystem sourcing. + bootstrap.mutable_dynamic_resources()->mutable_cds_config()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); + bootstrap.mutable_dynamic_resources() + ->mutable_cds_config() + ->mutable_path_config_source() + ->set_path(cds_helper_.cdsPath()); + bootstrap.mutable_static_resources()->clear_clusters(); + + std::string filter_config = fmt::format( + R"EOF( name: udp_proxy typed_config: '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig @@ -66,7 +69,7 @@ name: udp_proxy - name: setter typed_config: '@type': type.googleapis.com/test.extensions.filters.udp.udp_proxy.session_filters.DynamicForwardProxySetterFilterConfig - host: localhost + host: {} port: {} - name: dfp typed_config: @@ -79,24 +82,23 @@ name: udp_proxy dns_cache_circuit_breaker: max_pending_requests: {} )EOF", - fake_upstreams_[0]->localAddress()->ip()->port(), - Network::Test::ipVersionToDnsFamily(GetParam()), - max_hosts, max_pending_requests); + upsteam_host, fake_upstreams_[0]->localAddress()->ip()->port(), + Network::Test::ipVersionToDnsFamily(GetParam()), max_hosts, max_pending_requests); - if (buffer_config.has_value()) { - filter_config += fmt::format(R"EOF( + if (buffer_config.has_value()) { + filter_config += fmt::format(R"EOF( buffer_options: max_buffered_datagrams: {} max_buffered_bytes: {} )EOF", - buffer_config.value().max_buffered_datagrams_, - buffer_config.value().max_buffered_bytes_); - } + buffer_config.value().max_buffered_datagrams_, + buffer_config.value().max_buffered_bytes_); + } - auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); - auto* filter = listener->add_listener_filters(); - TestUtility::loadFromYaml(filter_config, *filter); - }); + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter = listener->add_listener_filters(); + TestUtility::loadFromYaml(filter_config, *filter); + }); // Setup the initial CDS cluster. cluster_.mutable_connect_timeout()->CopyFrom( @@ -157,7 +159,7 @@ TEST_P(DynamicForwardProxyIntegrationTest, BasicFlow) { } TEST_P(DynamicForwardProxyIntegrationTest, BasicFlowWithBuffering) { - setup(BufferConfig{1, 1024}); + setup("localhost", BufferConfig{1, 1024}); const uint32_t port = lookupPort("listener_0"); const auto listener_address = Network::Utility::resolveUrl( fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); @@ -175,7 +177,7 @@ TEST_P(DynamicForwardProxyIntegrationTest, BasicFlowWithBuffering) { } TEST_P(DynamicForwardProxyIntegrationTest, BufferOverflowDueToDatagramSize) { - setup(BufferConfig{1, 2}); + setup("localhost", BufferConfig{1, 2}); const uint32_t port = lookupPort("listener_0"); const auto listener_address = Network::Utility::resolveUrl( fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); @@ -196,6 +198,26 @@ TEST_P(DynamicForwardProxyIntegrationTest, BufferOverflowDueToDatagramSize) { EXPECT_EQ("hello2", request_datagram.buffer_->toString()); } +TEST_P(DynamicForwardProxyIntegrationTest, EmptyDnsResponseDueToDummyHost) { + setup("dummyhost"); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + Network::Test::UdpSyncPeer client(version_); + + client.write("hello1", *listener_address); + test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); + + // The DNS response is empty, so will not be found any valid host and session will be removed. + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_none_healthy", 1); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + + // DNS cache hit but still no host found. + client.write("hello2", *listener_address); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_none_healthy", 2); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + } // namespace } // namespace DynamicForwardProxy } // namespace SessionFilters diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc index 06aafcca58c2..6af769197b9d 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc @@ -209,8 +209,12 @@ TEST_F(DynamicProxyFilterTest, EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); EXPECT_CALL(callbacks_, continueFilterChain()); - filter_->onLoadDnsCacheComplete( - std::make_shared()); + + auto host_info = std::make_shared(); + host_info->address_ = Network::Utility::parseInternetAddress("1.2.3.4", 50); + EXPECT_CALL(*host_info, address()); + filter_->onLoadDnsCacheComplete(host_info); + EXPECT_CALL(*handle, onDestroy()); } @@ -237,8 +241,12 @@ TEST_F(DynamicProxyFilterTest, LoadingCacheEntryWithDefaultBufferConfig) { EXPECT_CALL(callbacks_, continueFilterChain()); EXPECT_CALL(callbacks_, injectDatagramToFilterChain(_)).Times(2); - filter_->onLoadDnsCacheComplete( - std::make_shared()); + + auto host_info = std::make_shared(); + host_info->address_ = Network::Utility::parseInternetAddress("1.2.3.4", 50); + EXPECT_CALL(*host_info, address()); + filter_->onLoadDnsCacheComplete(host_info); + EXPECT_CALL(*handle, onDestroy()); EXPECT_FALSE(filter_config_->bufferEnabled()); } @@ -267,8 +275,12 @@ TEST_F(DynamicProxyFilterTest, LoadingCacheEntryWithBufferSizeOverflow) { EXPECT_CALL(callbacks_, continueFilterChain()); EXPECT_CALL(callbacks_, injectDatagramToFilterChain(_)); - filter_->onLoadDnsCacheComplete( - std::make_shared()); + + auto host_info = std::make_shared(); + host_info->address_ = Network::Utility::parseInternetAddress("1.2.3.4", 50); + EXPECT_CALL(*host_info, address()); + filter_->onLoadDnsCacheComplete(host_info); + EXPECT_CALL(*handle, onDestroy()); EXPECT_FALSE(filter_config_->bufferEnabled()); } @@ -297,12 +309,43 @@ TEST_F(DynamicProxyFilterTest, LoadingCacheEntryWithBufferBytesOverflow) { EXPECT_CALL(callbacks_, continueFilterChain()); EXPECT_CALL(callbacks_, injectDatagramToFilterChain(_)); - filter_->onLoadDnsCacheComplete( - std::make_shared()); + + auto host_info = std::make_shared(); + host_info->address_ = Network::Utility::parseInternetAddress("1.2.3.4", 50); + EXPECT_CALL(*host_info, address()); + filter_->onLoadDnsCacheComplete(host_info); + EXPECT_CALL(*handle, onDestroy()); EXPECT_FALSE(filter_config_->bufferEnabled()); } +TEST_F(DynamicProxyFilterTest, LoadingCacheEntryWithContinueFilterChainFailure) { + FilterConfig config; + config.mutable_buffer_options(); + setup(config); + + setFilterState("host", 50); + EXPECT_TRUE(filter_config_->bufferEnabled()); + Upstream::ResourceAutoIncDec* circuit_breakers_{ + new Upstream::ResourceAutoIncDec(pending_requests_)}; + EXPECT_CALL(*dns_cache_manager_->dns_cache_, canCreateDnsRequest_()) + .WillOnce(Return(circuit_breakers_)); + Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle* handle = + new Extensions::Common::DynamicForwardProxy::MockLoadDnsCacheEntryHandle(); + EXPECT_CALL(*dns_cache_manager_->dns_cache_, loadDnsCacheEntry_(Eq("host"), 50, _, _)) + .WillOnce(Return( + MockLoadDnsCacheEntryResult{LoadDnsCacheEntryStatus::Loading, handle, absl::nullopt})); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onNewSession()); + EXPECT_EQ(ReadFilterStatus::StopIteration, filter_->onData(recv_data_stub1_)); + + // Session is removed and no longer valid, no datagrams will be injected. + EXPECT_CALL(callbacks_, continueFilterChain()).WillOnce(Return(false)); + EXPECT_CALL(callbacks_, injectDatagramToFilterChain(_)).Times(0); + filter_->onLoadDnsCacheComplete(nullptr); + + EXPECT_CALL(*handle, onDestroy()); +} + } // namespace } // namespace DynamicForwardProxy } // namespace SessionFilters From e6340c05ac5d913c19b2129762d92f26b3955663 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Mon, 30 Oct 2023 00:39:52 -0400 Subject: [PATCH 26/72] mobile: Remove JWT token APIs in mobile xDS (#30576) mobile: Remove support for JWT tokens in xDS We started off using JWT tokens for authentication to the xDS management server (e.g. GCP's Traffic Director), but now that we have authentication token support via XdsBuilder::setAuthenticationToken, we remove JWT token support, as it is not recommended to use for this type of authentication and it is directly supported only on GoogleGrpc, and we are switching to EnvoyGrpc. Signed-off-by: Ali Beyad --- mobile/library/cc/engine_builder.cc | 14 ------- mobile/library/cc/engine_builder.h | 17 -------- mobile/library/common/jni/jni_interface.cc | 9 +--- .../engine/EnvoyConfiguration.java | 20 +++------ .../envoymobile/engine/JniLibrary.java | 7 ++-- .../impl/NativeCronvoyEngineBuilderImpl.java | 2 +- .../envoyproxy/envoymobile/EngineBuilder.kt | 24 ----------- .../library/objective-c/EnvoyConfiguration.h | 4 -- .../library/objective-c/EnvoyConfiguration.mm | 8 ---- mobile/library/swift/EngineBuilder.swift | 32 --------------- mobile/test/cc/unit/envoy_config_test.cc | 41 ------------------- .../engine/EnvoyConfigurationTest.kt | 4 -- .../envoymobile/EngineBuilderTest.kt | 2 - mobile/test/swift/EngineBuilderTests.swift | 15 ------- 14 files changed, 12 insertions(+), 187 deletions(-) diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index c469f5daf0ec..83512f01dd7e 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -52,14 +52,6 @@ XdsBuilder& XdsBuilder::setAuthenticationToken(std::string token_header, std::st return *this; } -XdsBuilder& XdsBuilder::setJwtAuthenticationToken(std::string token, - const int token_lifetime_in_seconds) { - jwt_token_ = std::move(token); - jwt_token_lifetime_in_seconds_ = - token_lifetime_in_seconds > 0 ? token_lifetime_in_seconds : DefaultJwtTokenLifetimeSeconds; - return *this; -} - XdsBuilder& XdsBuilder::setSslRootCerts(std::string root_certs) { ssl_root_certs_ = std::move(root_certs); return *this; @@ -106,12 +98,6 @@ void XdsBuilder::build(envoy::config::bootstrap::v3::Bootstrap* bootstrap) const auto* auth_token_metadata = grpc_service.add_initial_metadata(); auth_token_metadata->set_key(authentication_token_header_); auth_token_metadata->set_value(authentication_token_); - } else if (!jwt_token_.empty()) { - auto& jwt = *grpc_service.mutable_google_grpc() - ->add_call_credentials() - ->mutable_service_account_jwt_access(); - jwt.set_json_key(jwt_token_); - jwt.set_token_lifetime_seconds(jwt_token_lifetime_in_seconds_); } if (!sni_.empty()) { auto& channel_args = diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index e51aa730f085..2233388fb8a1 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -22,7 +22,6 @@ namespace Envoy { namespace Platform { -constexpr int DefaultJwtTokenLifetimeSeconds = 60 * 60 * 24 * 90; // 90 days constexpr int DefaultXdsTimeout = 5; // Forward declaration so it can be referenced by XdsBuilder. @@ -58,24 +57,10 @@ class XdsBuilder final { // https://cloud.google.com/docs/authentication/api-keys for details), invoke: // builder.setAuthenticationToken("x-goog-api-key", api_key_token) // - // If this method is called, then don't call setJwtAuthenticationToken. - // // `token_header`: the header name for which the the `token` will be set as a value. // `token`: the authentication token. XdsBuilder& setAuthenticationToken(std::string token_header, std::string token); - // Sets JWT as the authentication method to the xDS management server, using the given token. - // - // If setAuthenticationToken is called, then invocations of this method will be ignored. - // - // `token`: the JWT token used to authenticate the client to the xDS management server. - // `token_lifetime_in_seconds`: the lifetime of the JWT token, in seconds. If none - // (or 0) is specified, then DefaultJwtTokenLifetimeSeconds is used. - // TODO(abeyad): Deprecate and remove this. - XdsBuilder& - setJwtAuthenticationToken(std::string token, - int token_lifetime_in_seconds = DefaultJwtTokenLifetimeSeconds); - // Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC // connection. If no root certs are specified, the operating system defaults are used. XdsBuilder& setSslRootCerts(std::string root_certs); @@ -127,8 +112,6 @@ class XdsBuilder final { int xds_server_port_; std::string authentication_token_header_; std::string authentication_token_; - std::string jwt_token_; - int jwt_token_lifetime_in_seconds_ = DefaultJwtTokenLifetimeSeconds; std::string ssl_root_certs_; std::string sni_; std::string rtds_resource_name_; diff --git a/mobile/library/common/jni/jni_interface.cc b/mobile/library/common/jni/jni_interface.cc index 955a5eb35978..a1c618f42d91 100644 --- a/mobile/library/common/jni/jni_interface.cc +++ b/mobile/library/common/jni/jni_interface.cc @@ -1310,9 +1310,8 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr jboolean trust_chain_verification, jobjectArray filter_chain, jobjectArray stat_sinks, jboolean enable_platform_certificates_validation, jobjectArray runtime_guards, jstring rtds_resource_name, jlong rtds_timeout_seconds, jstring xds_address, jlong xds_port, - jstring xds_auth_header, jstring xds_auth_token, jstring xds_jwt_token, - jlong xds_jwt_token_lifetime, jstring xds_root_certs, jstring xds_sni, jstring node_id, - jstring node_region, jstring node_zone, jstring node_sub_zone, + jstring xds_auth_header, jstring xds_auth_token, jstring xds_root_certs, jstring xds_sni, + jstring node_id, jstring node_region, jstring node_zone, jstring node_sub_zone, jbyteArray serialized_node_metadata, jstring cds_resources_locator, jlong cds_timeout_seconds, jboolean enable_cds) { Envoy::Platform::EngineBuilder builder; @@ -1340,10 +1339,6 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr xds_builder.setAuthenticationToken(std::move(native_xds_auth_header), getCppString(env, xds_auth_token)); } - std::string native_jwt_token = getCppString(env, xds_jwt_token); - if (!native_jwt_token.empty()) { - xds_builder.setJwtAuthenticationToken(std::move(native_jwt_token), xds_jwt_token_lifetime); - } std::string native_root_certs = getCppString(env, xds_root_certs); if (!native_root_certs.empty()) { xds_builder.setSslRootCerts(std::move(native_root_certs)); diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index ac03d3184406..44b06960e648 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -66,8 +66,6 @@ public enum TrustChainVerification { public final Integer xdsPort; public final String xdsAuthHeader; public final String xdsAuthToken; - public final String xdsJwtToken; - public final Integer xdsJwtTokenLifetime; public final String xdsRootCerts; public final String xdsSni; public final String nodeId; @@ -147,9 +145,6 @@ public enum TrustChainVerification { * @param xdsAuthToken the token to send as the authentication * header value to authenticate with the * xDS server. - * @param xdsJwtToken the JWT token to use for authenticating - * with the xDS server. - * @param xdsJwtTokenLifetime the lifetime of the JWT token. * @param xdsRootCerts the root certificates to use for the TLS * handshake during connection establishment * with the xDS management server. @@ -183,10 +178,9 @@ public EnvoyConfiguration( Map keyValueStores, List statSinks, Map runtimeGuards, boolean enablePlatformCertificatesValidation, String rtdsResourceName, Integer rtdsTimeoutSeconds, String xdsAddress, Integer xdsPort, - String xdsAuthHeader, String xdsAuthToken, String xdsJwtToken, Integer xdsJwtTokenLifetime, - String xdsRootCerts, String xdsSni, String nodeId, String nodeRegion, String nodeZone, - String nodeSubZone, Struct nodeMetadata, String cdsResourcesLocator, - Integer cdsTimeoutSeconds, boolean enableCds) { + String xdsAuthHeader, String xdsAuthToken, String xdsRootCerts, String xdsSni, String nodeId, + String nodeRegion, String nodeZone, String nodeSubZone, Struct nodeMetadata, + String cdsResourcesLocator, Integer cdsTimeoutSeconds, boolean enableCds) { JniLibrary.load(); this.grpcStatsDomain = grpcStatsDomain; this.connectTimeoutSeconds = connectTimeoutSeconds; @@ -248,8 +242,6 @@ public EnvoyConfiguration( this.xdsPort = xdsPort; this.xdsAuthHeader = xdsAuthHeader; this.xdsAuthToken = xdsAuthToken; - this.xdsJwtToken = xdsJwtToken; - this.xdsJwtTokenLifetime = xdsJwtTokenLifetime; this.xdsRootCerts = xdsRootCerts; this.xdsSni = xdsSni; this.nodeId = nodeId; @@ -284,9 +276,9 @@ public long createBootstrap() { maxConnectionsPerHost, statsFlushSeconds, streamIdleTimeoutSeconds, perTryIdleTimeoutSeconds, appVersion, appId, enforceTrustChainVerification, filterChain, statsSinks, enablePlatformCertificatesValidation, runtimeGuards, rtdsResourceName, - rtdsTimeoutSeconds, xdsAddress, xdsPort, xdsAuthHeader, xdsAuthToken, xdsJwtToken, - xdsJwtTokenLifetime, xdsRootCerts, xdsSni, nodeId, nodeRegion, nodeZone, nodeSubZone, - nodeMetadata.toByteArray(), cdsResourcesLocator, cdsTimeoutSeconds, enableCds); + rtdsTimeoutSeconds, xdsAddress, xdsPort, xdsAuthHeader, xdsAuthToken, xdsRootCerts, xdsSni, + nodeId, nodeRegion, nodeZone, nodeSubZone, nodeMetadata.toByteArray(), cdsResourcesLocator, + cdsTimeoutSeconds, enableCds); } static class ConfigurationException extends RuntimeException { diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index 5535346c5bb3..298b2a772a95 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -319,8 +319,7 @@ public static native long createBootstrap( boolean trustChainVerification, byte[][] filterChain, byte[][] statSinks, boolean enablePlatformCertificatesValidation, byte[][] runtimeGuards, String rtdsResourceName, long rtdsTimeoutSeconds, String xdsAddress, long xdsPort, String xdsAuthenticationHeader, - String xdsAuthenticationToken, String xdsJwtToken, long xdsJwtTokenLifetime, - String xdsRootCerts, String xdsSni, String nodeId, String nodeRegion, String nodeZone, - String nodeSubZone, byte[] nodeMetadata, String cdsResourcesLocator, long cdsTimeoutSeconds, - boolean enableCds); + String xdsAuthenticationToken, String xdsRootCerts, String xdsSni, String nodeId, + String nodeRegion, String nodeZone, String nodeSubZone, byte[] nodeMetadata, + String cdsResourcesLocator, long cdsTimeoutSeconds, boolean enableCds); } diff --git a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java index 2d45070f23e5..82f49baba0ad 100644 --- a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java +++ b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java @@ -135,7 +135,7 @@ mEnableDrainPostDnsRefresh, quicEnabled(), quicConnectionOptions(), keyValueStores, statSinks, runtimeGuards, mEnablePlatformCertificatesValidation, /*rtdsResourceName=*/"", /*rtdsTimeoutSeconds=*/0, /*xdsAddress=*/"", /*xdsPort=*/0, /*xdsAuthenticationHeader=*/"", /*xdsAuthenticationToken=*/"", - /*xdsJwtToken=*/"", /*xdsJwtTokenLifetime=*/0, /*xdsSslRootCerts=*/"", + /*xdsSslRootCerts=*/"", /*xdsSni=*/"", mNodeId, mNodeRegion, mNodeZone, mNodeSubZone, Struct.getDefaultInstance(), /*cdsResourcesLocator=*/"", /*cdsTimeoutSeconds=*/0, /*enableCds=*/false); diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index 2fe601d87168..0be2e2152645 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -33,14 +33,11 @@ class Custom(val yaml: String) : BaseConfiguration() */ open class XdsBuilder(internal val xdsServerAddress: String, internal val xdsServerPort: Int) { companion object { - private const val DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS: Int = 60 * 60 * 24 * 90 // 90 days private const val DEFAULT_XDS_TIMEOUT_IN_SECONDS: Int = 5 } internal var authHeader: String? = null internal var authToken: String? = null - internal var jwtToken: String? = null - internal var jwtTokenLifetimeInSeconds: Int = DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS internal var sslRootCerts: String? = null internal var sni: String? = null internal var rtdsResourceName: String? = null @@ -63,25 +60,6 @@ open class XdsBuilder(internal val xdsServerAddress: String, internal val xdsSer return this } - /** - * Sets JWT as the authentication method to the xDS management server, using the given token. - * - * @param token The JWT token used to authenticate the client to the xDS management server. - * @param tokenLifetimeInSeconds The lifetime of the JWT token, in seconds. If none - * (or 0) is specified, then defaultJwtTokenLifetimeSeconds is used. - * @return this builder. - */ - fun setJwtAuthenticationToken( - token: String, - tokenLifetimeInSeconds: Int = DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS - ): XdsBuilder { - this.jwtToken = token - this.jwtTokenLifetimeInSeconds = - if (tokenLifetimeInSeconds > 0) tokenLifetimeInSeconds - else DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS - return this - } - /** * Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC * connection. If no root certs are specified, the operating system defaults are used. @@ -734,8 +712,6 @@ open class EngineBuilder(private val configuration: BaseConfiguration = Standard xdsBuilder?.xdsServerPort ?: 0, xdsBuilder?.authHeader, xdsBuilder?.authToken, - xdsBuilder?.jwtToken, - xdsBuilder?.jwtTokenLifetimeInSeconds ?: 0, xdsBuilder?.sslRootCerts, xdsBuilder?.sni, nodeId, diff --git a/mobile/library/objective-c/EnvoyConfiguration.h b/mobile/library/objective-c/EnvoyConfiguration.h index 0f9fb7d43cb5..4a266109c680 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.h +++ b/mobile/library/objective-c/EnvoyConfiguration.h @@ -54,8 +54,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UInt32 xdsServerPort; @property (nonatomic, strong, nullable) NSString *xdsAuthHeader; @property (nonatomic, strong, nullable) NSString *xdsAuthToken; -@property (nonatomic, strong, nullable) NSString *xdsJwtToken; -@property (nonatomic, assign) UInt32 xdsJwtTokenLifetimeSeconds; @property (nonatomic, strong, nullable) NSString *xdsSslRootCerts; @property (nonatomic, strong, nullable) NSString *xdsSni; @property (nonatomic, strong, nullable) NSString *rtdsResourceName; @@ -117,8 +115,6 @@ NS_ASSUME_NONNULL_BEGIN xdsServerPort:(UInt32)xdsServerPort xdsAuthHeader:(nullable NSString *)xdsAuthHeader xdsAuthToken:(nullable NSString *)xdsAuthToken - xdsJwtToken:(nullable NSString *)xdsJwtToken - xdsJwtTokenLifetimeSeconds:(UInt32)xdsJwtTokenLifetimeSeconds xdsSslRootCerts:(nullable NSString *)xdsSslRootCerts xdsSni:(nullable NSString *)xdsSni rtdsResourceName:(nullable NSString *)rtdsResourceName diff --git a/mobile/library/objective-c/EnvoyConfiguration.mm b/mobile/library/objective-c/EnvoyConfiguration.mm index f52ac74a4425..d16868d8807d 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.mm +++ b/mobile/library/objective-c/EnvoyConfiguration.mm @@ -116,8 +116,6 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain xdsServerPort:(UInt32)xdsServerPort xdsAuthHeader:(nullable NSString *)xdsAuthHeader xdsAuthToken:(nullable NSString *)xdsAuthToken - xdsJwtToken:(nullable NSString *)xdsJwtToken - xdsJwtTokenLifetimeSeconds:(UInt32)xdsJwtTokenLifetimeSeconds xdsSslRootCerts:(nullable NSString *)xdsSslRootCerts xdsSni:(nullable NSString *)xdsSni rtdsResourceName:(nullable NSString *)rtdsResourceName @@ -172,8 +170,6 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain self.xdsServerPort = xdsServerPort; self.xdsAuthHeader = xdsAuthHeader; self.xdsAuthToken = xdsAuthToken; - self.xdsJwtToken = xdsJwtToken; - self.xdsJwtTokenLifetimeSeconds = xdsJwtTokenLifetimeSeconds; self.xdsSslRootCerts = xdsSslRootCerts; self.xdsSni = xdsSni; self.rtdsResourceName = rtdsResourceName; @@ -275,10 +271,6 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain xdsBuilder.setAuthenticationToken([self.xdsAuthHeader toCXXString], [self.xdsAuthToken toCXXString]); } - if (self.xdsJwtToken != nil) { - xdsBuilder.setJwtAuthenticationToken([self.xdsJwtToken toCXXString], - self.xdsJwtTokenLifetimeSeconds); - } if (self.xdsSslRootCerts != nil) { xdsBuilder.setSslRootCerts([self.xdsSslRootCerts toCXXString]); } diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index 42132ceff7dd..e75cda693e7e 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -14,15 +14,12 @@ import Foundation /// This class is typically used as input to the EngineBuilder's setXds() method. @objcMembers open class XdsBuilder: NSObject { - public static let defaultJwtTokenLifetimeInSeconds: UInt32 = 60 * 60 * 24 * 90 // 90 days public static let defaultXdsTimeoutInSeconds: UInt32 = 5 let xdsServerAddress: String let xdsServerPort: UInt32 var authHeader: String? var authToken: String? - var jwtToken: String? - var jwtTokenLifetimeInSeconds: UInt32 = XdsBuilder.defaultJwtTokenLifetimeInSeconds var sslRootCerts: String? var sni: String? var rtdsResourceName: String? @@ -56,25 +53,6 @@ open class XdsBuilder: NSObject { return self } - /// Sets JWT as the authentication method to the xDS management server, using the given token. - /// - /// - parameter token: The JWT token used to authenticate the client to the xDS - /// management server. - /// - parameter tokenLifetimeInSeconds: the lifetime of the JWT token, in seconds. If - /// none (or 0) is specified, then - /// defaultJwtTokenLifetimeSeconds is used. - /// - /// - returns: This builder. - @discardableResult - public func setJwtAuthenticationToken( - token: String, - tokenLifetimeInSeconds: UInt32 = XdsBuilder.defaultJwtTokenLifetimeInSeconds) -> Self { - self.jwtToken = token - self.jwtTokenLifetimeInSeconds = (tokenLifetimeInSeconds > 0) ? - tokenLifetimeInSeconds : XdsBuilder.defaultJwtTokenLifetimeInSeconds - return self - } - /// Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC /// connection. If no root certs are specified, the operating system defaults are used. /// @@ -787,8 +765,6 @@ open class EngineBuilder: NSObject { var xdsServerPort: UInt32 = 0 var xdsAuthHeader: String? var xdsAuthToken: String? - var xdsJwtToken: String? - var xdsJwtTokenLifetimeSeconds: UInt32 = 0 var xdsSslRootCerts: String? var xdsSni: String? var rtdsResourceName: String? @@ -802,8 +778,6 @@ open class EngineBuilder: NSObject { xdsServerPort = self.xdsBuilder?.xdsServerPort ?? 0 xdsAuthHeader = self.xdsBuilder?.authHeader xdsAuthToken = self.xdsBuilder?.authToken - xdsJwtToken = self.xdsBuilder?.jwtToken - xdsJwtTokenLifetimeSeconds = self.xdsBuilder?.jwtTokenLifetimeInSeconds ?? 0 xdsSslRootCerts = self.xdsBuilder?.sslRootCerts xdsSni = self.xdsBuilder?.sni rtdsResourceName = self.xdsBuilder?.rtdsResourceName @@ -856,8 +830,6 @@ open class EngineBuilder: NSObject { xdsServerPort: xdsServerPort, xdsAuthHeader: xdsAuthHeader, xdsAuthToken: xdsAuthToken, - xdsJwtToken: xdsJwtToken, - xdsJwtTokenLifetimeSeconds: xdsJwtTokenLifetimeSeconds, xdsSslRootCerts: xdsSslRootCerts, xdsSni: xdsSni, rtdsResourceName: rtdsResourceName, @@ -964,10 +936,6 @@ private extension EngineBuilder { cxxXdsBuilder.setAuthenticationToken(xdsAuthHeader.toCXX(), xdsBuilder.authToken?.toCXX() ?? "".toCXX()) } - if let xdsJwtToken = xdsBuilder.jwtToken { - cxxXdsBuilder.setJwtAuthenticationToken(xdsJwtToken.toCXX(), - Int32(xdsBuilder.jwtTokenLifetimeInSeconds)) - } if let xdsSslRootCerts = xdsBuilder.sslRootCerts { cxxXdsBuilder.setSslRootCerts(xdsSslRootCerts.toCXX()) } diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index f5291baf20a3..69c63165a5d7 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -322,47 +322,6 @@ TEST(TestConfig, XdsConfig) { .at("grpc.default_authority") .string_value(), "fake-td.googleapis.com"); - - // With JWT security credentials. - xds_builder = - XdsBuilder(/*xds_server_address=*/"fake-td.googleapis.com", /*xds_server_port=*/12345); - xds_builder.setJwtAuthenticationToken(/*token=*/"my_jwt_token", - /*token_lifetime_in_seconds=*/500); - xds_builder.setSslRootCerts(/*root_certs=*/"my_root_cert"); - xds_builder.setSni(/*sni=*/"fake-td.googleapis.com"); - engine_builder.setXds(std::move(xds_builder)); - bootstrap = engine_builder.generateBootstrap(); - auto& ads_config_with_jwt_tokens = bootstrap->dynamic_resources().ads_config(); - EXPECT_EQ(ads_config_with_jwt_tokens.api_type(), envoy::config::core::v3::ApiConfigSource::GRPC); - EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0).google_grpc().target_uri(), - "fake-td.googleapis.com:12345"); - EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0).google_grpc().stat_prefix(), "ads"); - EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) - .google_grpc() - .channel_credentials() - .ssl_credentials() - .root_certs() - .inline_string(), - "my_root_cert"); - EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) - .google_grpc() - .call_credentials(0) - .service_account_jwt_access() - .json_key(), - "my_jwt_token"); - EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) - .google_grpc() - .call_credentials(0) - .service_account_jwt_access() - .token_lifetime_seconds(), - 500); - EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) - .google_grpc() - .channel_args() - .args() - .at("grpc.default_authority") - .string_value(), - "fake-td.googleapis.com"); } TEST(TestConfig, CopyConstructor) { diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt index b3d6c05cea97..cd7ddb5a728f 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -105,8 +105,6 @@ class EnvoyConfigurationTest { xdsPort: Int = 0, xdsAuthHeader: String = "", xdsAuthToken: String = "", - xdsJwtToken: String = "", - xdsJwtTokenLifetimeSeconds: Int = 0, xdsSslRootCerts: String = "", xdsSni: String = "", nodeId: String = "", @@ -161,8 +159,6 @@ class EnvoyConfigurationTest { xdsPort, xdsAuthHeader, xdsAuthToken, - xdsJwtToken, - xdsJwtTokenLifetimeSeconds, xdsSslRootCerts, xdsSni, nodeId, diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt index 7a7b212f186b..2d137602a0c2 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt @@ -209,7 +209,6 @@ class EngineBuilderTest { fun `specifying xDS works`() { var xdsBuilder = XdsBuilder("fake_test_address", 0) xdsBuilder.setAuthenticationToken("x-goog-api-key", "A1B2C3") - xdsBuilder.setJwtAuthenticationToken("my_jwt_token") xdsBuilder.setSslRootCerts("my_root_certs") xdsBuilder.setSni("fake_test_address") xdsBuilder.addRuntimeDiscoveryService("some_rtds_resource") @@ -224,7 +223,6 @@ class EngineBuilderTest { assertThat(engine.envoyConfiguration.xdsAddress).isEqualTo("fake_test_address") assertThat(engine.envoyConfiguration.xdsAuthHeader).isEqualTo("x-goog-api-key") assertThat(engine.envoyConfiguration.xdsAuthToken).isEqualTo("A1B2C3") - assertThat(engine.envoyConfiguration.xdsJwtToken).isEqualTo("my_jwt_token") assertThat(engine.envoyConfiguration.xdsRootCerts).isEqualTo("my_root_certs") assertThat(engine.envoyConfiguration.xdsSni).isEqualTo("fake_test_address") assertThat(engine.envoyConfiguration.rtdsResourceName).isEqualTo("some_rtds_resource") diff --git a/mobile/test/swift/EngineBuilderTests.swift b/mobile/test/swift/EngineBuilderTests.swift index c2713374592b..eb275f91d9d8 100644 --- a/mobile/test/swift/EngineBuilderTests.swift +++ b/mobile/test/swift/EngineBuilderTests.swift @@ -407,21 +407,6 @@ final class EngineBuilderTests: XCTestCase { XCTAssertTrue(bootstrapDebugDescription.contains("fake_ssl_root_certs")) XCTAssertTrue(bootstrapDebugDescription.contains("fake_sni_address")) } - - func testAddingXdsJwtSecurityConfigurationWhenRunningEnvoy() { - let xdsBuilder = XdsBuilder(xdsServerAddress: "FAKE_SWIFT_ADDRESS", xdsServerPort: 0) - .setJwtAuthenticationToken(token: "fake_jwt_token", tokenLifetimeInSeconds: 12345) - .setSslRootCerts(rootCerts: "fake_ssl_root_certs") - .setSni(sni: "fake_sni_address") - .addRuntimeDiscoveryService(resourceName: "some_rtds_resource", timeoutInSeconds: 14325) - let bootstrapDebugDescription = EngineBuilder() - .addEngineType(MockEnvoyEngine.self) - .setXds(xdsBuilder) - .bootstrapDebugDescription() - XCTAssertTrue(bootstrapDebugDescription.contains("fake_jwt_token")) - XCTAssertTrue(bootstrapDebugDescription.contains("fake_ssl_root_certs")) - XCTAssertTrue(bootstrapDebugDescription.contains("fake_sni_address")) - } #endif func testXDSDefaultValues() { From b00537dc40bbe62dd3907d44d75626b34915a611 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 08:14:38 +0000 Subject: [PATCH 27/72] build(deps): bump mysql from 8.1.0 to 8.2.0 in /examples/mysql (#30608) Bumps mysql from 8.1.0 to 8.2.0. --- updated-dependencies: - dependency-name: mysql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/mysql/Dockerfile-mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mysql/Dockerfile-mysql b/examples/mysql/Dockerfile-mysql index 78d35f595fe0..dc99e678a59a 100644 --- a/examples/mysql/Dockerfile-mysql +++ b/examples/mysql/Dockerfile-mysql @@ -1 +1 @@ -FROM mysql:8.1.0@sha256:f61944ff3f2961363a4d22913b2ac581523273679d7e14dd26e8db8c9f571a7e +FROM mysql:8.2.0@sha256:1773f3c7aa9522f0014d0ad2bbdaf597ea3b1643c64c8ccc2123c64afd8b82b1 From 58918515b68cc52769ba1cc1f2e8157d73000987 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 30 Oct 2023 08:16:53 +0000 Subject: [PATCH 28/72] win/ci: Fix ref for PRs (#30604) Signed-off-by: Ryan Northey --- .github/workflows/envoy-windows.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/envoy-windows.yml b/.github/workflows/envoy-windows.yml index 348a96d9e1c5..ed264eddd8f3 100644 --- a/.github/workflows/envoy-windows.yml +++ b/.github/workflows/envoy-windows.yml @@ -79,8 +79,9 @@ jobs: image-tag: ltsc2022 runs-on: ${{ matrix.runs-on }} steps: - # TODO(phlax): checkout a more specific commit with safeguards - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} - uses: actions/download-artifact@v3 with: name: windows.release From 29002dd4b83ffd58b44f99e5c99d256d89578bdd Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 30 Oct 2023 08:17:09 +0000 Subject: [PATCH 29/72] ci/prechecks: Fix condition for postsubmit (#30603) Signed-off-by: Ryan Northey --- .azure-pipelines/env.yml | 19 ++++++++++--------- .azure-pipelines/stages.yml | 4 +--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index 8eecc9e2d529..3b3ebf6d2eeb 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -135,15 +135,6 @@ jobs: # TODO(phlax): move this to a script to ensure proper linting etc set -e - # Run everything in postsubmit - if [[ "$(Build.Reason)" != "PullRequest" ]]; then - echo "##vso[task.setvariable variable=build;isoutput=true]true" - echo "##vso[task.setvariable variable=checks;isoutput=true]true" - echo "##vso[task.setvariable variable=docker;isoutput=true]true" - echo "##vso[task.setvariable variable=packaging;isoutput=true]true" - exit 0 - fi - RUN_BUILD=true RUN_CHECKS=true RUN_DOCKER=true @@ -165,6 +156,16 @@ jobs: RUN_RELEASE_TESTS=false fi + # Run ~everything in postsubmit + if [[ "$(Build.Reason)" != "PullRequest" ]]; then + echo "##vso[task.setvariable variable=build;isoutput=true]true" + echo "##vso[task.setvariable variable=checks;isoutput=true]true" + echo "##vso[task.setvariable variable=docker;isoutput=true]true" + echo "##vso[task.setvariable variable=packaging;isoutput=true]true" + echo "##vso[task.setvariable variable=releaseTests;isoutput=true]${RUN_RELEASE_TESTS}" + exit 0 + 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}" diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index a49a99f7d9f8..2136c7a38e72 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -54,8 +54,6 @@ stages: - stage: prechecks displayName: Prechecks dependsOn: ["env"] - variables: - RUN_PRECHECKS: $[stageDependencies.env.repo.outputs['run.releaseTests']] jobs: - template: stage/prechecks.yml parameters: @@ -66,7 +64,7 @@ stages: authGPGKey: $(MaintainerGPGKeySecureFileDownloadPath) authGPGPath: $(MaintainerGPGKey.secureFilePath) bucketGCP: $(GcsArtifactBucket) - runPrechecks: variables['RUN_PRECHECKS'] + runPrechecks: stageDependencies.env.repo.outputs['run.releaseTests'] - stage: linux_x64 displayName: Linux x64 From 3bf9967ec1b598c1ae2f829eb95f96d03ab765bb Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 30 Oct 2023 08:45:01 +0000 Subject: [PATCH 30/72] deps/tooling: Make cve data preload optional (#30589) Signed-off-by: Ryan Northey --- .github/workflows/check-deps.yml | 2 +- .github/workflows/envoy-prechecks.yml | 1 + ci/do_ci.sh | 1 + tools/dependency/BUILD | 25 +++++++++++++++++++++---- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-deps.yml b/.github/workflows/check-deps.yml index a6b6a4743445..39c0d41c75b1 100644 --- a/.github/workflows/check-deps.yml +++ b/.github/workflows/check-deps.yml @@ -31,6 +31,6 @@ jobs: TODAY_DATE=$(date -u -I"date") export TODAY_DATE bazel run //tools/dependency:check --action_env=TODAY_DATE -- -c release_issues --fix - bazel run //tools/dependency:check --action_env=TODAY_DATE -- -c cves -w error + bazel run --//tools/dependency:preload_cve_data //tools/dependency:check --action_env=TODAY_DATE -- -c cves -w error env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 856a1187cdb3..79988b5e7f68 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -16,6 +16,7 @@ on: - 'WORKSPACE' - '.github/workflows/envoy-prechecks.yml' - '.github/workflows/_*.yml' + - 'tools/dependency/BUILD' concurrency: group: ${{ github.event.inputs.head_ref || github.run_id }}-${{ github.workflow }} diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 03f6e57b5ea2..939b9d2acf55 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -514,6 +514,7 @@ case $CI_TARGET in TODAY_DATE=$(date -u -I"date") export TODAY_DATE bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/dependency:check \ + --//tools/dependency:preload_cve_data \ --action_env=TODAY_DATE \ -- -v warn \ -c cves release_dates releases diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 1631a05cb26a..a7fb7edd96f5 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -1,4 +1,5 @@ load("@base_pip3//:requirements.bzl", "requirement") +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load("@envoy_repo//:path.bzl", "PATH") load("//bazel:envoy_build_system.bzl", "envoy_package") load("//tools/base:envoy_python.bzl", "envoy_entry_point", "envoy_genjson", "envoy_pytool_binary") @@ -10,18 +11,34 @@ envoy_package() envoy_py_namespace() +bool_flag( + name = "preload_cve_data", + build_setting_default = False, +) + +config_setting( + name = "preloaded_cve_data", + flag_values = { + ":preload_cve_data": "true", + }, +) + envoy_entry_point( name = "check", args = [ "--repository_locations=$(location //bazel:all_repository_locations)", "--cve_config=$(location :cve.yaml)", - "--cve_data=$(location :cve_data)", - ], + ] + select({ + ":preloaded_cve_data": ["--cve_data=$(location :cve_data)"], + "//conditions:default": [], + }), data = [ ":cve.yaml", - ":cve_data", "//bazel:all_repository_locations", - ], + ] + select({ + ":preloaded_cve_data": [":cve_data"], + "//conditions:default": [], + }), pkg = "envoy.dependency.check", deps = [requirement("orjson")], ) From 9d24eda1a78ac8aa4a0898c2cd49ae2994b51f70 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 30 Oct 2023 08:46:14 +0000 Subject: [PATCH 31/72] mac/ci: Shift to github (#30596) Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 4 -- .azure-pipelines/stage/macos.yml | 62 ------------------------------- .azure-pipelines/stages.yml | 17 --------- .bazelrc | 9 +++-- .github/workflows/envoy-macos.yml | 54 +++++++++++++++++++++++++++ ci/mac_ci_steps.sh | 9 ----- 6 files changed, 59 insertions(+), 96 deletions(-) delete mode 100644 .azure-pipelines/stage/macos.yml create mode 100644 .github/workflows/envoy-macos.yml diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 8025b1813d65..0b55d0a6219a 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -80,8 +80,6 @@ stages: - env checkStageDeps: - env - macBuildStageDeps: - - env # Postsubmit main/release branches - ${{ if eq(variables.pipelinePostsubmit, true) }}: @@ -94,5 +92,3 @@ stages: - env checkStageDeps: - env - macBuildStageDeps: - - env diff --git a/.azure-pipelines/stage/macos.yml b/.azure-pipelines/stage/macos.yml deleted file mode 100644 index fc990eafd737..000000000000 --- a/.azure-pipelines/stage/macos.yml +++ /dev/null @@ -1,62 +0,0 @@ - -parameters: - -# Auth -- name: authGCP - type: string - default: "" - -- name: runBuild - displayName: "Run build" - type: string - default: true - -jobs: -- job: test - displayName: Build and test - condition: | - and(not(canceled()), - eq(${{ parameters.runBuild }}, 'true')) - timeoutInMinutes: 180 - pool: - vmImage: "macos-11" - steps: - - script: ./ci/mac_ci_setup.sh - displayName: "Install dependencies" - - - bash: | - set -e - GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -t gcp_service_account.XXXXXX.json) - bash -c 'echo "$(GcpServiceAccountKey)"' | base64 --decode > "${GCP_SERVICE_ACCOUNT_KEY_PATH}" - BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}" - ./ci/mac_ci_steps.sh - displayName: "Run Mac CI" - env: - BAZEL_BUILD_EXTRA_OPTIONS: >- - --remote_download_toplevel - --flaky_test_attempts=2 - --remote_cache=grpcs://remotebuildexecution.googleapis.com - --remote_instance_name=projects/envoy-ci/instances/default_instance - ENVOY_RBE: 1 - - - task: PublishTestResults@2 - inputs: - testResultsFiles: "**/bazel-testlogs/**/test.xml" - testRunTitle: "macOS" - timeoutInMinutes: 10 - condition: not(canceled()) - -- job: tested - displayName: Complete - dependsOn: ["test"] - pool: - vmImage: $(agentUbuntu) - # This condition ensures that this (required) job passes if all of - # the preceeding jobs either pass or are skipped - # adapted from: - # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#job-to-job-dependencies-within-one-stage - condition: and(eq(variables['Build.Reason'], 'PullRequest'), in(dependencies.test.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')) - steps: - - checkout: none - - bash: | - echo "macos tested" diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 2136c7a38e72..d7f53fb6fc22 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -8,12 +8,6 @@ parameters: default: - env - prechecks -- name: macBuildStageDeps - displayName: "macOS stage dependencies" - type: object - default: - - env - - prechecks - name: checkStageDeps displayName: "Check stage dependencies" type: object @@ -151,14 +145,3 @@ stages: - template: stage/verify.yml parameters: authGCP: $(GcpServiceAccountKey) - -- stage: macos - displayName: macOS - dependsOn: ${{ parameters.macBuildStageDeps }} - variables: - RUN_BUILD: $[stageDependencies.env.repo.outputs['run.build']] - jobs: - - template: stage/macos.yml - parameters: - authGCP: $(GcpServiceAccountKey) - runBuild: variables['RUN_BUILD'] diff --git a/.bazelrc b/.bazelrc index 540554910b36..023b8c999874 100644 --- a/.bazelrc +++ b/.bazelrc @@ -486,11 +486,12 @@ build:windows --features=static_link_msvcrt build:windows --dynamic_mode=off # RBE (Google) -build:rbe-google --google_default_credentials=true -build:rbe-google --remote_cache=grpcs://remotebuildexecution.googleapis.com +build:cache-google --google_default_credentials=true +build:cache-google --remote_cache=grpcs://remotebuildexecution.googleapis.com +build:cache-google --remote_instance_name=projects/envoy-ci/instances/default_instance +build:cache-google --remote_timeout=7200 build:rbe-google --remote_executor=grpcs://remotebuildexecution.googleapis.com -build:rbe-google --remote_timeout=7200 -build:rbe-google --remote_instance_name=projects/envoy-ci/instances/default_instance +build:rbe-google --config=cache-google build:rbe-google-bes --bes_backend=grpcs://buildeventservice.googleapis.com build:rbe-google-bes --bes_results_url=https://source.cloud.google.com/results/invocations/ diff --git a/.github/workflows/envoy-macos.yml b/.github/workflows/envoy-macos.yml new file mode 100644 index 000000000000..86855854f37c --- /dev/null +++ b/.github/workflows/envoy-macos.yml @@ -0,0 +1,54 @@ +name: Envoy/macos + +permissions: + contents: read + +on: + push: + branches: + - main + - release/v* + pull_request_target: + +concurrency: + group: ${{ github.event.inputs.head_ref || github.run_id }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + env: + uses: ./.github/workflows/_env.yml + with: + prime_build_image: false + check_mobile_run: false + + macos: + needs: + - env + strategy: + fail-fast: false + matrix: + include: + - target: ci/mac_ci_steps.sh + name: macOS + uses: ./.github/workflows/_ci.yml + name: CI ${{ matrix.name || matrix.target }} + secrets: + rbe-key: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} + with: + target: ${{ matrix.target }} + runs-on: macos-12-xl + command_ci: + command_prefix: + repo_ref: ${{ github.event.pull_request.merge_commit_sha }} + run-du: false + env: | + ./ci/mac_ci_setup.sh + GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -t gcp_service_account.XXXXXX.json) + bash -c "echo \"${RBE_KEY}\" | base64 --decode > \"${GCP_SERVICE_ACCOUNT_KEY_PATH}\"" + _BAZEL_BUILD_EXTRA_OPTIONS=( + --remote_download_toplevel + --flaky_test_attempts=2 + --config=cache-google + --config=ci + --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}) + export BAZEL_BUILD_EXTRA_OPTIONS=${_BAZEL_BUILD_EXTRA_OPTIONS[*]} diff --git a/ci/mac_ci_steps.sh b/ci/mac_ci_steps.sh index dc779a665c71..2a83986768f9 100755 --- a/ci/mac_ci_steps.sh +++ b/ci/mac_ci_steps.sh @@ -2,15 +2,6 @@ set -e -function finish { - echo "disk space at end of build:" - df -h -} -trap finish EXIT - -echo "disk space at beginning of build:" -df -h - read -ra BAZEL_BUILD_EXTRA_OPTIONS <<< "${BAZEL_BUILD_EXTRA_OPTIONS:-}" read -ra BAZEL_EXTRA_TEST_OPTIONS <<< "${BAZEL_EXTRA_TEST_OPTIONS:-}" From f32c47e440fd95ae46ed2910a794329238afa886 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 30 Oct 2023 09:24:27 +0000 Subject: [PATCH 32/72] github/ci: Make it work in other repos (#30601) Signed-off-by: Ryan Northey --- .azure-pipelines/stage/publish.yml | 1 + .github/workflows/_env.yml | 6 +++++- .github/workflows/_stage_publish.yml | 3 ++- .github/workflows/_workflow-start.yml | 1 + .github/workflows/envoy-publish.yml | 5 ++++- .github/workflows/workflow-complete.yml | 1 + ci/do_ci.sh | 4 +++- 7 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index b361552e4e20..30e62ebc362c 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -292,6 +292,7 @@ jobs: publishEnvoy: false publishTestResults: false env: + ENVOY_REPO: $(Build.Repository.Name) ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: ENVOY_HEAD_REF: "$(Build.SourceBranch)" ENVOY_BRANCH: "$(System.PullRequest.TargetBranch)" diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index b3040fd62723..adc0799710da 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -106,7 +106,11 @@ concurrency: jobs: repo: - if: github.repository == 'envoyproxy/envoy' + if: >- + ${{ + (github.repository == 'envoyproxy/envoy' + || vars.ENVOY_CI) + }} runs-on: ubuntu-22.04 outputs: build_image_ubuntu: ${{ steps.env.outputs.build_image_ubuntu }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index b896c77ea558..399a20d1a10c 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -55,6 +55,7 @@ jobs: env: | export ENVOY_PUBLISH_DRY_RUN=1 export ENVOY_COMMIT=${{ inputs.sha }} + export ENVOY_REPO=${{ github.repository }} uses: ./.github/workflows/_ci.yml with: target: ${{ matrix.target }} @@ -111,7 +112,7 @@ jobs: # which builds a static version of the docs for the release and commits it to the archive. # In turn the archive repo triggers an update in the website so the new release docs are # included in the published site - if: ${{ inputs.trusted }} + if: ${{ inputs.trusted && github.repository == 'envoyproxy/envoy' }} runs-on: ubuntu-22.04 needs: - publish diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index 1be0e2004876..4de7aaaad79c 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -19,6 +19,7 @@ jobs: start: runs-on: ubuntu-22.04 permissions: + contents: read statuses: write steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 9890338b00f5..fc8bd1506d36 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -26,7 +26,8 @@ jobs: env: if: >- ${{ - github.repository == 'envoyproxy/envoy' + (github.repository == 'envoyproxy/envoy' + || vars.ENVOY_CI) && (!contains(github.actor, '[bot]') || github.actor == 'trigger-workflow-envoy[bot]' || github.actor == 'trigger-release-envoy[bot]') @@ -78,6 +79,8 @@ jobs: name: Verify ${{ needs.env.outputs.repo_ref_title }} needs: - env + permissions: + contents: read with: trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} given_ref: ${{ inputs.ref }} diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml index 3e97b7805f9d..38ce7e0f44d7 100644 --- a/.github/workflows/workflow-complete.yml +++ b/.github/workflows/workflow-complete.yml @@ -18,6 +18,7 @@ jobs: if: ${{ github.actor == 'trigger-workflow-envoy[bot]' }} runs-on: ubuntu-22.04 permissions: + contents: read statuses: write steps: - name: 'Download artifact' diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 939b9d2acf55..fc25e25c4068 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -789,6 +789,7 @@ case $CI_TARGET in setup_clang_toolchain BUILD_SHA="$(git rev-parse HEAD)" ENVOY_COMMIT="${ENVOY_COMMIT:-${BUILD_SHA}}" + ENVOY_REPO="${ENVOY_REPO:-envoyproxy/envoy}" VERSION_DEV="$(cut -d- -f2 < VERSION.txt)" PUBLISH_ARGS=( --publish-commitish="$ENVOY_COMMIT" @@ -799,7 +800,8 @@ case $CI_TARGET in fi bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ @envoy_repo//:publish \ - -- "${PUBLISH_ARGS[@]}" + -- --repo="$ENVOY_REPO" \ + "${PUBLISH_ARGS[@]}" ;; release|release.server_only) From 05457bb047b87f4cc1a25635400ddcaa7fccc923 Mon Sep 17 00:00:00 2001 From: Akshay Gupta Date: Mon, 30 Oct 2023 08:44:02 -0700 Subject: [PATCH 33/72] Update to c++ 20 for windows (#30472) Signed-off-by: Akshay Gupta --- .bazelrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelrc b/.bazelrc index 023b8c999874..a5b1ab886dba 100644 --- a/.bazelrc +++ b/.bazelrc @@ -443,7 +443,7 @@ build:windows --define hot_restart=disabled build:windows --define tcmalloc=disabled build:windows --define wasm=disabled build:windows --define manual_stamp=manual_stamp -build:windows --cxxopt="/std:c++17" +build:windows --cxxopt="/std:c++20" build:windows --output_groups=+pdb_file # TODO(wrowe,sunjayBhatia): Resolve bugs upstream in curl and rules_foreign_cc From 8b9054c428c1f00bebe10e25c7e22e3abd3eff99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Tue, 31 Oct 2023 02:24:48 +0800 Subject: [PATCH 34/72] docs: fix indentation in the compressor filter configuration (#30586) Signed-off-by: spacewander --- .../compressor-filter-request-response.yaml | 80 ++++++++++++++ .../_include/compressor-filter.yaml | 72 ++++++++++++ .../http/http_filters/compressor_filter.rst | 104 +++--------------- 3 files changed, 167 insertions(+), 89 deletions(-) create mode 100644 docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml create mode 100644 docs/root/configuration/http/http_filters/_include/compressor-filter.yaml diff --git a/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml b/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml new file mode 100644 index 000000000000..3d1667c133ad --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/compressor-filter-request-response.yaml @@ -0,0 +1,80 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + # This filter is only enabled for responses. + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + request_direction_config: + common_config: + enabled: + default_value: false + runtime_key: request_compressor_enabled + compressor_library: + name: for_response + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 3 + window_bits: 10 + compression_level: BEST_COMPRESSION + compression_strategy: DEFAULT_STRATEGY + # This filter is only enabled for requests. + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + response_direction_config: + common_config: + enabled: + default_value: false + runtime_key: response_compressor_enabled + request_direction_config: + common_config: + enabled: + default_value: true + runtime_key: request_compressor_enabled + compressor_library: + name: for_request + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 9 + window_bits: 15 + compression_level: BEST_SPEED + compression_strategy: DEFAULT_STRATEGY + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service + port_value: 8000 diff --git a/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml b/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml new file mode 100644 index 000000000000..7c3a2215a0e4 --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/compressor-filter.yaml @@ -0,0 +1,72 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + typed_per_filter_config: + envoy.filters.http.compression: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute + disabled: true + routes: + - match: { prefix: "/static" } + route: { cluster: service } + typed_per_filter_config: + envoy.filters.http.compression: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute + overrides: + response_direction_config: + - match: { prefix: "/" } + route: { cluster: service } + http_filters: + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + response_direction_config: + common_config: + min_content_length: 100 + content_type: + - text/html + - application/json + disable_on_etag_header: true + request_direction_config: + common_config: + enabled: + default_value: false + runtime_key: request_compressor_enabled + compressor_library: + name: text_optimized + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip + memory_level: 3 + window_bits: 10 + compression_level: BEST_COMPRESSION + compression_strategy: DEFAULT_STRATEGY + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service + port_value: 8000 diff --git a/docs/root/configuration/http/http_filters/compressor_filter.rst b/docs/root/configuration/http/http_filters/compressor_filter.rst index 9c3c0dba9a31..9bcc66fb4796 100644 --- a/docs/root/configuration/http/http_filters/compressor_filter.rst +++ b/docs/root/configuration/http/http_filters/compressor_filter.rst @@ -26,32 +26,11 @@ compression only. Other compression libraries can be supported as extensions. An example configuration of the filter may look like the following: -.. code-block:: yaml - - http_filters: - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - response_direction_config: - common_config: - min_content_length: 100 - content_type: - - text/html - - application/json - disable_on_etag_header: true - request_direction_config: - common_config: - enabled: - default_value: false - runtime_key: request_compressor_enabled - compressor_library: - name: text_optimized - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 3 - window_bits: 10 - compression_level: BEST_COMPRESSION - compression_strategy: DEFAULT_STRATEGY +.. literalinclude:: _include/compressor-filter.yaml + :language: yaml + :linenos: + :lines: 33-56 + :caption: :download:`compressor-filter.yaml <_include/compressor-filter.yaml>` By *default* request compression is disabled, but when enabled it will be *skipped* if: @@ -132,27 +111,11 @@ Per-Route Configuration Response compression can be enabled and disabled on individual virtual hosts and routes. For example, to disable response compression for a particular virtual host, but enable response compression for its ``/static`` route: -.. code-block:: yaml - - route_config: - name: local_route - virtual_hosts: - - name: local_service - domains: ["*"] - typed_per_filter_config: - envoy.filters.http.compression: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute - disabled: true - routes: - - match: { prefix: "/static" } - route: { cluster: some_service } - typed_per_filter_config: - envoy.filters.http.compression: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.CompressorPerRoute - overrides: - response_direction_config: - - match: { prefix: "/" } - route: { cluster: some_service } +.. literalinclude:: _include/compressor-filter.yaml + :language: yaml + :linenos: + :lines: 14-32 + :caption: :download:`compressor-filter.yaml <_include/compressor-filter.yaml>` Using different compressors for requests and responses -------------------------------------------------------- @@ -160,48 +123,11 @@ Using different compressors for requests and responses If different compression libraries are desired for requests and responses, it is possible to install multiple compressor filters enabled only for requests or responses. For instance: -.. code-block:: yaml - - http_filters: - # This filter is only enabled for responses. - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - request_direction_config: - common_config: - enabled: - default_value: false - runtime_key: request_compressor_enabled - compressor_library: - name: for_response - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 3 - window_bits: 10 - compression_level: BEST_COMPRESSION - compression_strategy: DEFAULT_STRATEGY - # This filter is only enabled for requests. - - name: envoy.filters.http.compressor - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor - response_direction_config: - common_config: - enabled: - default_value: false - runtime_key: response_compressor_enabled - request_direction_config: - common_config: - enabled: - default_value: true - runtime_key: request_compressor_enabled - compressor_library: - name: for_request - typed_config: - "@type": type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip - memory_level: 9 - window_bits: 15 - compression_level: BEST_SPEED - compression_strategy: DEFAULT_STRATEGY +.. literalinclude:: _include/compressor-filter-request-response.yaml + :language: yaml + :linenos: + :lines: 25-64 + :caption: :download:`compressor-filter-request-response.yaml <_include/compressor-filter-request-response.yaml>` .. _compressor-statistics: From c600637b9fddfe3f1381baf436e050f3fa7723b6 Mon Sep 17 00:00:00 2001 From: Thomas Ebner <96168670+samohte@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:57:29 +0100 Subject: [PATCH 35/72] Allow specifying samplers for the OpenTelemetry tracer via a new configuration. (#30259) Signed-off-by: thomas.ebner Signed-off-by: Joao Grassi --- api/BUILD | 1 + api/envoy/config/trace/v3/opentelemetry.proto | 9 + .../tracers/opentelemetry/samplers/v3/BUILD | 9 + .../samplers/v3/always_on_sampler.proto | 23 +++ api/versioning/BUILD | 1 + changelogs/current.yaml | 3 + .../config/trace/opentelemetry/samplers.rst | 10 + docs/root/api-v3/config/trace/trace.rst | 1 + source/extensions/extensions_build_config.bzl | 6 + source/extensions/extensions_metadata.yaml | 7 + source/extensions/tracers/opentelemetry/BUILD | 1 + .../opentelemetry_tracer_impl.cc | 31 ++- .../tracers/opentelemetry/samplers/BUILD | 25 +++ .../opentelemetry/samplers/always_on/BUILD | 33 +++ .../samplers/always_on/always_on_sampler.cc | 34 +++ .../samplers/always_on/always_on_sampler.h | 38 ++++ .../samplers/always_on/config.cc | 27 +++ .../opentelemetry/samplers/always_on/config.h | 42 ++++ .../tracers/opentelemetry/samplers/sampler.h | 111 ++++++++++ .../tracers/opentelemetry/tracer.cc | 47 ++++- .../extensions/tracers/opentelemetry/tracer.h | 9 +- .../tracers/opentelemetry/samplers/BUILD | 23 +++ .../opentelemetry/samplers/always_on/BUILD | 51 +++++ .../always_on_sampler_integration_test.cc | 142 +++++++++++++ .../always_on/always_on_sampler_test.cc | 56 +++++ .../samplers/always_on/config_test.cc | 38 ++++ .../opentelemetry/samplers/sampler_test.cc | 193 ++++++++++++++++++ tools/extensions/extensions_schema.yaml | 1 + 28 files changed, 957 insertions(+), 15 deletions(-) create mode 100644 api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD create mode 100644 api/envoy/extensions/tracers/opentelemetry/samplers/v3/always_on_sampler.proto create mode 100644 docs/root/api-v3/config/trace/opentelemetry/samplers.rst create mode 100644 source/extensions/tracers/opentelemetry/samplers/BUILD create mode 100644 source/extensions/tracers/opentelemetry/samplers/always_on/BUILD create mode 100644 source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/always_on/config.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/always_on/config.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/sampler.h create mode 100644 test/extensions/tracers/opentelemetry/samplers/BUILD create mode 100644 test/extensions/tracers/opentelemetry/samplers/always_on/BUILD create mode 100644 test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_integration_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/always_on/config_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/sampler_test.cc diff --git a/api/BUILD b/api/BUILD index d40a6c4d7470..78b712f8354a 100644 --- a/api/BUILD +++ b/api/BUILD @@ -307,6 +307,7 @@ proto_library( "//envoy/extensions/stat_sinks/open_telemetry/v3:pkg", "//envoy/extensions/stat_sinks/wasm/v3:pkg", "//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg", + "//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg", "//envoy/extensions/transport_sockets/alts/v3:pkg", "//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg", "//envoy/extensions/transport_sockets/internal_upstream/v3:pkg", diff --git a/api/envoy/config/trace/v3/opentelemetry.proto b/api/envoy/config/trace/v3/opentelemetry.proto index 5d9c9202cb5a..59028326f220 100644 --- a/api/envoy/config/trace/v3/opentelemetry.proto +++ b/api/envoy/config/trace/v3/opentelemetry.proto @@ -19,6 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Configuration for the OpenTelemetry tracer. // [#extension: envoy.tracers.opentelemetry] +// [#next-free-field: 6] message OpenTelemetryConfig { // The upstream gRPC cluster that will receive OTLP traces. // Note that the tracer drops traces if the server does not read data fast enough. @@ -48,4 +49,12 @@ message OpenTelemetryConfig { // An ordered list of resource detectors // [#extension-category: envoy.tracers.opentelemetry.resource_detectors] repeated core.v3.TypedExtensionConfig resource_detectors = 4; + + // Specifies the sampler to be used by the OpenTelemetry tracer. + // The configured sampler implements the Sampler interface defined by the OpenTelemetry specification. + // This field can be left empty. In this case, the default Envoy sampling decision is used. + // + // See: `OpenTelemetry sampler specification `_ + // [#extension-category: envoy.tracers.opentelemetry.samplers] + core.v3.TypedExtensionConfig sampler = 5; } diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/always_on_sampler.proto b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/always_on_sampler.proto new file mode 100644 index 000000000000..241dc06eb1fc --- /dev/null +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/always_on_sampler.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package envoy.extensions.tracers.opentelemetry.samplers.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.tracers.opentelemetry.samplers.v3"; +option java_outer_classname = "AlwaysOnSamplerProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/tracers/opentelemetry/samplers/v3;samplersv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Always On Sampler config] +// Configuration for the "AlwaysOn" Sampler extension. +// The sampler follows the "AlwaysOn" implementation from the OpenTelemetry +// SDK specification. +// +// See: +// `AlwaysOn sampler specification `_ +// [#extension: envoy.tracers.opentelemetry.samplers.always_on] + +message AlwaysOnSamplerConfig { +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 9ad67e06e99c..4e89b0209967 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -246,6 +246,7 @@ proto_library( "//envoy/extensions/stat_sinks/open_telemetry/v3:pkg", "//envoy/extensions/stat_sinks/wasm/v3:pkg", "//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg", + "//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg", "//envoy/extensions/transport_sockets/alts/v3:pkg", "//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg", "//envoy/extensions/transport_sockets/internal_upstream/v3:pkg", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cff26a84b048..0a83d5683614 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -92,5 +92,8 @@ new_features: - area: tracing change: | Added support for configuring resource detectors on the OpenTelemetry tracer. +- area: tracing + change: | + Added support to configure a sampler for the OpenTelemetry tracer. deprecated: diff --git a/docs/root/api-v3/config/trace/opentelemetry/samplers.rst b/docs/root/api-v3/config/trace/opentelemetry/samplers.rst new file mode 100644 index 000000000000..705155f640b9 --- /dev/null +++ b/docs/root/api-v3/config/trace/opentelemetry/samplers.rst @@ -0,0 +1,10 @@ +OpenTelemetry Samplers +====================== + +Samplers that can be configured with the OpenTelemetry Tracer: + +.. toctree:: + :glob: + :maxdepth: 3 + + ../../../extensions/tracers/opentelemetry/samplers/v3/* diff --git a/docs/root/api-v3/config/trace/trace.rst b/docs/root/api-v3/config/trace/trace.rst index 6cccb4f67d1b..1bd09c1a1300 100644 --- a/docs/root/api-v3/config/trace/trace.rst +++ b/docs/root/api-v3/config/trace/trace.rst @@ -13,3 +13,4 @@ HTTP tracers v3/* opentelemetry/resource_detectors + opentelemetry/samplers diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index a75696fbf8c8..052e3682bdd3 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -271,6 +271,12 @@ EXTENSIONS = { "envoy.tracers.opentelemetry.resource_detectors.environment": "//source/extensions/tracers/opentelemetry/resource_detectors/environment:config", + # + # OpenTelemetry tracer samplers + # + + "envoy.tracers.opentelemetry.samplers.always_on": "//source/extensions/tracers/opentelemetry/samplers/always_on:config", + # # Transport sockets # diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 2471dc695903..a47f3534929e 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1148,6 +1148,13 @@ envoy.tracers.opentelemetry: status: wip type_urls: - envoy.config.trace.v3.OpenTelemetryConfig +envoy.tracers.opentelemetry.samplers.always_on: + categories: + - envoy.tracers.opentelemetry.samplers + security_posture: unknown + status: wip + type_urls: + - envoy.extensions.tracers.opentelemetry.samplers.v3.AlwaysOnSamplerConfig envoy.tracers.skywalking: categories: - envoy.tracers diff --git a/source/extensions/tracers/opentelemetry/BUILD b/source/extensions/tracers/opentelemetry/BUILD index 6122aa34d05a..ea305b4ad950 100644 --- a/source/extensions/tracers/opentelemetry/BUILD +++ b/source/extensions/tracers/opentelemetry/BUILD @@ -42,6 +42,7 @@ envoy_cc_library( "//source/common/tracing:http_tracer_lib", "//source/extensions/tracers/common:factory_base_lib", "//source/extensions/tracers/opentelemetry/resource_detectors:resource_detector_lib", + "//source/extensions/tracers/opentelemetry/samplers:sampler_lib", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", "@opentelemetry_proto//:trace_cc_proto", ], diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc index 54f41ca2da12..8d5786b8c232 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc @@ -12,6 +12,7 @@ #include "source/extensions/tracers/opentelemetry/http_trace_exporter.h" #include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" #include "source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h" +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "source/extensions/tracers/opentelemetry/span_context_extractor.h" #include "source/extensions/tracers/opentelemetry/trace_exporter.h" @@ -25,6 +26,25 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { +namespace { + +SamplerSharedPtr +tryCreateSamper(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, + Server::Configuration::TracerFactoryContext& context) { + SamplerSharedPtr sampler; + if (opentelemetry_config.has_sampler()) { + auto& sampler_config = opentelemetry_config.sampler(); + auto* factory = Envoy::Config::Utility::getFactory(sampler_config); + if (!factory) { + throw EnvoyException(fmt::format("Sampler factory not found: '{}'", sampler_config.name())); + } + sampler = factory->createSampler(sampler_config.typed_config(), context); + } + return sampler; +} + +} // namespace + Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetry_config, Server::Configuration::TracerFactoryContext& context) : Driver(opentelemetry_config, context, ResourceProviderImpl{}) {} @@ -46,9 +66,12 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr "OpenTelemetry tracer will be disabled."); } + // Create the sampler if configured + SamplerSharedPtr sampler = tryCreateSamper(opentelemetry_config, context); + // Create the tracer in Thread Local Storage. - tls_slot_ptr_->set([opentelemetry_config, &factory_context, this, - resource_ptr](Event::Dispatcher& dispatcher) { + tls_slot_ptr_->set([opentelemetry_config, &factory_context, this, resource_ptr, + sampler](Event::Dispatcher& dispatcher) { OpenTelemetryTraceExporterPtr exporter; if (opentelemetry_config.has_grpc_service()) { Grpc::AsyncClientFactoryPtr&& factory = @@ -63,8 +86,7 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr } TracerPtr tracer = std::make_unique( std::move(exporter), factory_context.timeSource(), factory_context.api().randomGenerator(), - factory_context.runtime(), dispatcher, tracing_stats_, resource_ptr); - + factory_context.runtime(), dispatcher, tracing_stats_, resource_ptr, sampler); return std::make_shared(std::move(tracer)); }); } @@ -81,7 +103,6 @@ Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config, // No propagation header, so we can create a fresh span with the given decision. Tracing::SpanPtr new_open_telemetry_span = tracer.startSpan(config, operation_name, stream_info.startTime(), tracing_decision); - new_open_telemetry_span->setSampled(tracing_decision.traced); return new_open_telemetry_span; } else { // Try to extract the span context. If we can't, just return a null span. diff --git a/source/extensions/tracers/opentelemetry/samplers/BUILD b/source/extensions/tracers/opentelemetry/samplers/BUILD new file mode 100644 index 000000000000..32a1005b11e8 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/BUILD @@ -0,0 +1,25 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "sampler_lib", + srcs = [ + ], + hdrs = [ + "sampler.h", + ], + deps = [ + "//envoy/config:typed_config_interface", + "//envoy/server:tracer_config_interface", + "//source/common/common:logger_lib", + "//source/common/config:utility_lib", + "@opentelemetry_proto//:trace_cc_proto", + ], +) diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/BUILD b/source/extensions/tracers/opentelemetry/samplers/always_on/BUILD new file mode 100644 index 000000000000..744607330d57 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/BUILD @@ -0,0 +1,33 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":always_on_sampler_lib", + "//envoy/registry", + "//source/common/config:utility_lib", + "@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "always_on_sampler_lib", + srcs = ["always_on_sampler.cc"], + hdrs = ["always_on_sampler.h"], + deps = [ + "//source/common/config:datasource_lib", + "//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib", + "//source/extensions/tracers/opentelemetry/samplers:sampler_lib", + ], +) diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc new file mode 100644 index 000000000000..3bc0aa87ab3d --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc @@ -0,0 +1,34 @@ +#include "source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h" + +#include +#include +#include + +#include "source/common/config/datasource.h" +#include "source/extensions/tracers/opentelemetry/span_context.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +SamplingResult +AlwaysOnSampler::shouldSample(const absl::optional parent_context, + const std::string& /*trace_id*/, const std::string& /*name*/, + ::opentelemetry::proto::trace::v1::Span::SpanKind /*kind*/, + const std::map& /*attributes*/, + const std::vector& /*links*/) { + SamplingResult result; + result.decision = Decision::RECORD_AND_SAMPLE; + if (parent_context.has_value()) { + result.tracestate = parent_context.value().tracestate(); + } + return result; +} + +std::string AlwaysOnSampler::getDescription() const { return "AlwaysOnSampler"; } + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h new file mode 100644 index 000000000000..2d53a511fa29 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h @@ -0,0 +1,38 @@ +#pragma once + +#include "envoy/server/factory_context.h" + +#include "source/common/common/logger.h" +#include "source/common/config/datasource.h" +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * @brief A sampler which samples every span. + * https://opentelemetry.io/docs/specs/otel/trace/sdk/#alwayson + * - Returns RECORD_AND_SAMPLE always. + * - Description MUST be AlwaysOnSampler. + * + */ +class AlwaysOnSampler : public Sampler, Logger::Loggable { +public: + explicit AlwaysOnSampler(const Protobuf::Message& /*config*/, + Server::Configuration::TracerFactoryContext& /*context*/) {} + SamplingResult shouldSample(const absl::optional parent_context, + const std::string& trace_id, const std::string& name, + ::opentelemetry::proto::trace::v1::Span::SpanKind spankind, + const std::map& attributes, + const std::vector& links) override; + std::string getDescription() const override; + +private: +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/config.cc b/source/extensions/tracers/opentelemetry/samplers/always_on/config.cc new file mode 100644 index 000000000000..99288c4bf469 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/config.cc @@ -0,0 +1,27 @@ +#include "source/extensions/tracers/opentelemetry/samplers/always_on/config.h" + +#include "envoy/server/tracer_config.h" + +#include "source/common/config/utility.h" +#include "source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +SamplerSharedPtr +AlwaysOnSamplerFactory::createSampler(const Protobuf::Message& config, + Server::Configuration::TracerFactoryContext& context) { + return std::make_shared(config, context); +} + +/** + * Static registration for the Env sampler factory. @see RegisterFactory. + */ +REGISTER_FACTORY(AlwaysOnSamplerFactory, SamplerFactory); + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/config.h b/source/extensions/tracers/opentelemetry/samplers/always_on/config.h new file mode 100644 index 000000000000..5f93f43c0f1f --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/config.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/always_on_sampler.pb.h" +#include "envoy/registry/registry.h" + +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * Config registration for the AlwaysOnSampler. @see SamplerFactory. + */ +class AlwaysOnSamplerFactory : public SamplerFactory { +public: + /** + * @brief Create a Sampler. @see AlwaysOnSampler + * + * @param config Protobuf config for the sampler. + * @param context A reference to the TracerFactoryContext. + * @return SamplerSharedPtr + */ + SamplerSharedPtr createSampler(const Protobuf::Message& config, + Server::Configuration::TracerFactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::tracers::opentelemetry::samplers::v3::AlwaysOnSamplerConfig>(); + } + std::string name() const override { return "envoy.tracers.opentelemetry.samplers.always_on"; } +}; + +DECLARE_FACTORY(AlwaysOnSamplerFactory); + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/sampler.h b/source/extensions/tracers/opentelemetry/samplers/sampler.h new file mode 100644 index 000000000000..fd2be0bb647e --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/sampler.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include +#include + +#include "envoy/config/typed_config.h" +#include "envoy/server/tracer_config.h" +#include "envoy/tracing/trace_context.h" + +#include "absl/types/optional.h" +#include "opentelemetry/proto/trace/v1/trace.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +class SpanContext; + +enum class Decision { + // IsRecording will be false, the Span will not be recorded and all events and attributes will be + // dropped. + DROP, + // IsRecording will be true, but the Sampled flag MUST NOT be set. + RECORD_ONLY, + // IsRecording will be true and the Sampled flag MUST be set. + RECORD_AND_SAMPLE +}; + +struct SamplingResult { + /// @see Decision + Decision decision; + // A set of span Attributes that will also be added to the Span. Can be nullptr. + std::unique_ptr> attributes; + // A Tracestate that will be associated with the Span. If the sampler + // returns an empty Tracestate here, the Tracestate will be cleared, so samplers SHOULD normally + // return the passed-in Tracestate if they do not intend to change it + std::string tracestate; + + inline bool isRecording() const { + return decision == Decision::RECORD_ONLY || decision == Decision::RECORD_AND_SAMPLE; + } + + inline bool isSampled() const { return decision == Decision::RECORD_AND_SAMPLE; } +}; + +/** + * @brief The base type for all samplers + * see https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampler + * + */ +class Sampler { +public: + virtual ~Sampler() = default; + + /** + * @brief Decides if a trace should be sampled. + * + * @param parent_context Span context describing the parent span. The Span's SpanContext may be + * invalid to indicate a root span. + * @param trace_id Trace id of the Span to be created. If the parent SpanContext contains a valid + * TraceId, they MUST always match. + * @param name Name of the Span to be created. + * @param spankind Span kind of the Span to be created. + * @param attributes Initial set of Attributes of the Span to be created. + * @param links Collection of links that will be associated with the Span to be created. + * @return SamplingResult @see SamplingResult + */ + virtual SamplingResult shouldSample(const absl::optional parent_context, + const std::string& trace_id, const std::string& name, + ::opentelemetry::proto::trace::v1::Span::SpanKind spankind, + const std::map& attributes, + const std::vector& links) PURE; + + /** + * @brief Returns a sampler description or name. + * + * @return The sampler name or short description with the configuration. + */ + virtual std::string getDescription() const PURE; +}; + +using SamplerSharedPtr = std::shared_ptr; + +/* + * A factory for creating a sampler + */ +class SamplerFactory : public Envoy::Config::TypedFactory { +public: + ~SamplerFactory() override = default; + + /** + * @brief Creates a sampler + * @param config The sampler protobuf config. + * @param context The TracerFactoryContext. + * @return SamplerSharedPtr A sampler. + */ + virtual SamplerSharedPtr createSampler(const Protobuf::Message& config, + Server::Configuration::TracerFactoryContext& context) PURE; + + std::string category() const override { return "envoy.tracers.opentelemetry.samplers"; } +}; + +using SamplerFactoryPtr = std::unique_ptr; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/tracer.cc b/source/extensions/tracers/opentelemetry/tracer.cc index 5d55725906d2..73325ac7f317 100644 --- a/source/extensions/tracers/opentelemetry/tracer.cc +++ b/source/extensions/tracers/opentelemetry/tracer.cc @@ -22,6 +22,29 @@ constexpr absl::string_view kDefaultVersion = "00"; using opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest; +namespace { + +void callSampler(SamplerSharedPtr sampler, const absl::optional span_context, + Span& new_span, const std::string& operation_name) { + if (!sampler) { + return; + } + const auto sampling_result = sampler->shouldSample( + span_context, operation_name, new_span.getTraceIdAsHex(), new_span.spankind(), {}, {}); + new_span.setSampled(sampling_result.isSampled()); + + if (sampling_result.attributes) { + for (auto const& attribute : *sampling_result.attributes) { + new_span.setTag(attribute.first, attribute.second); + } + } + if (!sampling_result.tracestate.empty()) { + new_span.setTracestate(sampling_result.tracestate); + } +} + +} // namespace + Span::Span(const Tracing::Config& config, const std::string& name, SystemTime start_time, Envoy::TimeSource& time_source, Tracer& parent_tracer, bool downstream_span) : parent_tracer_(parent_tracer), time_source_(time_source) { @@ -108,9 +131,9 @@ void Span::setTag(absl::string_view name, absl::string_view value) { Tracer::Tracer(OpenTelemetryTraceExporterPtr exporter, Envoy::TimeSource& time_source, Random::RandomGenerator& random, Runtime::Loader& runtime, Event::Dispatcher& dispatcher, OpenTelemetryTracerStats tracing_stats, - const ResourceConstSharedPtr resource) + const ResourceConstSharedPtr resource, SamplerSharedPtr sampler) : exporter_(std::move(exporter)), time_source_(time_source), random_(random), runtime_(runtime), - tracing_stats_(tracing_stats), resource_(resource) { + tracing_stats_(tracing_stats), resource_(resource), sampler_(sampler) { flush_timer_ = dispatcher.createTimer([this]() -> void { tracing_stats_.timer_flushed_.inc(); flushSpans(); @@ -173,12 +196,16 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::str bool downstream_span) { // Create an Tracers::OpenTelemetry::Span class that will contain the OTel span. Span new_span(config, operation_name, start_time, time_source_, *this, downstream_span); - new_span.setSampled(tracing_decision.traced); uint64_t trace_id_high = random_.random(); uint64_t trace_id = random_.random(); new_span.setTraceId(absl::StrCat(Hex::uint64ToHex(trace_id_high), Hex::uint64ToHex(trace_id))); uint64_t span_id = random_.random(); new_span.setId(Hex::uint64ToHex(span_id)); + if (sampler_) { + callSampler(sampler_, absl::nullopt, new_span, operation_name); + } else { + new_span.setSampled(tracing_decision.traced); + } return std::make_unique(new_span); } @@ -187,7 +214,6 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::str bool downstream_span) { // Create a new span and populate details from the span context. Span new_span(config, operation_name, start_time, time_source_, *this, downstream_span); - new_span.setSampled(previous_span_context.sampled()); new_span.setTraceId(previous_span_context.traceId()); if (!previous_span_context.parentId().empty()) { new_span.setParentId(previous_span_context.parentId()); @@ -195,10 +221,15 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::str // Generate a new identifier for the span id. uint64_t span_id = random_.random(); new_span.setId(Hex::uint64ToHex(span_id)); - // Respect the previous span's sampled flag. - new_span.setSampled(previous_span_context.sampled()); - if (!previous_span_context.tracestate().empty()) { - new_span.setTracestate(std::string{previous_span_context.tracestate()}); + if (sampler_) { + // Sampler should make a sampling decision and set tracestate + callSampler(sampler_, previous_span_context, new_span, operation_name); + } else { + // Respect the previous span's sampled flag. + new_span.setSampled(previous_span_context.sampled()); + if (!previous_span_context.tracestate().empty()) { + new_span.setTracestate(std::string{previous_span_context.tracestate()}); + } } return std::make_unique(new_span); } diff --git a/source/extensions/tracers/opentelemetry/tracer.h b/source/extensions/tracers/opentelemetry/tracer.h index 74bdb55952b8..bea45d54f4cc 100644 --- a/source/extensions/tracers/opentelemetry/tracer.h +++ b/source/extensions/tracers/opentelemetry/tracer.h @@ -12,6 +12,7 @@ #include "source/extensions/tracers/common/factory_base.h" #include "source/extensions/tracers/opentelemetry/grpc_trace_exporter.h" #include "source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h" +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "absl/strings/escaping.h" @@ -36,7 +37,8 @@ class Tracer : Logger::Loggable { public: Tracer(OpenTelemetryTraceExporterPtr exporter, Envoy::TimeSource& time_source, Random::RandomGenerator& random, Runtime::Loader& runtime, Event::Dispatcher& dispatcher, - OpenTelemetryTracerStats tracing_stats, const ResourceConstSharedPtr resource); + OpenTelemetryTracerStats tracing_stats, const ResourceConstSharedPtr resource, + SamplerSharedPtr sampler); void sendSpan(::opentelemetry::proto::trace::v1::Span& span); @@ -66,6 +68,7 @@ class Tracer : Logger::Loggable { Event::TimerPtr flush_timer_; OpenTelemetryTracerStats tracing_stats_; const ResourceConstSharedPtr resource_; + SamplerSharedPtr sampler_; }; /** @@ -112,6 +115,8 @@ class Span : Logger::Loggable, public Tracing::Span { std::string getTraceIdAsHex() const override { return absl::BytesToHexString(span_.trace_id()); }; + ::opentelemetry::proto::trace::v1::Span::SpanKind spankind() const { return span_.kind(); } + /** * Sets the span's id. */ @@ -128,7 +133,7 @@ class Span : Logger::Loggable, public Tracing::Span { span_.set_parent_span_id(absl::HexStringToBytes(parent_span_id_hex)); } - std::string tracestate() { return span_.trace_state(); } + std::string tracestate() const { return span_.trace_state(); } /** * Sets the span's tracestate. diff --git a/test/extensions/tracers/opentelemetry/samplers/BUILD b/test/extensions/tracers/opentelemetry/samplers/BUILD new file mode 100644 index 000000000000..55414e7854c9 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/BUILD @@ -0,0 +1,23 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "sampler_test", + srcs = ["sampler_test.cc"], + deps = [ + "//envoy/registry", + "//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib", + "//source/extensions/tracers/opentelemetry/samplers:sampler_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/tracers/opentelemetry/samplers/always_on/BUILD b/test/extensions/tracers/opentelemetry/samplers/always_on/BUILD new file mode 100644 index 000000000000..063cda9f0ec1 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/always_on/BUILD @@ -0,0 +1,51 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.tracers.opentelemetry.samplers.always_on"], + deps = [ + "//envoy/registry", + "//source/extensions/tracers/opentelemetry/samplers/always_on:always_on_sampler_lib", + "//source/extensions/tracers/opentelemetry/samplers/always_on:config", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "always_on_sampler_test", + srcs = ["always_on_sampler_test.cc"], + extension_names = ["envoy.tracers.opentelemetry.samplers.always_on"], + deps = [ + "//source/extensions/tracers/opentelemetry/samplers/always_on:always_on_sampler_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "always_on_sampler_integration_test", + srcs = [ + "always_on_sampler_integration_test.cc", + ], + extension_names = ["envoy.tracers.opentelemetry.samplers.always_on"], + deps = [ + "//source/exe:main_common_lib", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_integration_test.cc b/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_integration_test.cc new file mode 100644 index 000000000000..051a21b6846f --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_integration_test.cc @@ -0,0 +1,142 @@ +#include +#include + +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" + +#include "test/integration/http_integration.h" +#include "test/test_common/utility.h" + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { +namespace { + +const char* TRACEPARENT_VALUE = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"; +const char* TRACEPARENT_VALUE_START = "00-0af7651916cd43dd8448eb211c80319c"; + +class AlwaysOnSamplerIntegrationTest : public Envoy::HttpIntegrationTest, + public testing::TestWithParam { +public: + AlwaysOnSamplerIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) { + + const std::string yaml_string = R"EOF( + provider: + name: envoy.tracers.opentelemetry + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig + grpc_service: + envoy_grpc: + cluster_name: opentelemetry_collector + timeout: 0.250s + service_name: "a_service_name" + sampler: + name: envoy.tracers.opentelemetry.samplers.dynatrace + typed_config: + "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.samplers.v3.AlwaysOnSamplerConfig + )EOF"; + + auto tracing_config = + std::make_unique<::envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager_Tracing>(); + TestUtility::loadFromYaml(yaml_string, *tracing_config.get()); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.set_allocated_tracing(tracing_config.release()); }); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, AlwaysOnSamplerIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +// Sends a request with traceparent and tracestate header. +TEST_P(AlwaysOnSamplerIntegrationTest, TestWithTraceparentAndTracestate) { + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, + {":authority", "host"}, {"tracestate", "key=value"}, {"traceparent", TRACEPARENT_VALUE}}; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); + + // traceparent should be set: traceid should be re-used, span id should be different + absl::string_view traceparent_value = upstream_request_->headers() + .get(Http::LowerCaseString("traceparent"))[0] + ->value() + .getStringView(); + EXPECT_TRUE(absl::StartsWith(traceparent_value, TRACEPARENT_VALUE_START)); + EXPECT_NE(TRACEPARENT_VALUE, traceparent_value); + // tracestate should be forwarded + absl::string_view tracestate_value = upstream_request_->headers() + .get(Http::LowerCaseString("tracestate"))[0] + ->value() + .getStringView(); + EXPECT_EQ("key=value", tracestate_value); +} + +// Sends a request with traceparent but no tracestate header. +TEST_P(AlwaysOnSamplerIntegrationTest, TestWithTraceparentOnly) { + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"traceparent", TRACEPARENT_VALUE}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); + + // traceparent should be set: traceid should be re-used, span id should be different + absl::string_view traceparent_value = upstream_request_->headers() + .get(Http::LowerCaseString("traceparent"))[0] + ->value() + .getStringView(); + EXPECT_TRUE(absl::StartsWith(traceparent_value, TRACEPARENT_VALUE_START)); + EXPECT_NE(TRACEPARENT_VALUE, traceparent_value); + // OTLP tracer adds an empty tracestate + absl::string_view tracestate_value = upstream_request_->headers() + .get(Http::LowerCaseString("tracestate"))[0] + ->value() + .getStringView(); + EXPECT_EQ("", tracestate_value); +} + +// Sends a request without traceparent and tracestate header. +TEST_P(AlwaysOnSamplerIntegrationTest, TestWithoutTraceparentAndTracestate) { + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}}; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); + + // traceparent will be added, trace_id and span_id will be generated, so there is nothing we can + // assert + EXPECT_EQ(upstream_request_->headers().get(::Envoy::Http::LowerCaseString("traceparent")).size(), + 1); + // OTLP tracer adds an empty tracestate + absl::string_view tracestate_value = upstream_request_->headers() + .get(Http::LowerCaseString("tracestate"))[0] + ->value() + .getStringView(); + EXPECT_EQ("", tracestate_value); +} + +} // namespace +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc new file mode 100644 index 000000000000..79d37ca9bfe8 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc @@ -0,0 +1,56 @@ +#include + +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/always_on_sampler.pb.h" + +#include "source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h" +#include "source/extensions/tracers/opentelemetry/span_context.h" + +#include "test/mocks/server/tracer_factory_context.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Verify sampler being invoked with an invalid span context +TEST(AlwaysOnSamplerTest, TestWithInvalidParentContext) { + envoy::extensions::tracers::opentelemetry::samplers::v3::AlwaysOnSamplerConfig config; + NiceMock context; + auto sampler = std::make_shared(config, context); + EXPECT_STREQ(sampler->getDescription().c_str(), "AlwaysOnSampler"); + + auto sampling_result = + sampler->shouldSample(absl::nullopt, "operation_name", "12345", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RECORD_AND_SAMPLE); + EXPECT_EQ(sampling_result.attributes, nullptr); + EXPECT_STREQ(sampling_result.tracestate.c_str(), ""); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with a valid span context +TEST(AlwaysOnSamplerTest, TestWithValidParentContext) { + envoy::extensions::tracers::opentelemetry::samplers::v3::AlwaysOnSamplerConfig config; + NiceMock context; + auto sampler = std::make_shared(config, context); + EXPECT_STREQ(sampler->getDescription().c_str(), "AlwaysOnSampler"); + + SpanContext span_context("0", "12345", "45678", false, "some_tracestate"); + auto sampling_result = + sampler->shouldSample(span_context, "operation_name", "12345", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RECORD_AND_SAMPLE); + EXPECT_EQ(sampling_result.attributes, nullptr); + EXPECT_STREQ(sampling_result.tracestate.c_str(), "some_tracestate"); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/always_on/config_test.cc b/test/extensions/tracers/opentelemetry/samplers/always_on/config_test.cc new file mode 100644 index 000000000000..226cd58e34f3 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/always_on/config_test.cc @@ -0,0 +1,38 @@ +#include "envoy/registry/registry.h" + +#include "source/extensions/tracers/opentelemetry/samplers/always_on/config.h" + +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Test create sampler via factory +TEST(AlwaysOnSamplerFactoryTest, Test) { + auto* factory = Registry::FactoryRegistry::getFactory( + "envoy.tracers.opentelemetry.samplers.always_on"); + ASSERT_NE(factory, nullptr); + EXPECT_STREQ(factory->name().c_str(), "envoy.tracers.opentelemetry.samplers.always_on"); + EXPECT_NE(factory->createEmptyConfigProto(), nullptr); + + envoy::config::core::v3::TypedExtensionConfig typed_config; + const std::string yaml = R"EOF( + name: envoy.tracers.opentelemetry.samplers.always_on + typed_config: + "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.samplers.v3.AlwaysOnSamplerConfig + )EOF"; + TestUtility::loadFromYaml(yaml, typed_config); + NiceMock context; + EXPECT_NE(factory->createSampler(typed_config.typed_config(), context), nullptr); + EXPECT_STREQ(factory->name().c_str(), "envoy.tracers.opentelemetry.samplers.always_on"); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc new file mode 100644 index 000000000000..f80103b4345c --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc @@ -0,0 +1,193 @@ +#include + +#include "envoy/config/trace/v3/opentelemetry.pb.h" +#include "envoy/registry/registry.h" + +#include "source/common/tracing/http_tracer_impl.h" +#include "source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.h" +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" +#include "source/extensions/tracers/opentelemetry/span_context.h" + +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +using ::testing::NiceMock; +using ::testing::StrictMock; + +class TestSampler : public Sampler { +public: + MOCK_METHOD(SamplingResult, shouldSample, + ((const absl::optional), (const std::string&), (const std::string&), + (::opentelemetry::proto::trace::v1::Span::SpanKind), + (const std::map&), (const std::vector&)), + (override)); + MOCK_METHOD(std::string, getDescription, (), (const, override)); +}; + +class TestSamplerFactory : public SamplerFactory { +public: + MOCK_METHOD(SamplerSharedPtr, createSampler, + (const Protobuf::Message& message, + Server::Configuration::TracerFactoryContext& context)); + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return "envoy.tracers.opentelemetry.samplers.testsampler"; } +}; + +class SamplerFactoryTest : public testing::Test { + +protected: + NiceMock config; + NiceMock stream_info; + Tracing::TestTraceContextImpl trace_context{}; + NiceMock context; +}; + +// Test OTLP tracer without a sampler +TEST_F(SamplerFactoryTest, TestWithoutSampler) { + // using StrictMock, calls to SamplerFactory would cause a test failure + auto test_sampler = std::make_shared>(); + StrictMock sampler_factory; + Registry::InjectFactory sampler_factory_registration(sampler_factory); + + // no sampler configured + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + )EOF"; + + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + auto driver = std::make_unique(opentelemetry_config, context); + + driver->startSpan(config, trace_context, stream_info, "operation_name", + {Tracing::Reason::Sampling, true}); +} + +// Test config containing an unknown sampler +TEST_F(SamplerFactoryTest, TestWithInvalidSampler) { + // using StrictMock, calls to SamplerFactory would cause a test failure + auto test_sampler = std::make_shared>(); + StrictMock sampler_factory; + Registry::InjectFactory sampler_factory_registration(sampler_factory); + + // invalid sampler configured + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + sampler: + name: envoy.tracers.opentelemetry.samplers.testsampler + typed_config: + "@type": type.googleapis.com/google.protobuf.Value + )EOF"; + + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + EXPECT_THROW(std::make_unique(opentelemetry_config, context), EnvoyException); +} + +// Test OTLP tracer with a sampler +TEST_F(SamplerFactoryTest, TestWithSampler) { + auto test_sampler = std::make_shared>(); + TestSamplerFactory sampler_factory; + Registry::InjectFactory sampler_factory_registration(sampler_factory); + + EXPECT_CALL(sampler_factory, createSampler(_, _)).WillOnce(Return(test_sampler)); + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + sampler: + name: envoy.tracers.opentelemetry.samplers.testsampler + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + )EOF"; + + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + auto driver = std::make_unique(opentelemetry_config, context); + + // shouldSample returns a result without additional attributes and Decision::RECORD_AND_SAMPLE + EXPECT_CALL(*test_sampler, shouldSample(_, _, _, _, _, _)) + .WillOnce([](const absl::optional, const std::string&, const std::string&, + ::opentelemetry::proto::trace::v1::Span::SpanKind, + const std::map&, const std::vector&) { + SamplingResult res; + res.decision = Decision::RECORD_AND_SAMPLE; + res.tracestate = "this_is=tracesate"; + return res; + }); + + Tracing::SpanPtr tracing_span = driver->startSpan( + config, trace_context, stream_info, "operation_name", {Tracing::Reason::Sampling, true}); + // startSpan returns a Tracing::SpanPtr. Tracing::Span has no sampled() method. + // We know that the underlying span is Extensions::Tracers::OpenTelemetry::Span + // So the dynamic_cast should be safe. + std::unique_ptr span(dynamic_cast(tracing_span.release())); + EXPECT_TRUE(span->sampled()); + EXPECT_STREQ(span->tracestate().c_str(), "this_is=tracesate"); + + // shouldSamples return a result containing additional attributes and Decision::DROP + EXPECT_CALL(*test_sampler, shouldSample(_, _, _, _, _, _)) + .WillOnce([](const absl::optional, const std::string&, const std::string&, + ::opentelemetry::proto::trace::v1::Span::SpanKind, + const std::map&, const std::vector&) { + SamplingResult res; + res.decision = Decision::DROP; + std::map attributes; + attributes["key"] = "value"; + attributes["another_key"] = "another_value"; + res.attributes = + std::make_unique>(std::move(attributes)); + res.tracestate = "this_is=another_tracesate"; + return res; + }); + tracing_span = driver->startSpan(config, trace_context, stream_info, "operation_name", + {Tracing::Reason::Sampling, true}); + std::unique_ptr unsampled_span(dynamic_cast(tracing_span.release())); + EXPECT_FALSE(unsampled_span->sampled()); + EXPECT_STREQ(unsampled_span->tracestate().c_str(), "this_is=another_tracesate"); +} + +// Test sampling result decision +TEST(SamplingResultTest, TestSamplingResult) { + SamplingResult result; + result.decision = Decision::RECORD_AND_SAMPLE; + EXPECT_TRUE(result.isRecording()); + EXPECT_TRUE(result.isSampled()); + result.decision = Decision::RECORD_ONLY; + EXPECT_TRUE(result.isRecording()); + EXPECT_FALSE(result.isSampled()); + result.decision = Decision::DROP; + EXPECT_FALSE(result.isRecording()); + EXPECT_FALSE(result.isSampled()); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml index e45938acb534..407d8fcfe4d1 100644 --- a/tools/extensions/extensions_schema.yaml +++ b/tools/extensions/extensions_schema.yaml @@ -137,6 +137,7 @@ categories: - envoy.http.custom_response - envoy.router.cluster_specifier_plugin - envoy.tracers.opentelemetry.resource_detectors +- envoy.tracers.opentelemetry.samplers status_values: - name: stable From c2630addb1ac2984531c07317af5f9dd882b2a01 Mon Sep 17 00:00:00 2001 From: Adam Kotwasinski Date: Mon, 30 Oct 2023 16:07:49 -0700 Subject: [PATCH 36/72] kafka: add ability to force response rewrite in broker filter (#30578) Signed-off-by: Adam Kotwasinski --- .../kafka_broker/v3/kafka_broker.proto | 5 ++ .../kafka_broker/v2alpha1/kafka_broker.proto | 5 ++ .../kafka/filters/network/source/broker/BUILD | 15 ++++ .../filters/network/source/broker/filter.cc | 15 ++-- .../filters/network/source/broker/filter.h | 31 +++++--- .../network/source/broker/filter_config.h | 13 +++- .../filters/network/source/broker/rewriter.cc | 54 +++++++++++++ .../filters/network/source/broker/rewriter.h | 76 +++++++++++++++++++ .../kafka/filters/network/test/broker/BUILD | 9 +++ .../test/broker/filter_protocol_test.cc | 2 +- .../network/test/broker/filter_unit_test.cc | 14 +++- .../integration_test/envoy_config_yaml.j2 | 1 + .../network/test/broker/rewriter_unit_test.cc | 76 +++++++++++++++++++ .../integration_test/envoy_config_yaml.j2 | 1 + 14 files changed, 296 insertions(+), 21 deletions(-) create mode 100644 contrib/kafka/filters/network/source/broker/rewriter.cc create mode 100644 contrib/kafka/filters/network/source/broker/rewriter.h create mode 100644 contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc diff --git a/api/contrib/envoy/extensions/filters/network/kafka_broker/v3/kafka_broker.proto b/api/contrib/envoy/extensions/filters/network/kafka_broker/v3/kafka_broker.proto index 83fdd27b378c..bc5a470608db 100644 --- a/api/contrib/envoy/extensions/filters/network/kafka_broker/v3/kafka_broker.proto +++ b/api/contrib/envoy/extensions/filters/network/kafka_broker/v3/kafka_broker.proto @@ -22,4 +22,9 @@ message KafkaBroker { // The prefix to use when emitting :ref:`statistics `. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; + + // Set to true if broker filter should attempt to serialize the received responses from the + // upstream broker instead of passing received bytes as is. + // Disabled by default. + bool force_response_rewrite = 2; } diff --git a/api/envoy/config/filter/network/kafka_broker/v2alpha1/kafka_broker.proto b/api/envoy/config/filter/network/kafka_broker/v2alpha1/kafka_broker.proto index 3611c1d6759f..5ca0de69651d 100644 --- a/api/envoy/config/filter/network/kafka_broker/v2alpha1/kafka_broker.proto +++ b/api/envoy/config/filter/network/kafka_broker/v2alpha1/kafka_broker.proto @@ -21,4 +21,9 @@ option (udpa.annotations.file_status).package_version_status = FROZEN; message KafkaBroker { // The prefix to use when emitting :ref:`statistics `. string stat_prefix = 1 [(validate.rules).string = {min_bytes: 1}]; + + // Set to true if broker filter should attempt to serialize the received responses from the + // upstream broker instead of passing received bytes as is. + // Disabled by default. + bool force_response_rewrite = 2; } diff --git a/contrib/kafka/filters/network/source/broker/BUILD b/contrib/kafka/filters/network/source/broker/BUILD index 3dfc7604add6..6af3b702e8b7 100644 --- a/contrib/kafka/filters/network/source/broker/BUILD +++ b/contrib/kafka/filters/network/source/broker/BUILD @@ -44,6 +44,7 @@ envoy_cc_library( ], deps = [ ":filter_config_lib", + ":rewriter_lib", "//contrib/kafka/filters/network/source:kafka_metrics_lib", "//contrib/kafka/filters/network/source:kafka_request_codec_lib", "//contrib/kafka/filters/network/source:kafka_response_codec_lib", @@ -54,3 +55,17 @@ envoy_cc_library( "//source/common/common:minimal_logger_lib", ], ) + +envoy_cc_library( + name = "rewriter_lib", + srcs = ["rewriter.cc"], + hdrs = [ + "rewriter.h", + ], + deps = [ + ":filter_config_lib", + "//contrib/kafka/filters/network/source:kafka_response_codec_lib", + "//envoy/buffer:buffer_interface", + "//source/common/common:minimal_logger_lib", + ], +) diff --git a/contrib/kafka/filters/network/source/broker/filter.cc b/contrib/kafka/filters/network/source/broker/filter.cc index 8440fcf876a8..8e7ba9a299cd 100644 --- a/contrib/kafka/filters/network/source/broker/filter.cc +++ b/contrib/kafka/filters/network/source/broker/filter.cc @@ -71,18 +71,22 @@ absl::flat_hash_map& KafkaMetricsFacadeImpl::getRequestA KafkaBrokerFilter::KafkaBrokerFilter(Stats::Scope& scope, TimeSource& time_source, const BrokerFilterConfig& filter_config) - : KafkaBrokerFilter{std::make_shared(scope, time_source, - filter_config.stat_prefix_)} {}; + : KafkaBrokerFilter{filter_config, std::make_shared( + scope, time_source, filter_config.stat_prefix_)} {}; -KafkaBrokerFilter::KafkaBrokerFilter(const KafkaMetricsFacadeSharedPtr& metrics) - : metrics_{metrics}, response_decoder_{new ResponseDecoder({metrics})}, +KafkaBrokerFilter::KafkaBrokerFilter(const BrokerFilterConfig& filter_config, + const KafkaMetricsFacadeSharedPtr& metrics) + : metrics_{metrics}, response_rewriter_{createRewriter(filter_config)}, + response_decoder_{new ResponseDecoder({metrics, response_rewriter_})}, request_decoder_{ new RequestDecoder({std::make_shared(*response_decoder_), metrics})} {}; KafkaBrokerFilter::KafkaBrokerFilter(KafkaMetricsFacadeSharedPtr metrics, + ResponseRewriterSharedPtr response_rewriter, ResponseDecoderSharedPtr response_decoder, RequestDecoderSharedPtr request_decoder) - : metrics_{metrics}, response_decoder_{response_decoder}, request_decoder_{request_decoder} {}; + : metrics_{metrics}, response_rewriter_{response_rewriter}, response_decoder_{response_decoder}, + request_decoder_{request_decoder} {}; Network::FilterStatus KafkaBrokerFilter::onNewConnection() { return Network::FilterStatus::Continue; @@ -107,6 +111,7 @@ Network::FilterStatus KafkaBrokerFilter::onWrite(Buffer::Instance& data, bool) { ENVOY_LOG(trace, "data from Kafka broker [{} response bytes]", data.length()); try { response_decoder_->onData(data); + response_rewriter_->process(data); return Network::FilterStatus::Continue; } catch (const EnvoyException& e) { ENVOY_LOG(debug, "could not process data from Kafka broker: {}", e.what()); diff --git a/contrib/kafka/filters/network/source/broker/filter.h b/contrib/kafka/filters/network/source/broker/filter.h index 519dee77a7aa..60d88b3d2cfa 100644 --- a/contrib/kafka/filters/network/source/broker/filter.h +++ b/contrib/kafka/filters/network/source/broker/filter.h @@ -7,6 +7,7 @@ #include "absl/container/flat_hash_map.h" #include "contrib/kafka/filters/network/source/broker/filter_config.h" +#include "contrib/kafka/filters/network/source/broker/rewriter.h" #include "contrib/kafka/filters/network/source/external/request_metrics.h" #include "contrib/kafka/filters/network/source/external/response_metrics.h" #include "contrib/kafka/filters/network/source/parser.h" @@ -112,7 +113,8 @@ class KafkaMetricsFacadeImpl : public KafkaMetricsFacade { /** * Implementation of Kafka broker-level filter. * Uses two decoders - request and response ones, that are connected using Forwarder instance. - * There's also a KafkaMetricsFacade, that is listening on codec events. + * KafkaMetricsFacade is listening for both request/response events to keep metrics. + * ResponseRewriter is listening for response events to capture and rewrite them if needed. * * +---------------------------------------------------+ * | | @@ -124,13 +126,18 @@ class KafkaMetricsFacadeImpl : public KafkaMetricsFacade { * | | v v v * +------+---+------+ +----+----+ +---------+---+----+ * |KafkaBrokerFilter| |Forwarder| |KafkaMetricsFacade| - * +----------+------+ +----+----+ +---------+--------+ - * | | ^ - * | | | - * | v | - * | +-------+-------+ | - * +---------->+ResponseDecoder+---------------+ - * +---------------+ + * +------+---+------+ +----+----+ +---------+--------+ + * | | | ^ + * | | | | + * | | v | + * | | +-------+-------+ | + * | +---------->+ResponseDecoder+---------------+ + * | +-------+-------+ + * | | + * | v + * | +-------+--------+ + * +-------------->+ResponseRewriter+ + * +----------------+ */ class KafkaBrokerFilter : public Network::Filter, private Logger::Loggable { public: @@ -145,7 +152,9 @@ class KafkaBrokerFilter : public Network::Filter, private Logger::Loggable(); + } else { + return std::make_shared(); + } +} + +} // namespace Broker +} // namespace Kafka +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/kafka/filters/network/source/broker/rewriter.h b/contrib/kafka/filters/network/source/broker/rewriter.h new file mode 100644 index 000000000000..bde1f6627575 --- /dev/null +++ b/contrib/kafka/filters/network/source/broker/rewriter.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +#include "envoy/buffer/buffer.h" + +#include "source/common/common/logger.h" + +#include "contrib/kafka/filters/network/source/broker/filter_config.h" +#include "contrib/kafka/filters/network/source/response_codec.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace Kafka { +namespace Broker { + +/** + * Responsible for modifying any outbound requests. + */ +class ResponseRewriter : public ResponseCallback { +public: + virtual ~ResponseRewriter() = default; + + /** + * Performs any desired payload changes. + * @param buffer buffer with the original data from upstream + */ + virtual void process(Buffer::Instance& buffer) PURE; +}; + +using ResponseRewriterSharedPtr = std::shared_ptr; + +/** + * Uses captured response objects instead of original data. + * Entry point for any response payload changes. + */ +class ResponseRewriterImpl : public ResponseRewriter, private Logger::Loggable { +public: + // ResponseCallback + void onMessage(AbstractResponseSharedPtr response) override; + void onFailedParse(ResponseMetadataSharedPtr parse_failure) override; + + // ResponseRewriter + void process(Buffer::Instance& buffer) override; + + size_t getStoredResponseCountForTest() const; + +private: + std::vector responses_to_rewrite_; +}; + +/** + * Does nothing, letting the data from upstream pass without any changes. + * It allows us to avoid the unnecessary deserialization-then-serialization steps. + */ +class DoNothingRewriter : public ResponseRewriter { +public: + // ResponseCallback + void onMessage(AbstractResponseSharedPtr response) override; + void onFailedParse(ResponseMetadataSharedPtr parse_failure) override; + + // ResponseRewriter + void process(Buffer::Instance& buffer) override; +}; + +/** + * Factory method that creates a rewriter depending on configuration. + */ +ResponseRewriterSharedPtr createRewriter(const BrokerFilterConfig& config); + +} // namespace Broker +} // namespace Kafka +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/kafka/filters/network/test/broker/BUILD b/contrib/kafka/filters/network/test/broker/BUILD index de856d0a1a4d..c7b20d851cf3 100644 --- a/contrib/kafka/filters/network/test/broker/BUILD +++ b/contrib/kafka/filters/network/test/broker/BUILD @@ -39,3 +39,12 @@ envoy_cc_test( "//test/test_common:test_time_lib", ], ) + +envoy_cc_test( + name = "rewriter_unit_test", + srcs = ["rewriter_unit_test.cc"], + deps = [ + "//contrib/kafka/filters/network/source/broker:rewriter_lib", + "//source/common/buffer:buffer_lib", + ], +) diff --git a/contrib/kafka/filters/network/test/broker/filter_protocol_test.cc b/contrib/kafka/filters/network/test/broker/filter_protocol_test.cc index e4a8a18ee0b7..9b5bf4276e0a 100644 --- a/contrib/kafka/filters/network/test/broker/filter_protocol_test.cc +++ b/contrib/kafka/filters/network/test/broker/filter_protocol_test.cc @@ -35,7 +35,7 @@ class KafkaBrokerFilterProtocolTest : public testing::Test, Stats::TestUtil::TestStore store_; Stats::Scope& scope_{*store_.rootScope()}; Event::TestRealTimeSystem time_source_; - KafkaBrokerFilter testee_{scope_, time_source_, {"prefix"}}; + KafkaBrokerFilter testee_{scope_, time_source_, {"prefix", false}}; Network::FilterStatus consumeRequestFromBuffer() { return testee_.onData(RequestB::buffer_, false); diff --git a/contrib/kafka/filters/network/test/broker/filter_unit_test.cc b/contrib/kafka/filters/network/test/broker/filter_unit_test.cc index a91316250db8..0b272e713dd0 100644 --- a/contrib/kafka/filters/network/test/broker/filter_unit_test.cc +++ b/contrib/kafka/filters/network/test/broker/filter_unit_test.cc @@ -4,6 +4,7 @@ #include "test/mocks/stats/mocks.h" #include "contrib/kafka/filters/network/source/broker/filter.h" +#include "contrib/kafka/filters/network/source/broker/filter_config.h" #include "contrib/kafka/filters/network/source/external/requests.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -32,6 +33,15 @@ class MockKafkaMetricsFacade : public KafkaMetricsFacade { using MockKafkaMetricsFacadeSharedPtr = std::shared_ptr; +class MockResponseRewriter : public ResponseRewriter { +public: + MOCK_METHOD(void, onMessage, (AbstractResponseSharedPtr)); + MOCK_METHOD(void, onFailedParse, (ResponseMetadataSharedPtr)); + MOCK_METHOD(void, process, (Buffer::Instance&)); +}; + +using MockResponseRewriterSharedPtr = std::shared_ptr; + class MockResponseDecoder : public ResponseDecoder { public: MockResponseDecoder() : ResponseDecoder{{}} {}; @@ -92,12 +102,13 @@ class MockResponse : public AbstractResponse { class KafkaBrokerFilterUnitTest : public testing::Test { protected: MockKafkaMetricsFacadeSharedPtr metrics_{std::make_shared()}; + MockResponseRewriterSharedPtr response_rewriter_{std::make_shared()}; MockResponseDecoderSharedPtr response_decoder_{std::make_shared()}; MockRequestDecoderSharedPtr request_decoder_{std::make_shared()}; NiceMock filter_callbacks_; - KafkaBrokerFilter testee_{metrics_, response_decoder_, request_decoder_}; + KafkaBrokerFilter testee_{metrics_, response_rewriter_, response_decoder_, request_decoder_}; void initialize() { testee_.initializeReadFilterCallbacks(filter_callbacks_); @@ -138,6 +149,7 @@ TEST_F(KafkaBrokerFilterUnitTest, ShouldAcceptDataSentByKafkaBroker) { // given Buffer::OwnedImpl data; EXPECT_CALL(*response_decoder_, onData(_)); + EXPECT_CALL(*response_rewriter_, process(_)); // when initialize(); diff --git a/contrib/kafka/filters/network/test/broker/integration_test/envoy_config_yaml.j2 b/contrib/kafka/filters/network/test/broker/integration_test/envoy_config_yaml.j2 index af945c5c61d7..bc3819f9f4fe 100644 --- a/contrib/kafka/filters/network/test/broker/integration_test/envoy_config_yaml.j2 +++ b/contrib/kafka/filters/network/test/broker/integration_test/envoy_config_yaml.j2 @@ -10,6 +10,7 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.kafka_broker.v3.KafkaBroker stat_prefix: testfilter + force_response_rewrite: true - name: tcp typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy diff --git a/contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc b/contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc new file mode 100644 index 000000000000..4196b4c2588f --- /dev/null +++ b/contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc @@ -0,0 +1,76 @@ +#include "source/common/buffer/buffer_impl.h" + +#include "contrib/kafka/filters/network/source/broker/filter_config.h" +#include "contrib/kafka/filters/network/source/broker/rewriter.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace Kafka { +namespace Broker { + +static void putBytesIntoBuffer(Buffer::Instance& buffer, const uint32_t size) { + std::vector data(size, 42); + absl::string_view sv = {data.data(), data.size()}; + buffer.add(sv); +} + +static Buffer::InstancePtr makeRandomBuffer(const uint32_t size) { + Buffer::InstancePtr result = std::make_unique(); + putBytesIntoBuffer(*result, size); + return result; +} + +class FakeResponse : public AbstractResponse { +public: + FakeResponse(const size_t size) : AbstractResponse{{0, 0, 0}}, size_{size} {} + + uint32_t computeSize() const override { return size_; }; + + virtual uint32_t encode(Buffer::Instance& dst) const override { + putBytesIntoBuffer(dst, size_); + return size_; + }; + +private: + size_t size_; +}; + +TEST(ResponseRewriterImplUnitTest, ShouldRewriteBuffer) { + // given + ResponseRewriterImpl testee; + + auto response1 = std::make_shared(7); + auto response2 = std::make_shared(13); + auto response3 = std::make_shared(42); + + // when - 1 + testee.onMessage(response1); + testee.onMessage(response2); + testee.onMessage(response3); + + // then - 1 + ASSERT_EQ(testee.getStoredResponseCountForTest(), 3); + + // when - 2 + auto buffer = makeRandomBuffer(4242); + testee.process(*buffer); + + // then - 2 + ASSERT_EQ(testee.getStoredResponseCountForTest(), 0); + ASSERT_EQ(buffer->length(), (3 * 4) + 7 + 13 + 42); // 4 bytes for message length +} + +TEST(ResponseRewriterUnitTest, ShouldCreateProperRewriter) { + ResponseRewriterSharedPtr r1 = createRewriter({"aaa", true}); + ASSERT_NE(std::dynamic_pointer_cast(r1), nullptr); + ResponseRewriterSharedPtr r2 = createRewriter({"aaa", false}); + ASSERT_NE(std::dynamic_pointer_cast(r2), nullptr); +} + +} // namespace Broker +} // namespace Kafka +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/kafka/filters/network/test/mesh/integration_test/envoy_config_yaml.j2 b/contrib/kafka/filters/network/test/mesh/integration_test/envoy_config_yaml.j2 index fbb22d2af3a9..cb2cdeeee807 100644 --- a/contrib/kafka/filters/network/test/mesh/integration_test/envoy_config_yaml.j2 +++ b/contrib/kafka/filters/network/test/mesh/integration_test/envoy_config_yaml.j2 @@ -10,6 +10,7 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.kafka_broker.v3.KafkaBroker stat_prefix: testfilter + force_response_rewrite: true - name: mesh typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.kafka_mesh.v3alpha.KafkaMesh From 613ec36aa30cd0f82a8064bde407adc44f84bf70 Mon Sep 17 00:00:00 2001 From: deveshkandpal1224 <100540598+deveshkandpal1224@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:08:15 -0700 Subject: [PATCH 37/72] redis_proxy: fix crash if catch_all_route is not defined (#30592) Signed-off-by: deveshkandpal1224 --- changelogs/current.yaml | 3 +++ .../filters/network/redis_proxy/router_impl.cc | 7 +++++++ .../network/redis_proxy/router_impl_test.cc | 14 ++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0a83d5683614..fcc708916ad3 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -36,6 +36,9 @@ bug_fixes: - area: grpc change: | Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: redis + change: | + Fixed a bug causing crash if incoming redis key does not match against a prefix_route and catch_all_route is not defined. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/filters/network/redis_proxy/router_impl.cc b/source/extensions/filters/network/redis_proxy/router_impl.cc index 59cba4554876..4aae1dad7dfa 100644 --- a/source/extensions/filters/network/redis_proxy/router_impl.cc +++ b/source/extensions/filters/network/redis_proxy/router_impl.cc @@ -106,6 +106,13 @@ RouteSharedPtr PrefixRoutes::upstreamPool(std::string& key, if (value == nullptr) { // prefix route not found, default to catch all route. value = catch_all_route_; + // prefix route not found, check if catch_all_route is defined to fallback to. + if (catch_all_route_ != nullptr) { + value = catch_all_route_; + } else { + // no route found. + return value; + } } if (value->removePrefix()) { diff --git a/test/extensions/filters/network/redis_proxy/router_impl_test.cc b/test/extensions/filters/network/redis_proxy/router_impl_test.cc index 366ac792700e..b3b0deb932e9 100644 --- a/test/extensions/filters/network/redis_proxy/router_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/router_impl_test.cc @@ -69,6 +69,20 @@ TEST(PrefixRoutesTest, RoutedToCatchAll) { EXPECT_EQ(upstream_c, router.upstreamPool(key, stream_info)->upstream("")); } +TEST(PrefixRoutesTest, MissingCatchAll) { + Upstreams upstreams; + upstreams.emplace("fake_clusterA", std::make_shared()); + upstreams.emplace("fake_clusterB", std::make_shared()); + + Runtime::MockLoader runtime_; + + PrefixRoutes router(createPrefixRoutes(), std::move(upstreams), runtime_); + + std::string key("c:bar"); + NiceMock stream_info; + EXPECT_EQ(nullptr, router.upstreamPool(key, stream_info)); +} + TEST(PrefixRoutesTest, RoutedToLongestPrefix) { auto upstream_a = std::make_shared(); From 60b286763af73e6f9040c463f48f53523e7c1fae Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Mon, 30 Oct 2023 16:09:21 -0700 Subject: [PATCH 38/72] aws: add metadata fetcher utility to use http async client (#29880) Signed-off-by: Sunil Narasimhamurthy --- source/extensions/common/aws/BUILD | 16 + .../extensions/common/aws/metadata_fetcher.cc | 179 +++++++++++ .../extensions/common/aws/metadata_fetcher.h | 97 ++++++ source/extensions/common/aws/utility.cc | 56 ++++ source/extensions/common/aws/utility.h | 22 ++ test/extensions/common/aws/BUILD | 20 ++ .../common/aws/metadata_fetcher_test.cc | 283 ++++++++++++++++++ test/extensions/common/aws/mocks.h | 21 ++ test/extensions/common/aws/utility_test.cc | 52 ++++ 9 files changed, 746 insertions(+) create mode 100644 source/extensions/common/aws/metadata_fetcher.cc create mode 100644 source/extensions/common/aws/metadata_fetcher.h create mode 100644 test/extensions/common/aws/metadata_fetcher_test.cc diff --git a/source/extensions/common/aws/BUILD b/source/extensions/common/aws/BUILD index 96382e2095c2..b5d884069500 100644 --- a/source/extensions/common/aws/BUILD +++ b/source/extensions/common/aws/BUILD @@ -40,6 +40,18 @@ envoy_cc_library( external_deps = ["abseil_optional"], ) +envoy_cc_library( + name = "metadata_fetcher_lib", + srcs = ["metadata_fetcher.cc"], + hdrs = ["metadata_fetcher.h"], + deps = [ + ":utility_lib", + "//envoy/upstream:cluster_manager_interface", + "//source/common/http:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "credentials_provider_impl_lib", srcs = ["credentials_provider_impl.cc"], @@ -63,10 +75,14 @@ envoy_cc_library( external_deps = ["curl"], deps = [ "//envoy/http:message_interface", + "//envoy/upstream:cluster_manager_interface", "//source/common/common:empty_string", "//source/common/common:matchers_lib", "//source/common/common:utility_lib", "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/http/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/common/aws/metadata_fetcher.cc b/source/extensions/common/aws/metadata_fetcher.cc new file mode 100644 index 000000000000..339f75be7c2c --- /dev/null +++ b/source/extensions/common/aws/metadata_fetcher.cc @@ -0,0 +1,179 @@ +#include "source/extensions/common/aws/metadata_fetcher.h" + +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/core/v3/http_uri.pb.h" + +#include "source/common/common/enum_to_int.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Aws { + +namespace { + +class MetadataFetcherImpl : public MetadataFetcher, + public Logger::Loggable, + public Http::AsyncClient::Callbacks { + +public: + MetadataFetcherImpl(Upstream::ClusterManager& cm, absl::string_view cluster_name) + : cm_(cm), cluster_name_(std::string(cluster_name)) {} + + ~MetadataFetcherImpl() override { cancel(); } + + void cancel() override { + if (request_ && !complete_) { + request_->cancel(); + ENVOY_LOG(debug, "fetch AWS Metadata [cluster = {}]: cancelled", cluster_name_); + } + reset(); + } + + absl::string_view failureToString(MetadataFetcher::MetadataReceiver::Failure reason) override { + switch (reason) { + case MetadataFetcher::MetadataReceiver::Failure::Network: + return "Network"; + case MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata: + return "InvalidMetadata"; + case MetadataFetcher::MetadataReceiver::Failure::MissingConfig: + return "MissingConfig"; + default: + return ""; + } + } + + void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, + MetadataFetcher::MetadataReceiver& receiver) override { + ASSERT(!request_); + ASSERT(!receiver_); + complete_ = false; + receiver_ = makeOptRef(receiver); + const auto thread_local_cluster = cm_.getThreadLocalCluster(cluster_name_); + if (thread_local_cluster == nullptr) { + ENVOY_LOG(error, "{} AWS Metadata failed: [cluster = {}] not found", __func__, cluster_name_); + complete_ = true; + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::MissingConfig); + reset(); + return; + } + + constexpr uint64_t MAX_RETRIES = 3; + constexpr uint64_t RETRY_DELAY = 1000; + constexpr uint64_t TIMEOUT = 5 * 1000; + + const auto host_attributes = Http::Utility::parseAuthority(message.headers().getHostValue()); + const auto host = host_attributes.host_; + const auto path = message.headers().getPathValue(); + const auto scheme = message.headers().getSchemeValue(); + const auto method = message.headers().getMethodValue(); + ENVOY_LOG(debug, "fetch AWS Metadata at [uri = {}]: start from cluster {}", + fmt::format("{}://{}{}", scheme, host, path), cluster_name_); + + Http::RequestHeaderMapPtr headersPtr = + Envoy::Http::createHeaderMap( + {{Envoy::Http::Headers::get().Method, std::string(method)}, + {Envoy::Http::Headers::get().Host, std::string(host)}, + {Envoy::Http::Headers::get().Scheme, std::string(scheme)}, + {Envoy::Http::Headers::get().Path, std::string(path)}}); + + // Copy the remaining headers. + message.headers().iterate( + [&headersPtr](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate { + // Skip pseudo-headers + if (!entry.key().getStringView().empty() && entry.key().getStringView()[0] == ':') { + return Http::HeaderMap::Iterate::Continue; + } + headersPtr->addCopy(Http::LowerCaseString(entry.key().getStringView()), + entry.value().getStringView()); + return Http::HeaderMap::Iterate::Continue; + }); + + auto messagePtr = std::make_unique(std::move(headersPtr)); + + auto options = Http::AsyncClient::RequestOptions() + .setTimeout(std::chrono::milliseconds(TIMEOUT)) + .setParentSpan(parent_span) + .setSendXff(false) + .setChildSpanName("AWS Metadata Fetch"); + + envoy::config::route::v3::RetryPolicy route_retry_policy; + route_retry_policy.mutable_num_retries()->set_value(MAX_RETRIES); + route_retry_policy.mutable_per_try_timeout()->CopyFrom( + Protobuf::util::TimeUtil::MillisecondsToDuration(TIMEOUT)); + route_retry_policy.mutable_per_try_idle_timeout()->CopyFrom( + Protobuf::util::TimeUtil::MillisecondsToDuration(RETRY_DELAY)); + route_retry_policy.set_retry_on("5xx,gateway-error,connect-failure,reset,refused-stream"); + + options.setRetryPolicy(route_retry_policy); + options.setBufferBodyForRetry(true); + request_ = makeOptRefFromPtr( + thread_local_cluster->httpAsyncClient().send(std::move(messagePtr), *this, options)); + } + + // HTTP async receive method on success. + void onSuccess(const Http::AsyncClient::Request&, Http::ResponseMessagePtr&& response) override { + complete_ = true; + const uint64_t status_code = Http::Utility::getResponseStatus(response->headers()); + if (status_code == enumToInt(Http::Code::OK)) { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: success", __func__, cluster_name_); + if (response->body().length() != 0) { + const auto body = response->bodyAsString(); + receiver_->onMetadataSuccess(std::move(body)); + } else { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: body is empty", __func__, + cluster_name_); + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); + } + } else { + if (response->body().length() != 0) { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: response status code {}, body: {}", + __func__, cluster_name_, status_code, response->bodyAsString()); + } else { + ENVOY_LOG(debug, + "{}: fetch AWS Metadata [cluster = {}]: response status code {}, body is empty", + __func__, cluster_name_, status_code); + } + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } + reset(); + } + + // HTTP async receive method on failure. + void onFailure(const Http::AsyncClient::Request&, + Http::AsyncClient::FailureReason reason) override { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: network error {}", __func__, + cluster_name_, enumToInt(reason)); + complete_ = true; + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + reset(); + } + + // TODO(suniltheta): Add metadata fetch status into the span like it is done on ext_authz filter. + void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} + +private: + bool complete_{}; + Upstream::ClusterManager& cm_; + const std::string cluster_name_; + OptRef receiver_; + OptRef request_; + + void reset() { + request_.reset(); + receiver_.reset(); + } +}; +} // namespace + +MetadataFetcherPtr MetadataFetcher::create(Upstream::ClusterManager& cm, + absl::string_view cluster_name) { + return std::make_unique(cm, cluster_name); +} +} // namespace Aws +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/aws/metadata_fetcher.h b/source/extensions/common/aws/metadata_fetcher.h new file mode 100644 index 000000000000..a39d1480447c --- /dev/null +++ b/source/extensions/common/aws/metadata_fetcher.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/http/message.h" +#include "envoy/upstream/cluster_manager.h" + +#include "source/common/http/message_impl.h" +#include "source/extensions/common/aws/utility.h" + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Aws { + +class MetadataFetcher; +using MetadataFetcherPtr = std::unique_ptr; + +/** + * MetadataFetcher interface can be used to retrieve AWS Metadata from various providers. + * An instance of this interface is designed to retrieve one AWS Metadata at a time. + * The implementation of AWS Metadata Fetcher is similar to JwksFetcher. + */ + +class MetadataFetcher { +public: + class MetadataReceiver { + public: + enum class Failure { + /* A network error occurred causing AWS Metadata retrieval failure. */ + Network, + /* A failure occurred when trying to parse the retrieved AWS Metadata data. */ + InvalidMetadata, + /* A missing config causing AWS Metadata retrieval failure. */ + MissingConfig, + }; + + virtual ~MetadataReceiver() = default; + + /** + * @brief Successful retrieval callback of returned AWS Metadata. + * @param body Fetched AWS Metadata. + */ + virtual void onMetadataSuccess(const std::string&& body) PURE; + + /** + * @brief Retrieval error callback. + * @param reason the failure reason. + */ + virtual void onMetadataError(Failure reason) PURE; + }; + + virtual ~MetadataFetcher() = default; + + /** + * @brief Cancel any in-flight request. + */ + virtual void cancel() PURE; + + /** + * @brief Retrieve a AWS Metadata from a remote HTTP host. + * At most one outstanding request may be in-flight. + * i.e. from the invocation of `fetch()` until either + * a callback or `cancel()` is invoked, no additional + * `fetch()` may be issued. The URI to fetch is to pre + * determined based on the credentials provider source. + * + * @param receiver the receiver of the fetched AWS Metadata or error + */ + virtual void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, + MetadataReceiver& receiver) PURE; + + /** + * @brief Return MetadataReceiver Failure enum as a string. + * + * @return absl::string_view + */ + virtual absl::string_view failureToString(MetadataReceiver::Failure) PURE; + + /** + * @brief Factory method for creating a Metadata Fetcher. + * + * @param cm the cluster manager to use during AWS Metadata retrieval + * @param provider the AWS Metadata provider + * @return a MetadataFetcher instance + */ + static MetadataFetcherPtr create(Upstream::ClusterManager& cm, absl::string_view cluster_name); +}; +} // namespace Aws +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/aws/utility.cc b/source/extensions/common/aws/utility.cc index 9d5669b2113a..1643e4068ba7 100644 --- a/source/extensions/common/aws/utility.cc +++ b/source/extensions/common/aws/utility.cc @@ -1,13 +1,18 @@ #include "source/extensions/common/aws/utility.h" +#include "envoy/upstream/cluster_manager.h" + #include "source/common/common/empty_string.h" #include "source/common/common/fmt.h" #include "source/common/common/utility.h" +#include "source/common/protobuf/message_validator_impl.h" +#include "source/common/protobuf/utility.h" #include "absl/strings/match.h" #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "curl/curl.h" +#include "fmt/printf.h" namespace Envoy { namespace Extensions { @@ -294,6 +299,57 @@ absl::optional Utility::fetchMetadata(Http::RequestMessage& message return buffer.empty() ? absl::nullopt : absl::optional(buffer); } +bool Utility::addInternalClusterStatic( + Upstream::ClusterManager& cm, absl::string_view cluster_name, + const envoy::config::cluster::v3::Cluster::DiscoveryType cluster_type, absl::string_view uri) { + // Check if local cluster exists with that name. + if (cm.getThreadLocalCluster(cluster_name) == nullptr) { + // Make sure we run this on main thread. + TRY_ASSERT_MAIN_THREAD { + envoy::config::cluster::v3::Cluster cluster; + absl::string_view host_port; + absl::string_view path; + Http::Utility::extractHostPathFromUri(uri, host_port, path); + const auto host_attributes = Http::Utility::parseAuthority(host_port); + const auto host = host_attributes.host_; + const auto port = host_attributes.port_ ? host_attributes.port_.value() : 80; + + cluster.set_name(cluster_name); + cluster.set_type(cluster_type); + cluster.mutable_connect_timeout()->set_seconds(5); + cluster.mutable_load_assignment()->set_cluster_name(cluster_name); + auto* endpoint = cluster.mutable_load_assignment() + ->add_endpoints() + ->add_lb_endpoints() + ->mutable_endpoint(); + auto* addr = endpoint->mutable_address(); + addr->mutable_socket_address()->set_address(host); + addr->mutable_socket_address()->set_port_value(port); + cluster.set_lb_policy(envoy::config::cluster::v3::Cluster::ROUND_ROBIN); + envoy::extensions::upstreams::http::v3::HttpProtocolOptions protocol_options; + auto* http_protocol_options = + protocol_options.mutable_explicit_http_config()->mutable_http_protocol_options(); + http_protocol_options->set_accept_http_10(true); + (*cluster.mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(protocol_options); + + // TODO(suniltheta): use random number generator here for cluster version. + cm.addOrUpdateCluster(cluster, "12345"); + ENVOY_LOG_MISC(info, + "Added a {} internal cluster [name: {}, address:{}:{}] to fetch aws " + "credentials", + cluster_type, cluster_name, host, port); + } + END_TRY + CATCH(const EnvoyException& e, { + ENVOY_LOG_MISC(error, "Failed to add internal cluster {}: {}", cluster_name, e.what()); + return false; + }); + } + return true; +} + } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/source/extensions/common/aws/utility.h b/source/extensions/common/aws/utility.h index 2ec7cae045cd..985ab0de6d9f 100644 --- a/source/extensions/common/aws/utility.h +++ b/source/extensions/common/aws/utility.h @@ -1,9 +1,13 @@ #pragma once +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.validate.h" #include "envoy/http/message.h" #include "source/common/common/matchers.h" #include "source/common/http/headers.h" +#include "source/common/http/utility.h" namespace Envoy { namespace Extensions { @@ -92,6 +96,24 @@ class Utility { * gRPC auth plugins that are able to schedule blocking plugins on a different thread. */ static absl::optional fetchMetadata(Http::RequestMessage& message); + + /** + * @brief Adds a static cluster towards a credentials provider + * to fetch the credentials using http async client. + * + * @param cm cluster manager + * @param cluster_name a name for credentials provider cluster + * @param cluster_type STATIC or STRICT_DNS or LOGICAL_DNS etc + * @param uri provider's IP (STATIC cluster) or URL (STRICT_DNS). Will use port 80 if the port is + * not specified in the uri or no matching cluster is found. + * @return true if successfully added the cluster or if a cluster with the cluster_name already + * exists. + * @return false if failed to add the cluster + */ + static bool + addInternalClusterStatic(Upstream::ClusterManager& cm, absl::string_view cluster_name, + const envoy::config::cluster::v3::Cluster::DiscoveryType cluster_type, + absl::string_view uri); }; } // namespace Aws diff --git a/test/extensions/common/aws/BUILD b/test/extensions/common/aws/BUILD index 55ebdf79f19f..43ce091b0f55 100644 --- a/test/extensions/common/aws/BUILD +++ b/test/extensions/common/aws/BUILD @@ -14,8 +14,11 @@ envoy_cc_mock( srcs = ["mocks.cc"], hdrs = ["mocks.h"], deps = [ + "//source/common/http:message_lib", "//source/extensions/common/aws:credentials_provider_interface", + "//source/extensions/common/aws:metadata_fetcher_lib", "//source/extensions/common/aws:signer_interface", + "//test/mocks/upstream:cluster_manager_mocks", ], ) @@ -37,6 +40,7 @@ envoy_cc_test( srcs = ["utility_test.cc"], deps = [ "//source/extensions/common/aws:utility_lib", + "//test/extensions/common/aws:aws_mocks", "//test/test_common:utility_lib", ], ) @@ -50,6 +54,22 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "metadata_fetcher_test", + srcs = ["metadata_fetcher_test.cc"], + deps = [ + "//source/extensions/common/aws:metadata_fetcher_lib", + "//test/extensions/common/aws:aws_mocks", + "//test/extensions/filters/http/common:mock_lib", + "//test/mocks/api:api_mocks", + "//test/mocks/event:event_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "credentials_provider_impl_test", srcs = ["credentials_provider_impl_test.cc"], diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc new file mode 100644 index 000000000000..d009625e952a --- /dev/null +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -0,0 +1,283 @@ +#include +#include +#include + +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/utility.h" +#include "source/extensions/common/aws/metadata_fetcher.h" + +#include "test/extensions/common/aws/mocks.h" +#include "test/extensions/filters/http/common/mock.h" +#include "test/mocks/api/mocks.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/environment.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +using Envoy::Extensions::HttpFilters::Common::MockUpstream; +using testing::_; +using testing::AllOf; +using testing::InSequence; +using testing::Mock; +using testing::NiceMock; +using testing::Ref; +using testing::Return; +using testing::Throw; +using testing::UnorderedElementsAre; + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Aws { + +MATCHER_P(OptionsHasBufferBodyForRetry, expectedValue, "") { + *result_listener << "\nexpected { buffer_body_for_retry: \"" << expectedValue + << "\"} but got {buffer_body_for_retry: \"" << arg.buffer_body_for_retry + << "\"}\n"; + return ExplainMatchResult(expectedValue, arg.buffer_body_for_retry, result_listener); +} + +MATCHER_P(NumRetries, expectedRetries, "") { + *result_listener << "\nexpected { num_retries: \"" << expectedRetries + << "\"} but got {num_retries: \"" << arg.num_retries().value() << "\"}\n"; + return ExplainMatchResult(expectedRetries, arg.num_retries().value(), result_listener); +} + +MATCHER_P(PerTryTimeout, expectedTimeout, "") { + *result_listener << "\nexpected { per_try_timeout: \"" << expectedTimeout + << "\"} but got { per_try_timeout: \"" << arg.per_try_timeout().seconds() + << "\"}\n"; + return ExplainMatchResult(expectedTimeout, arg.per_try_timeout().seconds(), result_listener); +} + +MATCHER_P(PerTryIdleTimeout, expectedIdleTimeout, "") { + *result_listener << "\nexpected { per_try_idle_timeout: \"" << expectedIdleTimeout + << "\"} but got { per_try_idle_timeout: \"" + << arg.per_try_idle_timeout().seconds() << "\"}\n"; + return ExplainMatchResult(expectedIdleTimeout, arg.per_try_idle_timeout().seconds(), + result_listener); +} + +MATCHER_P(RetryOnModes, expectedModes, "") { + const std::string& retry_on = arg.retry_on(); + std::set retry_on_modes = absl::StrSplit(retry_on, ','); + *result_listener << "\nexpected retry_on modes doesn't match " + << "received { retry_on modes: \"" << retry_on << "\"}\n"; + return ExplainMatchResult(expectedModes, retry_on_modes, result_listener); +} + +MATCHER_P(OptionsHasRetryPolicy, policyMatcher, "") { + if (!arg.retry_policy.has_value()) { + *result_listener << "Expected options to have retry policy, but it was unset"; + return false; + } + return ExplainMatchResult(policyMatcher, arg.retry_policy.value(), result_listener); +} + +class MetadataFetcherTest : public testing::Test { +public: + void setupFetcher() { + mock_factory_ctx_.cluster_manager_.initializeThreadLocalClusters({"cluster_name"}); + fetcher_ = MetadataFetcher::create(mock_factory_ctx_.cluster_manager_, "cluster_name"); + EXPECT_TRUE(fetcher_ != nullptr); + } + + testing::NiceMock mock_factory_ctx_; + std::unique_ptr fetcher_; + NiceMock parent_span_; +}; + +TEST_F(MetadataFetcherTest, TestGetSuccess) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + std::string body = "not_empty"; + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", body); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(std::move(body))); + EXPECT_CALL(receiver, onMetadataError(_)).Times(0); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestRequestMatchAndSpanPassedDown) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + message.headers().setScheme(Http::Headers::get().SchemeValues.Http); + message.headers().setMethod(Http::Headers::get().MethodValues.Get); + message.headers().setHost("169.254.170.2:80"); + message.headers().setPath("/v2/credentials/c68caeb5-ef71-4914-8170-111111111111"); + message.headers().setCopy(Http::LowerCaseString(":pseudo-header"), "peudo-header-value"); + message.headers().setCopy(Http::LowerCaseString("X-aws-ec2-metadata-token"), "Token"); + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + MockMetadataReceiver receiver; + Http::MockAsyncClientRequest httpClientRequest( + &mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_); + + EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, + send_(_, _, _)) + .WillOnce(Invoke( + [this, &httpClientRequest]( + Http::RequestMessagePtr& request, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { + Http::TestRequestHeaderMapImpl injected_headers = { + {":method", "GET"}, + {":scheme", "http"}, + {":authority", "169.254.170.2"}, + {":path", "/v2/credentials/c68caeb5-ef71-4914-8170-111111111111"}, + {"X-aws-ec2-metadata-token", "Token"}}; + EXPECT_THAT(request->headers(), IsSupersetOfHeaders(injected_headers)); + EXPECT_TRUE(request->headers().get(Http::LowerCaseString(":pseudo-header")).empty()); + + // Verify expectations for span + EXPECT_TRUE(options.parent_span_ == &this->parent_span_); + EXPECT_TRUE(options.child_span_name_ == "AWS Metadata Fetch"); + + // Let's say this ends up with a failure then verify it is handled properly by calling + // onMetadataError. + cb.onFailure(httpClientRequest, Http::AsyncClient::FailureReason::Reset); + return &httpClientRequest; + })); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestGet400) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "400", "not_empty"); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestGet400NoBody) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "400", ""); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestGetNoBody) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", ""); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); + EXPECT_CALL(receiver, + onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestHttpFailure) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, + Http::AsyncClient::FailureReason::Reset); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestClusterNotFound) { + // Setup without thread local cluster + fetcher_ = MetadataFetcher::create(mock_factory_ctx_.cluster_manager_, "cluster_name"); + Http::RequestMessageImpl message; + MockMetadataReceiver receiver; + + EXPECT_CALL(mock_factory_ctx_.cluster_manager_, getThreadLocalCluster(_)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::MissingConfig)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestCancel) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + Http::MockAsyncClientRequest request( + &(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_)); + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, &request); + MockMetadataReceiver receiver; + EXPECT_CALL(request, cancel()); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(_)).Times(0); + + // Act + fetcher_->fetch(message, parent_span_, receiver); + // Proper cancel + fetcher_->cancel(); + Mock::VerifyAndClearExpectations(&request); + Mock::VerifyAndClearExpectations(&receiver); + // Re-entrant cancel should do nothing. + EXPECT_CALL(request, cancel()).Times(0); + fetcher_->cancel(); +} + +TEST_F(MetadataFetcherTest, TestDefaultRetryPolicy) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + MockMetadataReceiver receiver; + + EXPECT_CALL( + mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, + send_(_, _, + AllOf(OptionsHasBufferBodyForRetry(true), + OptionsHasRetryPolicy(AllOf( + NumRetries(3), PerTryTimeout(5), PerTryIdleTimeout(1), + RetryOnModes(UnorderedElementsAre("5xx", "gateway-error", "connect-failure", + "refused-stream", "reset"))))))) + .WillOnce(Return(nullptr)); + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestFailureToStringConversion) { + // Setup + setupFetcher(); + EXPECT_EQ(fetcher_->failureToString(MetadataFetcher::MetadataReceiver::Failure::Network), + "Network"); + EXPECT_EQ(fetcher_->failureToString(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata), + "InvalidMetadata"); + EXPECT_EQ(fetcher_->failureToString(MetadataFetcher::MetadataReceiver::Failure::MissingConfig), + "MissingConfig"); +} + +} // namespace Aws +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index 5c3b0c7041af..6db726a8f936 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -1,8 +1,14 @@ #pragma once +#include "envoy/http/message.h" + +#include "source/common/http/message_impl.h" #include "source/extensions/common/aws/credentials_provider.h" +#include "source/extensions/common/aws/metadata_fetcher.h" #include "source/extensions/common/aws/signer.h" +#include "test/mocks/upstream/cluster_manager.h" + #include "gmock/gmock.h" namespace Envoy { @@ -10,6 +16,21 @@ namespace Extensions { namespace Common { namespace Aws { +class MockMetadataFetcher : public MetadataFetcher { +public: + MOCK_METHOD(void, cancel, ()); + MOCK_METHOD(absl::string_view, failureToString, (MetadataFetcher::MetadataReceiver::Failure)); + MOCK_METHOD(void, fetch, + (Http::RequestMessage & message, Tracing::Span& parent_span, + MetadataFetcher::MetadataReceiver& receiver)); +}; + +class MockMetadataReceiver : public MetadataFetcher::MetadataReceiver { +public: + MOCK_METHOD(void, onMetadataSuccess, (const std::string&& body)); + MOCK_METHOD(void, onMetadataError, (MetadataFetcher::MetadataReceiver::Failure reason)); +}; + class MockCredentialsProvider : public CredentialsProvider { public: MockCredentialsProvider(); diff --git a/test/extensions/common/aws/utility_test.cc b/test/extensions/common/aws/utility_test.cc index 629f28c2c957..221a94454d13 100644 --- a/test/extensions/common/aws/utility_test.cc +++ b/test/extensions/common/aws/utility_test.cc @@ -1,11 +1,18 @@ #include "source/extensions/common/aws/utility.h" +#include "test/extensions/common/aws/mocks.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" +using testing::_; using testing::ElementsAre; +using testing::InSequence; +using testing::NiceMock; using testing::Pair; +using testing::Ref; +using testing::Return; +using testing::Throw; namespace Envoy { namespace Extensions { @@ -13,6 +20,12 @@ namespace Common { namespace Aws { namespace { +MATCHER_P(WithName, expectedName, "") { + *result_listener << "\nexpected { name: \"" << expectedName << "\"} but got {name: \"" + << arg.name() << "\"}\n"; + return ExplainMatchResult(expectedName, arg.name(), result_listener); +} + // Headers must be in alphabetical order by virtue of std::map TEST(UtilityTest, CanonicalizeHeadersInAlphabeticalOrder) { Http::TestRequestHeaderMapImpl headers{ @@ -346,6 +359,45 @@ TEST(UtilityTest, JoinCanonicalHeaderNamesWithEmptyMap) { EXPECT_EQ("", names); } +// Verify that we don't add a thread local cluster if it already exists. +TEST(UtilityTest, ThreadLocalClusterExistsAlready) { + NiceMock cluster_; + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(&cluster_)); + EXPECT_CALL(cm_, addOrUpdateCluster(_, _)).Times(0); + EXPECT_TRUE(Utility::addInternalClusterStatic(cm_, "cluster_name", + envoy::config::cluster::v3::Cluster::STATIC, "")); +} + +// Verify that if thread local cluster doesn't exist we can create a new one. +TEST(UtilityTest, AddStaticClusterSuccess) { + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)).WillOnce(Return(true)); + EXPECT_TRUE(Utility::addInternalClusterStatic( + cm_, "cluster_name", envoy::config::cluster::v3::Cluster::STATIC, "127.0.0.1:80")); +} + +// Handle exception when adding thread local cluster fails. +TEST(UtilityTest, AddStaticClusterFailure) { + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)) + .WillOnce(Throw(EnvoyException("exeption message"))); + EXPECT_FALSE(Utility::addInternalClusterStatic( + cm_, "cluster_name", envoy::config::cluster::v3::Cluster::STATIC, "127.0.0.1:80")); +} + +// Verify that for uri argument in addInternalClusterStatic port value is optional +// and can contain request path which will be ignored. +TEST(UtilityTest, AddStaticClusterSuccessEvenWithMissingPort) { + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)).WillOnce(Return(true)); + EXPECT_TRUE(Utility::addInternalClusterStatic( + cm_, "cluster_name", envoy::config::cluster::v3::Cluster::STATIC, "127.0.0.1/something")); +} + } // namespace } // namespace Aws } // namespace Common From 72a247090789f6a3b131d8cb6a93949449f2a3e3 Mon Sep 17 00:00:00 2001 From: Huabing Zhao Date: Tue, 31 Oct 2023 07:14:02 +0800 Subject: [PATCH 39/72] HTTP Basic Auth filter (#30079) Signed-off-by: Huabing Zhao --- CODEOWNERS | 2 + api/BUILD | 1 + .../filters/http/basic_auth/v3/BUILD | 12 ++ .../http/basic_auth/v3/basic_auth.proto | 36 +++++ api/versioning/BUILD | 1 + changelogs/current.yaml | 4 + .../http/http_filters/basic_auth_filter.rst | 46 +++++++ .../http/http_filters/http_filters.rst | 1 + source/common/common/logger.h | 1 + source/extensions/extensions_build_config.bzl | 1 + source/extensions/extensions_metadata.yaml | 7 + .../extensions/filters/http/basic_auth/BUILD | 39 ++++++ .../http/basic_auth/basic_auth_filter.cc | 91 +++++++++++++ .../http/basic_auth/basic_auth_filter.h | 80 +++++++++++ .../filters/http/basic_auth/config.cc | 69 ++++++++++ .../filters/http/basic_auth/config.h | 27 ++++ .../filters/http/well_known_names.h | 2 + test/extensions/filters/http/basic_auth/BUILD | 45 +++++++ .../basic_auth/basic_auth_integration_test.cc | 119 ++++++++++++++++ .../filters/http/basic_auth/config_test.cc | 127 ++++++++++++++++++ .../filters/http/basic_auth/filter_test.cc | 103 ++++++++++++++ tools/spelling/spelling_dictionary.txt | 1 + 22 files changed, 815 insertions(+) create mode 100644 api/envoy/extensions/filters/http/basic_auth/v3/BUILD create mode 100644 api/envoy/extensions/filters/http/basic_auth/v3/basic_auth.proto create mode 100644 docs/root/configuration/http/http_filters/basic_auth_filter.rst create mode 100644 source/extensions/filters/http/basic_auth/BUILD create mode 100644 source/extensions/filters/http/basic_auth/basic_auth_filter.cc create mode 100644 source/extensions/filters/http/basic_auth/basic_auth_filter.h create mode 100644 source/extensions/filters/http/basic_auth/config.cc create mode 100644 source/extensions/filters/http/basic_auth/config.h create mode 100644 test/extensions/filters/http/basic_auth/BUILD create mode 100644 test/extensions/filters/http/basic_auth/basic_auth_integration_test.cc create mode 100644 test/extensions/filters/http/basic_auth/config_test.cc create mode 100644 test/extensions/filters/http/basic_auth/filter_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index 257c2577dbdd..9ca4bae5c252 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -192,6 +192,8 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /*/extensions/filters/http/rate_limit_quota @tyxia @yanavlasov # HTTP Bandwidth Limit /*/extensions/filters/http/bandwidth_limit @nitgoy @mattklein123 @yanavlasov @tonya11en +# HTTP Basic Auth +/*/extensions/filters/http/basic_auth @zhaohuabing @wbpcode # Original IP detection /*/extensions/http/original_ip_detection/custom_header @alyssawilk @mattklein123 /*/extensions/http/original_ip_detection/xff @alyssawilk @mattklein123 diff --git a/api/BUILD b/api/BUILD index 78b712f8354a..9ce02ee5a8ab 100644 --- a/api/BUILD +++ b/api/BUILD @@ -163,6 +163,7 @@ proto_library( "//envoy/extensions/filters/http/aws_lambda/v3:pkg", "//envoy/extensions/filters/http/aws_request_signing/v3:pkg", "//envoy/extensions/filters/http/bandwidth_limit/v3:pkg", + "//envoy/extensions/filters/http/basic_auth/v3:pkg", "//envoy/extensions/filters/http/buffer/v3:pkg", "//envoy/extensions/filters/http/cache/v3:pkg", "//envoy/extensions/filters/http/cdn_loop/v3:pkg", diff --git a/api/envoy/extensions/filters/http/basic_auth/v3/BUILD b/api/envoy/extensions/filters/http/basic_auth/v3/BUILD new file mode 100644 index 000000000000..1c1a6f6b4423 --- /dev/null +++ b/api/envoy/extensions/filters/http/basic_auth/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/config/core/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/filters/http/basic_auth/v3/basic_auth.proto b/api/envoy/extensions/filters/http/basic_auth/v3/basic_auth.proto new file mode 100644 index 000000000000..df23868a4260 --- /dev/null +++ b/api/envoy/extensions/filters/http/basic_auth/v3/basic_auth.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.basic_auth.v3; + +import "envoy/config/core/v3/base.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.http.basic_auth.v3"; +option java_outer_classname = "BasicAuthProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/basic_auth/v3;basic_authv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Basic Auth] +// Basic Auth :ref:`configuration overview `. +// [#extension: envoy.filters.http.basic_auth] + +// Basic HTTP authentication. +// +// Example: +// +// .. code-block:: yaml +// +// users: +// inline_string: |- +// user1:{SHA}hashed_user1_password +// user2:{SHA}hashed_user2_password +// +message BasicAuth { + // Username-password pairs used to verify user credentials in the "Authorization" header. + // The value needs to be the htpasswd format. + // Reference to https://httpd.apache.org/docs/2.4/programs/htpasswd.html + config.core.v3.DataSource users = 1 [(udpa.annotations.sensitive) = true]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 4e89b0209967..fe64655d843b 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -101,6 +101,7 @@ proto_library( "//envoy/extensions/filters/http/aws_lambda/v3:pkg", "//envoy/extensions/filters/http/aws_request_signing/v3:pkg", "//envoy/extensions/filters/http/bandwidth_limit/v3:pkg", + "//envoy/extensions/filters/http/basic_auth/v3:pkg", "//envoy/extensions/filters/http/buffer/v3:pkg", "//envoy/extensions/filters/http/cache/v3:pkg", "//envoy/extensions/filters/http/cdn_loop/v3:pkg", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index fcc708916ad3..f4d9093a0a68 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -65,6 +65,10 @@ removed_config_or_runtime: runtime flag and legacy code path. new_features: +- area: filters + change: | + Added :ref:`the Basic Auth filter `, which can be used to + authenticate user credentials in the HTTP Authentication heaer defined in `RFC7617 `_. - area: upstream change: | Added :ref:`enable_full_scan ` diff --git a/docs/root/configuration/http/http_filters/basic_auth_filter.rst b/docs/root/configuration/http/http_filters/basic_auth_filter.rst new file mode 100644 index 000000000000..da8b160fd054 --- /dev/null +++ b/docs/root/configuration/http/http_filters/basic_auth_filter.rst @@ -0,0 +1,46 @@ +.. _config_http_filters_basic_auth: + +Basic Auth +========== + +This HTTP filter can be used to authenticate user credentials in the HTTP Authentication header defined +in `RFC7617 `. + +The filter will extract the username and password from the HTTP Authentication header and verify them +against the configured username and password list. + +If the username and password are valid, the request will be forwared to the next filter in the filter chains. +If they're invalid or not provided in the HTTP request, the request will be denied with a 401 Unauthorized response. + +Configuration +------------- + +* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth``. +* :ref:`v3 API reference ` + +``users`` is a list of username-password pairs used to verify user credentials in the "Authorization" header. + The value needs to be the `htpasswd ` format. + + +An example configuration of the filter may look like the following: + +.. code-block:: yaml + + users: + inline_string: |- + user1:{SHA}hashed_user1_password + user2:{SHA}hashed_user2_password + +Note that only SHA format is currently supported. Other formats may be added in the future. + +Statistics +---------- + +The HTTP basic auth filter outputs statistics in the ``http..basic_auth.`` namespace. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + allowed, Counter, Total number of allowed requests + denied, Counter, Total number of denied requests diff --git a/docs/root/configuration/http/http_filters/http_filters.rst b/docs/root/configuration/http/http_filters/http_filters.rst index bbf38623d02f..eb1333ad0e03 100644 --- a/docs/root/configuration/http/http_filters/http_filters.rst +++ b/docs/root/configuration/http/http_filters/http_filters.rst @@ -11,6 +11,7 @@ HTTP filters aws_lambda_filter aws_request_signing_filter bandwidth_limit_filter + basic_auth_filter buffer_filter cache_filter cdn_loop_filter diff --git a/source/common/common/logger.h b/source/common/common/logger.h index f3ad2bc01bc7..a90e43628e1e 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -39,6 +39,7 @@ const static bool should_log = true; FUNCTION(aws) \ FUNCTION(assert) \ FUNCTION(backtrace) \ + FUNCTION(basic_auth) \ FUNCTION(cache_filter) \ FUNCTION(client) \ FUNCTION(config) \ diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 052e3682bdd3..423eb9ea0190 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -124,6 +124,7 @@ EXTENSIONS = { "envoy.filters.http.aws_lambda": "//source/extensions/filters/http/aws_lambda:config", "envoy.filters.http.aws_request_signing": "//source/extensions/filters/http/aws_request_signing:config", "envoy.filters.http.bandwidth_limit": "//source/extensions/filters/http/bandwidth_limit:config", + "envoy.filters.http.basic_auth": "//source/extensions/filters/http/basic_auth:config", "envoy.filters.http.buffer": "//source/extensions/filters/http/buffer:config", "envoy.filters.http.cache": "//source/extensions/filters/http/cache:config", "envoy.filters.http.cdn_loop": "//source/extensions/filters/http/cdn_loop:config", diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index a47f3534929e..1b6ba906ed52 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -217,6 +217,13 @@ envoy.filters.http.bandwidth_limit: status: stable type_urls: - envoy.extensions.filters.http.bandwidth_limit.v3.BandwidthLimit +envoy.filters.http.basic_auth: + categories: + - envoy.filters.http + security_posture: robust_to_untrusted_downstream + status: alpha + type_urls: + - envoy.extensions.filters.http.basic_auth.v3.BasicAuth envoy.filters.http.buffer: categories: - envoy.filters.http diff --git a/source/extensions/filters/http/basic_auth/BUILD b/source/extensions/filters/http/basic_auth/BUILD new file mode 100644 index 000000000000..f610d4fee905 --- /dev/null +++ b/source/extensions/filters/http/basic_auth/BUILD @@ -0,0 +1,39 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "basic_auth_lib", + srcs = ["basic_auth_filter.cc"], + hdrs = ["basic_auth_filter.h"], + external_deps = ["ssl"], + deps = [ + "//envoy/server:filter_config_interface", + "//source/common/common:base64_lib", + "//source/common/config:utility_lib", + "//source/common/http:header_map_lib", + "//source/common/protobuf:utility_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":basic_auth_lib", + "//envoy/registry", + "//source/common/config:datasource_lib", + "//source/common/protobuf:utility_lib", + "//source/extensions/filters/http/common:factory_base_lib", + "@envoy_api//envoy/extensions/filters/http/basic_auth/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/filters/http/basic_auth/basic_auth_filter.cc b/source/extensions/filters/http/basic_auth/basic_auth_filter.cc new file mode 100644 index 000000000000..ae7b10e6c573 --- /dev/null +++ b/source/extensions/filters/http/basic_auth/basic_auth_filter.cc @@ -0,0 +1,91 @@ +#include "source/extensions/filters/http/basic_auth/basic_auth_filter.h" + +#include + +#include "source/common/common/base64.h" +#include "source/common/http/header_utility.h" +#include "source/common/http/headers.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace BasicAuth { + +namespace { + +// Function to compute SHA1 hash +std::string computeSHA1(absl::string_view password) { + unsigned char hash[SHA_DIGEST_LENGTH]; + + // Calculate the SHA-1 hash + SHA1(reinterpret_cast(password.data()), password.length(), hash); + + // Encode the binary hash in Base64 + return Base64::encode(reinterpret_cast(hash), SHA_DIGEST_LENGTH); +} + +} // namespace + +FilterConfig::FilterConfig(UserMapConstPtr users, const std::string& stats_prefix, + Stats::Scope& scope) + : users_(std::move(users)), stats_(generateStats(stats_prefix + "basic_auth.", scope)) {} + +bool FilterConfig::validateUser(absl::string_view username, absl::string_view password) const { + auto user = users_->find(username); + if (user == users_->end()) { + return false; + } + + return computeSHA1(password) == user->second.hash; +} + +BasicAuthFilter::BasicAuthFilter(FilterConfigConstSharedPtr config) : config_(std::move(config)) {} + +Http::FilterHeadersStatus BasicAuthFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { + auto auth_header = headers.get(Http::CustomHeaders::get().Authorization); + if (!auth_header.empty()) { + absl::string_view auth_value = auth_header[0]->value().getStringView(); + + if (absl::StartsWith(auth_value, "Basic ")) { + // Extract and decode the Base64 part of the header. + absl::string_view base64Token = auth_value.substr(6); + const std::string decoded = Base64::decodeWithoutPadding(base64Token); + + // The decoded string is in the format "username:password". + const size_t colon_pos = decoded.find(':'); + + if (colon_pos != std::string::npos) { + absl::string_view decoded_view = decoded; + absl::string_view username = decoded_view.substr(0, colon_pos); + absl::string_view password = decoded_view.substr(colon_pos + 1); + + if (config_->validateUser(username, password)) { + config_->stats().allowed_.inc(); + return Http::FilterHeadersStatus::Continue; + } else { + config_->stats().denied_.inc(); + decoder_callbacks_->sendLocalReply( + Http::Code::Unauthorized, + "User authentication failed. Invalid username/password combination", nullptr, + absl::nullopt, "invalid_credential_for_basic_auth"); + return Http::FilterHeadersStatus::StopIteration; + } + } + } + } + + config_->stats().denied_.inc(); + decoder_callbacks_->sendLocalReply(Http::Code::Unauthorized, + "User authentication failed. Missing username and password", + nullptr, absl::nullopt, "no_credential_for_basic_auth"); + return Http::FilterHeadersStatus::StopIteration; +} + +void BasicAuthFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) { + decoder_callbacks_ = &callbacks; +} + +} // namespace BasicAuth +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/basic_auth/basic_auth_filter.h b/source/extensions/filters/http/basic_auth/basic_auth_filter.h new file mode 100644 index 000000000000..d900b304eb67 --- /dev/null +++ b/source/extensions/filters/http/basic_auth/basic_auth_filter.h @@ -0,0 +1,80 @@ +#pragma once + +#include "envoy/stats/stats_macros.h" + +#include "source/common/common/logger.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +#include "absl/container/flat_hash_map.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace BasicAuth { + +/** + * All Basic Auth filter stats. @see stats_macros.h + */ +#define ALL_BASIC_AUTH_STATS(COUNTER) \ + COUNTER(allowed) \ + COUNTER(denied) + +/** + * Struct definition for Basic Auth stats. @see stats_macros.h + */ +struct BasicAuthStats { + ALL_BASIC_AUTH_STATS(GENERATE_COUNTER_STRUCT) +}; + +/** + * Struct definition for username password pairs. + */ +struct User { + // the user name + std::string name; + // the hashed password, see https://httpd.apache.org/docs/2.4/misc/password_encryptions.html + std::string hash; +}; + +using UserMapConstPtr = + std::unique_ptr>; // username, User + +/** + * Configuration for the Basic Auth filter. + */ +class FilterConfig { +public: + FilterConfig(UserMapConstPtr users, const std::string& stats_prefix, Stats::Scope& scope); + const BasicAuthStats& stats() const { return stats_; } + bool validateUser(absl::string_view username, absl::string_view password) const; + +private: + static BasicAuthStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return BasicAuthStats{ALL_BASIC_AUTH_STATS(POOL_COUNTER_PREFIX(scope, prefix))}; + } + + UserMapConstPtr users_; + BasicAuthStats stats_; +}; +using FilterConfigConstSharedPtr = std::shared_ptr; + +// The Envoy filter to process HTTP basic auth. +class BasicAuthFilter : public Http::PassThroughDecoderFilter, + public Logger::Loggable { +public: + BasicAuthFilter(FilterConfigConstSharedPtr config); + + // Http::StreamDecoderFilter + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override; + void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override; + +private: + // The callback function. + Http::StreamDecoderFilterCallbacks* decoder_callbacks_; + FilterConfigConstSharedPtr config_; +}; + +} // namespace BasicAuth +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/basic_auth/config.cc b/source/extensions/filters/http/basic_auth/config.cc new file mode 100644 index 000000000000..02a3582cad69 --- /dev/null +++ b/source/extensions/filters/http/basic_auth/config.cc @@ -0,0 +1,69 @@ +#include "source/extensions/filters/http/basic_auth/config.h" + +#include "source/common/config/datasource.h" +#include "source/extensions/filters/http/basic_auth/basic_auth_filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace BasicAuth { + +using envoy::extensions::filters::http::basic_auth::v3::BasicAuth; + +namespace { + +UserMapConstPtr readHtpasswd(const std::string& htpasswd) { + std::unique_ptr> users = + std::make_unique>(); + std::istringstream htpsswd_ss(htpasswd); + std::string line; + + while (std::getline(htpsswd_ss, line)) { + const size_t colon_pos = line.find(':'); + + if (colon_pos != std::string::npos) { + std::string name = line.substr(0, colon_pos); + std::string hash = line.substr(colon_pos + 1); + + if (name.empty()) { + throw EnvoyException("basic auth: invalid user name"); + } + + if (absl::StartsWith(hash, "{SHA}")) { + hash = hash.substr(5); + // The base64 encoded SHA1 hash is 28 bytes long + if (hash.length() != 28) { + throw EnvoyException("basic auth: invalid SHA hash length"); + } + + users->insert({name, {name, hash}}); + continue; + } + } + + throw EnvoyException("basic auth: unsupported htpasswd format: please use {SHA}"); + } + + return users; +} + +} // namespace + +Http::FilterFactoryCb BasicAuthFilterFactory::createFilterFactoryFromProtoTyped( + const BasicAuth& proto_config, const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) { + const std::string htpasswd = Config::DataSource::read(proto_config.users(), false, context.api()); + UserMapConstPtr users = readHtpasswd(htpasswd); + FilterConfigConstSharedPtr config = + std::make_unique(std::move(users), stats_prefix, context.scope()); + return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(std::make_shared(config)); + }; +} + +REGISTER_FACTORY(BasicAuthFilterFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace BasicAuth +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/basic_auth/config.h b/source/extensions/filters/http/basic_auth/config.h new file mode 100644 index 000000000000..7abebaaa789c --- /dev/null +++ b/source/extensions/filters/http/basic_auth/config.h @@ -0,0 +1,27 @@ +#pragma once + +#include "envoy/extensions/filters/http/basic_auth/v3/basic_auth.pb.h" +#include "envoy/extensions/filters/http/basic_auth/v3/basic_auth.pb.validate.h" + +#include "source/extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace BasicAuth { + +class BasicAuthFilterFactory + : public Common::FactoryBase { +public: + BasicAuthFilterFactory() : FactoryBase("envoy.filters.http.basic_auth") {} + +private: + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::basic_auth::v3::BasicAuth& config, + const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; +}; + +} // namespace BasicAuth +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/well_known_names.h b/source/extensions/filters/http/well_known_names.h index cfd1783b55b4..8e33a0699c0a 100644 --- a/source/extensions/filters/http/well_known_names.h +++ b/source/extensions/filters/http/well_known_names.h @@ -16,6 +16,8 @@ class HttpFilterNameValues { const std::string Buffer = "envoy.filters.http.buffer"; // Bandwidth limit filter const std::string BandwidthLimit = "envoy.filters.http.bandwidth_limit"; + // Basic Auth filter + const std::string BasicAuth = "envoy.filters.http.basic_auth"; // Cache filter const std::string Cache = "envoy.filters.http.cache"; // CDN Loop filter diff --git a/test/extensions/filters/http/basic_auth/BUILD b/test/extensions/filters/http/basic_auth/BUILD new file mode 100644 index 000000000000..39e573d580cd --- /dev/null +++ b/test/extensions/filters/http/basic_auth/BUILD @@ -0,0 +1,45 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "filter_test", + srcs = ["filter_test.cc"], + extension_names = ["envoy.filters.http.basic_auth"], + deps = [ + "//source/extensions/filters/http/basic_auth:basic_auth_lib", + "//test/mocks/server:server_mocks", + "@envoy_api//envoy/extensions/filters/http/basic_auth/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.filters.http.basic_auth"], + deps = [ + "//source/extensions/filters/http/basic_auth:config", + "//test/mocks/server:server_mocks", + ], +) + +envoy_extension_cc_test( + name = "basic_auth_integration_test", + size = "large", + srcs = ["basic_auth_integration_test.cc"], + extension_names = ["envoy.filters.http.basic_auth"], + deps = [ + "//source/extensions/filters/http/basic_auth:config", + "//test/integration:http_protocol_integration_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/filters/http/basic_auth/basic_auth_integration_test.cc b/test/extensions/filters/http/basic_auth/basic_auth_integration_test.cc new file mode 100644 index 000000000000..2e70bf9efb5b --- /dev/null +++ b/test/extensions/filters/http/basic_auth/basic_auth_integration_test.cc @@ -0,0 +1,119 @@ +#include "test/integration/http_protocol_integration.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace BasicAuth { +namespace { + +class BasicAuthIntegrationTest : public HttpProtocolIntegrationTest { +public: + void initializeFilter() { + // user1, test1 + // user2, test2 + const std::string filter_config = + R"EOF( +name: envoy.filters.http.basic_auth +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth + users: + inline_string: |- + user1:{SHA}tESsBmE/yNY3lb6a0L6vVQEZNqw= + user2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8= +)EOF"; + config_helper_.prependFilter(filter_config); + initialize(); + } +}; + +// BasicAuth integration tests that should run with all protocols +class BasicAuthIntegrationTestAllProtocols : public BasicAuthIntegrationTest {}; + +INSTANTIATE_TEST_SUITE_P( + Protocols, BasicAuthIntegrationTestAllProtocols, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParamsWithoutHTTP3()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +// Request with valid credential +TEST_P(BasicAuthIntegrationTestAllProtocols, ValidCredential) { + initializeFilter(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Basic dXNlcjE6dGVzdDE="}, // user1, test1 + }); + + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + +// Request without credential +TEST_P(BasicAuthIntegrationTestAllProtocols, NoCredential) { + initializeFilter(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + }); + + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("401", response->headers().getStatusValue()); + EXPECT_EQ("User authentication failed. Missing username and password", response->body()); +} + +// Request without wrong password +TEST_P(BasicAuthIntegrationTestAllProtocols, WrongPasswrod) { + initializeFilter(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Basic dXNlcjE6dGVzdDI="}, // user1, test2 + }); + + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("401", response->headers().getStatusValue()); + EXPECT_EQ("User authentication failed. Invalid username/password combination", response->body()); +} + +// Request with none-existed user +TEST_P(BasicAuthIntegrationTestAllProtocols, NoneExistedUser) { + initializeFilter(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Basic dXNlcjM6dGVzdDI="}, // user3, test2 + }); + + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("401", response->headers().getStatusValue()); + EXPECT_EQ("User authentication failed. Invalid username/password combination", response->body()); +} +} // namespace +} // namespace BasicAuth +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/basic_auth/config_test.cc b/test/extensions/filters/http/basic_auth/config_test.cc new file mode 100644 index 000000000000..2be2d5812596 --- /dev/null +++ b/test/extensions/filters/http/basic_auth/config_test.cc @@ -0,0 +1,127 @@ +#include "source/extensions/filters/http/basic_auth/basic_auth_filter.h" +#include "source/extensions/filters/http/basic_auth/config.h" + +#include "test/mocks/server/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace BasicAuth { + +TEST(Factory, ValidConfig) { + const std::string yaml = R"( + users: + inline_string: |- + user1:{SHA}tESsBmE/yNY3lb6a0L6vVQEZNqw= + user2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8= + )"; + + BasicAuthFilterFactory factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + auto callback = factory.createFilterFactoryFromProto(*proto_config, "stats", context); + Http::MockFilterChainFactoryCallbacks filter_callback; + EXPECT_CALL(filter_callback, addStreamDecoderFilter(_)); + callback(filter_callback); +} + +TEST(Factory, InvalidConfigNoColon) { + const std::string yaml = R"( + users: + inline_string: |- + user1{SHA}tESsBmE/yNY3lb6a0L6vVQEZNqw= + user2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8= + )"; + + BasicAuthFilterFactory factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + EXPECT_THROW(factory.createFilterFactoryFromProto(*proto_config, "stats", context), + EnvoyException); +} + +TEST(Factory, InvalidConfigNoUser) { + const std::string yaml = R"( + users: + inline_string: |- + :{SHA}tESsBmE/yNY3lb6a0L6vVQEZNqw= + user2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8= + )"; + + BasicAuthFilterFactory factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + EXPECT_THROW(factory.createFilterFactoryFromProto(*proto_config, "stats", context), + EnvoyException); +} + +TEST(Factory, InvalidConfigNoPassword) { + const std::string yaml = R"( + users: + inline_string: |- + user1: + user2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8= + )"; + + BasicAuthFilterFactory factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + EXPECT_THROW(factory.createFilterFactoryFromProto(*proto_config, "stats", context), + EnvoyException); +} + +TEST(Factory, InvalidConfigNoHash) { + const std::string yaml = R"( + users: + inline_string: |- + user1:{SHA} + user2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8= + )"; + + BasicAuthFilterFactory factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + EXPECT_THROW(factory.createFilterFactoryFromProto(*proto_config, "stats", context), + EnvoyException); +} + +TEST(Factory, InvalidConfigNotSHA) { + const std::string yaml = R"( + users: + inline_string: |- + user1:{SHA}tESsBmE/yNY3lb6a0L6vVQEZNqw= + user2:$apr1$0vAnUTEB$4EJJr0GR3y48WF2AiieWs. + )"; + + BasicAuthFilterFactory factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + EXPECT_THROW(factory.createFilterFactoryFromProto(*proto_config, "stats", context), + EnvoyException); +} + +} // namespace BasicAuth +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/basic_auth/filter_test.cc b/test/extensions/filters/http/basic_auth/filter_test.cc new file mode 100644 index 000000000000..d8d3f3000be4 --- /dev/null +++ b/test/extensions/filters/http/basic_auth/filter_test.cc @@ -0,0 +1,103 @@ +#include "envoy/extensions/filters/http/basic_auth/v3/basic_auth.pb.h" + +#include "source/extensions/filters/http/basic_auth/basic_auth_filter.h" + +#include "test/mocks/http/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace BasicAuth { + +class FilterTest : public testing::Test { +public: + FilterTest() { + std::unique_ptr> users = + std::make_unique>(); + users->insert({"user1", {"user1", "tESsBmE/yNY3lb6a0L6vVQEZNqw="}}); // user1:test1 + users->insert({"user2", {"user2", "EJ9LPFDXsN9ynSmbxvjp75Bmlx8="}}); // user2:test2 + config_ = std::make_unique(std::move(users), "stats", *stats_.rootScope()); + filter_ = std::make_shared(config_); + filter_->setDecoderFilterCallbacks(decoder_filter_callbacks_); + } + + NiceMock stats_; + NiceMock decoder_filter_callbacks_; + FilterConfigConstSharedPtr config_; + std::shared_ptr filter_; +}; + +TEST_F(FilterTest, BasicAuth) { + // user1:test1 + Http::TestRequestHeaderMapImpl request_headers_user1{{"Authorization", "Basic dXNlcjE6dGVzdDE="}}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers_user1, true)); + + // user2:test2 + Http::TestRequestHeaderMapImpl request_headers_user2{{"Authorization", "Basic dXNlcjI6dGVzdDI="}}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(request_headers_user2, true)); +} + +TEST_F(FilterTest, UserNotExist) { + // user3:test2 + Http::TestRequestHeaderMapImpl request_headers_user1{{"Authorization", "Basic dXNlcjM6dGVzdDI="}}; + + EXPECT_CALL(decoder_filter_callbacks_, sendLocalReply(_, _, _, _, _)) + .WillOnce(Invoke([&](Http::Code code, absl::string_view body, + std::function, + const absl::optional grpc_status, + absl::string_view details) { + EXPECT_EQ(Http::Code::Unauthorized, code); + EXPECT_EQ("User authentication failed. Invalid username/password combination", body); + EXPECT_EQ(grpc_status, absl::nullopt); + EXPECT_EQ(details, "invalid_credential_for_basic_auth"); + })); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_user1, true)); +} + +TEST_F(FilterTest, InvalidPassword) { + // user1:test2 + Http::TestRequestHeaderMapImpl request_headers_user1{{"Authorization", "Basic dXNlcjE6dGVzdDI="}}; + + EXPECT_CALL(decoder_filter_callbacks_, sendLocalReply(_, _, _, _, _)) + .WillOnce(Invoke([&](Http::Code code, absl::string_view body, + std::function, + const absl::optional grpc_status, + absl::string_view details) { + EXPECT_EQ(Http::Code::Unauthorized, code); + EXPECT_EQ("User authentication failed. Invalid username/password combination", body); + EXPECT_EQ(grpc_status, absl::nullopt); + EXPECT_EQ(details, "invalid_credential_for_basic_auth"); + })); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_user1, true)); +} + +TEST_F(FilterTest, NoAuthHeader) { + Http::TestRequestHeaderMapImpl request_headers_user1; + + EXPECT_CALL(decoder_filter_callbacks_, sendLocalReply(_, _, _, _, _)) + .WillOnce(Invoke([&](Http::Code code, absl::string_view body, + std::function, + const absl::optional grpc_status, + absl::string_view details) { + EXPECT_EQ(Http::Code::Unauthorized, code); + EXPECT_EQ("User authentication failed. Missing username and password", body); + EXPECT_EQ(grpc_status, absl::nullopt); + EXPECT_EQ(details, "no_credential_for_basic_auth"); + })); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_user1, true)); +} + +} // namespace BasicAuth +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index ba9fe8bc3477..29e9a2538994 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -823,6 +823,7 @@ hostnames hostset hotrestart hrefs +htpasswd huffman hystrix idempotency From 08693a8512ff2a56a1f823027ce590c0d3302bb5 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 31 Oct 2023 20:03:36 +0800 Subject: [PATCH 40/72] fix: minor fix to the refresh clangdb script (#30625) Signed-off-by: wbpcode --- tools/vscode/refresh_compdb.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/vscode/refresh_compdb.sh b/tools/vscode/refresh_compdb.sh index ff5d4363d191..996a9d576102 100755 --- a/tools/vscode/refresh_compdb.sh +++ b/tools/vscode/refresh_compdb.sh @@ -5,10 +5,13 @@ bazel_or_isk=bazelisk command -v bazelisk &> /dev/null || bazel_or_isk=bazel -[[ -z "${EXCLUDE_CONTRIB}" ]] || opts="--exclude_contrib" +opts=(--vscode --bazel="$bazel_or_isk") + +[[ -z "${EXCLUDE_CONTRIB}" ]] || opts+=(--exclude_contrib) # Setting TEST_TMPDIR here so the compdb headers won't be overwritten by another bazel run -TEST_TMPDIR=${BUILD_DIR:-/tmp}/envoy-compdb tools/gen_compilation_database.py --vscode --bazel=$bazel_or_isk "${opts}" +TEST_TMPDIR=${BUILD_DIR:-/tmp}/envoy-compdb tools/gen_compilation_database.py \ + "${opts[@]}" # Kill clangd to reload the compilation database pkill clangd || : From 0f44db5c227352866f321b67565305dd6ddba830 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 31 Oct 2023 12:11:10 +0000 Subject: [PATCH 41/72] build/setup: Dont assume `/build` writeability (#30634) Signed-off-by: Ryan Northey --- ci/build_setup.sh | 13 ++++++++----- ci/run_envoy_docker.sh | 3 +++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ci/build_setup.sh b/ci/build_setup.sh index ccfa25aa3121..e579db81ec96 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -80,11 +80,14 @@ function setup_clang_toolchain() { echo "clang toolchain with ${ENVOY_STDLIB} configured" } -export BUILD_DIR=${BUILD_DIR:-/build} -if [[ ! -d "${BUILD_DIR}" ]] -then - echo "${BUILD_DIR} mount missing - did you forget -v :${BUILD_DIR}? Creating." - mkdir -p "${BUILD_DIR}" +if [[ -z "${BUILD_DIR}" ]]; then + echo "BUILD_DIR not set - defaulting to ~/.cache/envoy-bazel" >&2 + BUILD_DIR="$(realpath ~/.cache/envoy-bazel)" +fi +export BUILD_DIR +if [[ ! -d "${BUILD_DIR}" ]]; then + echo "${BUILD_DIR} missing - Creating." >&2 + mkdir -p "${BUILD_DIR}" fi # Environment setup. diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 33a667d4c913..4705cd38efd7 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -91,6 +91,8 @@ VOLUMES=( -v "${ENVOY_DOCKER_BUILD_DIR}":"${BUILD_DIR_MOUNT_DEST}" -v "${SOURCE_DIR}":"${SOURCE_DIR_MOUNT_DEST}") +export BUILD_DIR="${BUILD_DIR_MOUNT_DEST}" + 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, @@ -111,6 +113,7 @@ fi docker run --rm \ "${ENVOY_DOCKER_OPTIONS[@]}" \ "${VOLUMES[@]}" \ + -e BUILD_DIR \ -e HTTP_PROXY \ -e HTTPS_PROXY \ -e NO_PROXY \ From 03fdc59b12d06ca700561bfd89f619f3467069a4 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Tue, 31 Oct 2023 18:17:26 +0530 Subject: [PATCH 42/72] test: increase test coverage of crypto utils (#30585) This PR add some additional tests for importPublicKey() and verifySignature() methods to increase the unit test coverage of the crypto utils class. Old: 88.1% | New: 95.5% Commit Message: increase test coverage of crypto utils. Additional Description: added additional tests for importPublicKey() and verifySignature() to increase the unit test coverage of crypto utils. Risk Level: Very Low Testing: Yes Docs Changes: N/A Release Notes: N/A Signed-off-by: Rohit Agrawal --- test/common/crypto/utility_test.cc | 107 ++++++++++++++++++++++------- test/per_file_coverage.sh | 2 +- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/test/common/crypto/utility_test.cc b/test/common/crypto/utility_test.cc index 46eabc6b03cb..005c820ba86a 100644 --- a/test/common/crypto/utility_test.cc +++ b/test/common/crypto/utility_test.cc @@ -72,25 +72,22 @@ TEST(UtilityTest, TestImportPublicKey) { wrapper = Common::Crypto::Access::getTyped(*crypto_ptr); pkey = wrapper->getEVP_PKEY(); EXPECT_EQ(nullptr, pkey); + + EVP_PKEY* empty_pkey = EVP_PKEY_new(); + wrapper->setEVP_PKEY(empty_pkey); + pkey = wrapper->getEVP_PKEY(); + EXPECT_NE(nullptr, pkey); } TEST(UtilityTest, TestVerifySignature) { - auto key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d" - "73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a6" - "2cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644" - "ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d" - "395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884" - "d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5" - "183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001"; - auto hash_func = "sha256"; - auto signature = - "345ac3a167558f4f387a81c2d64234d901a7ceaa544db779d2f797b0ea4ef851b740905a63e2f4d5af42cee093a2" - "9c7155db9a63d3d483e0ef948f5ac51ce4e10a3a6606fd93ef68ee47b30c37491103039459122f78e1c7ea71a1a5" - "ea24bb6519bca02c8c9915fe8be24927c91812a13db72dbcb500103a79e8f67ff8cb9e2a631974e0668ab3977bf5" - "70a91b67d1b6bcd5dce84055f21427d64f4256a042ab1dc8e925d53a769f6681a873f5859693a7728fcbe95beace" - "1563b5ffbcd7c93b898aeba31421dafbfadeea50229c49fd6c445449314460f3d19150bd29a91333beaced557ed6" - "295234f7c14fa46303b7e977d2c89ba8a39a46a35f33eb07a332"; - auto data = "hello"; + auto key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100ba10ebe185465586093" + "228fb3b0093c560853b7ebf28497aefb9961a6cc886dd3f6d3278a93244fa5084a9c263bd57feb4ea1868" + "aa8a2718aa46708c803ce49318619982ba06a6615d24bb853c0fb85ebed833a802245e4518d4e2ba10da1" + "f22c732505433c558bed8895eb1e97cb5d65f821be9330143e93a738ef6896165879f692d75c2d7928e01" + "fd7fe601d16931bdd876c7b15b741e48546fe80db45df56e22ed2fa974ab937af7644d20834f41a61aeb9" + "a70d0248d274642b14ed6585892403bed8e03a9a12485ae44e3d39ab53e5bd70dee58476fb81860a18679" + "9429b71f79f204894cf21d31cc19118d547bb1b946532d080e074ec97e23667818490203010001"; + auto data = "hello\n"; Common::Crypto::CryptoObjectPtr crypto_ptr( Common::Crypto::UtilitySingleton::get().importPublicKey(Hex::decode(key))); @@ -98,30 +95,92 @@ TEST(UtilityTest, TestVerifySignature) { std::vector text(data, data + strlen(data)); - auto sig = Hex::decode(signature); - auto result = UtilitySingleton::get().verifySignature(hash_func, *crypto, sig, text); + // Map of hash function names and their respective signatures + std::map hashSignatures = { + {"sha1", + "9ed4cc60e8b2b51ff00b1cc06c628263476c8be6136510fc47e4668423c3492d8711489000b32163cd022661049" + "360fa0b8366692e41a4d4fb4237479694484012833ccc88938c1a471e33757c198b42623961d91cdf41ca01b375" + "002930b37a62377517cad297d5658e31610acaf79216a3d5c0afe4715dfe6e5bad3c20dac77bbbd2e7a4cb44172" + "abb620fe60b968426726ed25d2aed99abf9e8f705b7021e3448a78824e6982e9d14dbd0a317f45d42198f785f3b" + "0ca8e311695cedb4ce19626c246b8010a5de7b7978b8a3b56c1558f87bd658023e52b6e155c39594bae6e3cbf77" + "9d487a9ce3bffd7d8a2641f336771bec5c9d4a40dc8d4163fd2c1dd3b"}, // Signature for SHA-1 hash + // function + {"sha224", + "03813d50082dfb43444ebf47788e69271ebbfa17e64f7e7600ce761bd89ff459e21ecea6bc7de8396cfd80fe0ee" + "3d92967f0c467c930f7d0b1b1734e5d139ffaa5d84c5047cb38793b152ba8b284ec6d31e0b410b1e1a06ffda171" + "42c83b30593ac02a2f07f8e863ade752d23b2f41d56bd1ab6328c46de47233e2e2e4189e5bd3bce0b0428f485ff" + "e75f7343d89b376bd7dc2953467e63f5c1eb9279ca349fa74535d37e80f57216b8b73b0e67b32f0f18f41bae6a7" + "6e350dbc6188525eda1c79c0977bf433bb170d49de47985bc14a418d7a03d72eda740666dc05185fdcea6bb2914" + "d7bd0271bd06b3de72bc9db82d625799bf3441e2abff8fcd273efe6c7"}, // Signature for SHA-224 hash + // function + {"sha256", + "504c24addc615c01c915e3244b8248b470a41c82faa6a82526ddd8f21e197adae6c8b985e40960157d47f336d8b" + "a31ce4b3b1795379a7415af57daa757d3027e870b3c6644e749b583c51a16f9984afd39c909325d271d8d4c8d00" + "6288bd8f7945aa24a783613ecece95a9692b3b56dd1d831fc06d82eca40fd432a15a6cdb837d7ce378ac889c4ab" + "00b0c1f9c2be279952696b70c9ea2bb014d6f20b72ed4917904d5f24d5776058bd11121f3ed02e03c384cf42734" + "6b1d300867969f22e27aa9f0607344cc9d8e9a90802e97ac39af9324f60ddad24682e48424346dd5a53fe550370" + "bdf9f94dec8a80810edd648a33cc767f9e4328660e3ee1be8b47e9cfa"}, // Signature for SHA-256 hash + // function + {"sha384", + "91491000964d11f12ff658101f7c21ce8b37da6fff3abe836859c17e268858d77ee9c96e88ca86b36bca2b06472" + "a1f551d642bf056f1241d11d5b853e1458c2a9d86f9096e872c81480a06000346a61e51cb94e5174a98b9daacf5" + "204dd28e081c99a72066c406334a046ae5f3eb0e0eea86f0ae7eeb27d5dea245e850d05cc6c972f8249b8a4f018" + "6531735137a2e45f1f6410bf8e2382e95b57618802a0068ca197b2d8bcca53d6738e04b86ed9c69d45dad6d9bd7" + "be55596a719f12531d363e74c9d659738eaa50ab854869416f2b445f054aa2c1223c9edd223cbc5ac0d3582cb9b" + "5af494138bd6ace049e3ab326bb23fadd3dbcd74e9a3b372843f926ec"}, // Signature for SHA-384 hash + // function + {"sha512", + "5d001462d000c0aa23d931f6cce5def5f8472c7aaa0185cab87b256697b7a0c8fb6a4c9f84debf1b4ff3bf53213" + "0bcb25f724e09a74b5d5c915feb9c943a005ab879078b2fbcab0828e128ebfb7befee25d219bcd6cf1ad1f62b94" + "b460021eebc4c249e34219c71b4f526628976ecea8fb70e1166053da212747e8ba4b29cb91fa6541d53d3400a9d" + "34881a227e01eebf157104d84555c9e20320280723a72d3a724eba99f1fb14d59399321636ebfe7070d83d7b6b2" + "381fcdb683fb73e7796d36fe45dfb14a622c3426fe5bf69af9c24f9f1b30affad129b5f2b7dfa6fa384c73ad212" + "f414606882c3f9133d4702f487f9b08df8d0265fe5e8e12a11c6cb35c"}, // Signature for SHA-512 hash + // function + }; + + // Loop through each hash function and its signature + for (const auto& entry : hashSignatures) { + const std::string& hash_func = entry.first; + const std::string& signature = entry.second; + auto sig = Hex::decode(signature); + + auto result = UtilitySingleton::get().verifySignature(hash_func, *crypto, sig, text); + EXPECT_EQ(true, result.result_); + EXPECT_EQ("", result.error_message_); + } - EXPECT_EQ(true, result.result_); - EXPECT_EQ("", result.error_message_); + auto signature = + "504c24addc615c01c915e3244b8248b470a41c82faa6a82526ddd8f21e197adae6c8b985e40960157d47f336d8ba" + "31ce4b3b1795379a7415af57daa757d3027e870b3c6644e749b583c51a16f9984afd39c909325d271d8d4c8d0062" + "88bd8f7945aa24a783613ecece95a9692b3b56dd1d831fc06d82eca40fd432a15a6cdb837d7ce378ac889c4ab00b" + "0c1f9c2be279952696b70c9ea2bb014d6f20b72ed4917904d5f24d5776058bd11121f3ed02e03c384cf427346b1d" + "300867969f22e27aa9f0607344cc9d8e9a90802e97ac39af9324f60ddad24682e48424346dd5a53fe550370bdf9f" + "94dec8a80810edd648a33cc767f9e4328660e3ee1be8b47e9cfa"; + auto sig = Hex::decode(signature); - result = UtilitySingleton::get().verifySignature("unknown", *crypto, sig, text); + // Test an unknown hash function + auto result = UtilitySingleton::get().verifySignature("unknown", *crypto, sig, text); EXPECT_EQ(false, result.result_); EXPECT_EQ("unknown is not supported.", result.error_message_); + // Test with an empty crypto object auto empty_crypto = std::make_unique(); - result = UtilitySingleton::get().verifySignature(hash_func, *empty_crypto, sig, text); + result = UtilitySingleton::get().verifySignature("sha256", *empty_crypto, sig, text); EXPECT_EQ(false, result.result_); EXPECT_EQ("Failed to initialize digest verify.", result.error_message_); + // Test with incorrect data data = "baddata"; text = std::vector(data, data + strlen(data)); - result = UtilitySingleton::get().verifySignature(hash_func, *crypto, sig, text); + result = UtilitySingleton::get().verifySignature("sha256", *crypto, sig, text); EXPECT_EQ(false, result.result_); EXPECT_EQ("Failed to verify digest. Error code: 0", result.error_message_); + // Test with incorrect signature data = "hello"; text = std::vector(data, data + strlen(data)); - result = UtilitySingleton::get().verifySignature(hash_func, *crypto, Hex::decode("000000"), text); + result = UtilitySingleton::get().verifySignature("sha256", *crypto, Hex::decode("000000"), text); EXPECT_EQ(false, result.result_); EXPECT_EQ("Failed to verify digest. Error code: 0", result.error_message_); } diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index de38b62ab65f..b883f9afb130 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -7,7 +7,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/api:84.5" "source/common/api/posix:81.8" "source/common/config:95.3" -"source/common/crypto:88.1" +"source/common/crypto:95.5" "source/common/event:95.1" # Emulated edge events guards don't report LCOV "source/common/filesystem/posix:96.2" # FileReadToEndNotReadable fails in some env; createPath can't test all failure branches. "source/common/http/http2:95.2" From 9fb9844ca0480d369b56ba2adeed6e2cda61f81d Mon Sep 17 00:00:00 2001 From: code Date: Tue, 31 Oct 2023 21:10:12 +0800 Subject: [PATCH 43/72] generic proxy: fix test in debug mode (#30632) Signed-off-by: wbpcode --- contrib/generic_proxy/filters/network/source/BUILD | 2 ++ contrib/generic_proxy/filters/network/test/proxy_test.cc | 4 ++-- source/common/upstream/BUILD | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contrib/generic_proxy/filters/network/source/BUILD b/contrib/generic_proxy/filters/network/source/BUILD index e46ae6c4ba10..0699a6ff6add 100644 --- a/contrib/generic_proxy/filters/network/source/BUILD +++ b/contrib/generic_proxy/filters/network/source/BUILD @@ -171,4 +171,6 @@ envoy_cc_library( ":file_access_log_lib", "//contrib/generic_proxy/filters/network/source/interface:stream_interface", ], + # Ensure this factory in the source is always linked in. + alwayslink = 1, ) diff --git a/contrib/generic_proxy/filters/network/test/proxy_test.cc b/contrib/generic_proxy/filters/network/test/proxy_test.cc index 779d1f9980b5..0ff8359ce145 100644 --- a/contrib/generic_proxy/filters/network/test/proxy_test.cc +++ b/contrib/generic_proxy/filters/network/test/proxy_test.cc @@ -764,8 +764,6 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithMultipleFrames) { EXPECT_EQ(1, filter_->activeStreamsForTest().size()); EXPECT_EQ(0, filter_->frameHandlersForTest().size()); - std::cout << "OK decoding" << std::endl; - auto active_stream = filter_->activeStreamsForTest().begin()->get(); EXPECT_CALL( @@ -913,6 +911,8 @@ TEST_F(FilterTest, TestStats) { auto active_stream = filter_->activeStreamsForTest().begin()->get(); Buffer::OwnedImpl buffer; buffer.add("123"); + // Mock response. + active_stream->onResponseStart(std::make_unique()); active_stream->onEncodingSuccess(buffer, true); EXPECT_EQ(1, filter_config_->stats().response_.value()); EXPECT_EQ(0, filter_config_->stats().request_active_.value()); diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 9e1f42d23e91..5a705463c2ec 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -619,4 +619,6 @@ envoy_cc_library( "//source/common/network:resolver_lib", "@envoy_api//envoy/config/upstream/local_address_selector/v3:pkg_cc_proto", ], + # Ensure this factory in the source is always linked in. + alwayslink = 1, ) From 4b989c95493122ca8949840db8c3413491f0a61f Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Tue, 31 Oct 2023 09:20:29 -0400 Subject: [PATCH 44/72] HTTP2: Enable deferred processing by default (#30618) Signed-off-by: Kevin Baichoo --- changelogs/current.yaml | 5 +++++ source/common/runtime/runtime_features.cc | 4 +--- test/common/http/codec_impl_fuzz_test.cc | 20 +++++++++++-------- .../shadow_policy_integration_test.cc | 13 +++++++++++- test/mocks/event/mocks.cc | 11 ++++++++++ test/mocks/event/mocks.h | 2 ++ 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f4d9093a0a68..1bddbbff6676 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -23,6 +23,11 @@ minor_behavior_changes: Added new configuration field :ref:`rate_limited_as_resource_exhausted ` to allow for setting if rate limit grpc response should be RESOURCE_EXHAUSTED instead of the default UNAVAILABLE. +- area: http2 + change: | + Flip the runtime guard ``envoy.reloadable_features.defer_processing_backedup_streams`` to be on by default. + This feature improves flow control within the proxy by deferring work on the receiving end if the other + end is backed up. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index fb5b91a9a949..cb5212ddf07e 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -36,6 +36,7 @@ RUNTIME_GUARD(envoy_reloadable_features_conn_pool_delete_when_idle); RUNTIME_GUARD(envoy_reloadable_features_convert_legacy_lb_config); RUNTIME_GUARD(envoy_reloadable_features_copy_response_code_to_downstream_stream_info); RUNTIME_GUARD(envoy_reloadable_features_count_unused_mapped_pages_as_free); +RUNTIME_GUARD(envoy_reloadable_features_defer_processing_backedup_streams); RUNTIME_GUARD(envoy_reloadable_features_detect_and_raise_rst_tcp_connection); RUNTIME_GUARD(envoy_reloadable_features_dfp_mixed_scheme); RUNTIME_GUARD(envoy_reloadable_features_enable_aws_credentials_file); @@ -98,9 +99,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_test_feature_false); FALSE_RUNTIME_GUARD(envoy_reloadable_features_streaming_shadow); // TODO(adisuissa) reset to true to enable unified mux by default FALSE_RUNTIME_GUARD(envoy_reloadable_features_unified_mux); -// TODO(kbaichoo): Make this enabled by default when fairness and chunking -// are implemented, and we've had more cpu time. -FALSE_RUNTIME_GUARD(envoy_reloadable_features_defer_processing_backedup_streams); // TODO(birenroy) flip after a burn-in period FALSE_RUNTIME_GUARD(envoy_reloadable_features_http2_use_oghttp2); // Used to track if runtime is initialized. diff --git a/test/common/http/codec_impl_fuzz_test.cc b/test/common/http/codec_impl_fuzz_test.cc index 46db8ea12b5c..6101a2ed88c9 100644 --- a/test/common/http/codec_impl_fuzz_test.cc +++ b/test/common/http/codec_impl_fuzz_test.cc @@ -377,20 +377,24 @@ class HttpStream : public LinkedObject { dispatcher = &context_.client_connection_.dispatcher_; } - // With this feature enabled for http2 we end up creating a schedulable - // callback the first time we re-enable reading as it's used to process - // the backed up data. + // With this feature enabled for http2 the codec may end up creating a + // schedulable callback the first time it re-enables reading as it's used + // to process the backed up data if there's any to process. if (Runtime::runtimeFeatureEnabled(Runtime::defer_processing_backedup_streams)) { - const bool expecting_schedulable_callback_creation = + const bool might_schedulable_callback_creation = http_protocol_ == Protocol::Http2 && state.read_disable_count_ == 0 && !disable && !state.created_schedulable_callback_; - if (expecting_schedulable_callback_creation) { + if (might_schedulable_callback_creation) { ASSERT(dispatcher != nullptr); state.created_schedulable_callback_ = true; - // The unique pointer of this object will be returned in createSchedulableCallback_ of - // dispatcher, so there is no risk of object leak. - new Event::MockSchedulableCallback(dispatcher); + ON_CALL(*dispatcher, createSchedulableCallback_(_)) + .WillByDefault(testing::Invoke([dispatcher](std::function cb) { + // The unique pointer of this object will be returned in + // createSchedulableCallback_ of dispatcher, so there is no risk of this object + // leaking. + return new Event::MockSchedulableCallback(dispatcher, cb); + })); } } diff --git a/test/integration/shadow_policy_integration_test.cc b/test/integration/shadow_policy_integration_test.cc index 4f4c1b8855d7..fb196931d04d 100644 --- a/test/integration/shadow_policy_integration_test.cc +++ b/test/integration/shadow_policy_integration_test.cc @@ -510,6 +510,11 @@ TEST_P(ShadowPolicyIntegrationTest, MainRequestOverBufferLimit) { GTEST_SKIP() << "Not applicable for non-streaming shadows."; } autonomous_upstream_ = true; + if (Runtime::runtimeFeatureEnabled(Runtime::defer_processing_backedup_streams)) { + // With deferred processing, a local reply is triggered so the upstream + // stream will be incomplete. + autonomous_allow_incomplete_streams_ = true; + } cluster_with_custom_filter_ = 0; filter_name_ = "encoder-decoder-buffer-filter"; initialConfigSetup("cluster_1", ""); @@ -537,7 +542,13 @@ TEST_P(ShadowPolicyIntegrationTest, MainRequestOverBufferLimit) { EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_cx_total")->value(), 1); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_completed", 1); + if (Runtime::runtimeFeatureEnabled(Runtime::defer_processing_backedup_streams)) { + // With deferred processing, the encoder-decoder-buffer-filter will + // buffer too much data triggering a local reply. + test_server_->waitForCounterEq("http.config_test.downstream_rq_4xx", 1); + } else { + test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_completed", 1); + } } TEST_P(ShadowPolicyIntegrationTest, ShadowRequestOverBufferLimit) { diff --git a/test/mocks/event/mocks.cc b/test/mocks/event/mocks.cc index 16468bf697cb..e6a0b436f671 100644 --- a/test/mocks/event/mocks.cc +++ b/test/mocks/event/mocks.cc @@ -76,12 +76,23 @@ MockSchedulableCallback::~MockSchedulableCallback() { } } +MockSchedulableCallback::MockSchedulableCallback(MockDispatcher* dispatcher, + std::function callback, + testing::MockFunction* destroy_cb) + : dispatcher_(dispatcher), callback_(callback), destroy_cb_(destroy_cb) { + ON_CALL(*this, scheduleCallbackCurrentIteration()).WillByDefault(Assign(&enabled_, true)); + ON_CALL(*this, scheduleCallbackNextIteration()).WillByDefault(Assign(&enabled_, true)); + ON_CALL(*this, cancel()).WillByDefault(Assign(&enabled_, false)); + ON_CALL(*this, enabled()).WillByDefault(ReturnPointee(&enabled_)); +} + MockSchedulableCallback::MockSchedulableCallback(MockDispatcher* dispatcher, testing::MockFunction* destroy_cb) : dispatcher_(dispatcher), destroy_cb_(destroy_cb) { EXPECT_CALL(*dispatcher, createSchedulableCallback_(_)) .WillOnce(DoAll(SaveArg<0>(&callback_), Return(this))) .RetiresOnSaturation(); + ON_CALL(*this, scheduleCallbackCurrentIteration()).WillByDefault(Assign(&enabled_, true)); ON_CALL(*this, scheduleCallbackNextIteration()).WillByDefault(Assign(&enabled_, true)); ON_CALL(*this, cancel()).WillByDefault(Assign(&enabled_, false)); diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index f3279645a31e..4a0b2f6f9e27 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -224,6 +224,8 @@ class MockSchedulableCallback : public SchedulableCallback { public: MockSchedulableCallback(MockDispatcher* dispatcher, testing::MockFunction* destroy_cb = nullptr); + MockSchedulableCallback(MockDispatcher* dispatcher, std::function callback, + testing::MockFunction* destroy_cb = nullptr); ~MockSchedulableCallback() override; void invokeCallback() { From 4800e4289f012bab58fc9b890f85139b95b6fd3f Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 31 Oct 2023 13:47:39 +0000 Subject: [PATCH 45/72] build/setup: Fix build dir creation (#30635) Signed-off-by: Ryan Northey --- ci/build_setup.sh | 4 ++-- ci/run_envoy_docker.sh | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ci/build_setup.sh b/ci/build_setup.sh index e579db81ec96..c56a6eb746a7 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -82,13 +82,13 @@ function setup_clang_toolchain() { if [[ -z "${BUILD_DIR}" ]]; then echo "BUILD_DIR not set - defaulting to ~/.cache/envoy-bazel" >&2 - BUILD_DIR="$(realpath ~/.cache/envoy-bazel)" + BUILD_DIR="${HOME}/.cache/envoy-bazel" fi -export BUILD_DIR if [[ ! -d "${BUILD_DIR}" ]]; then echo "${BUILD_DIR} missing - Creating." >&2 mkdir -p "${BUILD_DIR}" fi +export BUILD_DIR # Environment setup. export ENVOY_TEST_TMPDIR="${ENVOY_TEST_TMPDIR:-$BUILD_DIR/tmp}" diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index 4705cd38efd7..8e4e0b6d2e54 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -91,7 +91,9 @@ VOLUMES=( -v "${ENVOY_DOCKER_BUILD_DIR}":"${BUILD_DIR_MOUNT_DEST}" -v "${SOURCE_DIR}":"${SOURCE_DIR_MOUNT_DEST}") -export BUILD_DIR="${BUILD_DIR_MOUNT_DEST}" +if ! is_windows; then + export BUILD_DIR="${BUILD_DIR_MOUNT_DEST}" +fi 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 From a699868c39fc1da067e9a442e391f1cd2694f175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:41:10 +0000 Subject: [PATCH 46/72] build(deps): bump jmalloc/echo-server from `5711091` to `86f2c45` in /examples/shared/echo (#30628) build(deps): bump jmalloc/echo-server in /examples/shared/echo Bumps jmalloc/echo-server from `5711091` to `86f2c45`. --- updated-dependencies: - dependency-name: jmalloc/echo-server dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/echo/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/echo/Dockerfile b/examples/shared/echo/Dockerfile index 81f8c35019fd..16d356efc745 100644 --- a/examples/shared/echo/Dockerfile +++ b/examples/shared/echo/Dockerfile @@ -1 +1 @@ -FROM jmalloc/echo-server@sha256:57110914108448e6692cd28fc602332357f91951d74ca12217a347b1f7df599c +FROM jmalloc/echo-server@sha256:86f2c45aa7e7ebe1be30b21f8cfff25a7ed6e3b059751822d4b35bf244a688d5 From f5b777c40dca270d2d3c1c14787af89da4467df1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:14:24 +0000 Subject: [PATCH 47/72] build(deps): bump grpcio-tools from 1.59.0 to 1.59.2 in /examples/grpc-bridge/client (#30630) build(deps): bump grpcio-tools in /examples/grpc-bridge/client Bumps [grpcio-tools](https://github.com/grpc/grpc) from 1.59.0 to 1.59.2. - [Release notes](https://github.com/grpc/grpc/releases) - [Changelog](https://github.com/grpc/grpc/blob/master/doc/grpc_release_schedule.md) - [Commits](https://github.com/grpc/grpc/compare/v1.59.0...v1.59.2) --- updated-dependencies: - dependency-name: grpcio-tools dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/grpc-bridge/client/requirements.txt | 220 +++++++++---------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index 04c24d3edc2e..475500a3a2c8 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -100,119 +100,119 @@ charset-normalizer==3.3.0 \ --hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e \ --hash=sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8 # via requests -grpcio==1.59.0 \ - --hash=sha256:0ae444221b2c16d8211b55326f8ba173ba8f8c76349bfc1768198ba592b58f74 \ - --hash=sha256:0b84445fa94d59e6806c10266b977f92fa997db3585f125d6b751af02ff8b9fe \ - --hash=sha256:14890da86a0c0e9dc1ea8e90101d7a3e0e7b1e71f4487fab36e2bfd2ecadd13c \ - --hash=sha256:15f03bd714f987d48ae57fe092cf81960ae36da4e520e729392a59a75cda4f29 \ - --hash=sha256:1a839ba86764cc48226f50b924216000c79779c563a301586a107bda9cbe9dcf \ - --hash=sha256:225e5fa61c35eeaebb4e7491cd2d768cd8eb6ed00f2664fa83a58f29418b39fd \ - --hash=sha256:228b91ce454876d7eed74041aff24a8f04c0306b7250a2da99d35dd25e2a1211 \ - --hash=sha256:2ea95cd6abbe20138b8df965b4a8674ec312aaef3147c0f46a0bac661f09e8d0 \ - --hash=sha256:2f120d27051e4c59db2f267b71b833796770d3ea36ca712befa8c5fff5da6ebd \ - --hash=sha256:34341d9e81a4b669a5f5dca3b2a760b6798e95cdda2b173e65d29d0b16692857 \ - --hash=sha256:3859917de234a0a2a52132489c4425a73669de9c458b01c9a83687f1f31b5b10 \ - --hash=sha256:38823bd088c69f59966f594d087d3a929d1ef310506bee9e3648317660d65b81 \ - --hash=sha256:38da5310ef84e16d638ad89550b5b9424df508fd5c7b968b90eb9629ca9be4b9 \ - --hash=sha256:3b8ff795d35a93d1df6531f31c1502673d1cebeeba93d0f9bd74617381507e3f \ - --hash=sha256:50eff97397e29eeee5df106ea1afce3ee134d567aa2c8e04fabab05c79d791a7 \ - --hash=sha256:5711c51e204dc52065f4a3327dca46e69636a0b76d3e98c2c28c4ccef9b04c52 \ - --hash=sha256:598f3530231cf10ae03f4ab92d48c3be1fee0c52213a1d5958df1a90957e6a88 \ - --hash=sha256:611d9aa0017fa386809bddcb76653a5ab18c264faf4d9ff35cb904d44745f575 \ - --hash=sha256:61bc72a00ecc2b79d9695220b4d02e8ba53b702b42411397e831c9b0589f08a3 \ - --hash=sha256:63982150a7d598281fa1d7ffead6096e543ff8be189d3235dd2b5604f2c553e5 \ - --hash=sha256:6c4b1cc3a9dc1924d2eb26eec8792fedd4b3fcd10111e26c1d551f2e4eda79ce \ - --hash=sha256:81d86a096ccd24a57fa5772a544c9e566218bc4de49e8c909882dae9d73392df \ - --hash=sha256:849c47ef42424c86af069a9c5e691a765e304079755d5c29eff511263fad9c2a \ - --hash=sha256:871371ce0c0055d3db2a86fdebd1e1d647cf21a8912acc30052660297a5a6901 \ - --hash=sha256:8cd2d38c2d52f607d75a74143113174c36d8a416d9472415eab834f837580cf7 \ - --hash=sha256:936b2e04663660c600d5173bc2cc84e15adbad9c8f71946eb833b0afc205b996 \ - --hash=sha256:93e9cb546e610829e462147ce724a9cb108e61647a3454500438a6deef610be1 \ - --hash=sha256:956f0b7cb465a65de1bd90d5a7475b4dc55089b25042fe0f6c870707e9aabb1d \ - --hash=sha256:986de4aa75646e963466b386a8c5055c8b23a26a36a6c99052385d6fe8aaf180 \ - --hash=sha256:aca8a24fef80bef73f83eb8153f5f5a0134d9539b4c436a716256b311dda90a6 \ - --hash=sha256:acf70a63cf09dd494000007b798aff88a436e1c03b394995ce450be437b8e54f \ - --hash=sha256:b34c7a4c31841a2ea27246a05eed8a80c319bfc0d3e644412ec9ce437105ff6c \ - --hash=sha256:b95ec8ecc4f703f5caaa8d96e93e40c7f589bad299a2617bdb8becbcce525539 \ - --hash=sha256:ba0ca727a173ee093f49ead932c051af463258b4b493b956a2c099696f38aa66 \ - --hash=sha256:c041a91712bf23b2a910f61e16565a05869e505dc5a5c025d429ca6de5de842c \ - --hash=sha256:c0488c2b0528e6072010182075615620071371701733c63ab5be49140ed8f7f0 \ - --hash=sha256:c173a87d622ea074ce79be33b952f0b424fa92182063c3bda8625c11d3585d09 \ - --hash=sha256:c251d22de8f9f5cca9ee47e4bade7c5c853e6e40743f47f5cc02288ee7a87252 \ - --hash=sha256:c4dfdb49f4997dc664f30116af2d34751b91aa031f8c8ee251ce4dcfc11277b0 \ - --hash=sha256:ca87ee6183421b7cea3544190061f6c1c3dfc959e0b57a5286b108511fd34ff4 \ - --hash=sha256:ceb1e68135788c3fce2211de86a7597591f0b9a0d2bb80e8401fd1d915991bac \ - --hash=sha256:d09bd2a4e9f5a44d36bb8684f284835c14d30c22d8ec92ce796655af12163588 \ - --hash=sha256:d0fcf53df684fcc0154b1e61f6b4a8c4cf5f49d98a63511e3f30966feff39cd0 \ - --hash=sha256:d74f7d2d7c242a6af9d4d069552ec3669965b74fed6b92946e0e13b4168374f9 \ - --hash=sha256:de2599985b7c1b4ce7526e15c969d66b93687571aa008ca749d6235d056b7205 \ - --hash=sha256:e5378785dce2b91eb2e5b857ec7602305a3b5cf78311767146464bfa365fc897 \ - --hash=sha256:ec78aebb9b6771d6a1de7b6ca2f779a2f6113b9108d486e904bde323d51f5589 \ - --hash=sha256:f1feb034321ae2f718172d86b8276c03599846dc7bb1792ae370af02718f91c5 \ - --hash=sha256:f21917aa50b40842b51aff2de6ebf9e2f6af3fe0971c31960ad6a3a2b24988f4 \ - --hash=sha256:f367e4b524cb319e50acbdea57bb63c3b717c5d561974ace0b065a648bb3bad3 \ - --hash=sha256:f6cfe44a5d7c7d5f1017a7da1c8160304091ca5dc64a0f85bca0d63008c3137a \ - --hash=sha256:fa66cac32861500f280bb60fe7d5b3e22d68c51e18e65367e38f8669b78cea3b \ - --hash=sha256:fc8bf2e7bc725e76c0c11e474634a08c8f24bcf7426c0c6d60c8f9c6e70e4d4a \ - --hash=sha256:fe976910de34d21057bcb53b2c5e667843588b48bf11339da2a75f5c4c5b4055 +grpcio==1.59.2 \ + --hash=sha256:023088764012411affe7db183d1ada3ad9daf2e23ddc719ff46d7061de661340 \ + --hash=sha256:08d77e682f2bf730a4961eea330e56d2f423c6a9b91ca222e5b1eb24a357b19f \ + --hash=sha256:0a4a3833c0e067f3558538727235cd8a49709bff1003200bbdefa2f09334e4b1 \ + --hash=sha256:0a754aff9e3af63bdc4c75c234b86b9d14e14a28a30c4e324aed1a9b873d755f \ + --hash=sha256:11168ef43e4a43ff1b1a65859f3e0ef1a173e277349e7fb16923ff108160a8cd \ + --hash=sha256:128e20f57c5f27cb0157e73756d1586b83c1b513ebecc83ea0ac37e4b0e4e758 \ + --hash=sha256:1f9524d1d701e399462d2c90ba7c193e49d1711cf429c0d3d97c966856e03d00 \ + --hash=sha256:1ff16d68bf453275466a9a46739061a63584d92f18a0f5b33d19fc97eb69867c \ + --hash=sha256:2067274c88bc6de89c278a672a652b4247d088811ece781a4858b09bdf8448e3 \ + --hash=sha256:2171c39f355ba5b551c5d5928d65aa6c69807fae195b86ef4a7d125bcdb860a9 \ + --hash=sha256:242adc47725b9a499ee77c6a2e36688fa6c96484611f33b1be4c57ab075a92dd \ + --hash=sha256:27f879ae604a7fcf371e59fba6f3ff4635a4c2a64768bd83ff0cac503142fef4 \ + --hash=sha256:2b230028a008ae1d0f430acb227d323ff8a619017415cf334c38b457f814119f \ + --hash=sha256:3059668df17627f0e0fa680e9ef8c995c946c792612e9518f5cc1503be14e90b \ + --hash=sha256:31176aa88f36020055ace9adff2405a33c8bdbfa72a9c4980e25d91b2f196873 \ + --hash=sha256:36f53c2b3449c015880e7d55a89c992c357f176327b0d2873cdaaf9628a37c69 \ + --hash=sha256:3b4368b33908f683a363f376dfb747d40af3463a6e5044afee07cf9436addf96 \ + --hash=sha256:3c61d641d4f409c5ae46bfdd89ea42ce5ea233dcf69e74ce9ba32b503c727e29 \ + --hash=sha256:4abb717e320e74959517dc8e84a9f48fbe90e9abe19c248541e9418b1ce60acd \ + --hash=sha256:4c93f4abbb54321ee6471e04a00139c80c754eda51064187963ddf98f5cf36a4 \ + --hash=sha256:535561990e075fa6bd4b16c4c3c1096b9581b7bb35d96fac4650f1181e428268 \ + --hash=sha256:53c9aa5ddd6857c0a1cd0287225a2a25873a8e09727c2e95c4aebb1be83a766a \ + --hash=sha256:5d573e70a6fe77555fb6143c12d3a7d3fa306632a3034b4e7c59ca09721546f8 \ + --hash=sha256:6009386a2df66159f64ac9f20425ae25229b29b9dd0e1d3dd60043f037e2ad7e \ + --hash=sha256:686e975a5d16602dc0982c7c703948d17184bd1397e16c8ee03511ecb8c4cdda \ + --hash=sha256:6959fb07e8351e20501ffb8cc4074c39a0b7ef123e1c850a7f8f3afdc3a3da01 \ + --hash=sha256:6b25ed37c27e652db01be341af93fbcea03d296c024d8a0e680017a268eb85dd \ + --hash=sha256:6da6dea3a1bacf99b3c2187e296db9a83029ed9c38fd4c52b7c9b7326d13c828 \ + --hash=sha256:72ca2399097c0b758198f2ff30f7178d680de8a5cfcf3d9b73a63cf87455532e \ + --hash=sha256:73abb8584b0cf74d37f5ef61c10722adc7275502ab71789a8fe3cb7ef04cf6e2 \ + --hash=sha256:74100fecaec8a535e380cf5f2fb556ff84957d481c13e54051c52e5baac70541 \ + --hash=sha256:75c6ecb70e809cf1504465174343113f51f24bc61e22a80ae1c859f3f7034c6d \ + --hash=sha256:7cf05053242f61ba94014dd3a986e11a083400a32664058f80bf4cf817c0b3a1 \ + --hash=sha256:9411e24328a2302e279e70cae6e479f1fddde79629fcb14e03e6d94b3956eabf \ + --hash=sha256:a213acfbf186b9f35803b52e4ca9addb153fc0b67f82a48f961be7000ecf6721 \ + --hash=sha256:bb7e0fe6ad73b7f06d7e2b689c19a71cf5cc48f0c2bf8608469e51ffe0bd2867 \ + --hash=sha256:c2504eed520958a5b77cc99458297cb7906308cb92327f35fb7fbbad4e9b2188 \ + --hash=sha256:c35aa9657f5d5116d23b934568e0956bd50c615127810fffe3ac356a914c176a \ + --hash=sha256:c5f09cffa619adfb44799fa4a81c2a1ad77c887187613fb0a8f201ab38d89ba1 \ + --hash=sha256:c978f864b35f2261e0819f5cd88b9830b04dc51bcf055aac3c601e525a10d2ba \ + --hash=sha256:cbe946b3e6e60a7b4618f091e62a029cb082b109a9d6b53962dd305087c6e4fd \ + --hash=sha256:cc3e4cd087f07758b16bef8f31d88dbb1b5da5671d2f03685ab52dece3d7a16e \ + --hash=sha256:cf0dead5a2c5a3347af2cfec7131d4f2a2e03c934af28989c9078f8241a491fa \ + --hash=sha256:d2794f0e68b3085d99b4f6ff9c089f6fdd02b32b9d3efdfbb55beac1bf22d516 \ + --hash=sha256:d2fa68a96a30dd240be80bbad838a0ac81a61770611ff7952b889485970c4c71 \ + --hash=sha256:d6f70406695e3220f09cd7a2f879333279d91aa4a8a1d34303b56d61a8180137 \ + --hash=sha256:d8f9cd4ad1be90b0cf350a2f04a38a36e44a026cac1e036ac593dc48efe91d52 \ + --hash=sha256:da2d94c15f88cd40d7e67f7919d4f60110d2b9d5b1e08cf354c2be773ab13479 \ + --hash=sha256:e1727c1c0e394096bb9af185c6923e8ea55a5095b8af44f06903bcc0e06800a2 \ + --hash=sha256:e420ced29b5904cdf9ee5545e23f9406189d8acb6750916c2db4793dada065c6 \ + --hash=sha256:e82c5cf1495244adf5252f925ac5932e5fd288b3e5ab6b70bec5593074b7236c \ + --hash=sha256:f1ef0d39bc1feb420caf549b3c657c871cad4ebbcf0580c4d03816b0590de0cf \ + --hash=sha256:f8753a6c88d1d0ba64302309eecf20f70d2770f65ca02d83c2452279085bfcd3 \ + --hash=sha256:f93dbf58f03146164048be5426ffde298b237a5e059144847e4940f5b80172c3 # via # -r requirements.in # grpcio-tools -grpcio-tools==1.59.0 \ - --hash=sha256:0548e901894399886ff4a4cd808cb850b60c021feb4a8977a0751f14dd7e55d9 \ - --hash=sha256:05bf7b3ed01c8a562bb7e840f864c58acedbd6924eb616367c0bd0a760bdf483 \ - --hash=sha256:1d551ff42962c7c333c3da5c70d5e617a87dee581fa2e2c5ae2d5137c8886779 \ - --hash=sha256:1df755951f204e65bf9232a9cac5afe7d6b8e4c87ac084d3ecd738fdc7aa4174 \ - --hash=sha256:204e08f807b1d83f5f0efea30c4e680afe26a43dec8ba614a45fa698a7ef0a19 \ - --hash=sha256:240a7a3c2c54f77f1f66085a635bca72003d02f56a670e7db19aec531eda8f78 \ - --hash=sha256:26eb2eebf150a33ebf088e67c1acf37eb2ac4133d9bfccbaa011ad2148c08b42 \ - --hash=sha256:27a7f226b741b2ebf7e2d0779d2c9b17f446d1b839d59886c1619e62cc2ae472 \ - --hash=sha256:2d970aa26854f535ffb94ea098aa8b43de020d9a14682e4a15dcdaeac7801b27 \ - --hash=sha256:2ee960904dde12a7fa48e1591a5b3eeae054bdce57bacf9fd26685a98138f5bf \ - --hash=sha256:335e2f355a0c544a88854e2c053aff8a3f398b84a263a96fa19d063ca1fe513a \ - --hash=sha256:387662bee8e4c0b52cc0f61eaaca0ca583f5b227103f685b76083a3590a71a3e \ - --hash=sha256:40cbf712769242c2ba237745285ef789114d7fcfe8865fc4817d87f20015e99a \ - --hash=sha256:4499d4bc5aa9c7b645018d8b0db4bebd663d427aabcd7bee7777046cb1bcbca7 \ - --hash=sha256:498e7be0b14385980efa681444ba481349c131fc5ec88003819f5d929646947c \ - --hash=sha256:4a10e59cca462208b489478340b52a96d64e8b8b6f1ac097f3e8cb211d3f66c0 \ - --hash=sha256:4ee443abcd241a5befb05629013fbf2eac637faa94aaa3056351aded8a31c1bc \ - --hash=sha256:51d9595629998d8b519126c5a610f15deb0327cd6325ed10796b47d1d292e70b \ - --hash=sha256:520c0c83ea79d14b0679ba43e19c64ca31d30926b26ad2ca7db37cbd89c167e2 \ - --hash=sha256:5b2d6da553980c590487f2e7fd3ec9c1ad8805ff2ec77977b92faa7e3ca14e1f \ - --hash=sha256:6119f62c462d119c63227b9534210f0f13506a888151b9bf586f71e7edf5088b \ - --hash=sha256:6aec8a4ed3808b7dfc1276fe51e3e24bec0eeaf610d395bcd42934647cf902a3 \ - --hash=sha256:71cc6db1d66da3bc3730d9937bddc320f7b1f1dfdff6342bcb5741515fe4110b \ - --hash=sha256:784aa52965916fec5afa1a28eeee6f0073bb43a2a1d7fedf963393898843077a \ - --hash=sha256:821dba464d84ebbcffd9d420302404db2fa7a40c7ff4c4c4c93726f72bfa2769 \ - --hash=sha256:868892ad9e00651a38dace3e4924bae82fc4fd4df2c65d37b74381570ee8deb1 \ - --hash=sha256:882b809b42b5464bee55288f4e60837297f9618e53e69ae3eea6d61b05ce48fa \ - --hash=sha256:8c4634b3589efa156a8d5860c0a2547315bd5c9e52d14c960d716fe86e0927be \ - --hash=sha256:8f0da5861ee276ca68493b217daef358960e8527cc63c7cb292ca1c9c54939af \ - --hash=sha256:962d1a3067129152cee3e172213486cb218a6bad703836991f46f216caefcf00 \ - --hash=sha256:99b3bde646720bbfb77f263f5ba3e1a0de50632d43c38d405a0ef9c7e94373cd \ - --hash=sha256:9af7e138baa9b2895cf1f3eb718ac96fc5ae2f8e31fca405e21e0e5cd1643c52 \ - --hash=sha256:9ed05197c5ab071e91bcef28901e97ca168c4ae94510cb67a14cb4931b94255a \ - --hash=sha256:9fc02a6e517c34dcf885ff3b57260b646551083903e3d2c780b4971ce7d4ab7c \ - --hash=sha256:a4f6cae381f21fee1ef0a5cbbbb146680164311157ae618edf3061742d844383 \ - --hash=sha256:aa4018f2d8662ac4d9830445d3d253a11b3e096e8afe20865547137aa1160e93 \ - --hash=sha256:b519f2ecde9a579cad2f4a7057d5bb4e040ad17caab8b5e691ed7a13b9db0be9 \ - --hash=sha256:b8e95d921cc2a1521d4750eedefec9f16031457920a6677edebe9d1b2ad6ae60 \ - --hash=sha256:bb87158dbbb9e5a79effe78d54837599caa16df52d8d35366e06a91723b587ae \ - --hash=sha256:bfa4b2b7d21c5634b62e5f03462243bd705adc1a21806b5356b8ce06d902e160 \ - --hash=sha256:c683be38a9bf4024c223929b4cd2f0a0858c94e9dc8b36d7eaa5a48ce9323a6f \ - --hash=sha256:cb63055739808144b541986291679d643bae58755d0eb082157c4d4c04443905 \ - --hash=sha256:d0f0806de1161c7f248e4c183633ee7a58dfe45c2b77ddf0136e2e7ad0650b1b \ - --hash=sha256:db030140d0da2368319e2f23655df3baec278c7e0078ecbe051eaf609a69382c \ - --hash=sha256:de156c18b0c638aaee3be6ad650c8ba7dec94ed4bac26403aec3dce95ffe9407 \ - --hash=sha256:df85096fcac7cea8aa5bd84b7a39c4cdbf556b93669bb4772eb96aacd3222a4e \ - --hash=sha256:e312ddc2d8bec1a23306a661ad52734f984c9aad5d8f126ebb222a778d95407d \ - --hash=sha256:eeed386971bb8afc3ec45593df6a1154d680d87be1209ef8e782e44f85f47e64 \ - --hash=sha256:ef3e8aca2261f7f07436d4e2111556c1fb9bf1f9cfcdf35262743ccdee1b6ce9 \ - --hash=sha256:f14a6e4f700dfd30ff8f0e6695f944affc16ae5a1e738666b3fae4e44b65637e \ - --hash=sha256:f1c684c0d9226d04cadafced620a46ab38c346d0780eaac7448da96bf12066a3 \ - --hash=sha256:f381ae3ad6a5eb27aad8d810438937d8228977067c54e0bd456fce7e11fdbf3d \ - --hash=sha256:f6263b85261b62471cb97b7505df72d72b8b62e5e22d8184924871a6155b4dbf \ - --hash=sha256:f965707da2b48a33128615bcfebedd215a3a30e346447e885bb3da37a143177a +grpcio-tools==1.59.2 \ + --hash=sha256:072a7ce979ea4f7579c3c99fcbde3d1882c3d1942a3b51d159f67af83b714cd8 \ + --hash=sha256:09749e832e06493841000275248b031f7154665900d1e1b0e42fc17a64bf904d \ + --hash=sha256:09d809ca88999b2578119683f9f0f6a9b42de95ea21550852114a1540b6a642c \ + --hash=sha256:12cc7698fad48866f68fdef831685cb31ef5814ac605d248c4e5fc964a6fb3f6 \ + --hash=sha256:12fdee2de80d83eadb1294e0f8a0cb6cefcd2e4988ed680038ab09cd04361ee4 \ + --hash=sha256:17ef468836d7cf0b2419f4d5c7ac84ec2d598a1ae410773585313edacf7c393e \ + --hash=sha256:1e949e66d4555ce319fd7acef90df625138078d8729c4dc6f6a9f05925034433 \ + --hash=sha256:2a9ce2a209871ed1c5ae2229e6f4f5a3ea96d83b7871df5d9773d72a72545683 \ + --hash=sha256:2f410375830a9bb7140a07da4d75bf380e0958377bed50d77d1dae302de4314e \ + --hash=sha256:32141ef309543a446337e934f0b7a2565a6fca890ff4e543630a09ef72c8d00b \ + --hash=sha256:3491cb69c909d586c23d7e6d0ac87844ca22f496f505ce429c0d3301234f2cf3 \ + --hash=sha256:3cf9949a2aadcece3c1e0dd59249aea53dbfc8cc94f7d707797acd67cf6cf931 \ + --hash=sha256:41b5dd6a06c2563ac3b3adda6d875b15e63eb7b1629e85fc9af608c3a76c4c82 \ + --hash=sha256:48782727c5cff8b8c96e028a8a58614ff6a37eadc0db85866516210c7aafe9ae \ + --hash=sha256:4a1810bc5de51cc162a19ed3c11da8ddc64d8cfcba049ef337c20fcb397f048b \ + --hash=sha256:531f87c8e884c6a2e58f040039dfbfe997a4e33baa58f7c7d9993db37b1f5ad0 \ + --hash=sha256:55c401599d5093c4cfa83b8f0ee9757b4d6d3029b10bd67be2cffeada7a44961 \ + --hash=sha256:5f2ce5ecd63c492949b03af73b1dd6d502c567cc2f9c2057137e518b0c702a01 \ + --hash=sha256:670f5889853215999eb3511a623dd7dff01b1ce1a64610d13366e0fd337f8c79 \ + --hash=sha256:6e735a26e8ea8bb89dc69343d1d00ea607449c6d81e21f339ee118562f3d1931 \ + --hash=sha256:724f4f0eecc17fa66216eebfff145631070f04ed7fb4ddf7a7d1c4f954ecc2a1 \ + --hash=sha256:75905266cf90f1866b322575c2edcd4b36532c33fc512bb1b380dc58d84b1030 \ + --hash=sha256:77ec33ddee691e60511e2a7c793aad4cf172ae20e08d95c786cbba395f6203a7 \ + --hash=sha256:7ec536cdae870a74080c665cfb1dca8d0784a931aa3c26376ef971a3a51b59d4 \ + --hash=sha256:7f0e26af7c07bfa906c91ca9f5932514928a7f032f5f20aecad6b5541037de7e \ + --hash=sha256:896f5cdf58f658025a4f7e4ea96c81183b4b6a4b1b4d92ae66d112ac91f062f1 \ + --hash=sha256:99ddc0f5304071a355c261ae49ea5d29b9e9b6dcf422dfc55ada70a243e27e8f \ + --hash=sha256:9b2885c0e2c9a97bde33497a919032afbd8b5c6dc2f8d4dd4198e77226e0de05 \ + --hash=sha256:9c106ebbed0db446f59f0efe5c3fce33a0a21bf75b392966585e4b5934891b92 \ + --hash=sha256:a2ccb59dfbf2ebd668a5a7c4b7bb2b859859641d2b199114b557cd045aac6102 \ + --hash=sha256:a3cb707da722a0b6c4021fc2cc1c005a8d4037d8ad0252f93df318b9b8a6b4f3 \ + --hash=sha256:a85da4200295ee17e3c1ae068189a43844420ed7e9d531a042440f52de486dfb \ + --hash=sha256:b0b712acec00a9cbc2204c271d638062a2cb8ce74f25d158b023ff6e93182659 \ + --hash=sha256:b0dc271a200dbab6547b2c73fcbdb7efe94c31cb633aa20d073f7cf4493493e1 \ + --hash=sha256:b38f8edb2909702c2478b52f6213982c21e4f66f739ac953b91f97863ba2c06a \ + --hash=sha256:b53db1523015a3acda75722357df6c94afae37f6023800c608e09a5c05393804 \ + --hash=sha256:ba8dba19e7b2b6f7369004533866f222ba483b9e14d2d152ecf9339c0df1283a \ + --hash=sha256:cbeeb3d8ec4cb25c92e17bfbdcef3c3669e85c5ee787a6e581cb942bc0ae2b88 \ + --hash=sha256:d08b398509ea4d544bcecddd9a21f59dc556396916c3915904cac206af2db72b \ + --hash=sha256:d634b65cc8ee769edccf1647d8a16861a27e0d8cbd787c711168d2c5e9bddbd1 \ + --hash=sha256:db0925545180223fabd6da9b34513efac83aa16673ef8b1cb0cc678e8cf0923c \ + --hash=sha256:dd5c78f8e7c6e721b9009c92481a0e3b30a9926ef721120723a03b8a34a34fb9 \ + --hash=sha256:dee5f7e7a56177234e61a483c70ca2ae34e73128372c801bb7039993870889f1 \ + --hash=sha256:df35d145bc2f6e5f57b74cb69f66526675a5f2dcf7d54617ce0deff0c82cca0a \ + --hash=sha256:e21fc172522d2dda815223a359b2aca9bc317a1b5e5dea5a58cd5079333af133 \ + --hash=sha256:e972746000aa192521715f776fab617a3437bed29e90fe0e0fd0d0d6f498d7d4 \ + --hash=sha256:eb597d6bf9f5bfa54d00546e828f0d4e2c69250d1bc17c27903c0c7b66372135 \ + --hash=sha256:ec2fbb02ebb9f2ae1b1c69cccf913dee8c41f5acad94014d3ce11b53720376e3 \ + --hash=sha256:ed8e6632d8d839456332d97b96db10bd2dbf3078e728d063394ac2d54597ad80 \ + --hash=sha256:f50ff312b88918c5a6461e45c5e03869749a066b1c24a7327e8e13e117efe4fc \ + --hash=sha256:f518f22a3082de00f0d7a216e96366a87e6973111085ba1603c3bfa7dba2e728 \ + --hash=sha256:f52e0ce8f2dcf1f160c847304016c446075a83ab925d98933d4681bfa8af2962 \ + --hash=sha256:fa1b9dee7811fad081816e884d063c4dd4946dba61aa54243b4c76c311090c48 \ + --hash=sha256:feca316e17cfead823af6eae0fc20c0d5299a94d71cfb7531a0e92d050a5fb2f # via -r requirements.in idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ From 1cc4b96293c50eb595f5bd268cb9f6dd11217104 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 31 Oct 2023 16:48:11 +0000 Subject: [PATCH 48/72] win/ci: Use smaller VMs (#30636) Signed-off-by: Ryan Northey --- .github/workflows/envoy-windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/envoy-windows.yml b/.github/workflows/envoy-windows.yml index ed264eddd8f3..d18b627d58cb 100644 --- a/.github/workflows/envoy-windows.yml +++ b/.github/workflows/envoy-windows.yml @@ -36,7 +36,7 @@ jobs: rbe-key: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} with: target: ${{ matrix.target }} - runs-on: envoy-win19-medium + runs-on: envoy-win19-small command_ci: temp-dir: 'C:\Users\runner\AppData\Local\Temp\bazel-shared' run-du: false @@ -67,13 +67,13 @@ jobs: include: - target: windows2019 name: Windows 2019 - runs-on: envoy-win19-medium + runs-on: envoy-win19-small build-type: windows image-base: mcr.microsoft.com/windows/servercore image-tag: ltsc2019 - target: windows2022 name: Windows 2022 - runs-on: envoy-win22-medium + runs-on: envoy-win22-small build-type: windows-ltsc2022 image-base: mcr.microsoft.com/windows/nanoserver image-tag: ltsc2022 From 0e441fbe53b4fe530ba41b8f24541afd5a734d86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 20:49:57 +0000 Subject: [PATCH 49/72] build(deps): bump envoyproxy/toolshed from actions-v0.1.2 to 0.1.3 (#30642) Bumps [envoyproxy/toolshed](https://github.com/envoyproxy/toolshed) from actions-v0.1.2 to 0.1.3. This release includes the previously tagged commit. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/actions-v0.1.2...actions-v0.1.3) --- updated-dependencies: - dependency-name: envoyproxy/toolshed dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_cache_docker.yml | 2 +- .github/workflows/_ci.yml | 12 ++++++------ .github/workflows/_stage_publish.yml | 2 +- .github/workflows/_workflow-start.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/commands.yml | 2 +- .github/workflows/envoy-dependency.yml | 14 +++++++------- .github/workflows/envoy-release.yml | 16 ++++++++-------- .github/workflows/envoy-sync.yml | 2 +- .github/workflows/mobile-android_tests.yml | 4 ++-- .github/workflows/workflow-complete.yml | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/_cache_docker.yml b/.github/workflows/_cache_docker.yml index f777d9668ab3..db349a600fb1 100644 --- a/.github/workflows/_cache_docker.yml +++ b/.github/workflows/_cache_docker.yml @@ -37,7 +37,7 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.3 name: Prime Docker cache (${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}) with: image_tag: "${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}" diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index e0b78a8f8595..5f6a9600880c 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -125,11 +125,11 @@ jobs: steps: - if: ${{ inputs.cache_build_image }} name: Restore Docker cache (${{ inputs.cache_build_image }}) - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.3 with: image_tag: ${{ inputs.cache_build_image }} - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 id: checkout name: Checkout Envoy repository with: @@ -158,7 +158,7 @@ jobs: run: git config --global --add safe.directory /__w/envoy/envoy - if: ${{ inputs.diskspace_hack }} - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 - run: | echo "disk space at beginning of build:" df -h @@ -167,12 +167,12 @@ jobs: - if: ${{ inputs.run_pre }} name: Run pre action ${{ inputs.run_pre && format('({0})', inputs.run_pre) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.3 with: uses: ${{ inputs.run_pre }} with: ${{ inputs.run_pre_with }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 name: 'Run CI target ${{ inputs.target }}' with: catch-errors: ${{ inputs.catch-errors }} @@ -197,7 +197,7 @@ jobs: - if: ${{ inputs.run_post }} name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.3 with: uses: ${{ inputs.run_post }} with: ${{ inputs.run_post_with }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 399a20d1a10c..9dad9782b434 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -117,7 +117,7 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.3 with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index 4de7aaaad79c..bbd10e873fdf 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -30,7 +30,7 @@ jobs: - if: ${{ steps.env.outputs.trusted != 'true' }} name: Start status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.3 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: ${{ inputs.workflow_name }} diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index f72b75167709..10b3ecfbe30c 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Pre-cleanup - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 055649d8a0ab..c6e5f5ce966b 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: actions: write checks: read steps: - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.3 with: token: ${{ secrets.GITHUB_TOKEN }} azp_org: cncf diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index e890b09dbe9d..ff4420b95f72 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -43,13 +43,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: version name: Shorten (possible) SHA - uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.3 with: string: ${{ inputs.version }} length: 7 @@ -64,13 +64,13 @@ jobs: TARGET: ${{ inputs.task == 'bazel' && 'update' || 'api-update' }} TASK: ${{ inputs.task == 'bazel' && 'bazel' || 'api/bazel' }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.3 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.version.outputs.string }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 with: base: main body: | @@ -95,7 +95,7 @@ jobs: name: Update build image (PR) runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 id: checkout name: Checkout Envoy repository with: @@ -134,7 +134,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.3 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -163,7 +163,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 99c15c8537ca..10fc0ac35e77 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,7 +55,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -84,10 +84,10 @@ jobs: GITHUB_REF_NAME: ${{ github.ref_name }} - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.3 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 name: Create release with: source: | @@ -112,7 +112,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 with: base: ${{ github.ref_name }} commit: false @@ -137,7 +137,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -151,7 +151,7 @@ jobs: id: branch env: GITHUB_REF_NAME: ${{ github.ref_name }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 name: Sync version histories with: command: >- @@ -161,7 +161,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 with: append-commit-message: true base: ${{ github.ref_name }} @@ -191,7 +191,7 @@ jobs: name: Create release branch steps: - name: Checkout repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 21cad96ac358..9d4f00f06ad9 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -28,7 +28,7 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.2 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.3 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index 253018e621c5..1ffae6f8000a 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -34,7 +34,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy @@ -68,7 +68,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml index 38ce7e0f44d7..4054f86455aa 100644 --- a/.github/workflows/workflow-complete.yml +++ b/.github/workflows/workflow-complete.yml @@ -54,7 +54,7 @@ jobs: echo "state=${STATE}" >> "$GITHUB_OUTPUT" id: job - name: Complete status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.2 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.3 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: Verify/examples From a1e48d94936e9ab2f3bef7f24056fa513f81f37b Mon Sep 17 00:00:00 2001 From: asingh-g <100796504+asingh-g@users.noreply.github.com> Date: Wed, 1 Nov 2023 01:22:24 -0400 Subject: [PATCH 50/72] Update QUICHE from 30c4298fb to acfc06337 (#30595) https://github.com/google/quiche/compare/30c4298fb..acfc06337 ``` $ git log 30c4298fb..acfc06337 --date=short --no-merges --format="%ad %al %s" 2023-10-24 wub Deprecate --gfe2_reloadable_flag_quic_extract_supported_groups_early. 2023-10-24 wub Add more information to the `quic_send_alarm_postponed` QUIC_BUG. 2023-10-24 bnc Disallow colon in received header names (other than leading colon of pseudo-headers). 2023-10-23 bnc Add option to allow uppercase header name characters in HeaderValidator. 2023-10-23 quiche-dev Add proxy layer to auth and sign and get initial data apis 2023-10-23 bnc Remove `validated_key` local variable from HeaderValidator::ValidateSingleHeader(). 2023-10-19 rch When quic_limit_sending_max_streams is enabled and MAX_STREAMS frame is ack'd, delay sending a new MAX_STREAMS frame if the connection is currently processing a packet. 2023-10-19 martinduke Remove flag check from test. 2023-10-19 vasilvv Remove std::function from third_party/http2 2023-10-19 bnc Validate request headers in H3Stream. 2023-10-19 martinduke Allow connections to request unroutable connection IDs when Maglev is out of sync. ``` Signed-off-by: asingh-g --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index f6c51daf44f0..d1faa45b75da 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1123,12 +1123,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "30c4298fbadc820dbbbf7721c72b279722856930", - sha256 = "ec26667dd7e0d6e22d2d7f34d5310a5fd18da6449488f4aab24b3f2a89cff795", + version = "acfc063373a7c3d691d34fa87678bb368c07b15e", + sha256 = "668b86b0645384234a34c46cc5d2a03fc9b86111117e9ccd5081170d7d2f3cc6", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2023-10-18", + release_date = "2023-10-24", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", From f967781f639675619e7b7255b8993f373bb68af1 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 1 Nov 2023 06:24:02 +0100 Subject: [PATCH 51/72] access log: remove envoy.reloadable_features.format_ports_as_numbers (#30555) Fixes envoyproxy#30417 Signed-off-by: Michael Kaufmann Co-authored-by: Michael Kaufmann --- changelogs/current.yaml | 4 ++ .../common/formatter/stream_info_formatter.cc | 13 ++--- source/common/runtime/runtime_features.cc | 1 - .../formatter/substitution_formatter_test.cc | 56 ------------------- 4 files changed, 10 insertions(+), 64 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 1bddbbff6676..e084d62e12fb 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -68,6 +68,10 @@ removed_config_or_runtime: change: | Removed the deprecated ``envoy.reloadable_features.service_sanitize_non_utf8_strings`` runtime flag and legacy code path. +- area: access log + change: | + Removed the deprecated ``envoy.reloadable_features.format_ports_as_numbers`` + runtime flag and legacy code path. new_features: - area: filters diff --git a/source/common/formatter/stream_info_formatter.cc b/source/common/formatter/stream_info_formatter.cc index 1bc5ff5d6e9d..a164985e188d 100644 --- a/source/common/formatter/stream_info_formatter.cc +++ b/source/common/formatter/stream_info_formatter.cc @@ -530,15 +530,14 @@ class StreamInfoAddressFormatterProvider : public StreamInfoFormatterProvider { return SubstitutionFormatUtils::unspecifiedValue(); } - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.format_ports_as_numbers")) { - if (extraction_type_ == StreamInfoAddressFieldExtractionType::JustPort) { - const auto port = StreamInfo::Utility::extractDownstreamAddressJustPort(*address); - if (port) { - return ValueUtil::numberValue(*port); - } - return SubstitutionFormatUtils::unspecifiedValue(); + if (extraction_type_ == StreamInfoAddressFieldExtractionType::JustPort) { + const auto port = StreamInfo::Utility::extractDownstreamAddressJustPort(*address); + if (port) { + return ValueUtil::numberValue(*port); } + return SubstitutionFormatUtils::unspecifiedValue(); } + return ValueUtil::stringValue(toString(*address)); } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index cb5212ddf07e..dfa4f146a23c 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -45,7 +45,6 @@ RUNTIME_GUARD(envoy_reloadable_features_enable_connect_udp_support); RUNTIME_GUARD(envoy_reloadable_features_enable_intermediate_ca); RUNTIME_GUARD(envoy_reloadable_features_enable_zone_routing_different_zone_counts); RUNTIME_GUARD(envoy_reloadable_features_ext_authz_http_send_original_xff); -RUNTIME_GUARD(envoy_reloadable_features_format_ports_as_numbers); RUNTIME_GUARD(envoy_reloadable_features_handle_uppercase_scheme); RUNTIME_GUARD(envoy_reloadable_features_hmac_base64_encoding_only); RUNTIME_GUARD(envoy_reloadable_features_http1_allow_codec_error_response_after_1xx_headers); diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index c32f48a2943f..8c9e6dc4afd0 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -604,14 +604,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(18443))); - { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.format_ports_as_numbers", "false"}}); - - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("18443"))); - } - // Validate for IPv6 address address = Network::Address::InstanceConstSharedPtr{new Network::Address::Ipv6Instance("::1", 19443)}; @@ -620,14 +612,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(19443))); - { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.format_ports_as_numbers", "false"}}); - - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("19443"))); - } - // Validate for Pipe address = Network::Address::InstanceConstSharedPtr{new Network::Address::PipeInstance("/foo")}; stream_info.upstreamInfo()->setUpstreamLocalAddress(address); @@ -660,14 +644,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_EQ("443", upstream_format.formatWithContext({}, stream_info)); EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(443))); - - { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.format_ports_as_numbers", "false"}}); - - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("443"))); - } } { @@ -754,14 +730,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(8443))); - { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.format_ports_as_numbers", "false"}}); - - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("8443"))); - } - // Validate for IPv6 address address = Network::Address::InstanceConstSharedPtr{new Network::Address::Ipv6Instance("::1", 9443)}; @@ -770,14 +738,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(9443))); - { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.format_ports_as_numbers", "false"}}); - - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("9443"))); - } - // Validate for Pipe address = Network::Address::InstanceConstSharedPtr{new Network::Address::PipeInstance("/foo")}; stream_info.downstream_connection_info_provider_->setLocalAddress(address); @@ -805,14 +765,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_EQ("0", upstream_format.formatWithContext({}, stream_info)); EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(0))); - - { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.format_ports_as_numbers", "false"}}); - - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("0"))); - } } { @@ -834,14 +786,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { EXPECT_EQ("63443", upstream_format.formatWithContext({}, stream_info)); EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::numberValue(63443))); - - { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.format_ports_as_numbers", "false"}}); - - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("63443"))); - } } { From e4a88aaf23cb88251eade5332e6991fe836dd7de Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 1 Nov 2023 09:09:17 +0000 Subject: [PATCH 52/72] github/ci: Fix ref properly for PRs (#30643) Signed-off-by: Ryan Northey --- .github/workflows/_env.yml | 18 ++++++++++++++++-- .github/workflows/envoy-macos.yml | 5 ++++- .github/workflows/envoy-prechecks.yml | 3 ++- .github/workflows/envoy-publish.yml | 1 + .github/workflows/envoy-windows.yml | 6 +++++- .github/workflows/mobile-android_build.yml | 1 + .github/workflows/mobile-android_tests.yml | 1 + .github/workflows/mobile-asan.yml | 1 + .github/workflows/mobile-cc_tests.yml | 1 + .../workflows/mobile-compile_time_options.yml | 1 + .github/workflows/mobile-core.yml | 1 + .github/workflows/mobile-coverage.yml | 1 + .github/workflows/mobile-docs.yml | 1 + .github/workflows/mobile-format.yml | 1 + .github/workflows/mobile-ios_build.yml | 1 + .github/workflows/mobile-ios_tests.yml | 1 + .github/workflows/mobile-release.yml | 1 + .../workflows/mobile-release_validation.yml | 1 + .github/workflows/mobile-tsan.yml | 1 + 19 files changed, 42 insertions(+), 5 deletions(-) diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index adc0799710da..dd98205c98cd 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -78,7 +78,7 @@ on: value: ${{ jobs.repo.outputs.mobile_tsan }} repo_ref: - value: ${{ jobs.repo.outputs.repo_ref }} + value: ${{ jobs.ref.outputs.value }} repo_ref_name: value: ${{ jobs.repo.outputs.repo_ref_name }} repo_ref_sha: @@ -112,6 +112,9 @@ jobs: || vars.ENVOY_CI) }} runs-on: ubuntu-22.04 + permissions: + contents: read + pull-requests: read outputs: build_image_ubuntu: ${{ steps.env.outputs.build_image_ubuntu }} build_image_ubuntu_mobile: ${{ steps.env.outputs.build_image_ubuntu_mobile }} @@ -158,13 +161,24 @@ jobs: build_image_tag: ${{ inputs.build_image_tag }} build_image_mobile_sha: ${{ inputs.build_image_mobile_sha }} build_image_sha: ${{ inputs.build_image_sha }} + - uses: envoyproxy/toolshed/gh-actions/github/merge-commit@actions-v0.1.3 + id: merge-commit + if: ${{ github.event_name == 'pull_request_target' }} + with: + repository: ${{ github.repository }} + pr: ${{ github.event.number }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: 'Set ref' + id: ref + run: | + echo "value=${{ steps.merge-commit.outputs.sha || steps.env.outputs.repo_ref }}" >> $GITHUB_OUTPUT - name: 'Print env' run: | echo "version_dev=${{ steps.env.outputs.version_dev }}" echo "version_patch=${{ steps.env.outputs.version_patch }}" echo "trusted=${{ steps.env.outputs.trusted }}" - echo "repo_ref=${{ steps.env.outputs.repo_ref }}" + echo "repo_ref=${{ steps.ref.outputs.value }}" echo "repo_ref_name=${{ steps.env.outputs.repo_ref_name }}" echo "repo_ref_pr_number=${{ steps.env.outputs.repo_ref_pr_number }}" echo "repo_ref_sha=${{ steps.env.outputs.repo_ref_sha }}" diff --git a/.github/workflows/envoy-macos.yml b/.github/workflows/envoy-macos.yml index 86855854f37c..bcafe822ef6f 100644 --- a/.github/workflows/envoy-macos.yml +++ b/.github/workflows/envoy-macos.yml @@ -17,6 +17,9 @@ concurrency: jobs: env: uses: ./.github/workflows/_env.yml + permissions: + contents: read + pull-requests: read with: prime_build_image: false check_mobile_run: false @@ -39,7 +42,7 @@ jobs: runs-on: macos-12-xl command_ci: command_prefix: - repo_ref: ${{ github.event.pull_request.merge_commit_sha }} + repo_ref: ${{ needs.env.outputs.repo_ref }} run-du: false env: | ./ci/mac_ci_setup.sh diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index 79988b5e7f68..beb40189b164 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -31,6 +31,7 @@ jobs: permissions: contents: read packages: read + pull-requests: read prechecks: needs: @@ -53,7 +54,7 @@ jobs: bazel_extra: '--config=rbe-envoy-engflow' managed: ${{ matrix.managed }} cache_build_image: ${{ needs.env.outputs.build_image_ubuntu }} - repo_ref: ${{ github.event.pull_request.head.sha }} + repo_ref: ${{ needs.env.outputs.repo_ref }} catch-errors: true error-match: | ERROR diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index fc8bd1506d36..c00a0ae271a3 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -42,6 +42,7 @@ jobs: permissions: contents: read packages: read + pull-requests: read check: if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/envoy-windows.yml b/.github/workflows/envoy-windows.yml index d18b627d58cb..e6314980f61d 100644 --- a/.github/workflows/envoy-windows.yml +++ b/.github/workflows/envoy-windows.yml @@ -17,6 +17,9 @@ concurrency: jobs: env: uses: ./.github/workflows/_env.yml + permissions: + contents: read + pull-requests: read with: prime_build_image: false check_mobile_run: false @@ -39,6 +42,7 @@ jobs: runs-on: envoy-win19-small command_ci: temp-dir: 'C:\Users\runner\AppData\Local\Temp\bazel-shared' + repo_ref: ${{ needs.env.outputs.repo_ref }} run-du: false upload-name: windows.release upload-path: 'C:\Users\runner\AppData\Local\Temp\envoy' @@ -81,7 +85,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.merge_commit_sha }} + ref: ${{ needs.env.outputs.repo_ref }} - uses: actions/download-artifact@v3 with: name: windows.release diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index ca135f7bf9ed..53a438225414 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read androidbuild: if: ${{ needs.env.outputs.mobile_android_build == 'true' }} diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index 1ffae6f8000a..d9a6b5c44dd5 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -20,6 +20,7 @@ jobs: prime_build_image: true permissions: contents: read + pull-requests: read javatestslinux: if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} diff --git a/.github/workflows/mobile-asan.yml b/.github/workflows/mobile-asan.yml index a92e3730cfe3..5faace5c24e1 100644 --- a/.github/workflows/mobile-asan.yml +++ b/.github/workflows/mobile-asan.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read asan: if: ${{ needs.env.outputs.mobile_asan == 'true' }} diff --git a/.github/workflows/mobile-cc_tests.yml b/.github/workflows/mobile-cc_tests.yml index a9001c1df8d8..2a37430af34d 100644 --- a/.github/workflows/mobile-cc_tests.yml +++ b/.github/workflows/mobile-cc_tests.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read cctests: if: ${{ needs.env.outputs.mobile_cc_tests == 'true' }} diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml index b03ceb2983c2..263185231dd6 100644 --- a/.github/workflows/mobile-compile_time_options.yml +++ b/.github/workflows/mobile-compile_time_options.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read cc_test_no_yaml: needs: env diff --git a/.github/workflows/mobile-core.yml b/.github/workflows/mobile-core.yml index 037eb72b3284..4630506537e9 100644 --- a/.github/workflows/mobile-core.yml +++ b/.github/workflows/mobile-core.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read unittests: if: ${{ github.repository == 'envoyproxy/envoy' }} diff --git a/.github/workflows/mobile-coverage.yml b/.github/workflows/mobile-coverage.yml index 8d3aaa8e93b5..bd7b7214a990 100644 --- a/.github/workflows/mobile-coverage.yml +++ b/.github/workflows/mobile-coverage.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read coverage: if: ${{ needs.env.outputs.mobile_coverage == 'true' }} diff --git a/.github/workflows/mobile-docs.yml b/.github/workflows/mobile-docs.yml index 936674a46568..d4d226946ede 100644 --- a/.github/workflows/mobile-docs.yml +++ b/.github/workflows/mobile-docs.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read docs: if: ${{ github.repository == 'envoyproxy/envoy' }} diff --git a/.github/workflows/mobile-format.yml b/.github/workflows/mobile-format.yml index 777a62f56c93..13bf1b2dbb20 100644 --- a/.github/workflows/mobile-format.yml +++ b/.github/workflows/mobile-format.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read formatall: if: ${{ needs.env.outputs.mobile_formatting == 'true' }} diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index ca5b865880b5..3fa51f594200 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read iosbuild: if: ${{ needs.env.outputs.mobile_ios_build == 'true' }} diff --git a/.github/workflows/mobile-ios_tests.yml b/.github/workflows/mobile-ios_tests.yml index 150429a30d05..6016dd03e86f 100644 --- a/.github/workflows/mobile-ios_tests.yml +++ b/.github/workflows/mobile-ios_tests.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read swifttests: if: ${{ needs.env.outputs.mobile_ios_tests == 'true' }} diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 3489f87e9777..3357cca65187 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -15,6 +15,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read android_release_artifacts: if: >- diff --git a/.github/workflows/mobile-release_validation.yml b/.github/workflows/mobile-release_validation.yml index 156ad5fbd71d..76775184fddb 100644 --- a/.github/workflows/mobile-release_validation.yml +++ b/.github/workflows/mobile-release_validation.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read validate_swiftpm_example: if: ${{ needs.env.outputs.mobile_release_validation == 'true' }} diff --git a/.github/workflows/mobile-tsan.yml b/.github/workflows/mobile-tsan.yml index 27386c81fd3a..b06b2a8da3fc 100644 --- a/.github/workflows/mobile-tsan.yml +++ b/.github/workflows/mobile-tsan.yml @@ -19,6 +19,7 @@ jobs: uses: ./.github/workflows/_env.yml permissions: contents: read + pull-requests: read tsan: if: ${{ needs.env.outputs.mobile_tsan == 'true' }} From 55a95a171c1371b2402e9c8e2092f5b0ca02462d Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 1 Nov 2023 09:23:43 +0000 Subject: [PATCH 53/72] github/ci: Minor fix for PR refs (#30655) Signed-off-by: Ryan Northey --- .github/workflows/_env.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index dd98205c98cd..608f49479456 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -78,7 +78,7 @@ on: value: ${{ jobs.repo.outputs.mobile_tsan }} repo_ref: - value: ${{ jobs.ref.outputs.value }} + value: ${{ jobs.repo.outputs.repo_ref }} repo_ref_name: value: ${{ jobs.repo.outputs.repo_ref_name }} repo_ref_sha: @@ -131,7 +131,7 @@ jobs: mobile_ios_tests: ${{ steps.env.outputs.mobile_ios_tests }} mobile_release_validation: ${{ steps.env.outputs.mobile_release_validation }} mobile_tsan: ${{ steps.env.outputs.mobile_tsan }} - repo_ref: ${{ steps.env.outputs.repo_ref }} + repo_ref: ${{ steps.ref.outputs.value }} repo_ref_name: ${{ steps.env.outputs.repo_ref_name }} repo_ref_sha: ${{ steps.env.outputs.repo_ref_sha }} repo_ref_sha_short: ${{ steps.env.outputs.repo_ref_sha_short }} From 513e3b02cb9f29e8ef40e2426364725f09fcdf7c Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 1 Nov 2023 10:12:31 +0000 Subject: [PATCH 54/72] github/ci: Assorted refactoring, fixes and cleanups (#30602) Signed-off-by: Ryan Northey --- .github/actions/env/action.yml | 72 +++---- .../actions/publish/release/setup/action.yml | 26 --- .github/workflows/_ci.yml | 178 +++++++----------- .github/workflows/_env.yml | 96 +++++----- .github/workflows/_precheck_deps.yml | 55 ++++++ .github/workflows/_stage_publish.yml | 88 +++++---- .github/workflows/_stage_verify.yml | 74 +++++--- .github/workflows/_workflow-start.yml | 2 +- .github/workflows/check-deps.yml | 36 ---- .github/workflows/depsreview.yml | 16 -- .github/workflows/envoy-dependency.yml | 49 ++++- .github/workflows/envoy-prechecks.yml | 40 ++-- .github/workflows/envoy-publish.yml | 20 +- .github/workflows/envoy-release.yml | 31 +-- .github/workflows/envoy-windows.yml | 12 +- .github/workflows/mobile-android_tests.yml | 2 +- 16 files changed, 387 insertions(+), 410 deletions(-) delete mode 100644 .github/actions/publish/release/setup/action.yml create mode 100644 .github/workflows/_precheck_deps.yml delete mode 100644 .github/workflows/check-deps.yml delete mode 100644 .github/workflows/depsreview.yml diff --git a/.github/actions/env/action.yml b/.github/actions/env/action.yml index d30cab498dc5..40b913b1ea62 100644 --- a/.github/actions/env/action.yml +++ b/.github/actions/env/action.yml @@ -12,11 +12,11 @@ inputs: type: string required: true - repo_ref: + repo-ref: type: string - repo_ref_sha: + repo-ref-sha: type: string - repo_ref_name: + repo-ref-name: type: string trusted_bots: @@ -24,7 +24,7 @@ inputs: default: | trigger-release-envoy[bot] - check_mobile_run: + check-mobile-run: type: boolean default: true @@ -60,24 +60,24 @@ outputs: value: ${{ steps.should_run.outputs.mobile_release_validation }} mobile_tsan: value: ${{ steps.should_run.outputs.mobile_tsan }} - repo_ref: - value: ${{ steps.context.outputs.repo_ref }} - repo_ref_name: - value: ${{ steps.context.outputs.repo_ref_name }} - repo_ref_pr_number: - value: ${{ steps.context.outputs.repo_ref_pr_number }} - repo_ref_sha: - value: ${{ steps.context.outputs.repo_ref_sha }} - repo_ref_sha_short: - value: ${{ steps.context.outputs.repo_ref_sha_short }} - repo_ref_title: - value: ${{ steps.context.outputs.repo_ref_title }} + repo-ref: + value: ${{ steps.context.outputs.repo-ref }} + repo-ref-name: + value: ${{ steps.context.outputs.repo-ref-name }} + repo-ref-pr-number: + value: ${{ steps.context.outputs.repo-ref-pr-number }} + repo-ref-sha: + value: ${{ steps.context.outputs.repo-ref-sha }} + repo-ref-sha-short: + value: ${{ steps.context.outputs.repo-ref-sha-short }} + repo-ref-title: + value: ${{ steps.context.outputs.repo-ref-title }} trusted: value: ${{ steps.trusted.outputs.trusted }} - version_dev: - value: ${{ steps.context.outputs.version_dev }} - version_patch: - value: ${{ steps.context.outputs.version_patch }} + version-dev: + value: ${{ steps.context.outputs.version-dev }} + version-patch: + value: ${{ steps.context.outputs.version-patch }} runs: using: composite @@ -126,16 +126,16 @@ runs: # 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 + 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 }} + 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' }} + - if: ${{ inputs.check-mobile-run != 'false' }} id: should_run name: 'Check what to run' run: ./mobile/tools/what_to_run.sh @@ -151,7 +151,7 @@ runs: fi VERSION_PATCH="$(cat VERSION.txt | cut -d- -f1 | rev | cut -d. -f1 | rev)" # TODO: strip merge from pr names - REF_NAME=${{ inputs.repo_ref_name || github.ref_name }} + REF_NAME=${{ inputs.repo-ref-name || github.ref_name }} if [[ "$REF_NAME" =~ ^refs/pull/ ]]; then REF_NAME="${REF_NAME:10}" REF_PR_NUMBER="$(echo "${REF_NAME}" | cut -d/ -f1)" @@ -160,8 +160,8 @@ runs: fi echo "SET PR NUMBER: ${REF_PR_NUMBER}" - REF="${{ steps.trusted.outputs.trusted != 'true' && inputs.repo_ref || '' }}" - REF_SHA=${{ inputs.repo_ref_sha || github.event.pull_request.head.sha || github.sha }} + REF="${{ steps.trusted.outputs.trusted != 'true' && inputs.repo-ref || '' }}" + REF_SHA=${{ inputs.repo-ref-sha || github.event.pull_request.head.sha || github.sha }} REF_SHA_SHORT="${REF_SHA:0:7}" REF_TITLE=( "${{ steps.trusted.outputs.trusted == 'true' && 'postsubmit' || 'pr' }}/" @@ -169,14 +169,14 @@ runs: "@${REF_SHA_SHORT}") REF_TITLE="$(printf %s "${REF_TITLE[@]}" $'\n')" { - echo "repo_ref=$REF" - echo "repo_ref_name=$REF_NAME" - echo "repo_ref_pr_number=$REF_PR_NUMBER" - echo "repo_ref_sha=$REF_SHA" - echo "repo_ref_title=$REF_TITLE" - echo "repo_ref_sha_short=$REF_SHA_SHORT" - echo "version_dev=$VERSION_DEV" - echo "version_patch=$VERSION_PATCH" + echo "repo-ref=$REF" + echo "repo-ref-name=$REF_NAME" + echo "repo-ref-pr-number=$REF_PR_NUMBER" + echo "repo-ref-sha=$REF_SHA" + echo "repo-ref-title=$REF_TITLE" + echo "repo-ref-sha-short=$REF_SHA_SHORT" + echo "version-dev=$VERSION_DEV" + echo "version-patch=$VERSION_PATCH" } >> "$GITHUB_OUTPUT" shell: bash diff --git a/.github/actions/publish/release/setup/action.yml b/.github/actions/publish/release/setup/action.yml deleted file mode 100644 index 9660078fceb2..000000000000 --- a/.github/actions/publish/release/setup/action.yml +++ /dev/null @@ -1,26 +0,0 @@ -inputs: - ref: - type: string - required: true - bucket: - type: string - required: true - -runs: - using: composite - steps: - - id: url - run: | - echo "base=https://storage.googleapis.com/${{ inputs.bucket }}/${REF:0:7}/release" \ - >> "$GITHUB_OUTPUT" - env: - REF: ${{ inputs.ref }} - shell: bash - - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.1.1 - id: fetch - with: - url: "${{ steps.url.outputs.base }}/release.signed.tar.zst" - - run: | - mkdir -p ${{ runner.temp }}/release.signed - mv ${{ steps.fetch.outputs.path }} ${{ runner.temp }}/release.signed - shell: bash diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 5f6a9600880c..66bf551021b8 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -3,111 +3,81 @@ name: Envoy CI on: workflow_call: secrets: - app_id: - app_key: + app-id: + app-key: rbe-key: inputs: - target: - required: true - type: string - rbe: - type: boolean - default: true - managed: - type: boolean - default: true - runs-on: - default: ubuntu-22.04 - type: string - run-du: - default: true - type: boolean - temp-dir: - default: - type: string - upload-name: - default: - type: string - upload-path: - default: + bazel-extra: type: string - auth_bazel_rbe: - type: string - default: '' - - bazel_extra: - type: string - default: - bazel_local_cache: - type: string - default: - bazel_rbe_cache: - type: string - default: grpcs://remotebuildexecution.googleapis.com - bazel_rbe_instance: - type: string - default: projects/envoy-ci/instances/default_instance - bazel_rbe_jobs: + bazel-rbe-jobs: type: number default: 75 - - cache_build_image: + cache-build-image: type: string - - command_prefix: + catch-errors: + type: boolean + default: false + command-prefix: type: string default: ./ci/run_envoy_docker.sh - command_ci: + command-ci: type: string default: ./ci/do_ci.sh - catch-errors: + diskspace-hack: type: boolean default: false error-match: type: string default: | ERROR - warning-match: - type: string - default: | - WARNING notice-match: type: string default: | NOTICE - - diskspace_hack: + rbe: type: boolean - default: false - - run_pre: - type: string - default: - run_pre_with: - type: string - default: - - run_post: - type: string - default: - run_post_with: - type: string - default: - - repo_fetch_depth: + default: true + repo-fetch-depth: type: number default: 1 - repo_ref: + repo-ref: + type: string + runs-on: type: string + default: ubuntu-22.04 skip: type: boolean default: false + source: + type: string + steps-pre: + type: string + steps-pre-name: + type: string + steps-post: + type: string + default: | + - run: | + du -ch "%{{ inputs.temp-dir || runner.temp }}" | grep -E "[0-9]{2,}M|[0-9]G" + shell: bash + steps-post-name: + type: string + target: + type: string + required: true + temp-dir: + type: string trusted: type: boolean default: false - - env: + upload-name: + type: string + upload-path: + type: string + warning-match: type: string + default: | + WARNING concurrency: group: | @@ -121,35 +91,35 @@ jobs: do_ci: if: ${{ ! inputs.skip }} runs-on: ${{ inputs.runs-on }} - name: ${{ inputs.command_ci }} ${{ inputs.target }} + name: ${{ inputs.command-ci }} ${{ inputs.target }} steps: - - if: ${{ inputs.cache_build_image }} - name: Restore Docker cache (${{ inputs.cache_build_image }}) + - if: ${{ inputs.cache-build-image }} + name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.3 with: - image_tag: ${{ inputs.cache_build_image }} + image_tag: ${{ inputs.cache-build-image }} - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 id: checkout name: Checkout Envoy repository with: - app_id: ${{ inputs.trusted && secrets.app_id || '' }} - app_key: ${{ inputs.trusted && secrets.app_key || '' }} + app_id: ${{ inputs.trusted && secrets.app-id || '' }} + app_key: ${{ inputs.trusted && secrets.app-key || '' }} config: | - fetch-depth: ${{ ! inputs.trusted && inputs.repo_fetch_depth || 0 }} + fetch-depth: ${{ ! inputs.trusted && inputs.repo-fetch-depth || 0 }} # WARNING: This allows untrusted code to run!!! # If this is set, then anything before or after in the job should be regarded as # compromised. - ref: ${{ ! inputs.trusted && inputs.repo_ref || github.ref }} + ref: ${{ ! inputs.trusted && inputs.repo-ref || github.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 + 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 }}" + git checkout "${{ inputs.repo-ref }}" if: ${{ inputs.trusted }} name: Check provided ref shell: bash @@ -157,7 +127,8 @@ jobs: - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy - - if: ${{ inputs.diskspace_hack }} + - if: ${{ inputs.diskspace-hack }} + name: Free diskspace uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 - run: | echo "disk space at beginning of build:" @@ -165,21 +136,21 @@ jobs: name: "Check disk space at beginning" shell: bash - - if: ${{ inputs.run_pre }} - name: Run pre action ${{ inputs.run_pre && format('({0})', inputs.run_pre) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.3 + name: Run pre steps + if: ${{ inputs.steps-pre }} with: - uses: ${{ inputs.run_pre }} - with: ${{ inputs.run_pre_with }} + name: ${{ inputs.steps-pre-name }} + steps: ${{ inputs.steps-pre }} - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 name: 'Run CI target ${{ inputs.target }}' with: catch-errors: ${{ inputs.catch-errors }} - container-command: ${{ inputs.command_prefix }} - command-prefix: ${{ inputs.command_ci }} + container-command: ${{ inputs.command-prefix }} + command-prefix: ${{ inputs.command-ci }} command: ${{ inputs.target }} - source: ${{ inputs.env }} + source: ${{ inputs.source }} error-match: ${{ inputs.error-match }} notice-match: ${{ inputs.notice-match }} warning-match: ${{ inputs.warning-match }} @@ -190,29 +161,26 @@ jobs: RBE_KEY: ${{ secrets.rbe-key }} BAZEL_BUILD_EXTRA_OPTIONS: >- --config=remote-ci - ${{ inputs.bazel_extra }} - ${{ inputs.rbe != 'false' && format('--jobs={0}', inputs.bazel_rbe_jobs) || '' }} + ${{ inputs.bazel-extra }} + ${{ inputs.rbe != 'false' && format('--jobs={0}', inputs.bazel-rbe-jobs) || '' }} BAZEL_FAKE_SCM_REVISION: ${{ github.event_name == 'pull_request' && 'e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9' || '' }} CI_TARGET_BRANCH: ${{ github.event_name == 'pull_request' && github.event.base.ref || github.ref }} - - if: ${{ inputs.run_post }} - name: Run post action ${{ inputs.run_pre && format('({0})', inputs.run_post) || '' }} - uses: envoyproxy/toolshed/gh-actions/using/recurse@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.3 + name: Run post steps + if: ${{ inputs.steps-post }} with: - uses: ${{ inputs.run_post }} - with: ${{ inputs.run_post_with }} + name: ${{ inputs.steps-post-name }} + steps: ${{ inputs.steps-post }} - run: | echo "disk space at end of build:" df -h - echo - if [[ "${{ inputs.run-du }}" != "false" ]]; then - du -ch "${{ inputs.temp-dir || runner.temp }}" | grep -E "[0-9]{2,}M|[0-9]G" - fi name: "Check disk space at end" shell: bash - uses: actions/upload-artifact@v3 + name: Upload artefacts if: ${{ inputs.upload-name && inputs.upload-path }} with: name: ${{ inputs.upload-name }} diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 608f49479456..2e7be8b57e7a 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -24,20 +24,19 @@ on: type: string default: 7467652575122d8d54e767a68f141598bd855383 - check_mobile_run: + check-mobile-run: type: boolean default: true - prime_build_image: + prime-build-image: type: boolean default: false - - repo_ref: + repo-ref: type: string default: - repo_ref_sha: + repo-ref-name: type: string default: - repo_ref_name: + repo-ref-sha: type: string default: @@ -76,25 +75,22 @@ on: value: ${{ jobs.repo.outputs.mobile_release_validation }} mobile_tsan: value: ${{ jobs.repo.outputs.mobile_tsan }} - - repo_ref: - value: ${{ jobs.repo.outputs.repo_ref }} - repo_ref_name: - value: ${{ jobs.repo.outputs.repo_ref_name }} - repo_ref_sha: - value: ${{ jobs.repo.outputs.repo_ref_sha }} - repo_ref_sha_short: - value: ${{ jobs.repo.outputs.repo_ref_sha_short }} - repo_ref_title: - value: ${{ jobs.repo.outputs.repo_ref_title }} - + repo-ref: + value: ${{ jobs.repo.outputs.repo-ref }} + repo-ref-name: + value: ${{ jobs.repo.outputs.repo-ref-name }} + repo-ref-sha: + value: ${{ jobs.repo.outputs.repo-ref-sha }} + repo-ref-sha-short: + value: ${{ jobs.repo.outputs.repo-ref-sha-short }} + repo-ref-title: + value: ${{ jobs.repo.outputs.repo-ref-title }} trusted: value: ${{ jobs.repo.outputs.trusted }} - - version_dev: - value: ${{ jobs.repo.outputs.version_dev }} - version_patch: - value: ${{ jobs.repo.outputs.version_patch }} + version-dev: + value: ${{ jobs.repo.outputs.version-dev }} + version-patch: + value: ${{ jobs.repo.outputs.version-patch }} concurrency: group: | @@ -106,11 +102,7 @@ concurrency: jobs: repo: - if: >- - ${{ - (github.repository == 'envoyproxy/envoy' - || vars.ENVOY_CI) - }} + if: ${{ github.repository == 'envoyproxy/envoy' || vars.ENVOY_CI }} runs-on: ubuntu-22.04 permissions: contents: read @@ -131,32 +123,32 @@ jobs: mobile_ios_tests: ${{ steps.env.outputs.mobile_ios_tests }} mobile_release_validation: ${{ steps.env.outputs.mobile_release_validation }} mobile_tsan: ${{ steps.env.outputs.mobile_tsan }} - repo_ref: ${{ steps.ref.outputs.value }} - repo_ref_name: ${{ steps.env.outputs.repo_ref_name }} - repo_ref_sha: ${{ steps.env.outputs.repo_ref_sha }} - repo_ref_sha_short: ${{ steps.env.outputs.repo_ref_sha_short }} - repo_ref_title: ${{ steps.env.outputs.repo_ref_title }} + repo-ref: ${{ steps.ref.outputs.value }} + repo-ref-name: ${{ steps.env.outputs.repo-ref-name }} + repo-ref-sha: ${{ steps.env.outputs.repo-ref-sha }} + repo-ref-sha_short: ${{ steps.env.outputs.repo-ref-sha-short }} + repo-ref-title: ${{ steps.env.outputs.repo-ref-title }} trusted: ${{ steps.env.outputs.trusted }} - version_dev: ${{ steps.env.outputs.version_dev }} - version_patch: ${{ steps.env.outputs.version_patch }} + version-dev: ${{ steps.env.outputs.version-dev }} + version-patch: ${{ steps.env.outputs.version-patch }} steps: - uses: actions/checkout@v4 name: Checkout Envoy repository with: - fetch-depth: ${{ ! (inputs.check_mobile_run || ! startsWith(github.event_name, 'pull_request')) && 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: ${{ startsWith(github.event_name, 'pull_request') && inputs.repo_ref || '' }} + ref: ${{ startsWith(github.event_name, 'pull_request') && inputs.repo-ref || '' }} - uses: ./.github/actions/env name: Generate environment variables id: env with: - check_mobile_run: ${{ inputs.check_mobile_run }} - repo_ref: ${{ inputs.repo_ref }} - repo_ref_name: ${{ inputs.repo_ref_name }} - repo_ref_sha: ${{ inputs.repo_ref_sha }} + check-mobile-run: ${{ inputs.check-mobile-run }} + repo-ref: ${{ inputs.repo-ref }} + repo-ref-name: ${{ inputs.repo-ref-name }} + repo-ref-sha: ${{ inputs.repo-ref-sha }} build_image_repo: ${{ inputs.build_image_repo }} build_image_tag: ${{ inputs.build_image_tag }} build_image_mobile_sha: ${{ inputs.build_image_mobile_sha }} @@ -175,24 +167,24 @@ jobs: - name: 'Print env' run: | - echo "version_dev=${{ steps.env.outputs.version_dev }}" - echo "version_patch=${{ steps.env.outputs.version_patch }}" + echo "version-dev=${{ steps.env.outputs.version-dev }}" + echo "version-patch=${{ steps.env.outputs.version-patch }}" echo "trusted=${{ steps.env.outputs.trusted }}" - echo "repo_ref=${{ steps.ref.outputs.value }}" - echo "repo_ref_name=${{ steps.env.outputs.repo_ref_name }}" - echo "repo_ref_pr_number=${{ steps.env.outputs.repo_ref_pr_number }}" - echo "repo_ref_sha=${{ steps.env.outputs.repo_ref_sha }}" - echo "repo_ref_sha_short=${{ steps.env.outputs.repo_ref_sha_short }}" - echo "repo_ref_title=${{ steps.env.outputs.repo_ref_title }}" + echo "repo-ref=${{ steps.ref.outputs.value }}" + echo "repo-ref-name=${{ steps.env.outputs.repo-ref-name }}" + echo "repo-ref-pr-number=${{ steps.env.outputs.repo-ref-pr-number }}" + echo "repo-ref-sha=${{ steps.env.outputs.repo-ref-sha }}" + echo "repo-ref-sha-short=${{ steps.env.outputs.repo-ref-sha-short }}" + echo "repo-ref-title=${{ steps.env.outputs.repo-ref-title }}" echo "build_image_ubuntu=${{ steps.env.outputs.build_image_ubuntu }}" echo "build_image_ubuntu_mobile=${{ steps.env.outputs.build_image_ubuntu_mobile }}" echo - if [[ -n "${{ steps.env.outputs.repo_ref_pr_number }}" ]]; then - echo "PR: https://github.com/envoyproxy/envoy/pull/${{ steps.env.outputs.repo_ref_pr_number }}" + if [[ -n "${{ steps.env.outputs.repo-ref-pr-number }}" ]]; then + echo "PR: https://github.com/${{ github.repository }}/pull/${{ steps.env.outputs.repo-ref-pr-number }}" fi cache: - if: ${{ inputs.prime_build_image }} + if: ${{ inputs.prime-build-image }} uses: ./.github/workflows/_cache_docker.yml with: image_repo: ${{ inputs.build_image_repo }} diff --git a/.github/workflows/_precheck_deps.yml b/.github/workflows/_precheck_deps.yml new file mode 100644 index 000000000000..c0578a4e1af5 --- /dev/null +++ b/.github/workflows/_precheck_deps.yml @@ -0,0 +1,55 @@ +name: Publish + +permissions: + contents: read + +on: + workflow_call: + inputs: + build-image-ubuntu: + type: string + default: '' + dependency-review: + type: boolean + default: false + repo-ref: + type: string + +concurrency: + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }}-publish + cancel-in-progress: true + +jobs: + prechecks: + strategy: + matrix: + include: + - target: deps + rbe: false + uses: ./.github/workflows/_ci.yml + name: ${{ matrix.target }} + permissions: + contents: read + packages: read + with: + target: ${{ matrix.target }} + rbe: ${{ matrix.rbe }} + bazel-extra: '--config=rbe-envoy-engflow' + cache-build-image: ${{ inputs.build-image-ubuntu }} + repo-ref: ${{ inputs.repo-ref }} + catch-errors: true + error-match: | + ERROR + ClientConnectorError + + dependency-review: + runs-on: ubuntu-22.04 + if: ${{ inputs.dependency-review }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + ref: ${{ inputs.repo-ref }} + persist-credentials: false + - name: Dependency Review + uses: actions/dependency-review-action@6c5ccdad469c9f8a2996bfecaec55a631a347034 diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 9dad9782b434..8ccf884cbea8 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -18,13 +18,13 @@ on: build_image_ubuntu: type: string default: '' - version_dev: + version-dev: type: string default: '' head_ref: type: string default: '' - repo_ref: + repo-ref: type: string sha: type: string @@ -42,35 +42,51 @@ jobs: publish_ci: if: ${{ ! inputs.trusted }} name: ${{ matrix.name || matrix.target }} + uses: ./.github/workflows/_ci.yml + with: + target: ${{ matrix.target }} + rbe: false + cache-build-image: ${{ inputs.build_image_ubuntu }} + source: ${{ matrix.source }} + trusted: false + repo-ref: ${{ inputs.repo-ref }} + steps-pre: ${{ inputs.steps-pre }} strategy: fail-fast: false matrix: include: - target: publish name: github - run_pre: ./.github/actions/publish/release/setup - run_pre_with: | - ref: ${{ inputs.repo_ref }} - bucket: envoy-pr - env: | + steps-pre: | + - id: short_name + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.2 + with: + length: 7 + input: ${{ inputs.repo-ref }} + - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.1.2 + with: + url: https://storage.googleapis.com/envoy-pr/%{{ steps.short_name.outputs.string }}/release/release.signed.tar.zst + path: %{{ runner.temp }}/release.signed + source: | export ENVOY_PUBLISH_DRY_RUN=1 export ENVOY_COMMIT=${{ inputs.sha }} export ENVOY_REPO=${{ github.repository }} - uses: ./.github/workflows/_ci.yml - with: - target: ${{ matrix.target }} - rbe: false - managed: true - cache_build_image: ${{ inputs.build_image_ubuntu }} - run_pre: ${{ matrix.run_pre }} - run_pre_with: ${{ matrix.run_pre_with }} - env: ${{ matrix.env }} - trusted: false - repo_ref: ${{ inputs.repo_ref }} publish: if: ${{ inputs.trusted }} name: ${{ matrix.name || matrix.target }} + uses: ./.github/workflows/_ci.yml + with: + target: ${{ matrix.target }} + rbe: false + cache-build-image: ${{ inputs.build_image_ubuntu }} + source: ${{ matrix.source }} + trusted: true + repo-ref: ${{ inputs.repo-ref }} + steps-pre: ${{ inputs.steps-pre }} + secrets: + app-id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} + app-key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} permissions: contents: read packages: read @@ -80,29 +96,21 @@ jobs: include: - target: publish name: github - run_pre: ./.github/actions/publish/release/setup - run_pre_with: | - ref: ${{ inputs.repo_ref }} - bucket: envoy-postsubmit - env: | + steps-pre: | + - id: short_name + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.2 + with: + length: 7 + input: ${{ inputs.repo-ref }} + - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.1.2 + with: + url: https://storage.googleapis.com/envoy-postsubmit/%{{ steps.short_name.outputs.string }}/release/release.signed.tar.zst + path: %{{ runner.temp }}/release.signed + source: | export ENVOY_COMMIT=${{ inputs.sha }} - if [[ '${{ inputs.version_dev }}' == 'dev' ]]; then + if [[ '${{ inputs.version-dev }}' == 'dev' ]]; then export ENVOY_PUBLISH_DRY_RUN=1 fi - uses: ./.github/workflows/_ci.yml - with: - target: ${{ matrix.target }} - rbe: false - managed: true - cache_build_image: ${{ inputs.build_image_ubuntu }} - run_pre: ${{ matrix.run_pre }} - run_pre_with: ${{ matrix.run_pre_with }} - env: ${{ matrix.env }} - trusted: true - repo_ref: ${{ inputs.repo_ref }} - secrets: - app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} - app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} publish_docs: # For normal commits to Envoy main this will trigger an update in the website repo, @@ -122,7 +130,7 @@ jobs: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" ref: main - repository: ${{ inputs.version_dev == 'dev' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} + repository: ${{ inputs.version-dev == 'dev' && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} workflow: envoy-sync.yaml inputs: | - commit_sha: ${{ inputs.version_dev == 'dev' && github.sha || '' }} + commit_sha: ${{ inputs.version-dev == 'dev' && github.sha || '' }} diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index cbcbb771d756..e28538c9f600 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -9,7 +9,7 @@ on: trusted: type: boolean default: false - repo_ref: + repo-ref: type: string given_ref: type: string @@ -21,32 +21,58 @@ concurrency: jobs: verify: name: ${{ matrix.name || matrix.target }} - strategy: - fail-fast: false - matrix: - include: - - target: verify_examples - name: examples - rbe: false - managed: true - cache_build_image: "" - command_prefix: "" - run_pre: ./.github/actions/verify/examples/setup - run_pre_with: | - bucket: envoy-${{ inputs.trusted && 'postsubmit' || 'pr' }} - ref: ${{ inputs.given_ref }} - env: | - export NO_BUILD_SETUP=1 uses: ./.github/workflows/_ci.yml with: target: ${{ matrix.target }} rbe: ${{ matrix.rbe }} - managed: ${{ matrix.managed }} - cache_build_image: ${{ matrix.cache_build_image }} - command_prefix: ${{ matrix.command_prefix }} - run_pre: ${{ matrix.run_pre }} - run_pre_with: ${{ matrix.run_pre_with }} - env: ${{ matrix.env }} + cache-build-image: + command-prefix: + source: ${{ matrix.source }} trusted: ${{ inputs.trusted }} - repo_ref: ${{ inputs.repo_ref }} + repo-ref: ${{ inputs.repo-ref }} runs-on: envoy-x64-small + steps-pre: ${{ inputs.steps-pre }} + strategy: + fail-fast: false + matrix: + include: + - name: examples + target: verify_examples + source: | + export NO_BUILD_SETUP=1 + rbe: false + steps-pre: | + - id: short_name + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.1 + with: + length: 7 + input: ${{ inputs.repo-ref }} + - id: gcp + run: | + PREFIX=https://storage.googleapis.com/envoy- + BUCKET=${{ inputs.trusted && 'postsubmit' || 'pr' }} + NAME=%{{ steps.short_name.outputs.string }} + echo "url=${PREFIX}${BUCKET}/${NAME}" >> $GITHUB_OUTPUT + shell: bash + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.1.1 + with: + url: | + %{{ steps.gcp.outputs.url }}/envoy.tar + variant: dev + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.1.1 + with: + url: | + %{{ steps.gcp.outputs.url }}/envoy-contrib.tar + variant: contrib-dev + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.1.1 + with: + url: | + %{{ steps.gcp.outputs.url }}/envoy-google-vrp.tar + variant: google-vrp-dev + - run: docker images | grep envoy + shell: bash + - run: | + export DEBIAN_FRONTEND=noninteractive + sudo apt-get -qq update -y + sudo apt-get -qq install -y --no-install-recommends expect + shell: bash diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index bbd10e873fdf..66693ad79b4d 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -26,7 +26,7 @@ jobs: - uses: ./.github/actions/env id: env with: - check_mobile_run: false + check-mobile-run: false - if: ${{ steps.env.outputs.trusted != 'true' }} name: Start status check diff --git a/.github/workflows/check-deps.yml b/.github/workflows/check-deps.yml deleted file mode 100644 index 39c0d41c75b1..000000000000 --- a/.github/workflows/check-deps.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Check dependencies - -permissions: - contents: read - -on: - schedule: - - cron: '0 8 * * *' - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-22.04 - if: >- - ${{ - github.repository == 'envoyproxy/envoy' - && (github.event.schedule - || !contains(github.actor, '[bot]')) - }} - permissions: - contents: read # to fetch code (actions/checkout) - issues: write # required to open/close dependency issues - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - - - name: Run dependency checker - run: | - TODAY_DATE=$(date -u -I"date") - export TODAY_DATE - bazel run //tools/dependency:check --action_env=TODAY_DATE -- -c release_issues --fix - bazel run --//tools/dependency:preload_cve_data //tools/dependency:check --action_env=TODAY_DATE -- -c cves -w error - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/depsreview.yml b/.github/workflows/depsreview.yml deleted file mode 100644 index 3890070d58d5..000000000000 --- a/.github/workflows/depsreview.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: 'Dependency Review' -on: [pull_request] - -concurrency: - group: ${{ github.head_ref-github.workflow || github.run_id }} - cancel-in-progress: true - -jobs: - dependency-review: - runs-on: ubuntu-22.04 - if: github.repository == 'envoyproxy/envoy' - steps: - - name: 'Checkout Repository' - uses: actions/checkout@v4 - - name: 'Dependency Review' - uses: actions/dependency-review-action@6c5ccdad469c9f8a2996bfecaec55a631a347034 diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index ff4420b95f72..5b3c06294cd0 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -4,6 +4,8 @@ permissions: contents: read on: + schedule: + - cron: '0 8 * * *' workflow_dispatch: inputs: task: @@ -15,6 +17,7 @@ on: - bazel - bazel-api - build-image + - check dependency: description: Dependency to update (if applicable) version: @@ -22,11 +25,11 @@ on: pr: type: boolean default: true - pr_message: + pr-message: description: Additional message for PR, eg to fix an issue (optional) concurrency: - group: ${{ github.run_id }}-${{ github.workflow }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} cancel-in-progress: true env: @@ -34,8 +37,12 @@ env: COMMITTER_EMAIL: 148525496+dependency-envoy[bot]@users.noreply.github.com jobs: - update_bazel: - if: startsWith(inputs.task, 'bazel') + update-bazel: + if: >- + ${{ + github.event_name == 'workflow_dispatch' + && startsWith(inputs.task, 'bazel') + }} name: >- Update dep (${{ inputs.pr && 'PR/' || '' }}${{ inputs.task == 'bazel' && 'bazel' || 'bazel/api' }}/${{ inputs.dependency }}/${{ inputs.version }}) @@ -76,7 +83,7 @@ jobs: body: | Created by Envoy dependency bot for @${{ github.actor }} - ${{ inputs.pr_message }} + ${{ inputs.pr-message }} branch: >- dependency/${{ inputs.task }}/${{ inputs.dependency }}/${{ steps.version.outputs.string }} commit-message: | @@ -90,8 +97,12 @@ jobs: -> ${{ steps.version.outputs.string }} GITHUB_TOKEN: ${{ steps.checkout.outputs.token }} - update_build_image: - if: github.event.inputs.task == 'build-image' + update-build-image: + if: >- + ${{ + github.event_name == 'workflow_dispatch' + && github.event.inputs.task == 'build-image' + }} name: Update build image (PR) runs-on: ubuntu-22.04 steps: @@ -177,3 +188,27 @@ jobs: title: 'deps: Bump build images -> `${{ steps.build-tools.outputs.tag_short }}`' GITHUB_TOKEN: ${{ steps.checkout.outputs.token }} working-directory: envoy + + scheduled: + runs-on: ubuntu-22.04 + if: >- + ${{ + github.repository == 'envoyproxy/envoy' + && (github.event.schedule + || (!contains(github.actor, '[bot]') + && inputs.task == 'check')) + }} + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Run dependency checker + run: | + TODAY_DATE=$(date -u -I"date") + export TODAY_DATE + bazel run //tools/dependency:check --action_env=TODAY_DATE -- -c release_issues --fix + bazel run //tools/dependency:check --action_env=TODAY_DATE -- -c cves -w error + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index beb40189b164..2520b5414a20 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -13,10 +13,10 @@ on: - '**/requirements*.txt' - '**/go.mod' - '**/*.bzl' - - 'WORKSPACE' - - '.github/workflows/envoy-prechecks.yml' - - '.github/workflows/_*.yml' - - 'tools/dependency/BUILD' + - tools/dependency/BUILD + - WORKSPACE + - .github/workflows/envoy-prechecks.yml + - .github/workflows/_*.yml concurrency: group: ${{ github.event.inputs.head_ref || github.run_id }}-${{ github.workflow }} @@ -26,36 +26,22 @@ jobs: env: uses: ./.github/workflows/_env.yml with: - prime_build_image: true - check_mobile_run: false + prime-build-image: true + check-mobile-run: false permissions: contents: read packages: read pull-requests: read - prechecks: + deps: needs: - env - strategy: - fail-fast: false - matrix: - include: - - target: deps - rbe: false - managed: true - uses: ./.github/workflows/_ci.yml - name: CI ${{ matrix.target }} + uses: ./.github/workflows/_precheck_deps.yml + name: Precheck ${{ needs.env.outputs.repo-ref-title }} + with: + build-image-ubuntu: ${{ needs.env.outputs.build_image_ubuntu }} + dependency-review: ${{ github.event_name == 'pull_request_target' && github.repository == 'envoyproxy/envoy' }} + repo-ref: ${{ needs.env.outputs.repo-ref }} permissions: contents: read packages: read - with: - target: ${{ matrix.target }} - rbe: ${{ matrix.rbe }} - bazel_extra: '--config=rbe-envoy-engflow' - managed: ${{ matrix.managed }} - cache_build_image: ${{ needs.env.outputs.build_image_ubuntu }} - repo_ref: ${{ needs.env.outputs.repo_ref }} - catch-errors: true - error-match: | - ERROR - ClientConnectorError diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index c00a0ae271a3..d5c4178a40eb 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -34,11 +34,11 @@ jobs: }} uses: ./.github/workflows/_env.yml with: - check_mobile_run: false - prime_build_image: true - repo_ref: ${{ inputs.ref }} - repo_ref_sha: ${{ inputs.sha }} - repo_ref_name: ${{ inputs.head_ref }} + check-mobile-run: false + prime-build-image: true + repo-ref: ${{ inputs.ref }} + repo-ref-sha: ${{ inputs.sha }} + repo-ref-name: ${{ inputs.head_ref }} permissions: contents: read packages: read @@ -59,12 +59,12 @@ jobs: - env - check uses: ./.github/workflows/_stage_publish.yml - name: Publish ${{ needs.env.outputs.repo_ref_title }} + name: Publish ${{ needs.env.outputs.repo-ref-title }} with: build_image_ubuntu: ${{ needs.env.outputs.build_image_ubuntu }} trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} - version_dev: ${{ needs.env.outputs.version_dev }} - repo_ref: ${{ inputs.ref }} + version-dev: ${{ needs.env.outputs.version-dev }} + repo-ref: ${{ inputs.ref }} sha: ${{ inputs.sha }} permissions: contents: read @@ -77,7 +77,7 @@ jobs: verify: uses: ./.github/workflows/_stage_verify.yml - name: Verify ${{ needs.env.outputs.repo_ref_title }} + name: Verify ${{ needs.env.outputs.repo-ref-title }} needs: - env permissions: @@ -85,4 +85,4 @@ jobs: with: trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} given_ref: ${{ inputs.ref }} - repo_ref: ${{ inputs.ref }} + repo-ref: ${{ inputs.ref }} diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 10fc0ac35e77..e0ba458a3b59 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -18,13 +18,13 @@ on: default: create-release type: choice options: - - create-release - - sync-version-histories + - create-release + - sync-version-histories pr: type: boolean default: true description: Create a PR - pr_message: + pr-message: description: Additional message for PR, eg to fix an issue or additional signoff (optional) wip: type: boolean @@ -61,6 +61,7 @@ jobs: app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} + strip-prefix: release/ - run: | if [[ ! -s "changelogs/summary.md" ]]; then if [[ "${{ inputs.summary }}" == "false" ]]; then @@ -74,14 +75,6 @@ jobs: echo "committer=${COMMITTER}" >> $GITHUB_OUTPUT id: changelog name: Check changelog summary - - run: | - BRANCHNAME="${GITHUB_REF_NAME#release/}" - echo "name=${BRANCHNAME}" >> $GITHUB_OUTPUT - echo "full_name=release/create/${BRANCHNAME}" >> $GITHUB_OUTPUT - name: Get branch name - id: branch - env: - GITHUB_REF_NAME: ${{ github.ref_name }} - if: ${{ inputs.author }} name: Validate signoff email uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.3 @@ -120,8 +113,8 @@ jobs: body: | Created by Envoy publish bot for @${{ github.actor }} ${{ ! inputs.summary && ':warning: Created without changelog summary, this will need to be updated before publishing' || '' }} - branch: ${{ steps.branch.outputs.full_name }} - diff-upload: release-${{ steps.branch.outputs.name }} + branch: release/create/${{ steps.checkout.outputs.branch_name }} + diff-upload: release-${{ steps.checkout.outputs.branch_name }} diff-show: true dry-run: ${{ ! inputs.pr }} wip: ${{ ! inputs.summary || inputs.wip }} @@ -143,14 +136,6 @@ jobs: app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} - - run: | - BRANCHNAME="${GITHUB_REF_NAME#release/}" - echo "name=${BRANCHNAME}" >> $GITHUB_OUTPUT - echo "full_name=release/sync/${BRANCHNAME}" >> $GITHUB_OUTPUT - name: Get branch name - id: branch - env: - GITHUB_REF_NAME: ${{ github.ref_name }} - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 name: Sync version histories with: @@ -168,8 +153,8 @@ jobs: commit: false body: | Created by Envoy publish bot for @${{ github.actor }} - branch: ${{ steps.branch.outputs.full_name }} - diff-upload: version-histories-${{ steps.branch.outputs.name }} + branch: release/sync/${{ steps.checkout.outputs.branch_name }} + diff-upload: version-histories-${{ steps.checkout.outputs.branch_name }} diff-show: true dry-run: ${{ ! inputs.pr }} GITHUB_TOKEN: ${{ steps.checkout.outputs.token }} diff --git a/.github/workflows/envoy-windows.yml b/.github/workflows/envoy-windows.yml index e6314980f61d..02bd5b7fbae0 100644 --- a/.github/workflows/envoy-windows.yml +++ b/.github/workflows/envoy-windows.yml @@ -21,8 +21,8 @@ jobs: contents: read pull-requests: read with: - prime_build_image: false - check_mobile_run: false + prime-build-image: false + check-mobile-run: false windows: needs: @@ -40,13 +40,13 @@ jobs: with: target: ${{ matrix.target }} runs-on: envoy-win19-small - command_ci: + command-ci: + repo-ref: ${{ needs.env.outputs.repo-ref }} + steps-post: temp-dir: 'C:\Users\runner\AppData\Local\Temp\bazel-shared' - repo_ref: ${{ needs.env.outputs.repo_ref }} - run-du: false upload-name: windows.release upload-path: 'C:\Users\runner\AppData\Local\Temp\envoy' - env: | + source: | export ENVOY_SHARED_TMP_DIR="C:\Users\runner\AppData\Local\Temp\bazel-shared" export ENVOY_DOCKER_BUILD_DIR="C:\Users\runner\AppData\Local\Temp" mkdir -p "$ENVOY_SHARED_TMP_DIR" diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index d9a6b5c44dd5..1ae32e3f9e37 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -17,7 +17,7 @@ jobs: env: uses: ./.github/workflows/_env.yml with: - prime_build_image: true + prime-build-image: true permissions: contents: read pull-requests: read From e55580d56c6c7fdb5eec6ee10ba4e1c834e303f1 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 1 Nov 2023 10:18:23 +0000 Subject: [PATCH 55/72] mac/ci: Fix bad merge in refactoring (#30656) Signed-off-by: Ryan Northey --- .github/workflows/envoy-macos.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/envoy-macos.yml b/.github/workflows/envoy-macos.yml index bcafe822ef6f..0c01e4a451b2 100644 --- a/.github/workflows/envoy-macos.yml +++ b/.github/workflows/envoy-macos.yml @@ -21,8 +21,8 @@ jobs: contents: read pull-requests: read with: - prime_build_image: false - check_mobile_run: false + prime-build-image: false + check-mobile-run: false macos: needs: @@ -40,12 +40,15 @@ jobs: with: target: ${{ matrix.target }} runs-on: macos-12-xl - command_ci: - command_prefix: - repo_ref: ${{ needs.env.outputs.repo_ref }} - run-du: false - env: | - ./ci/mac_ci_setup.sh + command-ci: + command-prefix: + repo-ref: ${{ needs.env.outputs.repo_ref }} + steps-post: + steps-pre: | + - run: ./ci/mac_ci_setup.sh + shell: bash + name: Setup macos + source: | GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -t gcp_service_account.XXXXXX.json) bash -c "echo \"${RBE_KEY}\" | base64 --decode > \"${GCP_SERVICE_ACCOUNT_KEY_PATH}\"" _BAZEL_BUILD_EXTRA_OPTIONS=( From 1aaf94e9dadc0f04366822f23ca53d62ed70d06b Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 1 Nov 2023 11:31:04 +0000 Subject: [PATCH 56/72] publish/verify: Fix pre steps for CI (#30657) Signed-off-by: Ryan Northey --- .github/workflows/_stage_publish.yml | 2 +- .github/workflows/_stage_verify.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 8ccf884cbea8..3c027f802db1 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -50,7 +50,7 @@ jobs: source: ${{ matrix.source }} trusted: false repo-ref: ${{ inputs.repo-ref }} - steps-pre: ${{ inputs.steps-pre }} + steps-pre: ${{ matrix.steps-pre }} strategy: fail-fast: false matrix: diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index e28538c9f600..af55024d936b 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -31,7 +31,7 @@ jobs: trusted: ${{ inputs.trusted }} repo-ref: ${{ inputs.repo-ref }} runs-on: envoy-x64-small - steps-pre: ${{ inputs.steps-pre }} + steps-pre: ${{ matrix.steps-pre }} strategy: fail-fast: false matrix: From 19fe9abfcc9f87e8ad86aea1577016697af4c956 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:55:39 +0000 Subject: [PATCH 57/72] build(deps): bump envoyproxy/toolshed from actions-v0.1.3 to 0.1.4 (#30659) Bumps [envoyproxy/toolshed](https://github.com/envoyproxy/toolshed) from actions-v0.1.3 to 0.1.4. This release includes the previously tagged commit. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/actions-v0.1.3...actions-v0.1.4) --- updated-dependencies: - dependency-name: envoyproxy/toolshed dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_cache_docker.yml | 2 +- .github/workflows/_ci.yml | 12 ++++++------ .github/workflows/_env.yml | 2 +- .github/workflows/_stage_publish.yml | 2 +- .github/workflows/_workflow-start.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/commands.yml | 2 +- .github/workflows/envoy-dependency.yml | 14 +++++++------- .github/workflows/envoy-release.yml | 16 ++++++++-------- .github/workflows/envoy-sync.yml | 2 +- .github/workflows/mobile-android_tests.yml | 4 ++-- .github/workflows/workflow-complete.yml | 2 +- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/_cache_docker.yml b/.github/workflows/_cache_docker.yml index db349a600fb1..85ac92dbf940 100644 --- a/.github/workflows/_cache_docker.yml +++ b/.github/workflows/_cache_docker.yml @@ -37,7 +37,7 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.4 name: Prime Docker cache (${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}) with: image_tag: "${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}" diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 66bf551021b8..320c1da733cc 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -95,11 +95,11 @@ jobs: steps: - if: ${{ inputs.cache-build-image }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.4 with: image_tag: ${{ inputs.cache-build-image }} - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 id: checkout name: Checkout Envoy repository with: @@ -129,21 +129,21 @@ jobs: - if: ${{ inputs.diskspace-hack }} name: Free diskspace - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 - run: | echo "disk space at beginning of build:" df -h name: "Check disk space at beginning" shell: bash - - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.4 name: Run pre steps if: ${{ inputs.steps-pre }} with: name: ${{ inputs.steps-pre-name }} steps: ${{ inputs.steps-pre }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.4 name: 'Run CI target ${{ inputs.target }}' with: catch-errors: ${{ inputs.catch-errors }} @@ -166,7 +166,7 @@ jobs: BAZEL_FAKE_SCM_REVISION: ${{ github.event_name == 'pull_request' && 'e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9' || '' }} CI_TARGET_BRANCH: ${{ github.event_name == 'pull_request' && github.event.base.ref || github.ref }} - - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.4 name: Run post steps if: ${{ inputs.steps-post }} with: diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index 2e7be8b57e7a..dd6c0f8c0bd8 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -153,7 +153,7 @@ jobs: build_image_tag: ${{ inputs.build_image_tag }} build_image_mobile_sha: ${{ inputs.build_image_mobile_sha }} build_image_sha: ${{ inputs.build_image_sha }} - - uses: envoyproxy/toolshed/gh-actions/github/merge-commit@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/github/merge-commit@actions-v0.1.4 id: merge-commit if: ${{ github.event_name == 'pull_request_target' }} with: diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 3c027f802db1..3667669bf1c9 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -125,7 +125,7 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.4 with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index 66693ad79b4d..10bbb6252d95 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -30,7 +30,7 @@ jobs: - if: ${{ steps.env.outputs.trusted != 'true' }} name: Start status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.4 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: ${{ inputs.workflow_name }} diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 10b3ecfbe30c..66c81e0ba7ae 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Pre-cleanup - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index c6e5f5ce966b..ff49b16271ab 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: actions: write checks: read steps: - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.4 with: token: ${{ secrets.GITHUB_TOKEN }} azp_org: cncf diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 5b3c06294cd0..2102749c770a 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -50,13 +50,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: version name: Shorten (possible) SHA - uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.4 with: string: ${{ inputs.version }} length: 7 @@ -71,13 +71,13 @@ jobs: TARGET: ${{ inputs.task == 'bazel' && 'update' || 'api-update' }} TASK: ${{ inputs.task == 'bazel' && 'bazel' || 'api/bazel' }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.4 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.version.outputs.string }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 with: base: main body: | @@ -106,7 +106,7 @@ jobs: name: Update build image (PR) runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 id: checkout name: Checkout Envoy repository with: @@ -145,7 +145,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.4 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -174,7 +174,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index e0ba458a3b59..6f917726514d 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,7 +55,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -77,10 +77,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.4 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.4 name: Create release with: source: | @@ -105,7 +105,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 with: base: ${{ github.ref_name }} commit: false @@ -130,13 +130,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.4 name: Sync version histories with: command: >- @@ -146,7 +146,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 with: append-commit-message: true base: ${{ github.ref_name }} @@ -176,7 +176,7 @@ jobs: name: Create release branch steps: - name: Checkout repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 9d4f00f06ad9..0757dd58e907 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -28,7 +28,7 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.3 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.4 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index 1ae32e3f9e37..b3b65cef711d 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -35,7 +35,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy @@ -69,7 +69,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml index 4054f86455aa..afc4a72dca9b 100644 --- a/.github/workflows/workflow-complete.yml +++ b/.github/workflows/workflow-complete.yml @@ -54,7 +54,7 @@ jobs: echo "state=${STATE}" >> "$GITHUB_OUTPUT" id: job - name: Complete status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.3 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.4 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: Verify/examples From cd6d36706f26efc9a5cf6c0abba348ef8f06de5a Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Wed, 1 Nov 2023 09:17:40 -0400 Subject: [PATCH 58/72] test: add configuration for listeners bound timeout (#30640) Signed-off-by: Adi Suissa-Peleg --- test/integration/base_integration_test.cc | 6 +----- test/integration/base_integration_test.h | 14 ++++++++++++++ test/integration/integration_test.cc | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 38cd432d54ce..2712e0cf4004 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -459,11 +459,7 @@ void BaseIntegrationTest::createGeneratedApiTestServer( if (config_helper_.bootstrap().static_resources().listeners_size() > 0 && !defer_listener_finalization_) { - // Wait for listeners to be created before invoking registerTestServerPorts() below, as that - // needs to know about the bound listener ports. - // Using 2x default timeout to cover for slow TLS implementations (no inline asm) on slow - // computers (e.g., Raspberry Pi) that sometimes time out on TLS listeners here. - Event::TestTimeSystem::RealTimeBound bound(2 * TestUtility::DefaultTimeout); + Event::TestTimeSystem::RealTimeBound bound(listeners_bound_timeout_ms_); const char* success = "listener_manager.listener_create_success"; const char* rejected = "listener_manager.lds.update_rejected"; for (Stats::CounterSharedPtr success_counter = test_server->counter(success), diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index c7f1e639005e..7b16822a4c7f 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -474,6 +474,13 @@ class BaseIntegrationTest : protected Logger::Loggable { void checkForMissingTagExtractionRules(); + // Sets the timeout to wait for listeners to be created before invoking + // registerTestServerPorts(), as that needs to know about the bound listener ports. + // Needs to be called before invoking createEnvoy() (invoked during initialize()). + void setListenersBoundTimeout(const std::chrono::milliseconds& duration) { + listeners_bound_timeout_ms_ = duration; + } + std::unique_ptr upstream_stats_store_; // Make sure the test server will be torn down after any fake client. @@ -527,6 +534,13 @@ class BaseIntegrationTest : protected Logger::Loggable { spdlog::level::level_enum default_log_level_; + // Timeout to wait for listeners to be created before invoking + // registerTestServerPorts(), as that needs to know about the bound listener ports. + // Using 2x default timeout to cover for slow TLS implementations (no inline asm) on slow + // computers (e.g., Raspberry Pi) that sometimes time out on TLS listeners, or when + // the number of listeners in a test is large. + std::chrono::milliseconds listeners_bound_timeout_ms_{2 * TestUtility::DefaultTimeout}; + // Target number of upstreams. uint32_t fake_upstreams_count_{1}; diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index f424bdc833b0..702c6bf6d9ed 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -1593,6 +1593,7 @@ TEST_P(IntegrationTest, AbsolutePathWithoutPort) { // Ensure that connect behaves the same with allow_absolute_url enabled and without TEST_P(IntegrationTest, Connect) { + setListenersBoundTimeout(3 * TestUtility::DefaultTimeout); const std::string& request = "CONNECT www.somewhere.com:80 HTTP/1.1\r\n\r\n"; config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { // Clone the whole listener. From 25349516f192cf1cb17ff19cc1973cd800a08a2f Mon Sep 17 00:00:00 2001 From: Paul Ogilby Date: Wed, 1 Nov 2023 10:26:34 -0400 Subject: [PATCH 59/72] http: defer http2 metadata (#30101) Signed-off-by: Paul Ogilby --- source/common/http/conn_manager_impl.cc | 22 ++++++-- source/common/http/conn_manager_impl.h | 1 + test/common/http/conn_manager_impl_test_2.cc | 41 +++++++++----- test/integration/BUILD | 1 + .../multiplexed_integration_test.cc | 55 +++++++++++++++++++ 5 files changed, 101 insertions(+), 19 deletions(-) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 4b8b59d8238c..3e07fc8efbc7 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1518,10 +1518,14 @@ void ConnectionManagerImpl::ActiveStream::decodeTrailers(RequestTrailerMapPtr&& void ConnectionManagerImpl::ActiveStream::decodeMetadata(MetadataMapPtr&& metadata_map) { resetIdleTimer(); - // After going through filters, the ownership of metadata_map will be passed to terminal filter. - // The terminal filter may encode metadata_map to the next hop immediately or store metadata_map - // and encode later when connection pool is ready. - filter_manager_.decodeMetadata(*metadata_map); + if (!state_.deferred_to_next_io_iteration_) { + // After going through filters, the ownership of metadata_map will be passed to terminal filter. + // The terminal filter may encode metadata_map to the next hop immediately or store metadata_map + // and encode later when connection pool is ready. + filter_manager_.decodeMetadata(*metadata_map); + } else { + deferred_metadata_.push(std::move(metadata_map)); + } } void ConnectionManagerImpl::ActiveStream::disarmRequestTimeout() { @@ -2229,12 +2233,18 @@ bool ConnectionManagerImpl::ActiveStream::onDeferredRequestProcessing() { return false; } state_.deferred_to_next_io_iteration_ = false; - bool end_stream = - state_.deferred_end_stream_ && deferred_data_ == nullptr && request_trailers_ == nullptr; + bool end_stream = state_.deferred_end_stream_ && deferred_data_ == nullptr && + request_trailers_ == nullptr && deferred_metadata_.empty(); filter_manager_.decodeHeaders(*request_headers_, end_stream); if (end_stream) { return true; } + // Send metadata before data, as data may have an associated end_stream. + while (!deferred_metadata_.empty()) { + MetadataMapPtr& metadata = deferred_metadata_.front(); + filter_manager_.decodeMetadata(*metadata); + deferred_metadata_.pop(); + } // Filter manager will return early from decodeData and decodeTrailers if // request has completed. if (deferred_data_ != nullptr) { diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 3b6c9c5a5a41..c7a330115344 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -520,6 +520,7 @@ class ConnectionManagerImpl : Logger::Loggable, std::shared_ptr still_alive_ = std::make_shared(true); std::unique_ptr deferred_data_; + std::queue deferred_metadata_; }; using ActiveStreamPtr = std::unique_ptr; diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 5d456ab6c130..dec00c0bfa54 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -3692,7 +3692,7 @@ TEST_F(HttpConnectionManagerImplTest, NoProxyProtocolAdded) { } // Validate that deferred streams are processed with a variety of -// headers, data and trailer arriving in the same I/O cycle +// headers, data, metadata, and trailers arriving in the same I/O cycle TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { const int kRequestsSentPerIOCycle = 100; EXPECT_CALL(runtime_.snapshot_, getInteger(_, _)).WillRepeatedly(ReturnArg<1>()); @@ -3701,13 +3701,14 @@ TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { setup(false, ""); // Store the basic request encoder during filter chain setup. - std::vector> encoder_filters; + std::vector> decoder_filters; int decode_headers_call_count = 0; for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + int mod5 = i % 5; std::shared_ptr filter(new NiceMock()); - // Each 4th request is headers only - EXPECT_CALL(*filter, decodeHeaders(_, i % 4 == 0 ? true : false)) + // Each 0th request is headers only + EXPECT_CALL(*filter, decodeHeaders(_, mod5 == 0 ? true : false)) .WillRepeatedly(Invoke([&](RequestHeaderMap&, bool) -> FilterHeadersStatus { ++decode_headers_call_count; return FilterHeadersStatus::StopIteration; @@ -3715,18 +3716,24 @@ TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { // Each 1st request is headers and data only // Each 2nd request is headers, data and trailers - if (i % 4 == 1 || i % 4 == 2) { - EXPECT_CALL(*filter, decodeData(_, i % 4 == 1 ? true : false)) + if (mod5 == 1 || mod5 == 2) { + EXPECT_CALL(*filter, decodeData(_, mod5 == 1 ? true : false)) .WillOnce(Return(FilterDataStatus::StopIterationNoBuffer)); } // Each 3rd request is headers and trailers (no data) - if (i % 4 == 2 || i % 4 == 3) { + if (mod5 == 2 || mod5 == 3) { EXPECT_CALL(*filter, decodeTrailers(_)).WillOnce(Return(FilterTrailersStatus::StopIteration)); } + // Each 4th request is headers, metadata, and data. + if (mod5 == 4) { + EXPECT_CALL(*filter, decodeMetadata(_)).WillOnce(Return(FilterMetadataStatus::Continue)); + EXPECT_CALL(*filter, decodeData(_, true)) + .WillOnce(Return(FilterDataStatus::StopIterationNoBuffer)); + } EXPECT_CALL(*filter, setDecoderFilterCallbacks(_)); - encoder_filters.push_back(std::move(filter)); + decoder_filters.push_back(std::move(filter)); } uint64_t random_value = 0; @@ -3736,11 +3743,11 @@ TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { EXPECT_CALL(filter_factory_, createFilterChain(_)) .Times(kRequestsSentPerIOCycle) - .WillRepeatedly(Invoke([&encoder_filters](FilterChainManager& manager) -> bool { + .WillRepeatedly(Invoke([&decoder_filters](FilterChainManager& manager) -> bool { static int index = 0; int i = index++; - FilterFactoryCb factory([&encoder_filters, i](FilterChainFactoryCallbacks& callbacks) { - callbacks.addStreamDecoderFilter(encoder_filters[i]); + FilterFactoryCb factory([&decoder_filters, i](FilterChainFactoryCallbacks& callbacks) { + callbacks.addStreamDecoderFilter(decoder_filters[i]); }); manager.applyFilterFactoryCb({}, factory); return true; @@ -3762,12 +3769,15 @@ TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ {":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; + MetadataMapPtr metadata = std::make_unique(); + (*metadata)["key1"] = "value1"; + RequestTrailerMapPtr trailers{ new TestRequestTrailerMapImpl{{"key1", "value1"}, {"key2", "value2"}}}; Buffer::OwnedImpl data("data"); - switch (i % 4) { + switch (i % 5) { case 0: decoder_->decodeHeaders(std::move(headers), true); break; @@ -3784,6 +3794,11 @@ TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { decoder_->decodeHeaders(std::move(headers), false); decoder_->decodeTrailers(std::move(trailers)); break; + case 4: + decoder_->decodeHeaders(std::move(headers), false); + decoder_->decodeMetadata(std::move(metadata)); + decoder_->decodeData(data, true); + break; } } @@ -3809,7 +3824,7 @@ TEST_F(HttpConnectionManagerImplTest, LimitWorkPerIOCycle) { ASSERT_EQ(deferred_request_count, kRequestsSentPerIOCycle); - for (auto& filter : encoder_filters) { + for (auto& filter : decoder_filters) { ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}}; filter->callbacks_->streamInfo().setResponseCodeDetails(""); filter->callbacks_->encodeHeaders(std::move(response_headers), true, "details"); diff --git a/test/integration/BUILD b/test/integration/BUILD index 2fba6896fbc0..f8655a5d2d6e 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -536,6 +536,7 @@ envoy_cc_test( "//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_control_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/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 580eb8b40716..215ab4dca48e 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2277,6 +2277,61 @@ TEST_P(Http2FrameIntegrationTest, MultipleRequests) { tcp_client_->close(); } +TEST_P(Http2FrameIntegrationTest, MultipleRequestsWithMetadata) { + // Allow metadata usage. + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); + ConfigHelper::HttpProtocolOptions protocol_options; + protocol_options.mutable_explicit_http_config() + ->mutable_http2_protocol_options() + ->set_allow_metadata(true); + ConfigHelper::setProtocolOptions(*bootstrap.mutable_static_resources()->mutable_clusters(0), + protocol_options); + }); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_http2_protocol_options()->set_allow_metadata(true); }); + + config_helper_.prependFilter(R"EOF( + name: metadata-control-filter + )EOF"); + + const int kRequestsSentPerIOCycle = 20; + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride("http.max_requests_per_io_cycle", "1"); + beginSession(); + + std::string buffer; + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto request = + Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(i), "a", "/", + {{"response_data_blocks", "0"}, {"no_trailers", "1"}}); + absl::StrAppend(&buffer, std::string(request)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + Http::MetadataMap metadata_map{{"should_continue", absl::StrCat(i)}}; + auto metadata = Http2Frame::makeMetadataFrameFromMetadataMap( + Http2Frame::makeClientStreamId(i), metadata_map, Http2Frame::MetadataFlags::EndMetadata); + absl::StrAppend(&buffer, std::string(metadata)); + } + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto data = Http2Frame::makeDataFrame(Http2Frame::makeClientStreamId(i), "", + Http2Frame::DataFlags::EndStream); + absl::StrAppend(&buffer, std::string(data)); + } + + ASSERT_TRUE(tcp_client_->write(buffer, false, false)); + + for (int i = 0; i < kRequestsSentPerIOCycle; ++i) { + auto frame = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus()); + } + tcp_client_->close(); +} + // Validate the request completion during processing of deferred list works. TEST_P(Http2FrameIntegrationTest, MultipleRequestsDecodeHeadersEndsRequest) { const int kRequestsSentPerIOCycle = 20; From 87c8318cde2c73808a0de06a9e33d5e3c5e2b858 Mon Sep 17 00:00:00 2001 From: danzh Date: Wed, 1 Nov 2023 10:26:48 -0400 Subject: [PATCH 60/72] quic: fix a potential use-after-free crash during async handshake (#30574) Commit Message: Currently the filter manager object gets freed in onConnectionCloseEvent(). If a filter directly closes the connection either via Network::Connection::close() or indirectly via any other callbacks which could potentially close the connection, i.e. Http::Connection::shutdownNotice(), it would destroy itself in the current call stack. This could lead to potential UAF crash if it continues access its own members. The fix is to make sure the filter manager has the same life time of QUIC session (Network::Connection implementation for QUIC) whose destruction is postponed to the next event loop. Additional Description: this type of UAF is NOT exploitable and is firstly observed in ConnectionManagerImpl::startDrainSequence() where, when the connection is still waiting for cert chains, codec_->shutdownNotice() would close the connection and delete the filter manager object and the HCM itself and the following accessing to drain_timer_ causes crash. startDrainSequence() is rarely called during certificates retrieval. This is because during certs retrieval no downstream requests would have been processed, and the timers in HCM is usually configured with much longer timeout than the certs retrieval timeout (unless the Envoy gets overloaded and the scaled timeout kicks in). The crash can be avoided if the min timeout of HTTP_DOWNSTREAM_CONNECTION_IDLE is configured larger than the overall QUIC crypto handshake timeout. Risk Level: medium, change quic object life time Testing: new and existing tests passed Docs Changes: N/A Release Notes: added to changelog Platform Specific Features: N/A Runtime guard: envoy.reloadable_features.quic_fix_filter_manager_uaf Signed-off-by: Dan Zhang --- changelogs/current.yaml | 4 ++ source/common/quic/envoy_quic_dispatcher.cc | 13 +++++ .../quic_filter_manager_connection_impl.cc | 6 +- .../quic_filter_manager_connection_impl.h | 11 ++-- source/common/runtime/runtime_features.cc | 1 + test/extensions/quic/proof_source/BUILD | 21 +++++++ .../pending_proof_source_factory_impl.cc | 39 +++++++++++++ .../pending_proof_source_factory_impl.h | 30 ++++++++++ ...ected_resource_monitor_integration_test.cc | 8 --- test/integration/BUILD | 4 +- test/integration/http_integration.cc | 6 +- test/integration/http_integration.h | 7 ++- test/integration/overload_integration_test.cc | 56 +++++++++++++++++++ .../integration/quic_http_integration_test.cc | 9 +-- 14 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 test/extensions/quic/proof_source/BUILD create mode 100644 test/extensions/quic/proof_source/pending_proof_source_factory_impl.cc create mode 100644 test/extensions/quic/proof_source/pending_proof_source_factory_impl.h diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e084d62e12fb..0b286d3d7663 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -41,6 +41,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: quic + change: | + Fixed a bug in QUIC and HCM interaction which could cause use-after-free during asynchronous certificates retrieval. + The fix is guarded by runtime ``envoy.reloadable_features.quic_fix_filter_manager_uaf``. - area: redis change: | Fixed a bug causing crash if incoming redis key does not match against a prefix_route and catch_all_route is not defined. diff --git a/source/common/quic/envoy_quic_dispatcher.cc b/source/common/quic/envoy_quic_dispatcher.cc index 665b51ac4797..dd4f14183b0a 100644 --- a/source/common/quic/envoy_quic_dispatcher.cc +++ b/source/common/quic/envoy_quic_dispatcher.cc @@ -177,13 +177,26 @@ void EnvoyQuicDispatcher::closeConnectionsWithFilterChain( // Retain the number of connections in the list early because closing the connection will change // the size. const size_t num_connections = connections.size(); + bool delete_sessions_immediately = false; for (size_t i = 0; i < num_connections; ++i) { Network::Connection& connection = connections.front().get(); // This will remove the connection from the list. And the last removal will remove connections // from the map as well. connection.close(Network::ConnectionCloseType::NoFlush); + if (!delete_sessions_immediately && + dynamic_cast(connection).fix_quic_lifetime_issues()) { + // If `envoy.reloadable_features.quic_fix_filter_manager_uaf` is true, the closed sessions + // need to be deleted right away to consistently handle quic lifetimes. Because upon + // returning the filter chain configs will be destroyed, and no longer safe to be accessed. + // If any filters access those configs during destruction, it'll be use-after-free + delete_sessions_immediately = true; + } } ASSERT(connections_by_filter_chain_.find(filter_chain) == connections_by_filter_chain_.end()); + if (delete_sessions_immediately) { + // Explicitly destroy closed sessions in the current call stack. + DeleteSessions(); + } } } diff --git a/source/common/quic/quic_filter_manager_connection_impl.cc b/source/common/quic/quic_filter_manager_connection_impl.cc index e9a94780a349..2f2068f3a58a 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.cc +++ b/source/common/quic/quic_filter_manager_connection_impl.cc @@ -27,6 +27,8 @@ QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl( stream_info_->protocol(Http::Protocol::Http3); network_connection_->connectionSocket()->connectionInfoProvider().setSslConnection( Ssl::ConnectionInfoConstSharedPtr(quic_ssl_info_)); + fix_quic_lifetime_issues_ = + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.quic_fix_filter_manager_uaf"); } void QuicFilterManagerConnectionImpl::addWriteFilter(Network::WriteFilterSharedPtr filter) { @@ -179,7 +181,9 @@ void QuicFilterManagerConnectionImpl::onConnectionCloseEvent( network_connection_ = nullptr; } - filter_manager_ = nullptr; + if (!fix_quic_lifetime_issues_) { + filter_manager_ = nullptr; + } if (!codec_stats_.has_value()) { // The connection was closed before it could be used. Stats are not recorded. return; diff --git a/source/common/quic/quic_filter_manager_connection_impl.h b/source/common/quic/quic_filter_manager_connection_impl.h index f64278028f83..4a1a57a093d3 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.h +++ b/source/common/quic/quic_filter_manager_connection_impl.h @@ -171,6 +171,8 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, max_headers_count_ = max_headers_count; } + bool fix_quic_lifetime_issues() const { return fix_quic_lifetime_issues_; } + protected: // Propagate connection close to network_connection_callbacks_. void onConnectionCloseEvent(const quic::QuicConnectionCloseFrame& frame, @@ -207,10 +209,10 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, // Called when aggregated buffered bytes across all the streams declines to low watermark. void onSendBufferLowWatermark(); - // Currently ConnectionManagerImpl is the one and only filter. If more network - // filters are added, ConnectionManagerImpl should always be the last one. - // Its onRead() is only called once to trigger ReadFilter::onNewConnection() - // and the rest incoming data bypasses these filters. + // ConnectionManagerImpl should always be the last filter. Its onRead() is only called once to + // trigger ReadFilter::onNewConnection() and the rest incoming data bypasses these filters. + // It has the same life time as this connection, so do all the filters. If the connection gets + // defer-deleted, they will be defer-deleted together. std::unique_ptr filter_manager_; std::unique_ptr stream_info_; @@ -224,6 +226,7 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, EnvoyQuicSimulatedWatermarkBuffer write_buffer_watermark_simulation_; Buffer::OwnedImpl empty_buffer_; absl::optional close_type_during_initialize_; + bool fix_quic_lifetime_issues_{false}; }; } // namespace Quic diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index dfa4f146a23c..c572b3d2820a 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -71,6 +71,7 @@ RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding); 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_proxy_status_upstream_request_timeout); +RUNTIME_GUARD(envoy_reloadable_features_quic_fix_filter_manager_uaf); RUNTIME_GUARD(envoy_reloadable_features_send_header_raw_value); RUNTIME_GUARD(envoy_reloadable_features_skip_dns_lookup_for_proxied_requests); RUNTIME_GUARD(envoy_reloadable_features_ssl_transport_failure_reason_format); diff --git a/test/extensions/quic/proof_source/BUILD b/test/extensions/quic/proof_source/BUILD new file mode 100644 index 000000000000..43dcae18636e --- /dev/null +++ b/test/extensions/quic/proof_source/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test_library( + name = "pending_proof_source_factory_impl_lib", + srcs = ["pending_proof_source_factory_impl.cc"], + hdrs = ["pending_proof_source_factory_impl.h"], + tags = ["nofips"], + deps = [ + "//envoy/registry", + "//source/common/quic:envoy_quic_proof_source_factory_interface", + "//source/common/quic:envoy_quic_proof_source_lib", + ], +) diff --git a/test/extensions/quic/proof_source/pending_proof_source_factory_impl.cc b/test/extensions/quic/proof_source/pending_proof_source_factory_impl.cc new file mode 100644 index 000000000000..9dd472518eb3 --- /dev/null +++ b/test/extensions/quic/proof_source/pending_proof_source_factory_impl.cc @@ -0,0 +1,39 @@ +#include "test/extensions/quic/proof_source/pending_proof_source_factory_impl.h" + +#include "source/common/quic/envoy_quic_proof_source.h" + +namespace Envoy { +namespace Quic { + +class PendingProofSource : public EnvoyQuicProofSource { +public: + PendingProofSource(Network::Socket& listen_socket, + Network::FilterChainManager& filter_chain_manager, + Server::ListenerStats& listener_stats, TimeSource& time_source) + : EnvoyQuicProofSource(listen_socket, filter_chain_manager, listener_stats, time_source) {} + +protected: + void signPayload(const quic::QuicSocketAddress& /*server_address*/, + const quic::QuicSocketAddress& /*client_address*/, + const std::string& /*hostname*/, uint16_t /*signature_algorithm*/, + absl::string_view /*in*/, + std::unique_ptr callback) override { + // Make the callback pending. + pending_callbacks_.push_back(std::move(callback)); + } + +private: + std::vector> pending_callbacks_; +}; + +std::unique_ptr PendingProofSourceFactoryImpl::createQuicProofSource( + Network::Socket& listen_socket, Network::FilterChainManager& filter_chain_manager, + Server::ListenerStats& listener_stats, TimeSource& time_source) { + return std::make_unique(listen_socket, filter_chain_manager, listener_stats, + time_source); +} + +REGISTER_FACTORY(PendingProofSourceFactoryImpl, EnvoyQuicProofSourceFactoryInterface); + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic/proof_source/pending_proof_source_factory_impl.h b/test/extensions/quic/proof_source/pending_proof_source_factory_impl.h new file mode 100644 index 000000000000..35b77043d385 --- /dev/null +++ b/test/extensions/quic/proof_source/pending_proof_source_factory_impl.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/registry/registry.h" + +#include "source/common/protobuf/protobuf.h" +#include "source/common/quic/envoy_quic_proof_source_factory_interface.h" + +namespace Envoy { +namespace Quic { + +// Provides a ProofSource implementation which makes signing pending. +class PendingProofSourceFactoryImpl : public EnvoyQuicProofSourceFactoryInterface { +public: + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + // Using Struct instead of a custom config proto. This is only allowed in tests. + return ProtobufTypes::MessagePtr{new Envoy::ProtobufWkt::Struct()}; + } + + std::string name() const override { return "envoy.quic.proof_source.pending_signing"; } + + std::unique_ptr + createQuicProofSource(Network::Socket& listen_socket, + Network::FilterChainManager& filter_chain_manager, + Server::ListenerStats& listener_stats, TimeSource& time_source) override; +}; + +DECLARE_FACTORY(PendingProofSourceFactoryImpl); + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc index 88cbd4b8a5fd..d9be99300106 100644 --- a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc +++ b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc @@ -42,14 +42,6 @@ class OverloadIntegrationTest : public testing::TestWithParam http2_options) override { - IntegrationCodecClientPtr codec = - HttpIntegrationTest::makeRawHttpConnection(std::move(conn), http2_options); - return codec; - } - void initialize() override { config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { const std::string overload_config = diff --git a/test/integration/BUILD b/test/integration/BUILD index f8655a5d2d6e..3b104213539a 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1554,7 +1554,9 @@ envoy_cc_test( "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/overload/v3:pkg_cc_proto", - ], + ] + envoy_select_enable_http3([ + "//test/extensions/quic/proof_source:pending_proof_source_factory_impl_lib", + ]), ) envoy_cc_test( diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 4ae499b7a06d..190f4bd8104c 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -266,7 +266,8 @@ IntegrationCodecClientPtr HttpIntegrationTest::makeHttpConnection(uint32_t port) IntegrationCodecClientPtr HttpIntegrationTest::makeRawHttpConnection( Network::ClientConnectionPtr&& conn, - absl::optional http2_options) { + absl::optional http2_options, + bool wait_till_connected) { std::shared_ptr cluster{new NiceMock()}; cluster->max_response_headers_count_ = 200; if (!http2_options.has_value()) { @@ -295,7 +296,8 @@ IntegrationCodecClientPtr HttpIntegrationTest::makeRawHttpConnection( // This call may fail in QUICHE because of INVALID_VERSION. QUIC connection doesn't support // in-connection version negotiation. auto codec = std::make_unique(*dispatcher_, random_, std::move(conn), - host_description, downstream_protocol_); + host_description, downstream_protocol_, + wait_till_connected); if (downstream_protocol_ == Http::CodecType::HTTP3 && codec->disconnected()) { // Connection may get closed during version negotiation or handshake. // TODO(#8479) QUIC connection doesn't support in-connection version negotiationPropagate diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index f0d1020e46bd..3cb15cda441f 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -156,9 +156,10 @@ class HttpIntegrationTest : public BaseIntegrationTest { IntegrationCodecClientPtr makeHttpConnection(uint32_t port); // Makes a http connection object without checking its connected state. - virtual IntegrationCodecClientPtr makeRawHttpConnection( - Network::ClientConnectionPtr&& conn, - absl::optional http2_options); + virtual IntegrationCodecClientPtr + makeRawHttpConnection(Network::ClientConnectionPtr&& conn, + absl::optional http2_options, + bool wait_till_connected = true); // Makes a downstream network connection object based on client codec version. Network::ClientConnectionPtr makeClientConnectionWithOptions( uint32_t port, const Network::ConnectionSocket::OptionsSharedPtr& options) override; diff --git a/test/integration/overload_integration_test.cc b/test/integration/overload_integration_test.cc index f621b89f658a..1232968546dc 100644 --- a/test/integration/overload_integration_test.cc +++ b/test/integration/overload_integration_test.cc @@ -260,6 +260,62 @@ TEST_P(OverloadScaledTimerIntegrationTest, CloseIdleHttpConnections) { codec_client_->close(); } +TEST_P(OverloadScaledTimerIntegrationTest, HTTP3CloseIdleHttpConnectionsDuringHandshake) { + if (downstreamProtocol() != Http::CodecClient::Type::HTTP3) { + return; + } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.quic_fix_filter_manager_uaf", "true"}}); + + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* proof_source_config = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_quic_options() + ->mutable_proof_source_config(); + proof_source_config->set_name("envoy.quic.proof_source.pending_signing"); + proof_source_config->mutable_typed_config(); + }); + initializeOverloadManager( + TestUtility::parseYaml(R"EOF( + timer_scale_factors: + - timer: HTTP_DOWNSTREAM_CONNECTION_IDLE + min_timeout: 3s + )EOF")); + + // Set the load so the timer is reduced but not to the minimum value. + updateResource(0.8); + test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + // Create an HTTP connection without finishing the handshake. + codec_client_ = makeRawHttpConnection(makeClientConnection((lookupPort("http"))), absl::nullopt, + /*wait_till_connected=*/false); + EXPECT_FALSE(codec_client_->connected()); + + // Advancing past the minimum time shouldn't close the connection. + timeSystem().advanceTimeWait(std::chrono::seconds(3)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_FALSE(codec_client_->connected()); + EXPECT_FALSE(codec_client_->disconnected()); + + // Increase load more so that the timer is reduced to the minimum. + updateResource(0.9); + test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + 100); + + // Create another HTTP connection without finishing handshake. + IntegrationCodecClientPtr codec_client2 = makeRawHttpConnection( + makeClientConnection((lookupPort("http"))), absl::nullopt, /*wait_till_connected=*/false); + EXPECT_FALSE(codec_client2->connected()); + // Advancing past the minimum time and wait for the proxy to notice and close both connections. + timeSystem().advanceTimeWait(std::chrono::seconds(3)); + test_server_->waitForCounterGe("http.config_test.downstream_cx_idle_timeout", 2); + ASSERT_TRUE(codec_client_->waitForDisconnect()); + EXPECT_FALSE(codec_client_->sawGoAway()); + EXPECT_FALSE(codec_client2->connected()); + ASSERT_TRUE(codec_client2->waitForDisconnect()); + EXPECT_FALSE(codec_client2->sawGoAway()); +} + TEST_P(OverloadScaledTimerIntegrationTest, CloseIdleHttpStream) { initializeOverloadManager( TestUtility::parseYaml(R"EOF( diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 304164ef09fd..75706cf7231f 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -229,12 +229,13 @@ class QuicHttpIntegrationTestBase : public HttpIntegrationTest { return session; } - IntegrationCodecClientPtr makeRawHttpConnection( - Network::ClientConnectionPtr&& conn, - absl::optional http2_options) override { + IntegrationCodecClientPtr + makeRawHttpConnection(Network::ClientConnectionPtr&& conn, + absl::optional http2_options, + bool wait_till_connected = true) override { ENVOY_LOG(debug, "Creating a new client {}", conn->connectionInfoProvider().localAddress()->asStringView()); - return makeRawHttp3Connection(std::move(conn), http2_options, true); + return makeRawHttp3Connection(std::move(conn), http2_options, wait_till_connected); } // Create Http3 codec client with the option not to wait for 1-RTT key establishment. From dd88a281c2b008583654b4cbaf8fa94b71d5e04e Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Wed, 1 Nov 2023 14:37:44 +0000 Subject: [PATCH 61/72] mobile: Upgrade to the latest JDK 11 for ktfmt (#30647) Occassionally I've seen an error with `NoClassDefFoundError` when running `ktfmt` with an older JDK 11. After updating to the latest JDK 11, I no longer see the error. Signed-off-by: Fredy Wijaya --- mobile/tools/ktfmt.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/tools/ktfmt.sh b/mobile/tools/ktfmt.sh index 59ce244ab79c..2f585038a07c 100755 --- a/mobile/tools/ktfmt.sh +++ b/mobile/tools/ktfmt.sh @@ -11,10 +11,10 @@ readonly ktfmt_url ktfmt_sha256="97fc7fbd194d01a9fa45d8147c0552403003d55bac4ab89d84d7bb4d5e3f48de" readonly ktfmt_sha256 -jdk_url="https://cdn.azul.com/zulu/bin/zulu11.1+23-ea-jdk11-linux_x64.tar.gz" +jdk_url="https://cdn.azul.com/zulu/bin/zulu11.68.17-ca-jdk11.0.21-linux_x64.tar.gz" readonly jdk_url -jdk_sha256="7cd09d542fa5623df5a59447304c3a41c0b682d3ca26b5e9d99e5214cf21fdd7" +jdk_sha256="725aba257da4bca14959060fea3faf59005eafdc2d5ccc3cb745403c5b60fb27" readonly jdk_sha256 script_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" From dcfe27b5a4f6c9de6412b81d07dafe470c3220d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:40:43 +0000 Subject: [PATCH 62/72] build(deps): bump envoyproxy/toolshed from actions-v0.1.4 to 0.1.5 (#30661) Bumps [envoyproxy/toolshed](https://github.com/envoyproxy/toolshed) from actions-v0.1.4 to 0.1.5. This release includes the previously tagged commit. - [Release notes](https://github.com/envoyproxy/toolshed/releases) - [Commits](https://github.com/envoyproxy/toolshed/compare/actions-v0.1.4...actions-v0.1.5) --- updated-dependencies: - dependency-name: envoyproxy/toolshed dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_cache_docker.yml | 2 +- .github/workflows/_ci.yml | 12 ++++++------ .github/workflows/_env.yml | 2 +- .github/workflows/_stage_publish.yml | 2 +- .github/workflows/_workflow-start.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/commands.yml | 2 +- .github/workflows/envoy-dependency.yml | 14 +++++++------- .github/workflows/envoy-release.yml | 16 ++++++++-------- .github/workflows/envoy-sync.yml | 2 +- .github/workflows/mobile-android_tests.yml | 4 ++-- .github/workflows/workflow-complete.yml | 2 +- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/_cache_docker.yml b/.github/workflows/_cache_docker.yml index 85ac92dbf940..032be0081e6f 100644 --- a/.github/workflows/_cache_docker.yml +++ b/.github/workflows/_cache_docker.yml @@ -37,7 +37,7 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.1.5 name: Prime Docker cache (${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}) with: image_tag: "${{ inputs.image_repo }}:${{ inputs.image_tag }}@sha256:${{ inputs.image_sha }}" diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 320c1da733cc..f7e3b9a862df 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -95,11 +95,11 @@ jobs: steps: - if: ${{ inputs.cache-build-image }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.1.5 with: image_tag: ${{ inputs.cache-build-image }} - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.5 id: checkout name: Checkout Envoy repository with: @@ -129,21 +129,21 @@ jobs: - if: ${{ inputs.diskspace-hack }} name: Free diskspace - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.5 - run: | echo "disk space at beginning of build:" df -h name: "Check disk space at beginning" shell: bash - - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.5 name: Run pre steps if: ${{ inputs.steps-pre }} with: name: ${{ inputs.steps-pre-name }} steps: ${{ inputs.steps-pre }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.5 name: 'Run CI target ${{ inputs.target }}' with: catch-errors: ${{ inputs.catch-errors }} @@ -166,7 +166,7 @@ jobs: BAZEL_FAKE_SCM_REVISION: ${{ github.event_name == 'pull_request' && 'e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9' || '' }} CI_TARGET_BRANCH: ${{ github.event_name == 'pull_request' && github.event.base.ref || github.ref }} - - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/using/steps@actions-v0.1.5 name: Run post steps if: ${{ inputs.steps-post }} with: diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index dd6c0f8c0bd8..f59321e124d9 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -153,7 +153,7 @@ jobs: build_image_tag: ${{ inputs.build_image_tag }} build_image_mobile_sha: ${{ inputs.build_image_mobile_sha }} build_image_sha: ${{ inputs.build_image_sha }} - - uses: envoyproxy/toolshed/gh-actions/github/merge-commit@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/github/merge-commit@actions-v0.1.5 id: merge-commit if: ${{ github.event_name == 'pull_request_target' }} with: diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 3667669bf1c9..c67f7301ca04 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -125,7 +125,7 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.5 with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: "${{ secrets.ENVOY_CI_SYNC_APP_KEY }}" diff --git a/.github/workflows/_workflow-start.yml b/.github/workflows/_workflow-start.yml index 10bbb6252d95..96142f0e0920 100644 --- a/.github/workflows/_workflow-start.yml +++ b/.github/workflows/_workflow-start.yml @@ -30,7 +30,7 @@ jobs: - if: ${{ steps.env.outputs.trusted != 'true' }} name: Start status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.5 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: ${{ inputs.workflow_name }} diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 66c81e0ba7ae..6e080f198a44 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Pre-cleanup - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.5 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index ff49b16271ab..5e92671f9f88 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: actions: write checks: read steps: - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.1.5 with: token: ${{ secrets.GITHUB_TOKEN }} azp_org: cncf diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 2102749c770a..4ebe135de871 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -50,13 +50,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.5 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} app_key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: version name: Shorten (possible) SHA - uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.5 with: string: ${{ inputs.version }} length: 7 @@ -71,13 +71,13 @@ jobs: TARGET: ${{ inputs.task == 'bazel' && 'update' || 'api-update' }} TASK: ${{ inputs.task == 'bazel' && 'bazel' || 'api/bazel' }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.1.5 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.version.outputs.string }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.5 with: base: main body: | @@ -106,7 +106,7 @@ jobs: name: Update build image (PR) runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.5 id: checkout name: Checkout Envoy repository with: @@ -145,7 +145,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.1.5 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -174,7 +174,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.5 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 6f917726514d..300d5a4db61d 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,7 +55,7 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.5 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} @@ -77,10 +77,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.1.5 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.5 name: Create release with: source: | @@ -105,7 +105,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.5 with: base: ${{ github.ref_name }} commit: false @@ -130,13 +130,13 @@ jobs: steps: - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.5 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.1.5 name: Sync version histories with: command: >- @@ -146,7 +146,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.1.5 with: append-commit-message: true base: ${{ github.ref_name }} @@ -176,7 +176,7 @@ jobs: name: Create release branch steps: - name: Checkout repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.1.5 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} app_key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 0757dd58e907..59df253a26de 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -28,7 +28,7 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.4 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.1.5 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index b3b65cef711d..2a7248894ed5 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -35,7 +35,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.5 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy @@ -69,7 +69,7 @@ jobs: - name: Pre-cleanup # Using the defaults in # https://github.com/envoyproxy/toolshed/blob/main/gh-actions/diskspace/action.yml. - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.1.5 - uses: actions/checkout@v4 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/workflow-complete.yml b/.github/workflows/workflow-complete.yml index afc4a72dca9b..d5c2a84079f3 100644 --- a/.github/workflows/workflow-complete.yml +++ b/.github/workflows/workflow-complete.yml @@ -54,7 +54,7 @@ jobs: echo "state=${STATE}" >> "$GITHUB_OUTPUT" id: job - name: Complete status check - uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.4 + uses: envoyproxy/toolshed/gh-actions/status@actions-v0.1.5 with: authToken: ${{ secrets.GITHUB_TOKEN }} context: Verify/examples From b8244f2a050104a85195656858121f4919243a3e Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 1 Nov 2023 15:00:47 +0000 Subject: [PATCH 63/72] publish/verify: Fix for ci downloads (#30658) Signed-off-by: Ryan Northey --- .github/workflows/_stage_publish.yml | 3 ++- .github/workflows/_stage_verify.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index c67f7301ca04..8b4105b805d2 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -62,7 +62,8 @@ jobs: uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.2 with: length: 7 - input: ${{ inputs.repo-ref }} + string: ${{ inputs.repo-ref }} + min: 40 - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.1.2 with: url: https://storage.googleapis.com/envoy-pr/%{{ steps.short_name.outputs.string }}/release/release.signed.tar.zst diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index af55024d936b..1af743bfadae 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -46,7 +46,8 @@ jobs: uses: envoyproxy/toolshed/gh-actions/str/sub@actions-v0.1.1 with: length: 7 - input: ${{ inputs.repo-ref }} + string: ${{ inputs.repo-ref }} + min: 40 - id: gcp run: | PREFIX=https://storage.googleapis.com/envoy- From 574b10e77ba7ba9e7764f223516abd9f851b6f54 Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Wed, 1 Nov 2023 16:10:47 +0100 Subject: [PATCH 64/72] Undeprecate global_downstream_max_connections key (#30620) Signed-off-by: Kateryna Nezdolii --- changelogs/current.yaml | 4 ++++ source/common/network/tcp_listener_impl.cc | 7 ++++--- source/server/server.cc | 12 ++---------- .../cx_limit_overload_integration_test.cc | 2 +- test/integration/cx_limit_integration_test.cc | 12 ------------ 5 files changed, 11 insertions(+), 26 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0b286d3d7663..8134d218beb8 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -9,6 +9,10 @@ behavior_changes: the flag to false explicitly. See doc :ref:`Http filter route specific config ` or issue https://github.com/envoyproxy/envoy/issues/29461 for more specific detail and examples. +- area: listener + change: | + undeprecated runtime key ``overload.global_downstream_max_connections`` until :ref:`downstream connections monitor + ` extension becomes stable. minor_behavior_changes: - area: upstream diff --git a/source/common/network/tcp_listener_impl.cc b/source/common/network/tcp_listener_impl.cc index a3ea313ddb49..7ff12abac413 100644 --- a/source/common/network/tcp_listener_impl.cc +++ b/source/common/network/tcp_listener_impl.cc @@ -25,14 +25,15 @@ bool TcpListenerImpl::rejectCxOverGlobalLimit() const { if (ignore_global_conn_limit_) { return false; } - + // TODO(nezdolik): deprecate `overload.global_downstream_max_connections` key once + // downstream connections monitor extension is stable. if (track_global_cx_limit_in_overload_manager_) { - // Check if deprecated runtime flag `overload.global_downstream_max_connections` is configured + // Check if runtime flag `overload.global_downstream_max_connections` is configured // simultaneously with downstream connections monitor in overload manager. if (runtime_.threadsafeSnapshot()->get(GlobalMaxCxRuntimeKey)) { ENVOY_LOG_ONCE_MISC( warn, - "Global downstream connections limits is configured via deprecated runtime key {} and in " + "Global downstream connections limits is configured via runtime key {} and in " "{}. Using overload manager config.", GlobalMaxCxRuntimeKey, Server::OverloadProactiveResources::get().GlobalDownstreamMaxConnections); diff --git a/source/server/server.cc b/source/server/server.cc index a2f391e6c0ea..6b7ee5e086a0 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -814,15 +814,6 @@ void InstanceImpl::onRuntimeReady() { shutdown(); }); } - - // TODO (nezdolik): Fully deprecate this runtime key in the next release. - if (runtime().snapshot().get(Network::TcpListenerImpl::GlobalMaxCxRuntimeKey)) { - ENVOY_LOG(warn, - "Usage of the deprecated runtime key {}, consider switching to " - "`envoy.resource_monitors.downstream_connections` instead." - "This runtime key will be removed in future.", - Network::TcpListenerImpl::GlobalMaxCxRuntimeKey); - } } void InstanceImpl::startWorkers() { @@ -908,7 +899,8 @@ RunHelper::RunHelper(Instance& instance, const Options& options, Event::Dispatch // If there is no global limit to the number of active connections, warn on startup. if (!overload_manager.getThreadLocalOverloadState().isResourceMonitorEnabled( - Server::OverloadProactiveResourceName::GlobalDownstreamMaxConnections)) { + Server::OverloadProactiveResourceName::GlobalDownstreamMaxConnections) && + !instance.runtime().snapshot().get(Network::TcpListenerImpl::GlobalMaxCxRuntimeKey)) { ENVOY_LOG(warn, "There is no configured limit to the number of allowed active downstream " "connections. Configure a " "limit in `envoy.resource_monitors.downstream_connections` resource monitor."); diff --git a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc index b2fa4afe9659..7242c9f89cd6 100644 --- a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc +++ b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc @@ -104,7 +104,7 @@ TEST_F(GlobalDownstreamCxLimitIntegrationTest, GlobalLimitSetViaRuntimeKeyAndOve config_helper_.addRuntimeOverride("overload.global_downstream_max_connections", "3"); initializeOverloadManager(2); const std::string log_line = - "Global downstream connections limits is configured via deprecated runtime key " + "Global downstream connections limits is configured via runtime key " "overload.global_downstream_max_connections and in " "envoy.resource_monitors.global_downstream_max_connections. Using overload manager " "config."; diff --git a/test/integration/cx_limit_integration_test.cc b/test/integration/cx_limit_integration_test.cc index ccf2b19dc1c6..c998727d01b9 100644 --- a/test/integration/cx_limit_integration_test.cc +++ b/test/integration/cx_limit_integration_test.cc @@ -136,18 +136,6 @@ TEST_P(ConnectionLimitIntegrationTest, TestListenerLimit) { doTest(init_func, "downstream_cx_overflow"); } -TEST_P(ConnectionLimitIntegrationTest, TestDeprecationWarningForGlobalCxRuntimeLimit) { - std::function init_func = [this]() { - setGlobalLimit(4); - initialize(); - }; - const std::string log_line = - "Usage of the deprecated runtime key overload.global_downstream_max_connections, " - "consider switching to `envoy.resource_monitors.downstream_connections` instead." - "This runtime key will be removed in future."; - EXPECT_LOG_CONTAINS("warn", log_line, { init_func(); }); -} - // TODO (nezdolik) move this test to overload manager test suite, once runtime key is fully // deprecated. TEST_P(ConnectionLimitIntegrationTest, TestEmptyGlobalCxRuntimeLimit) { From 98e8ec35d27c81c7e3c5b2fab74aa6d2d6067b85 Mon Sep 17 00:00:00 2001 From: RenjieTang Date: Wed, 1 Nov 2023 08:17:51 -0700 Subject: [PATCH 65/72] [mobile]add quic canonical suffix support (#30644) This completes the functionality of QUIC hints. Risk Level: Low Testing: unit and integration tests Docs Changes: api doc change Release Notes: n/a Platform Specific Features: mobile only Signed-off-by: Renjie Tang --- mobile/docs/root/api/starting_envoy.rst | 17 +++++++++ mobile/library/cc/engine_builder.cc | 13 +++++++ mobile/library/cc/engine_builder.h | 2 ++ mobile/library/common/jni/jni_interface.cc | 21 ++++++----- .../engine/EnvoyConfiguration.java | 35 +++++++++++-------- .../envoymobile/engine/JniLibrary.java | 10 +++--- .../net/impl/CronvoyEngineBuilderImpl.java | 8 +++++ .../impl/NativeCronvoyEngineBuilderImpl.java | 13 +++---- .../envoyproxy/envoymobile/EngineBuilder.kt | 13 +++++++ .../library/objective-c/EnvoyConfiguration.h | 2 ++ .../library/objective-c/EnvoyConfiguration.mm | 5 +++ mobile/library/swift/EngineBuilder.swift | 16 +++++++++ mobile/test/cc/unit/envoy_config_test.cc | 4 +++ .../integration/client_integration_test.cc | 5 ++- .../engine/EnvoyConfigurationTest.kt | 5 +++ 15 files changed, 135 insertions(+), 34 deletions(-) diff --git a/mobile/docs/root/api/starting_envoy.rst b/mobile/docs/root/api/starting_envoy.rst index cc2a86c01f8e..0385c0ccccb4 100644 --- a/mobile/docs/root/api/starting_envoy.rst +++ b/mobile/docs/root/api/starting_envoy.rst @@ -395,6 +395,23 @@ This allows HTTP/3 to be used for the first request to the hosts and avoid the H // Swift builder.addQuicHint("www.example.com", 443) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``addQuicCanonicalSuffix`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Add a canonical suffix that's known to speak QUIC. +This feature works as a extension to QUIC hints in such way that: +if `.abc.com` is added to canonical suffix, and `foo.abc.com` is added to QUIC hint, then all requests to +`*.abc.com` will be considered QUIC ready. + +**Example**:: + + // Kotlin + builder.addQuicCanonicalSuffix(".example.com") + + // Swift + builder.addQuicCanonicalSuffix(".example.com") + ~~~~~~~~~~~~~~~~~~~~~~~ ``enableSocketTagging`` ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 83512f01dd7e..11841e6e528a 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -299,6 +299,11 @@ EngineBuilder& EngineBuilder::addQuicHint(std::string host, int port) { return *this; } +EngineBuilder& EngineBuilder::addQuicCanonicalSuffix(std::string suffix) { + quic_suffixes_.emplace_back(std::move(suffix)); + return *this; +} + #endif EngineBuilder& EngineBuilder::setForceAlwaysUsev6(bool value) { @@ -453,6 +458,9 @@ std::unique_ptr EngineBuilder::generate entry->set_hostname(host); entry->set_port(port); } + for (const auto& suffix : quic_suffixes_) { + cache_config.mutable_alternate_protocols_cache_options()->add_canonical_suffixes(suffix); + } auto* cache_filter = hcm->add_http_filters(); cache_filter->set_name("alternate_protocols_cache"); cache_filter->mutable_typed_config()->PackFrom(cache_config); @@ -821,6 +829,11 @@ std::unique_ptr EngineBuilder::generate entry->set_hostname(host); entry->set_port(port); } + for (const auto& suffix : quic_suffixes_) { + alpn_options.mutable_auto_config() + ->mutable_alternate_protocols_cache_options() + ->add_canonical_suffixes(suffix); + } base_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(h3_proxy_socket); (*base_cluster->mutable_typed_extension_protocol_options()) diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index 2233388fb8a1..f3e5d5037817 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -158,6 +158,7 @@ class EngineBuilder { EngineBuilder& setHttp3ConnectionOptions(std::string options); EngineBuilder& setHttp3ClientConnectionOptions(std::string options); EngineBuilder& addQuicHint(std::string host, int port); + EngineBuilder& addQuicCanonicalSuffix(std::string suffix); #endif EngineBuilder& enableInterfaceBinding(bool interface_binding_on); EngineBuilder& enableDrainPostDnsRefresh(bool drain_post_dns_refresh_on); @@ -244,6 +245,7 @@ class EngineBuilder { std::string http3_connection_options_ = ""; std::string http3_client_connection_options_ = ""; std::vector> quic_hints_; + std::vector quic_suffixes_; bool always_use_v6_ = false; int dns_min_refresh_seconds_ = 60; int max_connections_per_host_ = 7; diff --git a/mobile/library/common/jni/jni_interface.cc b/mobile/library/common/jni/jni_interface.cc index a1c618f42d91..74a4845ccce8 100644 --- a/mobile/library/common/jni/jni_interface.cc +++ b/mobile/library/common/jni/jni_interface.cc @@ -1211,9 +1211,9 @@ void configureBuilder(JNIEnv* env, jstring grpc_stats_domain, jlong connect_time jboolean enable_dns_cache, jlong dns_cache_save_interval_seconds, jboolean enable_drain_post_dns_refresh, jboolean enable_http3, jstring http3_connection_options, jstring http3_client_connection_options, - jobjectArray quic_hints, jboolean enable_gzip_decompression, - jboolean enable_brotli_decompression, jboolean enable_socket_tagging, - jboolean enable_interface_binding, + jobjectArray quic_hints, jobjectArray quic_canonical_suffixes, + jboolean enable_gzip_decompression, jboolean enable_brotli_decompression, + jboolean enable_socket_tagging, jboolean enable_interface_binding, jlong h2_connection_keepalive_idle_interval_milliseconds, jlong h2_connection_keepalive_timeout_seconds, jlong max_connections_per_host, jlong stats_flush_seconds, jlong stream_idle_timeout_seconds, @@ -1252,6 +1252,11 @@ void configureBuilder(JNIEnv* env, jstring grpc_stats_domain, jlong connect_time for (std::pair& entry : hints) { builder.addQuicHint(entry.first, stoi(entry.second)); } + std::vector suffixes = javaObjectArrayToStringVector(env, quic_canonical_suffixes); + for (std::string& suffix : suffixes) { + builder.addQuicCanonicalSuffix(suffix); + } + #endif builder.enableInterfaceBinding(enable_interface_binding == JNI_TRUE); builder.enableDrainPostDnsRefresh(enable_drain_post_dns_refresh == JNI_TRUE); @@ -1301,9 +1306,9 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr jlong dns_cache_save_interval_seconds, jboolean enable_drain_post_dns_refresh, jboolean enable_http3, jstring http3_connection_options, jstring http3_client_connection_options, jobjectArray quic_hints, - jboolean enable_gzip_decompression, jboolean enable_brotli_decompression, - jboolean enable_socket_tagging, jboolean enable_interface_binding, - jlong h2_connection_keepalive_idle_interval_milliseconds, + jobjectArray quic_canonical_suffixes, jboolean enable_gzip_decompression, + jboolean enable_brotli_decompression, jboolean enable_socket_tagging, + jboolean enable_interface_binding, jlong h2_connection_keepalive_idle_interval_milliseconds, jlong h2_connection_keepalive_timeout_seconds, jlong max_connections_per_host, jlong stats_flush_seconds, jlong stream_idle_timeout_seconds, jlong per_try_idle_timeout_seconds, jstring app_version, jstring app_id, @@ -1321,8 +1326,8 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr dns_query_timeout_seconds, dns_min_refresh_seconds, dns_preresolve_hostnames, enable_dns_cache, dns_cache_save_interval_seconds, enable_drain_post_dns_refresh, enable_http3, http3_connection_options, http3_client_connection_options, - quic_hints, enable_gzip_decompression, enable_brotli_decompression, - enable_socket_tagging, enable_interface_binding, + quic_hints, quic_canonical_suffixes, enable_gzip_decompression, + enable_brotli_decompression, enable_socket_tagging, enable_interface_binding, h2_connection_keepalive_idle_interval_milliseconds, h2_connection_keepalive_timeout_seconds, max_connections_per_host, stats_flush_seconds, stream_idle_timeout_seconds, per_try_idle_timeout_seconds, diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index 44b06960e648..d43d666df579 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -40,6 +40,7 @@ public enum TrustChainVerification { public final String http3ConnectionOptions; public final String http3ClientConnectionOptions; public final Map quicHints; + public final List quicCanonicalSuffixes; public final Boolean enableGzipDecompression; public final Boolean enableBrotliDecompression; public final Boolean enableSocketTagging; @@ -109,6 +110,8 @@ public enum TrustChainVerification { * HTTP/3. * @param quicHints A list of host port pairs that's known * to speak QUIC. + * @param quicCanonicalSuffixes A list of canonical suffixes that are + * known to speak QUIC. * @param enableGzipDecompression whether to enable response gzip * decompression. * compression. @@ -166,11 +169,12 @@ public EnvoyConfiguration( int dnsMinRefreshSeconds, List dnsPreresolveHostnames, boolean enableDNSCache, int dnsCacheSaveIntervalSeconds, boolean enableDrainPostDnsRefresh, boolean enableHttp3, String http3ConnectionOptions, String http3ClientConnectionOptions, - Map quicHints, boolean enableGzipDecompression, - boolean enableBrotliDecompression, boolean enableSocketTagging, - boolean enableInterfaceBinding, int h2ConnectionKeepaliveIdleIntervalMilliseconds, - int h2ConnectionKeepaliveTimeoutSeconds, int maxConnectionsPerHost, int statsFlushSeconds, - int streamIdleTimeoutSeconds, int perTryIdleTimeoutSeconds, String appVersion, String appId, + Map quicHints, List quicCanonicalSuffixes, + boolean enableGzipDecompression, boolean enableBrotliDecompression, + boolean enableSocketTagging, boolean enableInterfaceBinding, + int h2ConnectionKeepaliveIdleIntervalMilliseconds, int h2ConnectionKeepaliveTimeoutSeconds, + int maxConnectionsPerHost, int statsFlushSeconds, int streamIdleTimeoutSeconds, + int perTryIdleTimeoutSeconds, String appVersion, String appId, TrustChainVerification trustChainVerification, List nativeFilterChain, List httpPlatformFilterFactories, @@ -200,6 +204,7 @@ public EnvoyConfiguration( for (Map.Entry hostAndPort : quicHints.entrySet()) { this.quicHints.put(hostAndPort.getKey(), String.valueOf(hostAndPort.getValue())); } + this.quicCanonicalSuffixes = quicCanonicalSuffixes; this.enableGzipDecompression = enableGzipDecompression; this.enableBrotliDecompression = enableBrotliDecompression; this.enableSocketTagging = enableSocketTagging; @@ -265,20 +270,22 @@ public long createBootstrap() { byte[][] dnsPreresolve = JniBridgeUtility.stringsToJniBytes(dnsPreresolveHostnames); byte[][] runtimeGuards = JniBridgeUtility.mapToJniBytes(this.runtimeGuards); byte[][] quicHints = JniBridgeUtility.mapToJniBytes(this.quicHints); + byte[][] quicSuffixes = JniBridgeUtility.stringsToJniBytes(quicCanonicalSuffixes); return JniLibrary.createBootstrap( grpcStatsDomain, connectTimeoutSeconds, dnsRefreshSeconds, dnsFailureRefreshSecondsBase, dnsFailureRefreshSecondsMax, dnsQueryTimeoutSeconds, dnsMinRefreshSeconds, dnsPreresolve, enableDNSCache, dnsCacheSaveIntervalSeconds, enableDrainPostDnsRefresh, enableHttp3, - http3ConnectionOptions, http3ClientConnectionOptions, quicHints, enableGzipDecompression, - enableBrotliDecompression, enableSocketTagging, enableInterfaceBinding, - h2ConnectionKeepaliveIdleIntervalMilliseconds, h2ConnectionKeepaliveTimeoutSeconds, - maxConnectionsPerHost, statsFlushSeconds, streamIdleTimeoutSeconds, - perTryIdleTimeoutSeconds, appVersion, appId, enforceTrustChainVerification, filterChain, - statsSinks, enablePlatformCertificatesValidation, runtimeGuards, rtdsResourceName, - rtdsTimeoutSeconds, xdsAddress, xdsPort, xdsAuthHeader, xdsAuthToken, xdsRootCerts, xdsSni, - nodeId, nodeRegion, nodeZone, nodeSubZone, nodeMetadata.toByteArray(), cdsResourcesLocator, - cdsTimeoutSeconds, enableCds); + http3ConnectionOptions, http3ClientConnectionOptions, quicHints, quicSuffixes, + enableGzipDecompression, enableBrotliDecompression, enableSocketTagging, + enableInterfaceBinding, h2ConnectionKeepaliveIdleIntervalMilliseconds, + h2ConnectionKeepaliveTimeoutSeconds, maxConnectionsPerHost, statsFlushSeconds, + streamIdleTimeoutSeconds, perTryIdleTimeoutSeconds, appVersion, appId, + enforceTrustChainVerification, filterChain, statsSinks, + enablePlatformCertificatesValidation, runtimeGuards, rtdsResourceName, rtdsTimeoutSeconds, + xdsAddress, xdsPort, xdsAuthHeader, xdsAuthToken, xdsRootCerts, xdsSni, nodeId, nodeRegion, + nodeZone, nodeSubZone, nodeMetadata.toByteArray(), cdsResourcesLocator, cdsTimeoutSeconds, + enableCds); } static class ConfigurationException extends RuntimeException { diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index 298b2a772a95..b5e703725e06 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -311,11 +311,11 @@ public static native long createBootstrap( long dnsQueryTimeoutSeconds, long dnsMinRefreshSeconds, byte[][] dnsPreresolveHostnames, boolean enableDNSCache, long dnsCacheSaveIntervalSeconds, boolean enableDrainPostDnsRefresh, boolean enableHttp3, String http3ConnectionOptions, String http3ClientConnectionOptions, - byte[][] quicHints, boolean enableGzipDecompression, boolean enableBrotliDecompression, - boolean enableSocketTagging, boolean enableInterfaceBinding, - long h2ConnectionKeepaliveIdleIntervalMilliseconds, long h2ConnectionKeepaliveTimeoutSeconds, - long maxConnectionsPerHost, long statsFlushSeconds, long streamIdleTimeoutSeconds, - long perTryIdleTimeoutSeconds, String appVersion, String appId, + byte[][] quicHints, byte[][] quicCanonicalSuffixes, boolean enableGzipDecompression, + boolean enableBrotliDecompression, boolean enableSocketTagging, + boolean enableInterfaceBinding, long h2ConnectionKeepaliveIdleIntervalMilliseconds, + long h2ConnectionKeepaliveTimeoutSeconds, long maxConnectionsPerHost, long statsFlushSeconds, + long streamIdleTimeoutSeconds, long perTryIdleTimeoutSeconds, String appVersion, String appId, boolean trustChainVerification, byte[][] filterChain, byte[][] statSinks, boolean enablePlatformCertificatesValidation, byte[][] runtimeGuards, String rtdsResourceName, long rtdsTimeoutSeconds, String xdsAddress, long xdsPort, String xdsAuthenticationHeader, diff --git a/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java b/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java index 5a58ce912e7a..7813bb95592a 100644 --- a/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java +++ b/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java @@ -50,6 +50,7 @@ final static class Pkp { // See setters below for verbose descriptions. private final Context mApplicationContext; private final Map mQuicHints = new HashMap<>(); + private final List mQuicCanonicalSuffixes = new LinkedList<>(); private final List mPkps = new LinkedList<>(); private boolean mPublicKeyPinningBypassForLocalTrustAnchorsEnabled; private String mUserAgent; @@ -227,6 +228,13 @@ public CronvoyEngineBuilderImpl addQuicHint(String host, int port, int alternate Map quicHints() { return mQuicHints; } + public CronvoyEngineBuilderImpl addQuicCanonicalSuffix(String suffix) { + mQuicCanonicalSuffixes.add(suffix); + return this; + } + + List quicCanonicalSuffixes() { return mQuicCanonicalSuffixes; } + @Override public CronvoyEngineBuilderImpl addPublicKeyPins(String hostName, Set pinsSha256, boolean includeSubdomains, Date expirationDate) { diff --git a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java index 82f49baba0ad..be2a5cc64d3f 100644 --- a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java +++ b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java @@ -127,12 +127,13 @@ private EnvoyConfiguration createEnvoyConfiguration() { mDnsFailureRefreshSecondsMax, mDnsQueryTimeoutSeconds, mDnsMinRefreshSeconds, mDnsPreresolveHostnames, mEnableDNSCache, mDnsCacheSaveIntervalSeconds, mEnableDrainPostDnsRefresh, quicEnabled(), quicConnectionOptions(), - quicClientConnectionOptions(), quicHints(), mEnableGzipDecompression, brotliEnabled(), - mEnableSocketTag, mEnableInterfaceBinding, mH2ConnectionKeepaliveIdleIntervalMilliseconds, - mH2ConnectionKeepaliveTimeoutSeconds, mMaxConnectionsPerHost, mStatsFlushSeconds, - mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, mAppVersion, mAppId, - mTrustChainVerification, nativeFilterChain, platformFilterChain, stringAccessors, - keyValueStores, statSinks, runtimeGuards, mEnablePlatformCertificatesValidation, + quicClientConnectionOptions(), quicHints(), quicCanonicalSuffixes(), + mEnableGzipDecompression, brotliEnabled(), mEnableSocketTag, mEnableInterfaceBinding, + mH2ConnectionKeepaliveIdleIntervalMilliseconds, mH2ConnectionKeepaliveTimeoutSeconds, + mMaxConnectionsPerHost, mStatsFlushSeconds, mStreamIdleTimeoutSeconds, + mPerTryIdleTimeoutSeconds, mAppVersion, mAppId, mTrustChainVerification, nativeFilterChain, + platformFilterChain, stringAccessors, keyValueStores, statSinks, runtimeGuards, + mEnablePlatformCertificatesValidation, /*rtdsResourceName=*/"", /*rtdsTimeoutSeconds=*/0, /*xdsAddress=*/"", /*xdsPort=*/0, /*xdsAuthenticationHeader=*/"", /*xdsAuthenticationToken=*/"", /*xdsSslRootCerts=*/"", diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index 0be2e2152645..1359814b436c 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -161,6 +161,7 @@ open class EngineBuilder(private val configuration: BaseConfiguration = Standard private var http3ConnectionOptions = "" private var http3ClientConnectionOptions = "" private var quicHints = mutableMapOf() + private var quicCanonicalSuffixes = mutableListOf() private var enableGzipDecompression = true private var enableBrotliDecompression = false private var enableSocketTagging = false @@ -662,6 +663,17 @@ open class EngineBuilder(private val configuration: BaseConfiguration = Standard return this } + /** + * Add a host suffix that's known to speak QUIC. + * + * @param suffix the suffix string. + * @return This builder. + */ + fun addQuicCanonicalSuffix(suffix: String): EngineBuilder { + this.quicCanonicalSuffixes.add(suffix) + return this + } + /** * Builds and runs a new Engine instance with the provided configuration. * @@ -686,6 +698,7 @@ open class EngineBuilder(private val configuration: BaseConfiguration = Standard http3ConnectionOptions, http3ClientConnectionOptions, quicHints, + quicCanonicalSuffixes, enableGzipDecompression, enableBrotliDecompression, enableSocketTagging, diff --git a/mobile/library/objective-c/EnvoyConfiguration.h b/mobile/library/objective-c/EnvoyConfiguration.h index 4a266109c680..87dec257ef59 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.h +++ b/mobile/library/objective-c/EnvoyConfiguration.h @@ -25,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UInt32 dnsCacheSaveIntervalSeconds; @property (nonatomic, assign) BOOL enableHttp3; @property (nonatomic, strong) NSDictionary *quicHints; +@property (nonatomic, strong) NSArray *quicCanonicalSuffixes; @property (nonatomic, assign) BOOL enableGzipDecompression; @property (nonatomic, assign) BOOL enableBrotliDecompression; @property (nonatomic, assign) BOOL enableInterfaceBinding; @@ -78,6 +79,7 @@ NS_ASSUME_NONNULL_BEGIN dnsCacheSaveIntervalSeconds:(UInt32)dnsCacheSaveIntervalSeconds enableHttp3:(BOOL)enableHttp3 quicHints:(NSDictionary *)quicHints + quicCanonicalSuffixes:(NSArray *)quicCanonicalSuffixes enableGzipDecompression:(BOOL)enableGzipDecompression enableBrotliDecompression:(BOOL)enableBrotliDecompression enableInterfaceBinding:(BOOL)enableInterfaceBinding diff --git a/mobile/library/objective-c/EnvoyConfiguration.mm b/mobile/library/objective-c/EnvoyConfiguration.mm index d16868d8807d..0719a75faac4 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.mm +++ b/mobile/library/objective-c/EnvoyConfiguration.mm @@ -79,6 +79,7 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain dnsCacheSaveIntervalSeconds:(UInt32)dnsCacheSaveIntervalSeconds enableHttp3:(BOOL)enableHttp3 quicHints:(NSDictionary *)quicHints + quicCanonicalSuffixes:(NSArray *)quicCanonicalSuffixes enableGzipDecompression:(BOOL)enableGzipDecompression enableBrotliDecompression:(BOOL)enableBrotliDecompression enableInterfaceBinding:(BOOL)enableInterfaceBinding @@ -140,6 +141,7 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain self.dnsCacheSaveIntervalSeconds = dnsCacheSaveIntervalSeconds; self.enableHttp3 = enableHttp3; self.quicHints = quicHints; + self.quicCanonicalSuffixes = quicCanonicalSuffixes; self.enableGzipDecompression = enableGzipDecompression; self.enableBrotliDecompression = enableBrotliDecompression; self.enableInterfaceBinding = enableInterfaceBinding; @@ -201,6 +203,9 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain for (NSString *host in self.quicHints) { builder.addQuicHint([host toCXXString], [[self.quicHints objectForKey:host] intValue]); } + for (NSString *suffix in self.quicCanonicalSuffixes) { + builder.addQuicCanonicalSuffix([suffix toCXXString]); + } #endif builder.enableGzipDecompression(self.enableGzipDecompression); diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index e75cda693e7e..cdcc49f183fe 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -161,6 +161,7 @@ open class EngineBuilder: NSObject { private var enableHttp3: Bool = false #endif private var quicHints: [String: Int] = [:] + private var quicCanonicalSuffixes: [String] = [] private var enableInterfaceBinding: Bool = false private var enforceTrustChainVerification: Bool = true private var enablePlatformCertificateValidation: Bool = false @@ -378,6 +379,17 @@ open class EngineBuilder: NSObject { self.quicHints[host] = port return self } + + /// Add a host suffix that's known to support QUIC. + /// + /// - parameter suffix: the string representation of the host suffix + /// + /// - returns: This builder. + @discardableResult + public func addQuicCanonicalSuffix(_ suffix: String) -> Self { + self.quicCanonicalSuffixes.append(suffix) + return self + } #endif /// Add an interval at which to flush Envoy stats. @@ -800,6 +812,7 @@ open class EngineBuilder: NSObject { dnsCacheSaveIntervalSeconds: self.dnsCacheSaveIntervalSeconds, enableHttp3: self.enableHttp3, quicHints: self.quicHints.mapValues { NSNumber(value: $0) }, + quicCanonicalSuffixes: self.quicCanonicalSuffixes, enableGzipDecompression: self.enableGzipDecompression, enableBrotliDecompression: self.enableBrotliDecompression, enableInterfaceBinding: self.enableInterfaceBinding, @@ -874,6 +887,9 @@ private extension EngineBuilder { for (host, port) in self.quicHints { cxxBuilder.addQuicHint(host.toCXX(), Int32(port)) } + for (suffix) in self.quicCanonicalSuffixes { + cxxBuilder.addQuicCanonicalSuffix(suffix.toCXX()) + } #endif cxxBuilder.enableGzipDecompression(self.enableGzipDecompression) cxxBuilder.enableBrotliDecompression(self.enableBrotliDecompression) diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index 69c63165a5d7..ed1ccd5fd353 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -38,6 +38,8 @@ TEST(TestConfig, ConfigIsApplied) { .setHttp3ClientConnectionOptions("MPQC") .addQuicHint("www.abc.com", 443) .addQuicHint("www.def.com", 443) + .addQuicCanonicalSuffix(".opq.com") + .addQuicCanonicalSuffix(".xyz.com") #endif .addConnectTimeoutSeconds(123) .addDnsRefreshSeconds(456) @@ -74,6 +76,8 @@ TEST(TestConfig, ConfigIsApplied) { "client_connection_options: \"MPQC\"", "hostname: \"www.abc.com\"", "hostname: \"www.def.com\"", + "canonical_suffixes: \".opq.com\"", + "canonical_suffixes: \".xyz.com\"", #endif "key: \"dns_persistent_cache\" save_interval { seconds: 101 }", "key: \"always_use_v6\" value { bool_value: true }", diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index 373a08fe6e75..dbe16a80f331 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -72,7 +72,10 @@ class ClientIntegrationTest : public BaseClientIntegrationTest, if (add_quic_hints_) { auto address = fake_upstreams_[0]->localAddress(); auto upstream_port = fake_upstreams_[0]->localAddress()->ip()->port(); - builder_.addQuicHint("www.lyft.com", upstream_port); + // With canonical suffix, having a quic hint of foo.lyft.com will make + // www.lyft.com being recognized as QUIC ready. + builder_.addQuicCanonicalSuffix(".lyft.com"); + builder_.addQuicHint("foo.lyft.com", upstream_port); ASSERT(test_key_value_store_); // Force www.lyft.com to resolve to the fake upstream. It's the only domain diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt index cd7ddb5a728f..44c536d51e4e 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -81,6 +81,7 @@ class EnvoyConfigurationTest { http3ConnectionOptions: String = "5RTO", http3ClientConnectionOptions: String = "MPQC", quicHints: Map = mapOf("www.abc.com" to 443, "www.def.com" to 443), + quicCanonicalSuffixes: MutableList = mutableListOf(".opq.com", ".xyz.com"), enableGzipDecompression: Boolean = true, enableBrotliDecompression: Boolean = false, enableSocketTagging: Boolean = false, @@ -133,6 +134,7 @@ class EnvoyConfigurationTest { http3ConnectionOptions, http3ClientConnectionOptions, quicHints, + quicCanonicalSuffixes, enableGzipDecompression, enableBrotliDecompression, enableSocketTagging, @@ -208,6 +210,9 @@ class EnvoyConfigurationTest { assertThat(resolvedTemplate).contains("hostname: www.abc.com"); assertThat(resolvedTemplate).contains("hostname: www.def.com"); assertThat(resolvedTemplate).contains("port: 443"); + assertThat(resolvedTemplate).contains("canonical_suffixes:"); + assertThat(resolvedTemplate).contains(".opq.com"); + assertThat(resolvedTemplate).contains(".xyz.com"); assertThat(resolvedTemplate).contains("connection_options: 5RTO"); assertThat(resolvedTemplate).contains("client_connection_options: MPQC"); From 29f6998d0751962eb1dd5ba628225564e998a42d Mon Sep 17 00:00:00 2001 From: lei zhang Date: Thu, 2 Nov 2023 00:22:05 +0800 Subject: [PATCH 66/72] Fix broken fips build (#30554) Envoy build with boringssl-fips is broken. #30001 introduces a patch to remove BoringSSL-specific definition of BN_bn2lebinpad in ipp-crypto library. It works for non-fips build but fips build fails with declaration error, remove the patch for fips build. Signed-off-by: LeiZhang --- api/bazel/envoy_http_archive.bzl | 4 ++-- bazel/repositories.bzl | 18 ++++++++++++++++++ .../private_key_providers/source/BUILD | 5 ++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/api/bazel/envoy_http_archive.bzl b/api/bazel/envoy_http_archive.bzl index 15fd65b2af27..849e2500678f 100644 --- a/api/bazel/envoy_http_archive.bzl +++ b/api/bazel/envoy_http_archive.bzl @@ -1,6 +1,6 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -def envoy_http_archive(name, locations, **kwargs): +def envoy_http_archive(name, locations, location_name = None, **kwargs): # `existing_rule_keys` contains the names of repositories that have already # been defined in the Bazel workspace. By skipping repos with existing keys, # users can override dependency versions by using standard Bazel repository @@ -10,7 +10,7 @@ def envoy_http_archive(name, locations, **kwargs): # This repository has already been defined, probably because the user # wants to override the version. Do nothing. return - location = locations[name] + location = locations[location_name or name] # HTTP tarball at a given URL. Add a BUILD file if requested. http_archive( diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 044bf232ff1a..9f7ab5854d4e 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -287,6 +287,7 @@ def envoy_dependencies(skip_targets = []): _com_github_grpc_grpc() _com_github_unicode_org_icu() _com_github_intel_ipp_crypto_crypto_mb() + _com_github_intel_ipp_crypto_crypto_mb_fips() _com_github_intel_qatlib() _com_github_jbeder_yaml_cpp() _com_github_libevent_libevent() @@ -533,6 +534,23 @@ def _com_github_intel_ipp_crypto_crypto_mb(): build_file_content = BUILD_ALL_CONTENT, ) +def _com_github_intel_ipp_crypto_crypto_mb_fips(): + # Temporary fix for building ipp-crypto when boringssl-fips is used. + # Build will fail if bn2lebinpad patch is applied. Remove this archive + # when upstream dependency fixes this issue. + external_http_archive( + name = "com_github_intel_ipp_crypto_crypto_mb_fips", + # Patch removes from CMakeLists.txt instructions to + # to create dynamic *.so library target. Linker fails when linking + # with boringssl_fips library. Envoy uses only static library + # anyways, so created dynamic library would not be used anyways. + patches = ["@envoy//bazel/foreign_cc:ipp-crypto-skip-dynamic-lib.patch"], + patch_args = ["-p1"], + build_file_content = BUILD_ALL_CONTENT, + # Use existing ipp-crypto repository location name to avoid redefinition. + location_name = "com_github_intel_ipp_crypto_crypto_mb", + ) + def _com_github_intel_qatlib(): external_http_archive( name = "com_github_intel_qatlib", diff --git a/contrib/cryptomb/private_key_providers/source/BUILD b/contrib/cryptomb/private_key_providers/source/BUILD index 199acb8fe411..610b66513373 100644 --- a/contrib/cryptomb/private_key_providers/source/BUILD +++ b/contrib/cryptomb/private_key_providers/source/BUILD @@ -22,7 +22,10 @@ envoy_cmake( defines = [ "OPENSSL_USE_STATIC_LIBS=TRUE", ], - lib_source = "@com_github_intel_ipp_crypto_crypto_mb//:all", + lib_source = select({ + "//bazel:boringssl_fips": "@com_github_intel_ipp_crypto_crypto_mb_fips//:all", + "//conditions:default": "@com_github_intel_ipp_crypto_crypto_mb//:all", + }), out_static_libs = ["libcrypto_mb.a"], tags = ["skip_on_windows"], target_compatible_with = envoy_contrib_linux_x86_64_constraints(), From 39fcbfb9ee6f933a3af8f29e9ae29ef503a8b3b0 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 1 Nov 2023 16:50:34 +0000 Subject: [PATCH 67/72] azp/ci: Boost x64 build/test (#30663) Signed-off-by: Ryan Northey --- .azure-pipelines/stages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index d7f53fb6fc22..bc308c659d4b 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -70,10 +70,10 @@ stages: - template: stage/linux.yml parameters: cacheTestResults: ${{ parameters.cacheTestResults }} + pool: envoy-x64-large # these are parsed differently and _must_ be expressed in this way runBuild: variables['RUN_BUILD'] runTests: $(RUN_TESTS) - tmpfsDockerDisabled: true - stage: linux_arm64 displayName: Linux arm64 From 6538c486d4d317c94f1127821313ea36cdd7d6d5 Mon Sep 17 00:00:00 2001 From: Paul Ogilby Date: Wed, 1 Nov 2023 13:09:34 -0400 Subject: [PATCH 68/72] ext_authz: option to not charge cluster response code (#29841) Signed-off-by: Paul Ogilby --- .../filters/http/ext_authz/v3/ext_authz.proto | 8 +++- changelogs/current.yaml | 5 +++ .../filters/http/ext_authz/ext_authz.cc | 27 +++++------ .../filters/http/ext_authz/ext_authz.h | 5 +++ .../filters/http/ext_authz/ext_authz_test.cc | 45 +++++++++++++++++++ 5 files changed, 76 insertions(+), 14 deletions(-) diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index 6cef61a44ec8..d81b8d8e8a35 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -10,6 +10,8 @@ import "envoy/type/matcher/v3/metadata.proto"; import "envoy/type/matcher/v3/string.proto"; import "envoy/type/v3/http_status.proto"; +import "google/protobuf/wrappers.proto"; + import "envoy/annotations/deprecation.proto"; import "udpa/annotations/sensitive.proto"; import "udpa/annotations/status.proto"; @@ -26,7 +28,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization :ref:`configuration overview `. // [#extension: envoy.filters.http.ext_authz] -// [#next-free-field: 20] +// [#next-free-field: 21] message ExtAuthz { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v2.ExtAuthz"; @@ -197,6 +199,10 @@ message ExtAuthz { // When this field is true, Envoy will include the SNI name used for TLSClientHello, if available, in the // :ref:`tls_session`. bool include_tls_session = 18; + + // Whether to increment cluster statistics (e.g. cluster..upstream_rq_*) on authorization failure. + // Defaults to true. + google.protobuf.BoolValue charge_cluster_response_stats = 20; } // Configuration for buffering the request data. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8134d218beb8..00f8ef6da138 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -119,5 +119,10 @@ new_features: - area: tracing change: | Added support to configure a sampler for the OpenTelemetry tracer. +- area: ext_authz + change: | + New config parameter :ref:`charge_cluster_response_stats + ` + for not incrementing cluster statistics on ext_authz response. Default true, no behavior change. deprecated: diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 3de1424b64ec..7fad55fe9517 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -369,19 +369,20 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { if (cluster_) { config_->incCounter(cluster_->statsScope(), config_->ext_authz_denied_); - - Http::CodeStats::ResponseStatInfo info{config_->scope(), - cluster_->statsScope(), - empty_stat_name, - enumToInt(response->status_code), - true, - empty_stat_name, - empty_stat_name, - empty_stat_name, - empty_stat_name, - empty_stat_name, - false}; - config_->httpContext().codeStats().chargeResponseStat(info, false); + if (config_->chargeClusterResponseStats()) { + Http::CodeStats::ResponseStatInfo info{config_->scope(), + cluster_->statsScope(), + empty_stat_name, + enumToInt(response->status_code), + true, + empty_stat_name, + empty_stat_name, + empty_stat_name, + empty_stat_name, + empty_stat_name, + false}; + config_->httpContext().codeStats().chargeResponseStat(info, false); + } } // setResponseFlag must be called before sendLocalReply diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 0175cbc1c48a..d49dec93c320 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -90,6 +90,8 @@ class FilterConfig { config.typed_metadata_context_namespaces().end()), include_peer_certificate_(config.include_peer_certificate()), include_tls_session_(config.include_tls_session()), + charge_cluster_response_stats_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, charge_cluster_response_stats, true)), stats_(generateStats(stats_prefix, config.stat_prefix(), scope)), ext_authz_ok_(pool_.add(createPoolStatName(config.stat_prefix(), "ok"))), ext_authz_denied_(pool_.add(createPoolStatName(config.stat_prefix(), "denied"))), @@ -177,6 +179,8 @@ class FilterConfig { bool includeTLSSession() const { return include_tls_session_; } const LabelsMap& destinationLabels() const { return destination_labels_; } + bool chargeClusterResponseStats() const { return charge_cluster_response_stats_; } + const Filters::Common::ExtAuthz::MatcherSharedPtr& requestHeaderMatchers() const { return request_header_matchers_; } @@ -230,6 +234,7 @@ class FilterConfig { const bool include_peer_certificate_; const bool include_tls_session_; + const bool charge_cluster_response_stats_; // The stats for the filter. ExtAuthzFilterStats stats_; diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 311bcb8e3597..d67b842d4836 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -2378,6 +2378,51 @@ TEST_P(HttpFilterTestParam, DeniedResponseWith401) { .value()); } +// Test that a denied response results in the connection closing with a 401 response to the client. +TEST_P(HttpFilterTestParam, DeniedResponseWith401NoClusterResponseCodeStats) { + initialize(R"EOF( + transport_api_version: V3 + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + charge_cluster_response_stats: + value: false + )EOF"); + + InSequence s; + + prepareCheck(); + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(Envoy::StreamInfo::ResponseFlag::UnauthorizedExternalService)); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "401"}}; + EXPECT_CALL(decoder_filter_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()).Times(0); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Denied; + response.status_code = Http::Code::Unauthorized; + request_callbacks_->onComplete(std::make_unique(response)); + EXPECT_EQ(1U, decoder_filter_callbacks_.clusterInfo() + ->statsScope() + .counterFromString("ext_authz.denied") + .value()); + EXPECT_EQ(1U, config_->stats().denied_.value()); + EXPECT_EQ(0, decoder_filter_callbacks_.clusterInfo() + ->statsScope() + .counterFromString("upstream_rq_4xx") + .value()); +} + // Test that a denied response results in the connection closing with a 403 response to the client. TEST_P(HttpFilterTestParam, DeniedResponseWith403) { InSequence s; From d1831a61a76a8586edebcdb4dab474c60000afec Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 1 Nov 2023 13:42:08 -0400 Subject: [PATCH 69/72] mobile: removing default build options from CI (#30666) Signed-off-by: Alyssa Wilk --- .github/workflows/mobile-compile_time_options.yml | 5 ----- .github/workflows/mobile-core.yml | 1 - 2 files changed, 6 deletions(-) diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml index 263185231dd6..e3d3f9c936c0 100644 --- a/.github/workflows/mobile-compile_time_options.yml +++ b/.github/workflows/mobile-compile_time_options.yml @@ -69,10 +69,7 @@ jobs: cd mobile ./bazelw build \ --config=mobile-remote-ci \ - --define=admin_html=disabled \ - --define=admin_functionality=disabled \ --define envoy_exceptions=disabled \ - --define envoy_mobile_listener=disabled \ --define=envoy_yaml=disabled \ --copt=-fno-unwind-tables \ --copt=-fno-exceptions \ @@ -103,7 +100,6 @@ jobs: --config=mobile-remote-ci \ --define=signal_trace=disabled \ --define=envoy_mobile_request_compression=disabled \ - --define=envoy_enable_http_datagrams=disabled \ --define=google_grpc=disabled \ --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ $TARGETS @@ -136,7 +132,6 @@ jobs: --define=envoy_mobile_request_compression=disabled \ --define=envoy_mobile_stats_reporting=disabled \ --define=envoy_mobile_swift_cxx_interop=disabled \ - --define=envoy_enable_http_datagrams=disabled \ --define=google_grpc=disabled \ --@envoy//bazel:http3=False \ --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ diff --git a/.github/workflows/mobile-core.yml b/.github/workflows/mobile-core.yml index 4630506537e9..460f1c7dcace 100644 --- a/.github/workflows/mobile-core.yml +++ b/.github/workflows/mobile-core.yml @@ -47,6 +47,5 @@ jobs: --build_tests_only \ --action_env=LD_LIBRARY_PATH \ --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - --define envoy_mobile_listener=disabled \ --config=mobile-remote-ci \ //test/common/... From 6b78796398528e6d9d5c60c24ab1f54bdc57db8e Mon Sep 17 00:00:00 2001 From: Yujian Zhao Date: Wed, 1 Nov 2023 15:21:26 -0700 Subject: [PATCH 70/72] Propagate route metadata in ext_authz (#30477) Add the ability to ext_authz that collect specified namespaces from route metadata, and propagate them to external auth service. #30252 The instruction of what namespace to select from route metadata, and the field in CheckRequest where the metadata context from route is filled are totally separate from those metadata context from connection or request. Risk Level: Low Testing: Unit tests Signed-off-by: Yujian Zhao --- .../filters/http/ext_authz/v3/ext_authz.proto | 14 +- .../service/auth/v3/attribute_context.proto | 5 +- changelogs/current.yaml | 10 + .../common/ext_authz/check_request_utils.cc | 2 + .../common/ext_authz/check_request_utils.h | 1 + .../filters/http/ext_authz/ext_authz.cc | 97 ++++++---- .../filters/http/ext_authz/ext_authz.h | 15 ++ .../ext_authz/check_request_utils_test.cc | 40 ++-- .../ext_authz/ext_authz_integration_test.cc | 1 + .../filters/http/ext_authz/ext_authz_test.cc | 171 ++++++++++++++++++ 10 files changed, 298 insertions(+), 58 deletions(-) diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index d81b8d8e8a35..9ea4703b6d71 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -28,7 +28,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization :ref:`configuration overview `. // [#extension: envoy.filters.http.ext_authz] -// [#next-free-field: 21] +// [#next-free-field: 23] message ExtAuthz { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v2.ExtAuthz"; @@ -120,6 +120,18 @@ message ExtAuthz { // repeated string typed_metadata_context_namespaces = 16; + // Specifies a list of route metadata namespaces whose values, if present, will be passed to the + // ext_authz service at :ref:`route_metadata_context ` in + // :ref:`CheckRequest `. + // :ref:`filter_metadata ` is passed as an opaque ``protobuf::Struct``. + repeated string route_metadata_context_namespaces = 21; + + // Specifies a list of route metadata namespaces whose values, if present, will be passed to the + // ext_authz service at :ref:`route_metadata_context ` in + // :ref:`CheckRequest `. + // :ref:`typed_filter_metadata ` is passed as an ``protobuf::Any``. + repeated string route_typed_metadata_context_namespaces = 22; + // Specifies if the filter is enabled. // // If :ref:`runtime_key ` is specified, diff --git a/api/envoy/service/auth/v3/attribute_context.proto b/api/envoy/service/auth/v3/attribute_context.proto index 77af84436de9..152672685bcc 100644 --- a/api/envoy/service/auth/v3/attribute_context.proto +++ b/api/envoy/service/auth/v3/attribute_context.proto @@ -38,7 +38,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // - field mask to send // - which return values from request_context are copied back // - which return values are copied into request_headers] -// [#next-free-field: 13] +// [#next-free-field: 14] message AttributeContext { option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v2.AttributeContext"; @@ -183,6 +183,9 @@ message AttributeContext { // Dynamic metadata associated with the request. config.core.v3.Metadata metadata_context = 11; + // Metadata associated with the selected route. + config.core.v3.Metadata route_metadata_context = 13; + // TLS session details of the underlying connection. // This is not populated by default and will be populated if ext_authz filter's // :ref:`include_tls_session ` is set to true. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 00f8ef6da138..ef01b2c786be 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -124,5 +124,15 @@ new_features: New config parameter :ref:`charge_cluster_response_stats ` for not incrementing cluster statistics on ext_authz response. Default true, no behavior change. +- area: ext_authz + change: | + forward :ref:`filter_metadata ` selected by + :ref:`route_metadata_context_namespaces + ` + and :ref:`typed_filter_metadata ` selected by + :ref:`route_typed_metadata_context_namespaces + ` + from the metadata of the selected route to external auth service. + This metadata propagation is independent from the dynamic metadata from connection and request. deprecated: diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.cc b/source/extensions/filters/common/ext_authz/check_request_utils.cc index 6d65dc7d4a4d..8a62aa1cdc98 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.cc +++ b/source/extensions/filters/common/ext_authz/check_request_utils.cc @@ -192,6 +192,7 @@ void CheckRequestUtils::createHttpCheck( const Envoy::Http::RequestHeaderMap& headers, Protobuf::Map&& context_extensions, envoy::config::core::v3::Metadata&& metadata_context, + envoy::config::core::v3::Metadata&& route_metadata_context, envoy::service::auth::v3::CheckRequest& request, uint64_t max_request_bytes, bool pack_as_bytes, bool include_peer_certificate, bool include_tls_session, const Protobuf::Map& destination_labels, @@ -224,6 +225,7 @@ void CheckRequestUtils::createHttpCheck( // Fill in the context extensions and metadata context. (*attrs->mutable_context_extensions()) = std::move(context_extensions); (*attrs->mutable_metadata_context()) = std::move(metadata_context); + (*attrs->mutable_route_metadata_context()) = std::move(route_metadata_context); } void CheckRequestUtils::createTcpCheck( diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.h b/source/extensions/filters/common/ext_authz/check_request_utils.h index 96d10334bcbb..1390485c0ae0 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.h +++ b/source/extensions/filters/common/ext_authz/check_request_utils.h @@ -93,6 +93,7 @@ class CheckRequestUtils { const Envoy::Http::RequestHeaderMap& headers, Protobuf::Map&& context_extensions, envoy::config::core::v3::Metadata&& metadata_context, + envoy::config::core::v3::Metadata&& route_metadata_context, envoy::service::auth::v3::CheckRequest& request, uint64_t max_request_bytes, bool pack_as_bytes, bool include_peer_certificate, bool include_tls_session, diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 7fad55fe9517..07db5ae73433 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -1,6 +1,9 @@ #include "source/extensions/filters/http/ext_authz/ext_authz.h" #include +#include +#include +#include #include "envoy/config/core/v3/base.pb.h" @@ -15,6 +18,46 @@ namespace Extensions { namespace HttpFilters { namespace ExtAuthz { +namespace { + +using MetadataProto = ::envoy::config::core::v3::Metadata; + +void fillMetadataContext(const std::vector& source_metadata, + const std::vector& metadata_context_namespaces, + const std::vector& typed_metadata_context_namespaces, + MetadataProto& metadata_context) { + for (const auto& context_key : metadata_context_namespaces) { + for (const MetadataProto* metadata : source_metadata) { + if (metadata == nullptr) { + continue; + } + const auto& filter_metadata = metadata->filter_metadata(); + if (const auto metadata_it = filter_metadata.find(context_key); + metadata_it != filter_metadata.end()) { + (*metadata_context.mutable_filter_metadata())[metadata_it->first] = metadata_it->second; + break; + } + } + } + + for (const auto& context_key : typed_metadata_context_namespaces) { + for (const MetadataProto* metadata : source_metadata) { + if (metadata == nullptr) { + continue; + } + const auto& typed_filter_metadata = metadata->typed_filter_metadata(); + if (const auto metadata_it = typed_filter_metadata.find(context_key); + metadata_it != typed_filter_metadata.end()) { + (*metadata_context.mutable_typed_filter_metadata())[metadata_it->first] = + metadata_it->second; + break; + } + } + } +} + +} // namespace + void FilterConfigPerRoute::merge(const FilterConfigPerRoute& other) { // We only merge context extensions here, and leave boolean flags untouched since those flags are // not used from the merged config. @@ -41,47 +84,29 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { context_extensions = maybe_merged_per_route_config.value().takeContextExtensions(); } + // If metadata_context_namespaces or typed_metadata_context_namespaces is specified, + // pass matching filter metadata to the ext_authz service. + // If metadata key is set in both the connection and request metadata, + // then the value will be the request metadata value. envoy::config::core::v3::Metadata metadata_context; - - // If metadata_context_namespaces is specified, pass matching filter metadata to the ext_authz - // service. If metadata key is set in both the connection and request metadata then the value - // will be the request metadata value. - const auto& connection_metadata = - decoder_callbacks_->connection()->streamInfo().dynamicMetadata().filter_metadata(); - const auto& request_metadata = - decoder_callbacks_->streamInfo().dynamicMetadata().filter_metadata(); - for (const auto& context_key : config_->metadataContextNamespaces()) { - if (const auto metadata_it = request_metadata.find(context_key); - metadata_it != request_metadata.end()) { - (*metadata_context.mutable_filter_metadata())[metadata_it->first] = metadata_it->second; - } else if (const auto metadata_it = connection_metadata.find(context_key); - metadata_it != connection_metadata.end()) { - (*metadata_context.mutable_filter_metadata())[metadata_it->first] = metadata_it->second; - } - } - - // If typed_metadata_context_namespaces is specified, pass matching typed filter metadata to the - // ext_authz service. If metadata key is set in both the connection and request metadata then - // the value will be the request metadata value. - const auto& connection_typed_metadata = - decoder_callbacks_->connection()->streamInfo().dynamicMetadata().typed_filter_metadata(); - const auto& request_typed_metadata = - decoder_callbacks_->streamInfo().dynamicMetadata().typed_filter_metadata(); - for (const auto& context_key : config_->typedMetadataContextNamespaces()) { - if (const auto metadata_it = request_typed_metadata.find(context_key); - metadata_it != request_typed_metadata.end()) { - (*metadata_context.mutable_typed_filter_metadata())[metadata_it->first] = metadata_it->second; - } else if (const auto metadata_it = connection_typed_metadata.find(context_key); - metadata_it != connection_typed_metadata.end()) { - (*metadata_context.mutable_typed_filter_metadata())[metadata_it->first] = metadata_it->second; - } + fillMetadataContext({&decoder_callbacks_->streamInfo().dynamicMetadata(), + &decoder_callbacks_->connection()->streamInfo().dynamicMetadata()}, + config_->metadataContextNamespaces(), + config_->typedMetadataContextNamespaces(), metadata_context); + + // Fill route_metadata_context from the selected route's metadata. + envoy::config::core::v3::Metadata route_metadata_context; + if (decoder_callbacks_->route() != nullptr) { + fillMetadataContext({&decoder_callbacks_->route()->metadata()}, + config_->routeMetadataContextNamespaces(), + config_->routeTypedMetadataContextNamespaces(), route_metadata_context); } Filters::Common::ExtAuthz::CheckRequestUtils::createHttpCheck( decoder_callbacks_, headers, std::move(context_extensions), std::move(metadata_context), - check_request_, config_->maxRequestBytes(), config_->packAsBytes(), - config_->includePeerCertificate(), config_->includeTLSSession(), config_->destinationLabels(), - config_->requestHeaderMatchers()); + std::move(route_metadata_context), check_request_, config_->maxRequestBytes(), + config_->packAsBytes(), config_->includePeerCertificate(), config_->includeTLSSession(), + config_->destinationLabels(), config_->requestHeaderMatchers()); ENVOY_STREAM_LOG(trace, "ext_authz filter calling authorization server", *decoder_callbacks_); // Store start time of ext_authz filter call diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index d49dec93c320..084bff2704e6 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -88,6 +88,11 @@ class FilterConfig { config.metadata_context_namespaces().end()), typed_metadata_context_namespaces_(config.typed_metadata_context_namespaces().begin(), config.typed_metadata_context_namespaces().end()), + route_metadata_context_namespaces_(config.route_metadata_context_namespaces().begin(), + config.route_metadata_context_namespaces().end()), + route_typed_metadata_context_namespaces_( + config.route_typed_metadata_context_namespaces().begin(), + config.route_typed_metadata_context_namespaces().end()), include_peer_certificate_(config.include_peer_certificate()), include_tls_session_(config.include_tls_session()), charge_cluster_response_stats_( @@ -169,6 +174,14 @@ class FilterConfig { return typed_metadata_context_namespaces_; } + const std::vector& routeMetadataContextNamespaces() { + return route_metadata_context_namespaces_; + } + + const std::vector& routeTypedMetadataContextNamespaces() { + return route_typed_metadata_context_namespaces_; + } + const ExtAuthzFilterStats& stats() const { return stats_; } void incCounter(Stats::Scope& scope, Stats::StatName name) { @@ -231,6 +244,8 @@ class FilterConfig { const std::vector metadata_context_namespaces_; const std::vector typed_metadata_context_namespaces_; + const std::vector route_metadata_context_namespaces_; + const std::vector route_typed_metadata_context_namespaces_; const bool include_peer_certificate_; const bool include_tls_session_; diff --git a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc index 83674fc8f177..9486088ba523 100644 --- a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc +++ b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc @@ -66,11 +66,11 @@ class CheckRequestUtilsTest : public testing::Test { auto metadata_val = MessageUtil::keyValueStruct("foo", "bar"); (*metadata_context.mutable_filter_metadata())["meta.key"] = metadata_val; - CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, std::move(context_extensions), - std::move(metadata_context), request, - /*max_request_bytes=*/0, /*pack_as_bytes=*/false, - include_peer_certificate, want_tls_session != nullptr, - labels, nullptr); + CheckRequestUtils::createHttpCheck( + &callbacks_, request_headers, std::move(context_extensions), std::move(metadata_context), + envoy::config::core::v3::Metadata(), request, /*max_request_bytes=*/0, + /*pack_as_bytes=*/false, include_peer_certificate, want_tls_session != nullptr, labels, + nullptr); EXPECT_EQ("source", request.attributes().source().principal()); EXPECT_EQ("destination", request.attributes().destination().principal()); @@ -78,7 +78,6 @@ class CheckRequestUtilsTest : public testing::Test { EXPECT_EQ("value", request.attributes().context_extensions().at("key")); EXPECT_EQ("value_1", request.attributes().destination().labels().at("label_1")); EXPECT_EQ("value_2", request.attributes().destination().labels().at("label_2")); - EXPECT_EQ("bar", request.attributes() .metadata_context() .filter_metadata() @@ -86,6 +85,7 @@ class CheckRequestUtilsTest : public testing::Test { .fields() .at("foo") .string_value()); + EXPECT_TRUE(request.attributes().has_route_metadata_context()); if (include_peer_certificate) { EXPECT_EQ(cert_data_, request.attributes().source().certificate()); @@ -190,7 +190,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttp) { expectBasicHttp(); CheckRequestUtils::createHttpCheck( &callbacks_, request_headers, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, size, + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, /*pack_as_bytes=*/false, /*include_peer_certificate=*/false, /*include_tls_session=*/false, Protobuf::Map(), nullptr); ASSERT_EQ(size, request_.attributes().request().http().body().size()); @@ -218,9 +218,9 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithDuplicateHeaders) { expectBasicHttp(); CheckRequestUtils::createHttpCheck( &callbacks_, request_headers, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, size, - /*pack_as_bytes=*/false, /*include_peer_certificate=*/false, - /*include_tls_session=*/false, Protobuf::Map(), nullptr); + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, + /*pack_as_bytes=*/false, /*include_peer_certificate=*/false, /*include_tls_session=*/false, + Protobuf::Map(), nullptr); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_EQ(",foo,bar", request_.attributes().request().http().headers().at("x-duplicate-header")); @@ -247,7 +247,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithRequestHeaderMatchers) { CheckRequestUtils::createHttpCheck( &callbacks_, request_headers, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, size, + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, /*pack_as_bytes=*/false, /*include_peer_certificate=*/false, /*include_tls_session=*/false, Protobuf::Map(), createRequestHeaderMatchers()); ASSERT_EQ(size, request_.attributes().request().http().body().size()); @@ -270,9 +270,9 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithPartialBody) { expectBasicHttp(); CheckRequestUtils::createHttpCheck( &callbacks_, headers_, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, size, - /*pack_as_bytes=*/false, /*include_peer_certificate=*/false, - /*include_tls_session=*/false, Protobuf::Map(), nullptr); + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, + /*pack_as_bytes=*/false, /*include_peer_certificate=*/false, /*include_tls_session=*/false, + Protobuf::Map(), nullptr); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_EQ("true", request_.attributes().request().http().headers().at( @@ -290,9 +290,9 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBody) { expectBasicHttp(); CheckRequestUtils::createHttpCheck( &callbacks_, headers_, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, buffer_->length(), /*pack_as_bytes=*/false, - /*include_peer_certificate=*/false, /*include_tls_session=*/false, - Protobuf::Map(), nullptr); + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, + buffer_->length(), /*pack_as_bytes=*/false, /*include_peer_certificate=*/false, + /*include_tls_session=*/false, Protobuf::Map(), nullptr); ASSERT_EQ(buffer_->length(), request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, buffer_->length()), request_.attributes().request().http().body()); @@ -323,9 +323,9 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBodyPackAsBytes) { // request_.SerializeToString() still returns "true" when it is failed to serialize the data. CheckRequestUtils::createHttpCheck( &callbacks_, headers_, Protobuf::Map(), - envoy::config::core::v3::Metadata(), request_, buffer_->length(), /*pack_as_bytes=*/true, - /*include_peer_certificate=*/false, /*include_tls_session=*/false, - Protobuf::Map(), nullptr); + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, + buffer_->length(), /*pack_as_bytes=*/true, /*include_peer_certificate=*/false, + /*include_tls_session=*/false, Protobuf::Map(), nullptr); // TODO(dio): Find a way to test this without using function from testing::internal namespace. testing::internal::CaptureStderr(); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index 5ce46084511f..a7b48eb4f768 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -184,6 +184,7 @@ class ExtAuthzGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, attributes->clear_source(); attributes->clear_destination(); attributes->clear_metadata_context(); + attributes->clear_route_metadata_context(); attributes->mutable_request()->clear_time(); http_request->clear_id(); http_request->clear_headers(); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index d67b842d4836..6f902ef0e36f 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -36,9 +36,12 @@ using Envoy::Http::LowerCaseString; using testing::_; +using testing::Contains; using testing::InSequence; using testing::Invoke; +using testing::Key; using testing::NiceMock; +using testing::Not; using testing::Return; using testing::ReturnRef; using testing::Values; @@ -1508,6 +1511,174 @@ TEST_F(HttpFilterTest, ConnectionMetadataContext) { "not.selected.data")); } +// Verifies that specified route metadata is passed along in the check request +TEST_F(HttpFilterTest, RouteMetadataContext) { + initialize(R"EOF( + transport_api_version: V3 + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + route_metadata_context_namespaces: + - request.connection.route.have.data + - request.route.have.data + - connection.route.have.data + - route.has.data + - request.has.data + - untyped.and.typed.route.data + - typed.route.data + - untyped.route.data + route_typed_metadata_context_namespaces: + - untyped.and.typed.route.data + - typed.route.data + - untyped.route.data + metadata_context_namespaces: + - request.connection.route.have.data + - request.route.have.data + - connection.route.have.data + - connection.has.data + - route.has.data + )EOF"); + + const std::string route_yaml = R"EOF( + filter_metadata: + request.connection.route.have.data: + data: route + request.route.have.data: + data: route + connection.route.have.data: + data: route + route.has.data: + data: route + untyped.and.typed.route.data: + data: route_untyped + untyped.route.data: + data: route_untyped + typed_filter_metadata: + untyped.and.typed.route.data: + '@type': type.googleapis.com/helloworld.HelloRequest + name: route_typed + typed.route.data: + '@type': type.googleapis.com/helloworld.HelloRequest + name: route_typed + )EOF"; + + const std::string request_yaml = R"EOF( + filter_metadata: + request.connection.route.have.data: + data: request + request.route.have.data: + data: request + )EOF"; + + const std::string connection_yaml = R"EOF( + filter_metadata: + request.connection.route.have.data: + data: connection + connection.route.have.data: + data: connection + connection.has.data: + data: connection + )EOF"; + + prepareCheck(); + + envoy::config::core::v3::Metadata request_metadata, connection_metadata, route_metadata; + TestUtility::loadFromYaml(request_yaml, request_metadata); + TestUtility::loadFromYaml(connection_yaml, connection_metadata); + TestUtility::loadFromYaml(route_yaml, route_metadata); + ON_CALL(decoder_filter_callbacks_.stream_info_, dynamicMetadata()) + .WillByDefault(ReturnRef(request_metadata)); + connection_.stream_info_.metadata_ = connection_metadata; + ON_CALL(*decoder_filter_callbacks_.route_, metadata()).WillByDefault(ReturnRef(route_metadata)); + + envoy::service::auth::v3::CheckRequest check_request; + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks&, + const envoy::service::auth::v3::CheckRequest& check_param, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { check_request = check_param; })); + + filter_->decodeHeaders(request_headers_, false); + Http::MetadataMap metadata_map{{"metadata", "metadata"}}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter_->decodeMetadata(metadata_map)); + + for (const auto& namespace_from_route : std::vector{ + "request.connection.route.have.data", + "request.route.have.data", + "connection.route.have.data", + "route.has.data", + }) { + ASSERT_THAT(check_request.attributes().route_metadata_context().filter_metadata(), + Contains(Key(namespace_from_route))); + EXPECT_EQ("route", check_request.attributes() + .route_metadata_context() + .filter_metadata() + .at(namespace_from_route) + .fields() + .at("data") + .string_value()); + } + EXPECT_THAT(check_request.attributes().route_metadata_context().filter_metadata(), + Not(Contains(Key("request.has.data")))); + + for (const auto& namespace_from_request : + std::vector{"request.connection.route.have.data", "request.route.have.data"}) { + ASSERT_THAT(check_request.attributes().metadata_context().filter_metadata(), + Contains(Key(namespace_from_request))); + EXPECT_EQ("request", check_request.attributes() + .metadata_context() + .filter_metadata() + .at(namespace_from_request) + .fields() + .at("data") + .string_value()); + } + for (const auto& namespace_from_connection : + std::vector{"connection.route.have.data", "connection.has.data"}) { + ASSERT_THAT(check_request.attributes().metadata_context().filter_metadata(), + Contains(Key(namespace_from_connection))); + EXPECT_EQ("connection", check_request.attributes() + .metadata_context() + .filter_metadata() + .at(namespace_from_connection) + .fields() + .at("data") + .string_value()); + } + EXPECT_THAT(check_request.attributes().metadata_context().filter_metadata(), + Not(Contains(Key("route.has.data")))); + + for (const auto& namespace_from_route_untyped : + std::vector{"untyped.and.typed.route.data", "untyped.route.data"}) { + ASSERT_THAT(check_request.attributes().route_metadata_context().filter_metadata(), + Contains(Key(namespace_from_route_untyped))); + EXPECT_EQ("route_untyped", check_request.attributes() + .route_metadata_context() + .filter_metadata() + .at(namespace_from_route_untyped) + .fields() + .at("data") + .string_value()); + } + EXPECT_THAT(check_request.attributes().route_metadata_context().filter_metadata(), + Not(Contains(Key("typed.route.data")))); + + for (const auto& namespace_from_route_typed : + std::vector{"untyped.and.typed.route.data", "typed.route.data"}) { + ASSERT_THAT(check_request.attributes().route_metadata_context().typed_filter_metadata(), + Contains(Key(namespace_from_route_typed))); + helloworld::HelloRequest hello; + EXPECT_TRUE(check_request.attributes() + .route_metadata_context() + .typed_filter_metadata() + .at(namespace_from_route_typed) + .UnpackTo(&hello)); + EXPECT_EQ("route_typed", hello.name()); + } + EXPECT_THAT(check_request.attributes().route_metadata_context().typed_filter_metadata(), + Not(Contains(Key("untyped.route.data")))); +} + // Test that filter can be disabled via the filter_enabled field. TEST_F(HttpFilterTest, FilterDisabled) { initialize(R"EOF( From 3d04be9609cc073e396f76c8cbe9996d17656c3e Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Thu, 2 Nov 2023 02:27:45 +0000 Subject: [PATCH 71/72] mobile: Implement JniHelper (#30641) This PR implements `JniHelper`, which is a small wrapper around JNI API with memory-safety. Currently, `JniHelper` does not wrap every function available in the JNI API. It only wraps functions that are currently used by Envoy Mobile. However, it should be easy to wrap more functions if needed in the future. This PR also adds an ability to test JNI functions with `-Xcheck:jni` enabled to conform with JNI best practices. Signed-off-by: Fredy Wijaya --- mobile/library/common/jni/BUILD | 14 + mobile/library/common/jni/jni_helper.cc | 226 +++++++++++++ mobile/library/common/jni/jni_helper.h | 317 ++++++++++++++++++ mobile/test/common/jni/BUILD | 20 ++ mobile/test/common/jni/jni_helper_test.cc | 186 ++++++++++ .../java/io/envoyproxy/envoymobile/jni/BUILD | 12 + .../envoymobile/jni/JniHelperTest.java | 299 +++++++++++++++++ 7 files changed, 1074 insertions(+) create mode 100644 mobile/library/common/jni/jni_helper.cc create mode 100644 mobile/library/common/jni/jni_helper.h create mode 100644 mobile/test/common/jni/jni_helper_test.cc create mode 100644 mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD create mode 100644 mobile/test/java/io/envoyproxy/envoymobile/jni/JniHelperTest.java diff --git a/mobile/library/common/jni/BUILD b/mobile/library/common/jni/BUILD index 6dfa3a14842a..15097aedcb3d 100644 --- a/mobile/library/common/jni/BUILD +++ b/mobile/library/common/jni/BUILD @@ -47,6 +47,20 @@ cc_library( ], ) +cc_library( + name = "jni_helper_lib", + srcs = [ + "jni_helper.cc", + ], + hdrs = [ + "jni_helper.h", + ], + deps = [ + "//library/common/jni/import:jni_import_lib", + "@envoy//source/common/common:assert_lib", + ], +) + # Implementations of the various "native" Java methods for classes # in library/java/io/envoyproxy/envoymobile. # TODO(RyanTheOptimist): Is there a better name for this? I'm not sure what diff --git a/mobile/library/common/jni/jni_helper.cc b/mobile/library/common/jni/jni_helper.cc new file mode 100644 index 000000000000..ead2d6a85f7a --- /dev/null +++ b/mobile/library/common/jni/jni_helper.cc @@ -0,0 +1,226 @@ +#include "library/common/jni/jni_helper.h" + +#include "source/common/common/assert.h" + +namespace Envoy { +namespace JNI { + +jmethodID JniHelper::getMethodId(jclass clazz, const char* name, const char* signature) { + jmethodID method_id = env_->GetMethodID(clazz, name, signature); + rethrowException(); + return method_id; +} + +jmethodID JniHelper::getStaticMethodId(jclass clazz, const char* name, const char* signature) { + jmethodID method_id = env_->GetStaticMethodID(clazz, name, signature); + rethrowException(); + return method_id; +} + +LocalRefUniquePtr JniHelper::findClass(const char* class_name) { + LocalRefUniquePtr result(env_->FindClass(class_name), LocalRefDeleter(env_)); + rethrowException(); + return result; +} + +LocalRefUniquePtr JniHelper::getObjectClass(jobject object) { + return {env_->GetObjectClass(object), LocalRefDeleter(env_)}; +} + +void JniHelper::throwNew(const char* java_class_name, const char* message) { + LocalRefUniquePtr java_class = findClass(java_class_name); + if (java_class != nullptr) { + jint error = env_->ThrowNew(java_class.get(), message); + RELEASE_ASSERT(error == JNI_OK, fmt::format("Failed calling ThrowNew.")); + } +} + +LocalRefUniquePtr JniHelper::exceptionOccurred() { + return {env_->ExceptionOccurred(), LocalRefDeleter(env_)}; +} + +GlobalRefUniquePtr JniHelper::newGlobalRef(jobject object) { + GlobalRefUniquePtr result(env_->NewGlobalRef(object), GlobalRefDeleter(env_)); + RELEASE_ASSERT(result != nullptr, "Failed calling NewGlobalRef."); + return result; +} + +LocalRefUniquePtr JniHelper::newObject(jclass clazz, jmethodID method_id, ...) { + va_list args; + va_start(args, method_id); + LocalRefUniquePtr result(env_->NewObjectV(clazz, method_id, args), + LocalRefDeleter(env_)); + rethrowException(); + va_end(args); + return result; +} + +LocalRefUniquePtr JniHelper::newStringUtf(const char* str) { + LocalRefUniquePtr result(env_->NewStringUTF(str), LocalRefDeleter(env_)); + rethrowException(); + return result; +} + +StringUtfUniquePtr JniHelper::getStringUtfChars(jstring str, jboolean* is_copy) { + StringUtfUniquePtr result(env_->GetStringUTFChars(str, is_copy), StringUtfDeleter(env_, str)); + rethrowException(); + return result; +} + +jsize JniHelper::getArrayLength(jarray array) { return env_->GetArrayLength(array); } + +#define DEFINE_NEW_ARRAY(JAVA_TYPE, JNI_TYPE) \ + LocalRefUniquePtr JniHelper::new##JAVA_TYPE##Array(jsize length) { \ + LocalRefUniquePtr result(env_->New##JAVA_TYPE##Array(length), \ + LocalRefDeleter(env_)); \ + rethrowException(); \ + return result; \ + } + +DEFINE_NEW_ARRAY(Byte, jbyteArray) +DEFINE_NEW_ARRAY(Char, jcharArray) +DEFINE_NEW_ARRAY(Short, jshortArray) +DEFINE_NEW_ARRAY(Int, jintArray) +DEFINE_NEW_ARRAY(Long, jlongArray) +DEFINE_NEW_ARRAY(Float, jfloatArray) +DEFINE_NEW_ARRAY(Double, jdoubleArray) +DEFINE_NEW_ARRAY(Boolean, jbooleanArray) + +LocalRefUniquePtr JniHelper::newObjectArray(jsize length, jclass element_class, + jobject initial_element) { + LocalRefUniquePtr result( + env_->NewObjectArray(length, element_class, initial_element), LocalRefDeleter(env_)); + + return result; +} + +#define DEFINE_GET_ARRAY_ELEMENTS(JAVA_TYPE, JNI_ARRAY_TYPE, JNI_ELEMENT_TYPE) \ + ArrayElementsUniquePtr \ + JniHelper::get##JAVA_TYPE##ArrayElements(JNI_ARRAY_TYPE array, jboolean* is_copy) { \ + ArrayElementsUniquePtr result( \ + env_->Get##JAVA_TYPE##ArrayElements(array, is_copy), \ + ArrayElementsDeleter(env_, array)); \ + rethrowException(); \ + return result; \ + } + +DEFINE_GET_ARRAY_ELEMENTS(Byte, jbyteArray, jbyte) +DEFINE_GET_ARRAY_ELEMENTS(Char, jcharArray, jchar) +DEFINE_GET_ARRAY_ELEMENTS(Short, jshortArray, jshort) +DEFINE_GET_ARRAY_ELEMENTS(Int, jintArray, jint) +DEFINE_GET_ARRAY_ELEMENTS(Long, jlongArray, jlong) +DEFINE_GET_ARRAY_ELEMENTS(Float, jfloatArray, jfloat) +DEFINE_GET_ARRAY_ELEMENTS(Double, jdoubleArray, jdouble) +DEFINE_GET_ARRAY_ELEMENTS(Boolean, jbooleanArray, jboolean) + +LocalRefUniquePtr JniHelper::getObjectArrayElement(jobjectArray array, jsize index) { + LocalRefUniquePtr result(env_->GetObjectArrayElement(array, index), + LocalRefDeleter(env_)); + rethrowException(); + return result; +} + +void JniHelper::setObjectArrayElement(jobjectArray array, jsize index, jobject value) { + env_->SetObjectArrayElement(array, index, value); + rethrowException(); +} + +PrimitiveArrayCriticalUniquePtr JniHelper::getPrimitiveArrayCritical(jarray array, + jboolean* is_copy) { + PrimitiveArrayCriticalUniquePtr result(env_->GetPrimitiveArrayCritical(array, is_copy), + PrimitiveArrayCriticalDeleter(env_, array)); + rethrowException(); + return result; +} + +#define DEFINE_CALL_METHOD(JAVA_TYPE, JNI_TYPE) \ + JNI_TYPE JniHelper::call##JAVA_TYPE##Method(jobject object, jmethodID method_id, ...) { \ + va_list args; \ + va_start(args, method_id); \ + JNI_TYPE result = env_->Call##JAVA_TYPE##MethodV(object, method_id, args); \ + va_end(args); \ + rethrowException(); \ + return result; \ + } + +DEFINE_CALL_METHOD(Byte, jbyte) +DEFINE_CALL_METHOD(Char, jchar) +DEFINE_CALL_METHOD(Short, jshort) +DEFINE_CALL_METHOD(Int, jint) +DEFINE_CALL_METHOD(Long, jlong) +DEFINE_CALL_METHOD(Float, jfloat) +DEFINE_CALL_METHOD(Double, jdouble) +DEFINE_CALL_METHOD(Boolean, jboolean) + +void JniHelper::callVoidMethod(jobject object, jmethodID method_id, ...) { + va_list args; + va_start(args, method_id); + env_->CallVoidMethodV(object, method_id, args); + va_end(args); + rethrowException(); +} + +LocalRefUniquePtr JniHelper::callObjectMethod(jobject object, jmethodID method_id, ...) { + va_list args; + va_start(args, method_id); + LocalRefUniquePtr result(env_->CallObjectMethodV(object, method_id, args), + LocalRefDeleter(env_)); + va_end(args); + rethrowException(); + return result; +} + +#define DEFINE_CALL_STATIC_METHOD(JAVA_TYPE, JNI_TYPE) \ + JNI_TYPE JniHelper::callStatic##JAVA_TYPE##Method(jclass clazz, jmethodID method_id, ...) { \ + va_list args; \ + va_start(args, method_id); \ + JNI_TYPE result = env_->CallStatic##JAVA_TYPE##MethodV(clazz, method_id, args); \ + va_end(args); \ + rethrowException(); \ + return result; \ + } + +DEFINE_CALL_STATIC_METHOD(Byte, jbyte) +DEFINE_CALL_STATIC_METHOD(Char, jchar) +DEFINE_CALL_STATIC_METHOD(Short, jshort) +DEFINE_CALL_STATIC_METHOD(Int, jint) +DEFINE_CALL_STATIC_METHOD(Long, jlong) +DEFINE_CALL_STATIC_METHOD(Float, jfloat) +DEFINE_CALL_STATIC_METHOD(Double, jdouble) +DEFINE_CALL_STATIC_METHOD(Boolean, jboolean) + +void JniHelper::callStaticVoidMethod(jclass clazz, jmethodID method_id, ...) { + va_list args; + va_start(args, method_id); + env_->CallStaticVoidMethodV(clazz, method_id, args); + va_end(args); + rethrowException(); +} + +LocalRefUniquePtr JniHelper::callStaticObjectMethod(jclass clazz, jmethodID method_id, + ...) { + va_list args; + va_start(args, method_id); + LocalRefUniquePtr result(env_->CallStaticObjectMethodV(clazz, method_id, args), + LocalRefDeleter(env_)); + va_end(args); + rethrowException(); + return result; +} + +jlong JniHelper::getDirectBufferCapacity(jobject buffer) { + jlong result = env_->GetDirectBufferCapacity(buffer); + RELEASE_ASSERT(result != -1, "Failed calling GetDirectBufferCapacity."); + return result; +} + +void JniHelper::rethrowException() { + if (env_->ExceptionCheck()) { + auto throwable = exceptionOccurred(); + env_->ExceptionClear(); + env_->Throw(throwable.release()); + } +} + +} // namespace JNI +} // namespace Envoy diff --git a/mobile/library/common/jni/jni_helper.h b/mobile/library/common/jni/jni_helper.h new file mode 100644 index 000000000000..6ac0388f5b05 --- /dev/null +++ b/mobile/library/common/jni/jni_helper.h @@ -0,0 +1,317 @@ +#pragma once + +#include + +#include "library/common/jni/import/jni_import.h" + +namespace Envoy { +namespace JNI { + +/** A custom deleter to delete JNI global ref. */ +class GlobalRefDeleter { +public: + explicit GlobalRefDeleter(JNIEnv* env) : env_(env) {} + + void operator()(jobject object) const { + if (object != nullptr) { + env_->DeleteGlobalRef(object); + } + } + +private: + JNIEnv* const env_; +}; + +/** A unique pointer for JNI global ref. */ +template +using GlobalRefUniquePtr = std::unique_ptr::type, GlobalRefDeleter>; + +/** A custom deleter to delete JNI local ref. */ +class LocalRefDeleter { +public: + explicit LocalRefDeleter(JNIEnv* env) : env_(env) {} + + void operator()(jobject object) const { + if (object != nullptr) { + env_->DeleteLocalRef(object); + } + } + +private: + JNIEnv* const env_; +}; + +/** A unique pointer for JNI local ref. */ +template +using LocalRefUniquePtr = std::unique_ptr::type, LocalRefDeleter>; + +/** A custom deleter for UTF strings. */ +class StringUtfDeleter { +public: + StringUtfDeleter(JNIEnv* env, jstring j_str) : env_(env), j_str_(j_str) {} + + void operator()(const char* c_str) const { + if (c_str != nullptr) { + env_->ReleaseStringUTFChars(j_str_, c_str); + } + } + +private: + JNIEnv* const env_; + jstring j_str_; +}; + +/** A unique pointer for JNI UTF string. */ +using StringUtfUniquePtr = std::unique_ptr; + +/** A custom deleter to delete JNI array elements. */ +template class ArrayElementsDeleter { +public: + ArrayElementsDeleter(JNIEnv* env, ArrayType array) : env_(env), array_(array) {} + + void operator()(ElementType* elements) const { + if (elements == nullptr) { + return; + } + if constexpr (std::is_same_v) { + env_->ReleaseByteArrayElements(array_, elements, 0); + } else if constexpr (std::is_same_v) { + env_->ReleaseCharArrayElements(array_, elements, 0); + } else if constexpr (std::is_same_v) { + env_->ReleaseShortArrayElements(array_, elements, 0); + } else if constexpr (std::is_same_v) { + env_->ReleaseIntArrayElements(array_, elements, 0); + } else if constexpr (std::is_same_v) { + env_->ReleaseLongArrayElements(array_, elements, 0); + } else if constexpr (std::is_same_v) { + env_->ReleaseFloatArrayElements(array_, elements, 0); + } else if constexpr (std::is_same_v) { + env_->ReleaseDoubleArrayElements(array_, elements, 0); + } else if constexpr (std::is_same_v) { + env_->ReleaseBooleanArrayElements(array_, elements, 0); + } + } + +private: + JNIEnv* const env_; + ArrayType array_; +}; + +/** A unique pointer for JNI array elements. */ +template +using ArrayElementsUniquePtr = std::unique_ptr< + typename std::remove_pointer::type, + ArrayElementsDeleter::type>>; + +/** A custom deleter for JNI primitive array critical. */ +class PrimitiveArrayCriticalDeleter { +public: + PrimitiveArrayCriticalDeleter(JNIEnv* env, jarray array) : env_(env), array_(array) {} + + void operator()(void* c_array) const { + if (c_array != nullptr) { + env_->ReleasePrimitiveArrayCritical(array_, c_array, 0); + } + } + +private: + JNIEnv* const env_; + jarray array_; +}; + +/** A unique pointer for JNI primitive array critical. */ +using PrimitiveArrayCriticalUniquePtr = std::unique_ptr; + +/** + * A thin wrapper around JNI API with memory-safety. + * + * NOTE: Do not put any other helper functions that are not part of the JNI API here. + */ +class JniHelper { +public: + explicit JniHelper(JNIEnv* env) : env_(env) {} + + /** + * Gets the object method with the given signature. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#getmethodid + */ + jmethodID getMethodId(jclass clazz, const char* name, const char* signature); + + /** + * Gets the static method with the given signature. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#getstaticmethodid + */ + jmethodID getStaticMethodId(jclass clazz, const char* name, const char* signature); + + /** + * Finds the given `class_name` using Java classloader. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#findclass + */ + LocalRefUniquePtr findClass(const char* class_name); + + /** + * Returns the class of a given `object`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#getobjectclass + */ + LocalRefUniquePtr getObjectClass(jobject object); + + /** + * Throws Java exception with the specified class name and error message. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#thrownew + */ + void throwNew(const char* java_class_name, const char* message); + + /** + * Determines if an exception is being thrown. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#exceptionoccurred + */ + LocalRefUniquePtr exceptionOccurred(); + + /** + * Creates a new global reference to the object referred to by the `object` argument. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#newglobalref + */ + GlobalRefUniquePtr newGlobalRef(jobject object); + + /** + * Creates a new instance of a given `clazz` from the given `method_id`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#newobject-newobjecta-newobjectv + */ + LocalRefUniquePtr newObject(jclass clazz, jmethodID method_id, ...); + + /** + * Creates a new Java string from the given `str`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#newstringutf + */ + LocalRefUniquePtr newStringUtf(const char* str); + + /** Gets the pointer to an array of bytes representing `str`. */ + StringUtfUniquePtr getStringUtfChars(jstring str, jboolean* is_copy); + + /** + * Gets the size of the array. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#getarraylength + */ + jsize getArrayLength(jarray array); + +/** A macro to create `NewArray`. helper function. */ +#define DECLARE_NEW_ARRAY(JAVA_TYPE, JNI_TYPE) \ + LocalRefUniquePtr new##JAVA_TYPE##Array(jsize length); + + /** + * Helper functions for `NewArray`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#newprimitivetypearray-routines + */ + DECLARE_NEW_ARRAY(Byte, jbyteArray) + DECLARE_NEW_ARRAY(Char, jcharArray) + DECLARE_NEW_ARRAY(Short, jshortArray) + DECLARE_NEW_ARRAY(Int, jintArray) + DECLARE_NEW_ARRAY(Long, jlongArray) + DECLARE_NEW_ARRAY(Float, jfloatArray) + DECLARE_NEW_ARRAY(Double, jdoubleArray) + DECLARE_NEW_ARRAY(Boolean, jbooleanArray) + LocalRefUniquePtr newObjectArray(jsize length, jclass element_class, + jobject initial_element = nullptr); + +/** A macro to create `GetArrayElement` function. */ +#define DECLARE_GET_ARRAY_ELEMENTS(JAVA_TYPE, JNI_ARRAY_TYPE, JNI_ELEMENT_TYPE) \ + ArrayElementsUniquePtr get##JAVA_TYPE##ArrayElements( \ + JNI_ARRAY_TYPE array, jboolean* is_copy); + + /** + * Helper functions for `GetArrayElements`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#getprimitivetypearrayelements-routines + */ + DECLARE_GET_ARRAY_ELEMENTS(Byte, jbyteArray, jbyte) + DECLARE_GET_ARRAY_ELEMENTS(Char, jcharArray, jchar) + DECLARE_GET_ARRAY_ELEMENTS(Short, jshortArray, jshort) + DECLARE_GET_ARRAY_ELEMENTS(Int, jintArray, jint) + DECLARE_GET_ARRAY_ELEMENTS(Long, jlongArray, jlong) + DECLARE_GET_ARRAY_ELEMENTS(Float, jfloatArray, jfloat) + DECLARE_GET_ARRAY_ELEMENTS(Double, jdoubleArray, jdouble) + DECLARE_GET_ARRAY_ELEMENTS(Boolean, jbooleanArray, jboolean) + + /** + * Gets an element of a given `array` with the specified `index`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#getobjectarrayelement + */ + LocalRefUniquePtr getObjectArrayElement(jobjectArray array, jsize index); + + /** + * Sets an element of a given `array` with the specified `index. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#setobjectarrayelement + */ + void setObjectArrayElement(jobjectArray array, jsize index, jobject value); + + PrimitiveArrayCriticalUniquePtr getPrimitiveArrayCritical(jarray array, jboolean* is_copy); + +/** A macro to create `CallMethod` helper function. */ +#define DECLARE_CALL_METHOD(JAVA_TYPE, JNI_TYPE) \ + JNI_TYPE call##JAVA_TYPE##Method(jobject object, jmethodID method_id, ...); + + /** + * Helper functions for `CallMethod`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#calltypemethod-routines-calltypemethoda-routines-calltypemethodv-routines + */ + DECLARE_CALL_METHOD(Byte, jbyte) + DECLARE_CALL_METHOD(Char, jchar) + DECLARE_CALL_METHOD(Short, jshort) + DECLARE_CALL_METHOD(Int, jint) + DECLARE_CALL_METHOD(Long, jlong) + DECLARE_CALL_METHOD(Float, jfloat) + DECLARE_CALL_METHOD(Double, jdouble) + DECLARE_CALL_METHOD(Boolean, jboolean) + void callVoidMethod(jobject object, jmethodID method_id, ...); + LocalRefUniquePtr callObjectMethod(jobject object, jmethodID method_id, ...); + +/** A macro to create `CallStaticMethod` helper function. */ +#define DECLARE_CALL_STATIC_METHOD(JAVA_TYPE, JNI_TYPE) \ + JNI_TYPE callStatic##JAVA_TYPE##Method(jclass clazz, jmethodID method_id, ...); + + /** + * Helper functions for `CallStaticMethod`. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#callstatictypemethod-routines-callstatictypemethoda-routines-callstatictypemethodv-routines + */ + DECLARE_CALL_STATIC_METHOD(Byte, jbyte) + DECLARE_CALL_STATIC_METHOD(Char, jchar) + DECLARE_CALL_STATIC_METHOD(Short, jshort) + DECLARE_CALL_STATIC_METHOD(Int, jint) + DECLARE_CALL_STATIC_METHOD(Long, jlong) + DECLARE_CALL_STATIC_METHOD(Float, jfloat) + DECLARE_CALL_STATIC_METHOD(Double, jdouble) + DECLARE_CALL_STATIC_METHOD(Boolean, jboolean) + void callStaticVoidMethod(jclass clazz, jmethodID method_id, ...); + LocalRefUniquePtr callStaticObjectMethod(jclass clazz, jmethodID method_id, ...); + + /** + * Returns the capacity of the memory region referenced by the given `java.nio.Buffer` object. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#getdirectbuffercapacity + */ + jlong getDirectBufferCapacity(jobject buffer); + +private: + /** Rethrows the Java exception occurred. */ + void rethrowException(); + + JNIEnv* const env_; +}; + +} // namespace JNI +} // namespace Envoy diff --git a/mobile/test/common/jni/BUILD b/mobile/test/common/jni/BUILD index 42e43f48749f..caa7d222ae37 100644 --- a/mobile/test/common/jni/BUILD +++ b/mobile/test/common/jni/BUILD @@ -83,3 +83,23 @@ envoy_mobile_so_to_jni_lib( testonly = True, native_dep = "libenvoy_jni_with_test_and_listener_extensions.so", ) + +cc_library( + name = "jni_helper_test_lib", + srcs = [ + "jni_helper_test.cc", + ], + deps = [ + "//library/common/jni:jni_helper_lib", + ], + alwayslink = True, +) + +cc_binary( + name = "libenvoy_jni_helper_test.so", + testonly = True, + linkshared = True, + deps = [ + ":jni_helper_test_lib", + ], +) diff --git a/mobile/test/common/jni/jni_helper_test.cc b/mobile/test/common/jni/jni_helper_test.cc new file mode 100644 index 000000000000..d687192ec6a2 --- /dev/null +++ b/mobile/test/common/jni/jni_helper_test.cc @@ -0,0 +1,186 @@ +#include + +#include "library/common/jni/jni_helper.h" + +// NOLINT(namespace-envoy) + +// This file contains JNI implementation used by +// `test/java/io/envoyproxy/envoymobile/jni/JniHelperTest.java` unit tests. + +extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_getMethodId( + JNIEnv* env, jclass, jclass clazz, jstring name, jstring signature) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); + jni_helper.getMethodId(clazz, name_ptr.get(), sig_ptr.get()); +} + +extern "C" JNIEXPORT void JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_getStaticMethodId(JNIEnv* env, jclass, + jclass clazz, jstring name, + jstring signature) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); + jni_helper.getStaticMethodId(clazz, name_ptr.get(), sig_ptr.get()); +} + +extern "C" JNIEXPORT jclass JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_findClass( + JNIEnv* env, jclass, jstring class_name) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr class_name_ptr = jni_helper.getStringUtfChars(class_name, nullptr); + Envoy::JNI::LocalRefUniquePtr clazz = jni_helper.findClass(class_name_ptr.get()); + return clazz.release(); +} + +extern "C" JNIEXPORT jclass JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_getObjectClass( + JNIEnv* env, jclass, jobject object) { + Envoy::JNI::JniHelper jni_helper(env); + return jni_helper.getObjectClass(object).release(); +} + +extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_throwNew( + JNIEnv* env, jclass, jstring class_name, jstring message) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr class_name_ptr = jni_helper.getStringUtfChars(class_name, nullptr); + Envoy::JNI::StringUtfUniquePtr message_ptr = jni_helper.getStringUtfChars(message, nullptr); + jni_helper.throwNew(class_name_ptr.get(), message_ptr.get()); +} + +extern "C" JNIEXPORT jobject JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_newObject( + JNIEnv* env, jclass, jclass clazz, jstring name, jstring signature) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); + jmethodID method_id = jni_helper.getMethodId(clazz, name_ptr.get(), sig_ptr.get()); + return jni_helper.newObject(clazz, method_id).release(); +} + +extern "C" JNIEXPORT jint JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_getArrayLength(JNIEnv* env, jclass, jarray array) { + Envoy::JNI::JniHelper jni_helper(env); + return jni_helper.getArrayLength(array); +} + +#define DEFINE_JNI_NEW_ARRAY(JAVA_TYPE, JNI_TYPE) \ + extern "C" JNIEXPORT JNI_TYPE JNICALL \ + Java_io_envoyproxy_envoymobile_jni_JniHelperTest_new##JAVA_TYPE##Array(JNIEnv* env, jclass, \ + jsize length) { \ + Envoy::JNI::JniHelper jni_helper(env); \ + return jni_helper.new##JAVA_TYPE##Array(length).release(); \ + } + +DEFINE_JNI_NEW_ARRAY(Byte, jbyteArray) +DEFINE_JNI_NEW_ARRAY(Char, jcharArray) +DEFINE_JNI_NEW_ARRAY(Short, jshortArray) +DEFINE_JNI_NEW_ARRAY(Int, jintArray) +DEFINE_JNI_NEW_ARRAY(Long, jlongArray) +DEFINE_JNI_NEW_ARRAY(Float, jfloatArray) +DEFINE_JNI_NEW_ARRAY(Double, jdoubleArray) +DEFINE_JNI_NEW_ARRAY(Boolean, jbooleanArray) + +extern "C" JNIEXPORT jobjectArray JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_newObjectArray(JNIEnv* env, jclass, jsize length, + jclass element_class, + jobject initial_element) { + Envoy::JNI::JniHelper jni_helper(env); + return jni_helper.newObjectArray(length, element_class, initial_element).release(); +} + +extern "C" JNIEXPORT jobject JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_getObjectArrayElement(JNIEnv* env, jclass, + jobjectArray array, + jsize index) { + Envoy::JNI::JniHelper jni_helper(env); + return jni_helper.getObjectArrayElement(array, index).release(); +} + +extern "C" JNIEXPORT void JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_setObjectArrayElement(JNIEnv* env, jclass, + jobjectArray array, + jsize index, jobject value) { + Envoy::JNI::JniHelper jni_helper(env); + jni_helper.setObjectArrayElement(array, index, value); +} + +#define DEFINE_JNI_CALL_METHOD(JAVA_TYPE, JNI_TYPE) \ + extern "C" JNIEXPORT JNI_TYPE JNICALL \ + Java_io_envoyproxy_envoymobile_jni_JniHelperTest_call##JAVA_TYPE##Method( \ + JNIEnv* env, jclass, jclass clazz, jobject object, jstring name, jstring signature) { \ + Envoy::JNI::JniHelper jni_helper(env); \ + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); \ + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); \ + jmethodID method_id = jni_helper.getMethodId(clazz, name_ptr.get(), sig_ptr.get()); \ + return jni_helper.call##JAVA_TYPE##Method(object, method_id); \ + } + +DEFINE_JNI_CALL_METHOD(Byte, jbyte) +DEFINE_JNI_CALL_METHOD(Char, jchar) +DEFINE_JNI_CALL_METHOD(Short, jshort) +DEFINE_JNI_CALL_METHOD(Int, jint) +DEFINE_JNI_CALL_METHOD(Long, jlong) +DEFINE_JNI_CALL_METHOD(Float, jfloat) +DEFINE_JNI_CALL_METHOD(Double, jdouble) +DEFINE_JNI_CALL_METHOD(Boolean, jboolean) + +extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_callVoidMethod( + JNIEnv* env, jclass, jclass clazz, jobject object, jstring name, jstring signature) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); + jmethodID method_id = jni_helper.getMethodId(clazz, name_ptr.get(), sig_ptr.get()); + jni_helper.callVoidMethod(object, method_id); +} + +extern "C" JNIEXPORT jobject JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_callObjectMethod(JNIEnv* env, jclass, jclass clazz, + jobject object, jstring name, + jstring signature) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); + jmethodID method_id = jni_helper.getMethodId(clazz, name_ptr.get(), sig_ptr.get()); + return jni_helper.callObjectMethod(object, method_id).release(); +} + +#define DEFINE_JNI_CALL_STATIC_METHOD(JAVA_TYPE, JNI_TYPE) \ + extern "C" JNIEXPORT JNI_TYPE JNICALL \ + Java_io_envoyproxy_envoymobile_jni_JniHelperTest_callStatic##JAVA_TYPE##Method( \ + JNIEnv* env, jclass, jclass clazz, jstring name, jstring signature) { \ + Envoy::JNI::JniHelper jni_helper(env); \ + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); \ + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); \ + jmethodID method_id = jni_helper.getStaticMethodId(clazz, name_ptr.get(), sig_ptr.get()); \ + return jni_helper.callStatic##JAVA_TYPE##Method(clazz, method_id); \ + } + +DEFINE_JNI_CALL_STATIC_METHOD(Byte, jbyte) +DEFINE_JNI_CALL_STATIC_METHOD(Char, jchar) +DEFINE_JNI_CALL_STATIC_METHOD(Short, jshort) +DEFINE_JNI_CALL_STATIC_METHOD(Int, jint) +DEFINE_JNI_CALL_STATIC_METHOD(Long, jlong) +DEFINE_JNI_CALL_STATIC_METHOD(Float, jfloat) +DEFINE_JNI_CALL_STATIC_METHOD(Double, jdouble) +DEFINE_JNI_CALL_STATIC_METHOD(Boolean, jboolean) + +extern "C" JNIEXPORT void JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_callStaticVoidMethod(JNIEnv* env, jclass, + jclass clazz, jstring name, + jstring signature) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); + jmethodID method_id = jni_helper.getStaticMethodId(clazz, name_ptr.get(), sig_ptr.get()); + jni_helper.callStaticVoidMethod(clazz, method_id); +} + +extern "C" JNIEXPORT jobject JNICALL +Java_io_envoyproxy_envoymobile_jni_JniHelperTest_callStaticObjectMethod(JNIEnv* env, jclass, + jclass clazz, jstring name, + jstring signature) { + Envoy::JNI::JniHelper jni_helper(env); + Envoy::JNI::StringUtfUniquePtr name_ptr = jni_helper.getStringUtfChars(name, nullptr); + Envoy::JNI::StringUtfUniquePtr sig_ptr = jni_helper.getStringUtfChars(signature, nullptr); + jmethodID method_id = jni_helper.getStaticMethodId(clazz, name_ptr.get(), sig_ptr.get()); + return jni_helper.callStaticObjectMethod(clazz, method_id).release(); +} diff --git a/mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD b/mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD new file mode 100644 index 000000000000..b82998b551f8 --- /dev/null +++ b/mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD @@ -0,0 +1,12 @@ +load("@envoy_mobile//bazel:kotlin_test.bzl", "envoy_mobile_android_test") + +envoy_mobile_android_test( + name = "jni_helper_test", + srcs = [ + "JniHelperTest.java", + ], + native_deps = [ + "//test/common/jni:libenvoy_jni_helper_test.so", + ], + native_lib_name = "envoy_jni_helper_test", +) diff --git a/mobile/test/java/io/envoyproxy/envoymobile/jni/JniHelperTest.java b/mobile/test/java/io/envoyproxy/envoymobile/jni/JniHelperTest.java new file mode 100644 index 000000000000..43964f1aee75 --- /dev/null +++ b/mobile/test/java/io/envoyproxy/envoymobile/jni/JniHelperTest.java @@ -0,0 +1,299 @@ +package io.envoyproxy.envoymobile.jni; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class JniHelperTest { + public JniHelperTest() { System.loadLibrary("envoy_jni_helper_test"); } + + //================================================================================ + // Native methods for testing. + //================================================================================ + public static native void getMethodId(Class clazz, String name, String signature); + public static native void getStaticMethodId(Class clazz, String name, String signature); + public static native Class findClass(String className); + public static native Class getObjectClass(Object object); + public static native Object newObject(Class clazz, String name, String signature); + public static native void throwNew(String className, String message); + public static native int getArrayLength(int[] array); + public static native byte[] newByteArray(int length); + public static native char[] newCharArray(int length); + public static native short[] newShortArray(int length); + public static native int[] newIntArray(int length); + public static native long[] newLongArray(int length); + public static native float[] newFloatArray(int length); + public static native double[] newDoubleArray(int length); + public static native boolean[] newBooleanArray(int length); + public static native Object[] newObjectArray(int length, Class elementClass, + Object initialElement); + public static native Object getObjectArrayElement(Object[] array, int index); + public static native void setObjectArrayElement(Object[] array, int index, Object value); + public static native byte callByteMethod(Class clazz, Object instance, String name, + String signature); + public static native char callCharMethod(Class clazz, Object instance, String name, + String signature); + public static native short callShortMethod(Class clazz, Object instance, String name, + String signature); + public static native int callIntMethod(Class clazz, Object instance, String name, + String signature); + public static native long callLongMethod(Class clazz, Object instance, String name, + String signature); + public static native float callFloatMethod(Class clazz, Object instance, String name, + String signature); + public static native double callDoubleMethod(Class clazz, Object instance, String name, + String signature); + public static native boolean callBooleanMethod(Class clazz, Object instance, String name, + String signature); + public static native void callVoidMethod(Class clazz, Object instance, String name, + String signature); + public static native Object callObjectMethod(Class clazz, Object instance, String name, + String signature); + public static native byte callStaticByteMethod(Class clazz, String name, String signature); + public static native char callStaticCharMethod(Class clazz, String name, String signature); + public static native short callStaticShortMethod(Class clazz, String name, String signature); + public static native int callStaticIntMethod(Class clazz, String name, String signature); + public static native long callStaticLongMethod(Class clazz, String name, String signature); + public static native float callStaticFloatMethod(Class clazz, String name, String signature); + public static native double callStaticDoubleMethod(Class clazz, String name, String signature); + public static native boolean callStaticBooleanMethod(Class clazz, String name, + String signature); + public static native void callStaticVoidMethod(Class clazz, String name, String signature); + public static native Object callStaticObjectMethod(Class clazz, String name, String signature); + + //================================================================================ + // Object methods used for CallMethod tests. + //================================================================================ + public byte byteMethod() { return 1; } + public char charMethod() { return 'a'; } + public short shortMethod() { return 1; } + public int intMethod() { return 1; } + public long longMethod() { return 1; } + public float floatMethod() { return 3.14f; } + public double doubleMethod() { return 3.14; } + public boolean booleanMethod() { return true; } + public void voidMethod() {} + public String objectMethod() { return "Hello"; } + + //================================================================================ + // Static methods used for CallStaticMethod tests. + //================================================================================ + public static byte staticByteMethod() { return 1; } + public static char staticCharMethod() { return 'a'; } + public static short staticShortMethod() { return 1; } + public static int staticIntMethod() { return 1; } + public static long staticLongMethod() { return 1; } + public static float staticFloatMethod() { return 3.14f; } + public static double staticDoubleMethod() { return 3.14; } + public static boolean staticBooleanMethod() { return true; } + public static void staticVoidMethod() {} + public static String staticObjectMethod() { return "Hello"; } + + static class Foo {} + + @Test + public void testMethodId() { + getMethodId(Foo.class, "", "()V"); + } + + @Test + public void testStaticMethodId() { + getStaticMethodId(JniHelperTest.class, "staticVoidMethod", "()V"); + } + + @Test + public void testFindClass() { + assertThat(findClass("java/lang/Exception")).isEqualTo(Exception.class); + } + + @Test + public void testGetObjectClass() { + String s = "Hello"; + assertThat(getObjectClass(s)).isEqualTo(String.class); + } + + @Test + public void testNewObject() { + assertThat(newObject(Foo.class, "", "()V")).isInstanceOf(Foo.class); + } + + @Test + public void testThrowNew() { + assertThatThrownBy(() -> throwNew("java/lang/RuntimeException", "Test")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Test"); + } + + @Test + public void testGetArrayLength() { + assertThat(getArrayLength(new int[] {1, 2, 3})).isEqualTo(3); + } + + @Test + public void testNewCharArray() { + assertThat(newCharArray(3)).isEqualTo(new char[] {0, 0, 0}); + } + + @Test + public void testNewShortArray() { + assertThat(newShortArray(3)).isEqualTo(new short[] {0, 0, 0}); + } + + @Test + public void testNewIntArray() { + assertThat(newIntArray(3)).isEqualTo(new int[] {0, 0, 0}); + } + + @Test + public void testNewLongArray() { + assertThat(newLongArray(3)).isEqualTo(new long[] {0, 0, 0}); + } + + @Test + public void testNewFloatArray() { + assertThat(newFloatArray(3)).isEqualTo(new float[] {0, 0, 0}); + } + + @Test + public void testNewDoubleArray() { + assertThat(newDoubleArray(3)).isEqualTo(new double[] {0, 0, 0}); + } + + @Test + public void testNewBooleanArray() { + assertThat(newBooleanArray(3)).isEqualTo(new boolean[] {false, false, false}); + } + + @Test + public void testNewObjectArray() { + assertThat(newObjectArray(3, String.class, "foo")) + .isEqualTo(new String[] {"foo", "foo", "foo"}); + } + + @Test + public void testGetObjectArrayElement() { + Object[] array = new Object[] {1, 2, 3}; + assertThat(getObjectArrayElement(array, 1)).isEqualTo(2); + } + + @Test + public void testSetObjectArrayElement() { + Object[] array = new Object[] {1, 2, 3}; + setObjectArrayElement(array, 1, 200); + assertThat(array).isEqualTo(new Object[] {1, 200, 3}); + } + + @Test + public void testCallByteMethod() { + assertThat(callByteMethod(JniHelperTest.class, this, "byteMethod", "()B")).isEqualTo((byte)1); + } + + @Test + public void testCallCharMethod() { + assertThat(callCharMethod(JniHelperTest.class, this, "charMethod", "()C")).isEqualTo('a'); + } + + @Test + public void testCallShortMethod() { + assertThat(callShortMethod(JniHelperTest.class, this, "shortMethod", "()S")) + .isEqualTo((short)1); + } + + @Test + public void testCallIntMethod() { + assertThat(callIntMethod(JniHelperTest.class, this, "intMethod", "()I")).isEqualTo(1); + } + + @Test + public void testCallLongMethod() { + assertThat(callLongMethod(JniHelperTest.class, this, "longMethod", "()J")).isEqualTo(1L); + } + + @Test + public void testCallFloatMethod() { + assertThat(callFloatMethod(JniHelperTest.class, this, "floatMethod", "()F")).isEqualTo(3.14f); + } + + @Test + public void testCallDoubleMethod() { + assertThat(callDoubleMethod(JniHelperTest.class, this, "doubleMethod", "()D")).isEqualTo(3.14); + } + + @Test + public void testCallBooleanMethod() { + assertThat(callBooleanMethod(JniHelperTest.class, this, "booleanMethod", "()Z")) + .isEqualTo(true); + } + + @Test + public void testCallVoidMethod() { + callVoidMethod(JniHelperTest.class, this, "voidMethod", "()V"); + } + + @Test + public void testCallObjectMethod() { + assertThat(callObjectMethod(JniHelperTest.class, this, "objectMethod", "()Ljava/lang/String;")) + .isEqualTo("Hello"); + } + + @Test + public void testCallStaticByteMethod() { + assertThat(callStaticByteMethod(JniHelperTest.class, "staticByteMethod", "()B")) + .isEqualTo((byte)1); + } + + @Test + public void testCallStaticCharMethod() { + assertThat(callStaticCharMethod(JniHelperTest.class, "staticCharMethod", "()C")).isEqualTo('a'); + } + + @Test + public void testCallStaticShortMethod() { + assertThat(callStaticShortMethod(JniHelperTest.class, "staticShortMethod", "()S")) + .isEqualTo((short)1); + } + + @Test + public void testCallStaticIntMethod() { + assertThat(callStaticIntMethod(JniHelperTest.class, "staticIntMethod", "()I")).isEqualTo(1); + } + + @Test + public void testCallStaticLongMethod() { + assertThat(callStaticLongMethod(JniHelperTest.class, "staticLongMethod", "()J")).isEqualTo(1L); + } + + @Test + public void testCallStaticFloatMethod() { + assertThat(callStaticFloatMethod(JniHelperTest.class, "staticFloatMethod", "()F")) + .isEqualTo(3.14f); + } + + @Test + public void testCallStaticDoubleMethod() { + assertThat(callStaticDoubleMethod(JniHelperTest.class, "staticDoubleMethod", "()D")) + .isEqualTo(3.14); + } + + @Test + public void testCallStaticBooleanMethod() { + assertThat(callStaticBooleanMethod(JniHelperTest.class, "staticBooleanMethod", "()Z")) + .isEqualTo(true); + } + + @Test + public void testCallStaticVoidMethod() { + callStaticVoidMethod(JniHelperTest.class, "staticVoidMethod", "()V"); + } + + @Test + public void testCallStaticObjectMethod() { + assertThat( + callStaticObjectMethod(JniHelperTest.class, "staticObjectMethod", "()Ljava/lang/String;")) + .isEqualTo("Hello"); + } +} From 4991c0fcc820671a0e6b1421da5f5de423022380 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 1 Nov 2023 23:57:47 -0400 Subject: [PATCH 72/72] server: make initialize non-virtual (#30531) Risk Level: low (any overrides must be updated due to constructor change) Testing: updated tests Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- mobile/library/common/engine_common.cc | 10 +-- source/exe/main_common.cc | 10 +-- source/server/server.cc | 94 ++++++++++++++------------ source/server/server.h | 14 ++-- test/integration/server.cc | 10 +-- test/server/server_fuzz_test.cc | 10 +-- test/server/server_test.cc | 24 +++---- 7 files changed, 92 insertions(+), 80 deletions(-) diff --git a/mobile/library/common/engine_common.cc b/mobile/library/common/engine_common.cc index 79e60393b31f..61875a82f0e2 100644 --- a/mobile/library/common/engine_common.cc +++ b/mobile/library/common/engine_common.cc @@ -71,10 +71,12 @@ EngineCommon::EngineCommon(std::unique_ptr&& options) Buffer::WatermarkFactorySharedPtr watermark_factory) { // TODO(alyssawilk) use InstanceLite not InstanceImpl. auto local_address = Network::Utility::getLocalAddress(options.localAddressIpVersion()); - return std::make_unique( - init_manager, options, time_system, local_address, hooks, restarter, store, - access_log_lock, component_factory, std::move(random_generator), tls, thread_factory, - file_system, std::move(process_context), watermark_factory); + auto server = std::make_unique( + init_manager, options, time_system, hooks, restarter, store, access_log_lock, + std::move(random_generator), tls, thread_factory, file_system, + std::move(process_context), watermark_factory); + server->initialize(local_address, component_factory); + return server; }; base_ = std::make_unique( *options_, real_time_system_, default_listener_hooks_, prod_component_factory_, diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index 10c5bfcb7e7c..e2d7d8703337 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -39,10 +39,12 @@ StrippedMainBase::CreateInstanceFunction createFunction() { Filesystem::Instance& file_system, std::unique_ptr process_context, Buffer::WatermarkFactorySharedPtr watermark_factory) { auto local_address = Network::Utility::getLocalAddress(options.localAddressIpVersion()); - return std::make_unique( - init_manager, options, time_system, local_address, hooks, restarter, store, - access_log_lock, component_factory, std::move(random_generator), tls, thread_factory, - file_system, std::move(process_context), watermark_factory); + auto server = std::make_unique( + init_manager, options, time_system, hooks, restarter, store, access_log_lock, + std::move(random_generator), tls, thread_factory, file_system, + std::move(process_context), watermark_factory); + server->initialize(local_address, component_factory); + return server; }; } diff --git a/source/server/server.cc b/source/server/server.cc index 6b7ee5e086a0..575b29716ffc 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -74,14 +74,15 @@ std::unique_ptr getHandler(Event::Dispatcher& dispatcher) { } // namespace -InstanceImpl::InstanceImpl( - Init::Manager& init_manager, const Options& options, Event::TimeSystem& time_system, - Network::Address::InstanceConstSharedPtr local_address, ListenerHooks& hooks, - HotRestart& restarter, Stats::StoreRoot& store, Thread::BasicLockable& access_log_lock, - ComponentFactory& component_factory, Random::RandomGeneratorPtr&& random_generator, - ThreadLocal::Instance& tls, Thread::ThreadFactory& thread_factory, - Filesystem::Instance& file_system, std::unique_ptr process_context, - Buffer::WatermarkFactorySharedPtr watermark_factory) +InstanceImpl::InstanceImpl(Init::Manager& init_manager, const Options& options, + Event::TimeSystem& time_system, ListenerHooks& hooks, + HotRestart& restarter, Stats::StoreRoot& store, + Thread::BasicLockable& access_log_lock, + Random::RandomGeneratorPtr&& random_generator, + ThreadLocal::Instance& tls, Thread::ThreadFactory& thread_factory, + Filesystem::Instance& file_system, + std::unique_ptr process_context, + Buffer::WatermarkFactorySharedPtr watermark_factory) : init_manager_(init_manager), live_(false), options_(options), validation_context_(options_.allowUnknownStaticFields(), !options.rejectUnknownDynamicFields(), @@ -103,43 +104,7 @@ InstanceImpl::InstanceImpl( grpc_context_(store.symbolTable()), http_context_(store.symbolTable()), router_context_(store.symbolTable()), process_context_(std::move(process_context)), hooks_(hooks), quic_stat_names_(store.symbolTable()), server_contexts_(*this), - enable_reuse_port_default_(true), stats_flush_in_progress_(false) { - std::function set_up_logger = [&] { - TRY_ASSERT_MAIN_THREAD { - file_logger_ = std::make_unique( - options.logPath(), access_log_manager_, Logger::Registry::getSink()); - } - END_TRY - CATCH(const EnvoyException& e, { - throw EnvoyException( - fmt::format("Failed to open log-file '{}'. e.what(): {}", options.logPath(), e.what())); - }); - }; - - TRY_ASSERT_MAIN_THREAD { - if (!options.logPath().empty()) { - set_up_logger(); - } - restarter_.initialize(*dispatcher_, *this); - drain_manager_ = component_factory.createDrainManager(*this); - initialize(std::move(local_address), component_factory); - } - END_TRY - MULTI_CATCH( - const EnvoyException& e, - { - ENVOY_LOG(critical, "error initializing config '{} {} {}': {}", - options.configProto().DebugString(), options.configYaml(), options.configPath(), - e.what()); - terminate(); - throw; - }, - { - ENVOY_LOG(critical, "error initializing due to unknown exception"); - terminate(); - throw; - }); -} + enable_reuse_port_default_(true), stats_flush_in_progress_(false) {} InstanceImpl::~InstanceImpl() { terminate(); @@ -422,6 +387,45 @@ void InstanceUtil::loadBootstrapConfig(envoy::config::bootstrap::v3::Bootstrap& void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_address, ComponentFactory& component_factory) { + std::function set_up_logger = [&] { + TRY_ASSERT_MAIN_THREAD { + file_logger_ = std::make_unique( + options_.logPath(), access_log_manager_, Logger::Registry::getSink()); + } + END_TRY + CATCH(const EnvoyException& e, { + throw EnvoyException( + fmt::format("Failed to open log-file '{}'. e.what(): {}", options_.logPath(), e.what())); + }); + }; + + TRY_ASSERT_MAIN_THREAD { + if (!options_.logPath().empty()) { + set_up_logger(); + } + restarter_.initialize(*dispatcher_, *this); + drain_manager_ = component_factory.createDrainManager(*this); + initializeOrThrow(std::move(local_address), component_factory); + } + END_TRY + MULTI_CATCH( + const EnvoyException& e, + { + ENVOY_LOG(critical, "error initializing config '{} {} {}': {}", + options_.configProto().DebugString(), options_.configYaml(), + options_.configPath(), e.what()); + terminate(); + throw; + }, + { + ENVOY_LOG(critical, "error initializing due to unknown exception"); + terminate(); + throw; + }); +} + +void InstanceImpl::initializeOrThrow(Network::Address::InstanceConstSharedPtr local_address, + ComponentFactory& component_factory) { ENVOY_LOG(info, "initializing epoch {} (base id={}, hot restart version={})", options_.restartEpoch(), restarter_.baseId(), restarter_.version()); diff --git a/source/server/server.h b/source/server/server.h index 13baedd12c4b..859eb5d4789b 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -233,14 +233,16 @@ class InstanceImpl final : Logger::Loggable, * @throw EnvoyException if initialization fails. */ InstanceImpl(Init::Manager& init_manager, const Options& options, Event::TimeSystem& time_system, - Network::Address::InstanceConstSharedPtr local_address, ListenerHooks& hooks, - HotRestart& restarter, Stats::StoreRoot& store, - Thread::BasicLockable& access_log_lock, ComponentFactory& component_factory, + ListenerHooks& hooks, HotRestart& restarter, Stats::StoreRoot& store, + Thread::BasicLockable& access_log_lock, Random::RandomGeneratorPtr&& random_generator, ThreadLocal::Instance& tls, Thread::ThreadFactory& thread_factory, Filesystem::Instance& file_system, std::unique_ptr process_context, Buffer::WatermarkFactorySharedPtr watermark_factory = nullptr); + // initialize the server. This must be called before run(). + void initialize(Network::Address::InstanceConstSharedPtr local_address, + ComponentFactory& component_factory); ~InstanceImpl() override; void run() override; @@ -313,8 +315,10 @@ class InstanceImpl final : Logger::Loggable, ProtobufTypes::MessagePtr dumpBootstrapConfig(); void flushStatsInternal(); void updateServerStats(); - void initialize(Network::Address::InstanceConstSharedPtr local_address, - ComponentFactory& component_factory); + // This does most of the work of initialization, but can throw errors caught + // by initialize(). + void initializeOrThrow(Network::Address::InstanceConstSharedPtr local_address, + ComponentFactory& component_factory); void loadServerFlags(const absl::optional& flags_path); void startWorkers(); void terminate(); diff --git a/test/integration/server.cc b/test/integration/server.cc index 3a19040a98b5..4995765c9481 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -232,11 +232,11 @@ void IntegrationTestServerImpl::createAndRunEnvoyServer( if (process_object.has_value()) { process_context = std::make_unique(process_object->get()); } - Server::InstanceImpl server(init_manager, options, time_system, local_address, hooks, restarter, - stat_store, access_log_lock, component_factory, - std::move(random_generator), tls, Thread::threadFactoryForTest(), - Filesystem::fileSystemForTest(), std::move(process_context), - watermark_factory); + Server::InstanceImpl server(init_manager, options, time_system, hooks, restarter, stat_store, + access_log_lock, std::move(random_generator), tls, + Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), + std::move(process_context), watermark_factory); + server.initialize(local_address, component_factory); // This is technically thread unsafe (assigning to a shared_ptr accessed // across threads), but because we synchronize below through serverReady(), the only // consumer on the main test thread in ~IntegrationTestServerImpl will not race. diff --git a/test/server/server_fuzz_test.cc b/test/server/server_fuzz_test.cc index e07229c88a82..93e269c00786 100644 --- a/test/server/server_fuzz_test.cc +++ b/test/server/server_fuzz_test.cc @@ -152,11 +152,11 @@ DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v3::Bootstrap& input) { std::unique_ptr server; try { server = std::make_unique( - init_manager, options, test_time.timeSystem(), - std::make_shared("127.0.0.1"), hooks, restart, stats_store, - fakelock, component_factory, std::make_unique(), - thread_local_instance, Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), - nullptr); + init_manager, options, test_time.timeSystem(), hooks, restart, stats_store, fakelock, + std::make_unique(), thread_local_instance, + Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr); + server->initialize(std::make_shared("127.0.0.1"), + component_factory); } catch (const EnvoyException& ex) { ENVOY_LOG_MISC(debug, "Controlled EnvoyException exit: {}", ex.what()); return; diff --git a/test/server/server_test.cc b/test/server/server_test.cc index 689946c01927..8e8bca32f2f0 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -258,12 +258,12 @@ class ServerInstanceImplTestBase { : std::make_unique("Server"); server_ = std::make_unique( - *init_manager_, options_, time_system_, - std::make_shared("127.0.0.1"), hooks, restart_, - stats_store_, fakelock_, component_factory_, + *init_manager_, options_, time_system_, hooks, restart_, stats_store_, fakelock_, std::make_unique>(), *thread_local_, Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), std::move(process_context_)); + server_->initialize(std::make_shared("127.0.0.1"), + component_factory_); EXPECT_TRUE(server_->api().fileSystem().fileExists(std::string(Platform::null_device_path))); } @@ -277,11 +277,11 @@ class ServerInstanceImplTestBase { thread_local_ = std::make_unique(); init_manager_ = std::make_unique("Server"); server_ = std::make_unique( - *init_manager_, options_, time_system_, - std::make_shared("127.0.0.1"), hooks_, restart_, - stats_store_, fakelock_, component_factory_, + *init_manager_, options_, time_system_, hooks_, restart_, stats_store_, fakelock_, std::make_unique>(), *thread_local_, Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr); + server_->initialize(std::make_shared("127.0.0.1"), + component_factory_); EXPECT_TRUE(server_->api().fileSystem().fileExists(std::string(Platform::null_device_path))); } @@ -1302,13 +1302,13 @@ TEST_P(ServerInstanceImplTest, LogToFileError) { TEST_P(ServerInstanceImplTest, NoOptionsPassed) { thread_local_ = std::make_unique(); init_manager_ = std::make_unique("Server"); + server_.reset(new InstanceImpl( + *init_manager_, options_, time_system_, hooks_, restart_, stats_store_, fakelock_, + std::make_unique>(), *thread_local_, + Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr)); EXPECT_THROW_WITH_MESSAGE( - server_.reset(new InstanceImpl(*init_manager_, options_, time_system_, - std::make_shared("127.0.0.1"), - hooks_, restart_, stats_store_, fakelock_, component_factory_, - std::make_unique>(), - *thread_local_, Thread::threadFactoryForTest(), - Filesystem::fileSystemForTest(), nullptr)), + server_->initialize(std::make_shared("127.0.0.1"), + component_factory_), EnvoyException, "At least one of --config-path or --config-yaml or Options::configProto() should be " "non-empty");